Way more of the token lifecycle implemented, there's base64 now

This commit is contained in:
John Doe 2022-11-24 01:49:46 -05:00
parent af0ab2d19d
commit f35923ab74
10 changed files with 851 additions and 33 deletions

214
base64.c Normal file
View file

@ -0,0 +1,214 @@
/*
This is derived from the libb64 project, which has been placed in the public domain.
For details, see http://sourceforge.net/projects/libb64
*/
#include "base64.h"
int base64_decode_value(char value_in)
{
static const char decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51};
static const char decoding_size = sizeof(decoding);
value_in -= 43;
if (value_in < 0 || value_in >= decoding_size) return -1;
return decoding[(int)value_in];
}
void base64_init_decodestate(base64_decodestate* state_in)
{
state_in->step = step_a;
state_in->plainchar = 0;
}
int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in)
{
const char* codechar = code_in;
char* plainchar = plaintext_out;
char fragment;
*plainchar = state_in->plainchar;
switch (state_in->step)
{
while (1)
{
case step_a:
do {
if (codechar == code_in+length_in)
{
state_in->step = step_a;
state_in->plainchar = *plainchar;
return plainchar - plaintext_out;
}
fragment = (char)base64_decode_value(*codechar++);
} while (fragment < 0);
*plainchar = (fragment & 0x03f) << 2;
// fall through
case step_b:
do {
if (codechar == code_in+length_in)
{
state_in->step = step_b;
state_in->plainchar = *plainchar;
return plainchar - plaintext_out;
}
fragment = (char)base64_decode_value(*codechar++);
} while (fragment < 0);
*plainchar++ |= (fragment & 0x030) >> 4;
*plainchar = (fragment & 0x00f) << 4;
// fall through
case step_c:
do {
if (codechar == code_in+length_in)
{
state_in->step = step_c;
state_in->plainchar = *plainchar;
return plainchar - plaintext_out;
}
fragment = (char)base64_decode_value(*codechar++);
} while (fragment < 0);
*plainchar++ |= (fragment & 0x03c) >> 2;
*plainchar = (fragment & 0x003) << 6;
// fall through
case step_d:
do {
if (codechar == code_in+length_in)
{
state_in->step = step_d;
state_in->plainchar = *plainchar;
return plainchar - plaintext_out;
}
fragment = (char)base64_decode_value(*codechar++);
} while (fragment < 0);
*plainchar++ |= (fragment & 0x03f);
}
}
/* control should not reach here */
return plainchar - plaintext_out;
}
void base64_init_encodestate(base64_encodestate* state_in)
{
state_in->step = step_A;
state_in->result = 0;
state_in->stepcount = 0;
}
char base64_encode_value(char value_in)
{
static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
if (value_in > 63) return '=';
return encoding[(int)value_in];
}
int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in)
{
const char* plainchar = plaintext_in;
const char* const plaintextend = plaintext_in + length_in;
char* codechar = code_out;
char result;
char fragment;
result = state_in->result;
switch (state_in->step)
{
while (1)
{
case step_A:
if (plainchar == plaintextend)
{
state_in->result = result;
state_in->step = step_A;
return codechar - code_out;
}
fragment = *plainchar++;
result = (fragment & 0x0fc) >> 2;
*codechar++ = base64_encode_value(result);
result = (fragment & 0x003) << 4;
// fall through
case step_B:
if (plainchar == plaintextend)
{
state_in->result = result;
state_in->step = step_B;
return codechar - code_out;
}
fragment = *plainchar++;
result |= (fragment & 0x0f0) >> 4;
*codechar++ = base64_encode_value(result);
result = (fragment & 0x00f) << 2;
// fall through
case step_C:
if (plainchar == plaintextend)
{
state_in->result = result;
state_in->step = step_C;
return codechar - code_out;
}
fragment = *plainchar++;
result |= (fragment & 0x0c0) >> 6;
*codechar++ = base64_encode_value(result);
result = (fragment & 0x03f) >> 0;
*codechar++ = base64_encode_value(result);
++(state_in->stepcount);
#ifdef BASE64_USE_NEWLINES
if (state_in->stepcount == BASE64_CHARS_PER_LINE/4)
{
*codechar++ = '\n';
state_in->stepcount = 0;
}
#endif
}
}
/* control should not reach here */
return codechar - code_out;
}
int base64_encode_blockend(char* code_out, base64_encodestate* state_in)
{
char* codechar = code_out;
switch (state_in->step)
{
case step_B:
*codechar++ = base64_encode_value(state_in->result);
*codechar++ = '=';
*codechar++ = '=';
break;
case step_C:
*codechar++ = base64_encode_value(state_in->result);
*codechar++ = '=';
break;
case step_A:
break;
}
#ifdef BASE64_USE_NEWLINES
*codechar++ = '\n';
#endif
return codechar - code_out;
}
// These "one-shot" functions were not in the original project, I have added them and still need to be sure they work
// note - output MUST be large enough to accomodate the base64'd input (i.e. must be 1/3 bigger than input_len plus the null terminator - 65 bytes for 48-byte input)
void base64_encode_oneshot(char* input, int input_len, char* output){
base64_encodestate s;
char* c = output;
base64_init_encodestate(&s);
c += base64_encode_block(input, input_len, output, &s);
c += base64_encode_blockend(c, &s);
*c = '\0';
}
// note - output MUST be large enough to accomodate the decoded input (i.e. must be at least 3/4 the size of the input - 48 bytes for 64-byte input)
void base64_decode_oneshot(char* input, int input_len, char* output){
base64_decodestate s;
base64_init_decodestate(&s);
base64_decode_block(input, input_len, output, &s);
}

52
base64.h Normal file
View file

@ -0,0 +1,52 @@
/*
This is derived from the libb64 project, which has been placed in the public domain.
For details, see http://sourceforge.net/projects/libb64
*/
#ifndef BASE64_H
#define BASE64_H
#define BASE64_CHARS_PER_LINE 4
//#define BASE64_USE_NEWLINES
typedef enum
{
step_a, step_b, step_c, step_d
} base64_decodestep;
typedef struct
{
base64_decodestep step;
char plainchar;
} base64_decodestate;
void base64_init_decodestate(base64_decodestate* state_in);
int base64_decode_value(char value_in);
int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in);
typedef enum
{
step_A, step_B, step_C
} base64_encodestep;
typedef struct
{
base64_encodestep step;
char result;
int stepcount;
} base64_encodestate;
void base64_init_encodestate(base64_encodestate* state_in);
char base64_encode_value(char value_in);
int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in);
int base64_encode_blockend(char* code_out, base64_encodestate* state_in);
void base64_encode_oneshot(char* input, int input_len, char* output);
void base64_decode_oneshot(char* input, int input_len, char* output);
#endif

BIN
blindsig2 Executable file

Binary file not shown.

188
blindsig2.c Normal file
View file

@ -0,0 +1,188 @@
// This is a (very rough) test of BLST blind signatures based on run.me from BLST's Python example code
// Do not trust this to be secure, also this doesn't do a lot of the sanity checking yet
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <assert.h>
#include "blst/blst.h"
const byte dst[] = "MY-DST";
double time_taken;
clock_t t;
byte signer_private_key[32];
byte signer_public_key[96];
void printbytes(byte *toprint, int length){
for(int i=0;i<length;i++){
printf("%.2x ", toprint[i]);
}
printf("\n");
}
void signer_key_setup(){
blst_scalar sk;
blst_p2 pk;
blst_p2_affine pk_affine;
byte myikm[32] = {'*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*'};
// On signer's side:
printf("IKM: ");
printbytes(myikm, 32);
blst_keygen(&sk, myikm, 32, 0, 0);
blst_bendian_from_scalar(signer_private_key, &sk);
printf("Secret Key: ");
printbytes(signer_private_key, 32);
blst_sk_to_pk_in_g2(&pk, &sk);
blst_p2_to_affine(&pk_affine, &pk);
blst_p2_affine_compress(signer_public_key, &pk_affine);
printf("Compressed Public Key (affine): ");
printbytes(signer_public_key, 96);
}
void signer(byte *compressed_signature, byte *msg_for_wire){
blst_scalar sk;
blst_p1 msg, signature;
blst_p1_affine msg_affine;
byte debug_print_buf[256];
// get the secret key as a scalar
blst_scalar_from_bendian(&sk, signer_private_key);
// Deserialize the message - it's already a serialized P1 point, we don't need to (literally) rehash it
blst_p1_uncompress(&msg_affine, msg_for_wire);
// i do not know why deserializing always gives you affine points
blst_p1_from_affine(&msg, &msg_affine);
// Confirm the message point is in the G1 group
assert(blst_p1_in_g1(&msg));
// sign with it
blst_sign_pk_in_g2(&signature, &msg, &sk);
// Serialize and print the signature
blst_p1_serialize(debug_print_buf, &signature);
printf("Signature: ");
printbytes(debug_print_buf, 96);
// Compress and print the signature
blst_p1_compress(compressed_signature, &signature);
printf("Compressed Signature: ");
printbytes(compressed_signature, 48);
}
void verifier(byte *compressed_signature, byte *msg){
blst_p1_affine sig;
blst_p2_affine pk;
blst_p1_uncompress(&sig, compressed_signature);
blst_p2_uncompress(&pk, signer_public_key);
BLST_ERROR returned;
// TODO: check if in g2 group
returned = blst_core_verify_pk_in_g2(&pk, &sig, 1, msg, 16, dst, strlen((char *) dst), signer_public_key, 96);
if(returned == BLST_SUCCESS){
printf("Verified!\n");
}else{
printf("Not verified!\n");
}
}
// main is the "user" in this test
int main(){
byte debug_print_buf[256];
byte compressed_blinded_signature[48];
byte compressed_signature[48];
byte msg[16] = {'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A'};
byte blinding_r_bytes[32] = {'R', 'R', 'R', 'R', 'R', 'R', 'R', 'R', 'R', 'R', 'R', 'R', 'R', 'R', 'R', 'R', 'R', 'R', 'R', 'R', 'R', 'R', 'R', 'R', 'R', 'R', 'R', 'R', 'R', 'R', 'R', 'R'};
blst_scalar blinding_r, inverse_blinding_r;
blst_p1 hash, msg_for_wire;
byte msg_for_wire_bytes[96];
blst_p1_affine returned_signature_affine;
blst_p1 returned_signature, unblinded_signature;
printf("msg is now ", msg);
printbytes(msg, 16);
printf("\n");
// Set up the signer's keys first so that we can know its public key
signer_key_setup();
// Get a hash of the message - we put the signer's public key in aug here, I don't know why
blst_hash_to_g1(&hash, msg, 16, dst, strlen((char *) dst), signer_public_key, 96);
printf("HASH: ");
blst_p1_serialize(debug_print_buf, &hash);
printbytes(debug_print_buf, 96);
// Get a BLST scalar of your "random" (LOL) blinding factor r
blst_scalar_from_bendian(&blinding_r, blinding_r_bytes);
printf("R BYTES: ");
printbytes(blinding_r_bytes, 32);
// Blind the message by signing it with the blinding factor R as if it was a secret key
blst_sign_pk_in_g2(&msg_for_wire, &hash, &blinding_r);
// Serialize the blinded message to send it over the wire
blst_p1_compress(msg_for_wire_bytes, &msg_for_wire);
printf("Blinded and compressed for wire: ");
printbytes(msg_for_wire_bytes, 48);
// Send the message off to be signed and get the results back
signer(compressed_blinded_signature, msg_for_wire_bytes);
printf("COMPRESSED BLINDED SIG: ");
printbytes(compressed_blinded_signature, 48);
// We now have the signature back. returned_signature is a blst_p1_affine because this is pk_in_g2.
blst_p1_uncompress(&returned_signature_affine, compressed_blinded_signature);
// Convert the uncompressed returned signature from an affine to a P1
blst_p1_from_affine(&returned_signature, &returned_signature_affine);
// Confirm the signature point is in the G1 group
assert(blst_p1_in_g1(&returned_signature));
printf("RETURNED SIGNATURE: ");
blst_p1_serialize(debug_print_buf, &returned_signature);
printbytes(debug_print_buf, 96);
// Get the inverse of R. We'll need this to unblind the signature.
blst_sk_inverse(&inverse_blinding_r, &blinding_r);
// Print the inverse of R
printf("INVERSE R: ");
blst_bendian_from_scalar(debug_print_buf, &inverse_blinding_r);
printbytes(debug_print_buf, 32);
// Sign the blinded signature we get back from the signer with the inverse of the blinding factor
blst_sign_pk_in_g2(&unblinded_signature, &returned_signature, &inverse_blinding_r);
blst_p1_compress(compressed_signature, &unblinded_signature);
printf("UNBLINDED SIGNATURE: ");
printbytes(compressed_signature, 48);
// uncomment to change the message and have the signature fail the check
//msg[8] = ' ';
printf("msg is now ", msg);
printbytes(msg, 16);
printf("\n");
// Now on verifier's side (after compressed_signature, serialized_public_key, and msg are passed over the network)
verifier(compressed_signature, msg);
}

View file

@ -8,4 +8,4 @@ ${CC} ${CFLAGS} -c blst/server.c
${CC} ${CFLAGS} -c blst/assembly.S
${AR} rc libblst.a server.o assembly.o
${CC} ${CFLAGS} -o ctm ctm.c fstoken.c debugprint.c libblst.a
${CC} ${CFLAGS} -o ctm ctm.c fstoken.c debugprint.c base64.c libblst.a

235
ctm.c
View file

@ -9,6 +9,7 @@
#include "blst/blst.h"
#include "debugprint.h"
#include "fstoken.h"
#include "base64.h"
char* token_path;
@ -22,13 +23,18 @@ void print_help(char* name){
printf("%s help - display this help\n", name);
printf("%s path - display your token path\n", name);
printf("%s list - list targets you have keys for - use \"%s list verbose\" to also display paths to the keys\n", name, name);
printf("%s keygen [targ] - create a new target keypair [targ] using the default token format (128/256) and the system true randomness source\n", name);
printf("%s keygen [targ] [tfs] - create a new target keypair [targ] using token format [tfs] and the system true randomness source\n", name);
printf("%s keygen [targ] [tfs] [ikm] - create a new target keypair [targ] using token format specifier [tfs] and 32-byte hexadecimal seed [ikm]\n", name);
printf("%s keydump [targ] - dump Public and, if available, Secret Keys for target [targ]\n", name);
printf("%s list - list available targets - use \"%s list verbose\" to also display format specifiers and paths to target files\n", name, name);
printf("%s keygen [targ] - create a new target [targ] using the default token format (128/256) and the system true randomness source\n", name);
printf("%s keygen [targ] [tfs] - create a new target[targ] using token format [tfs] and the system true randomness source\n", name);
printf("%s keygen [targ] [tfs] [ikm] - create a new target [targ] using token format specifier [tfs] and 32-byte hexadecimal seed [ikm]\n", name);
printf("%s info [targ] - dump all available target information for target [targ], including keys\n", name);
//printf("%s keyrepair [targ] - regenerate a missing Public Key for a target [targ] for which a Secret Key is available\n", name);
printf("%s req [targ] - generate a token request for target [targ]\n", name);
printf("%s strip [targin] [targout] - duplicate target [targin] as [targout], removing Secret Key (for distribution as a Public-Key-only target)\n", name);
printf("%s ireq [targ] - interactively generate a Token Request Code for target [targ] using a random Token ID and Blinding Factor\n", name);
printf("%s ireq [targ] [id] - interactively generate a Token Request Code for target [targ] using a set Token ID [id] and a random Blinding Factor\n", name);
printf("%s ireq [targ] [id] [factor] - interactively generate a Token Request Code for target [targ] using a set Token ID [id] and Blinding Factor [factor]\n", name);
printf("%s sign [targ] [req] - generate a signature for Token Request Code [req], on target [targ]\n", name);
printf("%s bsign [targ] - bulk-sign token requests, accepting Token Request Codes on stdin and outputting signatures to stdout\n", name);
}
int get_targ_path(char* target, char** targ_path){
@ -44,11 +50,10 @@ int get_targ_path(char* target, char** targ_path){
strcat(*targ_path, target);
strcat(*targ_path, ".target");
return 0;
}
int get_target(char* targ_name, target_descriptor* target){ // pk/sk/idbits/hasbits pointers can be NULL if you don't want to read those
int get_target(char* targ_name, fstoken_target_descriptor* target){ // pk/sk/idbits/hasbits pointers can be NULL if you don't want to read those
FILE *targ_file;
char *targ_path;
@ -58,16 +63,99 @@ int get_target(char* targ_name, target_descriptor* target){ // pk/sk/idbits/hasb
if(!targ_file){
return 0;
}
fread(target, sizeof(target_descriptor), 1, targ_file);
fread(target, sizeof(fstoken_target_descriptor), 1, targ_file);
fclose(targ_file);
// 0 = no keys (bad target), 1 = PK only, 2 = PK+SK
return target->sk_available + 1;
}
int keydump(char* targ_name){
// Fill a buffer dest of size dest_size with exactly (dest_size - 2) characters and a null terminator, returning 1 if the user supplies any other number
int get_fixed_length(char* dest, size_t dest_size){
dest[dest_size - 2] = '\0'; // Initialize this place, we're about to need to check it even if fgets never gets here
fgets(dest, dest_size, stdin);
if(dest[dest_size - 2] != '\n') return 1; //If there isn't a newline in the second-last character (i.e. the user specified too many) return 1
dest[dest_size - 2] = '\0'; // Cut it down to length and get rid of the newline we're now sure is in the right place
return 0;
}
int sign_single(char* targ_name, char* req_b64){
fstoken_request req;
fstoken_target_descriptor targ;
fstoken_signature sig;
base64_decode_oneshot((char*) req_b64, 64, (char*) req.id_blind);
//debug_print_bytes("Signature hex: ", req.id_blind, 48);
if(get_target(targ_name, &targ) != 2){
printf("Target or private key unavailable. Are you sure you have a private key for this target?\n");
return 1;
}
if(fstoken_sign_request(&req, &sig, &targ)){
printf("Signing failed. Is the token request valid?\n");
return 1;
};
print_base64(sig.signature, 48);
return 0;
}
int interactive_request(char* targ_name, byte* token_id, byte* blinding_factor){
fstoken_request req;
fstoken_request_descriptor reqdesc;
fstoken_target_descriptor target;
fstoken_signature sig;
fstoken_token token;
char req_base64_encoded[65];
char sig_base64[66];
int token_length;
if(!get_target(targ_name, &target)){
printf("Invalid target %s\n", targ_name);
return 1;
}
fstoken_gen_request(&req, &reqdesc, &target, token_id, blinding_factor);
base64_encode_oneshot((char*) req.id_blind, 48, req_base64_encoded);
printf("A Token Request Code has been generated. Provide this code to the signer. Once you have received a signature, enter it below to generate your token.\n");
printf("Token Request Code: %s\n", req_base64_encoded);
printf("Enter your signature: ");
if(get_fixed_length(sig_base64, sizeof(sig_base64))){
printf("Invalid signature length\n");
return 1;
};
base64_decode_oneshot(sig_base64, 64, (char*) sig.signature);
//debug_print_bytes("Signature as hex: ", sig.signature, 48);
if(fstoken_gen_token(&reqdesc, &sig, &token)){
printf("Failed to generate token.\n");
return 1;
};
token_length = 48 + (target.idbits + 7) / 8;
//debug_print_bytes("Token Unblinded Signature: ", token.signature, 48);
//debug_print_bytes("TOKEN: ", &token, token_length);
printf("\nToken: ");
print_base64(&token, token_length);
return 0;
}
int targinfo(char* targ_name){
int targ_status;
target_descriptor target;
fstoken_target_descriptor target;
targ_status = get_target(targ_name, &target);
@ -77,12 +165,12 @@ int keydump(char* targ_name){
return 1;
case 1:
printf("Public Key available - can verify and request for this target\n");
print_bytes("Public Key: ", target.pk, 96);
print_bytes("Public Key: ", target.pk, 96, '\0');
break;
case 2:
printf("Secret Key and Public Key available - can verify, request, and sign for this target.\n");
print_bytes("Secret Key: ", target.sk, 32);
print_bytes("Public Key: ", target.pk, 96);
print_bytes("Secret Key: ", target.sk, 32, '\0');
print_bytes("Public Key: ", target.pk, 96, '\0');
break;
}
@ -91,7 +179,40 @@ int keydump(char* targ_name){
return 0;
}
/*int keyrepair(char* target){
int striptarg(char* targ_name_in, char* targ_name_out){
FILE *targ_out_file;
fstoken_target_descriptor targ_out;
char* targ_out_path;
if(!get_target(targ_name_in, &targ_out)){
printf("Unknown input target. Exiting.\n");
return 1;
}
if(targ_out.sk_available == false){
printf("Target %s already lacks a Secret Key - only targets with Secret Keys can be stripped.\n", targ_name_in);
return 2;
}
memset(targ_out.sk, 0x00, 32);
targ_out.sk_available = false;
get_targ_path(targ_name_out, &targ_out_path);
targ_out_file = fopen(targ_out_path, "w");
if(!targ_out_file){
printf("Could not open output target file. Exiting.\n");
return 1;
}
fwrite(&targ_out, sizeof(fstoken_target_descriptor), 1, targ_out_file);
fclose(targ_out_file);
printf("Public-Key-only target %s succesfully created from original target %s.\n", targ_name_in, targ_name_out);
return 0;
}
/*int keyrepair(char* target){ // this is probably no longer necessary with single-file targets
FILE *key_file;
byte sk[32];
byte pk[96];
@ -130,7 +251,7 @@ int keydump(char* targ_name){
int keygen(char* target, byte* ikm, int idbits, int hashbits){
char *targ_path;
FILE *targ_file;
target_descriptor targ;
fstoken_target_descriptor targ;
debug_print_bytes("IKM: ", ikm, 32);
@ -153,7 +274,7 @@ int keygen(char* target, byte* ikm, int idbits, int hashbits){
printf("Could not open target file. Exiting.\n");
return 1;
}
fwrite(&targ, sizeof(targ), 1, targ_file);
fwrite(&targ, sizeof(fstoken_target_descriptor), 1, targ_file);
fclose(targ_file);
return 0;
@ -179,7 +300,7 @@ int list_targets(bool verbose){
int n, targname_len;
struct dirent **files;
char *targdir_path, *targ_path, *targ_name;
target_descriptor targ;
fstoken_target_descriptor targ;
targdir_path = malloc(strlen(token_path) + 9);
if(targdir_path == NULL) return 1;
@ -233,6 +354,14 @@ void bendian_from_hex_string(byte* bendian, char* string, int length){
}
}
void lendian_from_hex_string(byte* lendian, char* string, int length){
char byte[2];
for(int i = (length-1); i >= 0; i--){
memcpy(byte, &string[i*2], 2);
lendian[i] = strtol(byte, 0, 16);
}
}
// mostly boring command line parsing
int main(int argc, char *argv[]){
token_path = getenv("FEMTOSTAR_TOKEN_PATH");
@ -259,6 +388,7 @@ int main(int argc, char *argv[]){
if(argc > 5){
printf("Too many arguments. Exiting.\n");
return(1);
}
// Make sure there's a target name
@ -292,18 +422,18 @@ int main(int argc, char *argv[]){
return 1;
}
bendian_from_hex_string(ikm, argv[4], 64);
lendian_from_hex_string(ikm, argv[4], 32);
}
return keygen(argv[2], ikm, idbits, hashbits);
}else if(strcmp(argv[1], "keydump") == 0){
}else if(strcmp(argv[1], "info") == 0){
// Make sure there's a target name
if(argc < 3){
fprintf(stderr, "A target name must be provided, e.g. %s keydump [targ]\n", argv[0]);
fprintf(stderr, "A target name must be provided, e.g. %s info [targ]\n", argv[0]);
return(1);
}
return keydump(argv[2]);
return targinfo(argv[2]);
}/*else if(strcmp(argv[1], "keyrepair") == 0){
// Make sure there's a target name
if(argc < 3){
@ -312,5 +442,64 @@ int main(int argc, char *argv[]){
}
return keyrepair(argv[2]);
}*/
}*/else if(strcmp(argv[1], "strip") == 0){
// Make sure there's a target name
if(argc < 4){
fprintf(stderr, "Two target names must be provided, e.g. %s strip [targin] [targout]\n", argv[0]);
return(1);
}
return striptarg(argv[2], argv[3]);
}else if(strcmp(argv[1], "ireq") == 0){
byte blinding_factor[32];
byte token_id[IDBYTES_MAX];
// Make sure there's a target name
if(argc < 3){
fprintf(stderr, "A target name must be provided, e.g. %s req [targ]\n", argv[0]);
return(1);
}
if(argc > 5){
printf("Too many arguments. Exiting.\n");
return(1);
}
if(argc < 4){
getrandom(token_id, IDBYTES_MAX, GRND_NONBLOCK);
}else{
if(strlen(argv[3]) > (IDBYTES_MAX * 2)){
fprintf(stderr, "If providing a Token ID, it must be %i bits (%i hexadecimal digits) or less \n", IDBITS_MAX, IDBYTES_MAX * 2);
return 1;
}
lendian_from_hex_string(token_id, argv[3], (strlen(argv[3]) + 1) / 2);
}
// If no IKM is provided, use the system true randomness source
if(argc < 5){
getrandom(blinding_factor, 32, GRND_NONBLOCK);
}else{
if(strlen(argv[4]) != 64){
fprintf(stderr, "If providing a blinding factor, it must be 32 bytes (64 hexadecimal digits)\n");
return 1;
}
lendian_from_hex_string(blinding_factor, argv[4], 32);
}
return interactive_request(argv[2], token_id, blinding_factor);
}else if(strcmp(argv[1], "sign") == 0){
if(argc != 4){
fprintf(stderr, "Please provide a target [targ] and a Token Request Code [req]. See help.\n");
return 1;
}
if(strlen(argv[3]) != 64){
fprintf(stderr, "Token request code must be 48 bytes (64 base64 characters).\n");
return 1;
}
sign_single(argv[2], argv[3]);
}
}

View file

@ -1,18 +1,34 @@
#include <stdio.h>
#include <stdlib.h>
#include "debugprint.h"
#include "blst/blst.h"
#include "base64.h"
void print_bytes(const char* label, void *toprint, int length){
void print_bytes(const char* label, void *toprint, int length, char separator){
printf("%s", label);
for(int i=0;i<length;i++){
printf("%.2x ", ((byte *) toprint)[i]);
printf("%.2x", ((byte *) toprint)[i]);
if(separator != '\0') printf("%c", separator);
}
printf("\n");
}
void print_base64(void *toprint, int input_len){
int buffer_size;
char* output;
buffer_size = (((input_len + 2) / 3) * 4) + 1;
output = malloc(buffer_size); //create a buffer large enough for the base64'd input, including with padding if need be
base64_encode_oneshot(toprint, input_len, output);
printf("%s\n", output);
free(output);
}
void debug_print_bytes(__attribute__((unused)) const char* label, __attribute__((unused)) void *toprint, __attribute__((unused)) int length){
#ifdef INSECURE_CTM_DEBUG_PRINT
print_bytes(label, toprint, length);
print_bytes(label, toprint, length, ' ');
#endif
}

View file

@ -5,9 +5,10 @@
// Uncomment the line below to enable debug prints (including private keys!) - use for development only, this is insecure
#define INSECURE_CTM_DEBUG_PRINT
void print_bytes(const char* label, void *toprint, int length);
void print_bytes(const char* label, void *toprint, int length, char separator);
void debug_print_bytes(__attribute__((unused)) const char* label, __attribute__((unused)) void *toprint, __attribute__((unused)) int length);
void print_scalar(const char* label, blst_scalar *toprint);
void debug_print_scalar(__attribute__((unused)) const char* label, __attribute__((unused)) blst_scalar *toprint);
void print_base64(void *toprint, int input_len);
#endif

116
fstoken.c
View file

@ -18,8 +18,116 @@ void fstoken_get_pk_from_sk(byte* sk_byte, byte* pk_byte){
blst_p2_affine pk_affine;
blst_scalar sk;
blst_scalar_from_bendian(&sk, sk_byte);
blst_sk_to_pk_in_g2(&pk, &sk);
blst_p2_to_affine(&pk_affine, &pk);
blst_p2_affine_compress(pk_byte, &pk_affine);
blst_scalar_from_bendian(&sk, sk_byte); //convert the SK to a blst_scalar
blst_sk_to_pk_in_g2(&pk, &sk); // get a PK from it
blst_p2_to_affine(&pk_affine, &pk); // convert the PK to affine
blst_p2_affine_compress(pk_byte, &pk_affine); // compress the PK
}
void fstoken_gen_request(fstoken_request* req, fstoken_request_descriptor* reqdesc, fstoken_target_descriptor* target, byte* token_id, byte* blinding_factor){
byte debug_print_buf[256]; //get rid of this
int targ_idbytes;
blst_p1 hash, blinded_id;
blst_scalar blinding_r;
targ_idbytes = (target->idbits + 7)/8;
//printf("IDBYTES_MAX: %i, IDBYTES for target: %i\n", IDBYTES_MAX, targ_idbytes);
memcpy(&reqdesc->blinding_factor, blinding_factor, 32);
bit_truncated_copy(token_id, reqdesc->token_id, IDBYTES_MAX, target->idbits); //cut down the supplied token ID to match the target's IDBITS value
//debug_print_bytes("Working with ID: ", reqdesc->token_id, IDBYTES_MAX);
//debug_print_bytes("Working with Blinding Factor: ", blinding_factor, 32);
// Hash the Token ID to a BLST P1
blst_hash_to_g1(&hash, token_id, targ_idbytes, FSTOKEN_DST, strlen((char *) FSTOKEN_DST), target->pk, 96);
// Get the blinding factor as a BLST scalar
blst_scalar_from_bendian(&blinding_r, blinding_factor);
blst_p1_serialize(debug_print_buf, &hash);
//debug_print_bytes("HASH: ", debug_print_buf, 96);
// Sign the hash as if the blinding factor was a private key
blst_sign_pk_in_g2(&blinded_id, &hash, &blinding_r);
// Compress the resulting signature and put it in the signature request
blst_p1_compress(req->id_blind, &blinded_id);
//debug_print_bytes("Blinded and compressed for wire: ", req->id_blind, 48);
}
int fstoken_sign_request(fstoken_request* req, fstoken_signature* sig, fstoken_target_descriptor* targ){
blst_scalar sk;
blst_p1 req_id, signature;
blst_p1_affine req_id_affine;
// get the secret key as a scalar
blst_scalar_from_bendian(&sk, targ->sk);
// Deserialize the request - it's already a serialized P1 point, we don't need to (literally) rehash it
blst_p1_uncompress(&req_id_affine, req->id_blind);
// i do not know why deserializing always gives you affine points
blst_p1_from_affine(&req_id, &req_id_affine);
// Return 1 if the point isn't in G1, this is basically a sanity check
if(!blst_p1_in_g1(&req_id)) return 1;
// sign with it
blst_sign_pk_in_g2(&signature, &req_id, &sk);
// Compress and print the signature
blst_p1_compress(sig->signature, &signature);
//debug_print_bytes("Compressed signature:", sig->signature, 48);
return 0;
}
int fstoken_gen_token(fstoken_request_descriptor* req, fstoken_signature* sig, fstoken_token* token){
blst_p1 returned_signature, unblinded_signature;
blst_p1_affine returned_signature_affine;
blst_scalar blinding_r, inverse_r;
// We now have the signature back. returned_signature is a blst_p1_affine because this is pk_in_g2.
blst_p1_uncompress(&returned_signature_affine, sig->signature);
// Convert the decompressed returned signature from an affine to a P1
blst_p1_from_affine(&returned_signature, &returned_signature_affine);
// Confirm the signature point is in the G1 group
if(!blst_p1_in_g1(&returned_signature)) return 1;
// Retreive the blinding factor and take its inverse
blst_scalar_from_bendian(&blinding_r, req->blinding_factor);
blst_sk_inverse(&inverse_r, &blinding_r);
//print_scalar("Inverse R: ", &inverse_r);
// Unblind the signature using the inverse of the blinding factor
blst_sign_pk_in_g2(&unblinded_signature, &returned_signature, &inverse_r);
memcpy(token->token_id, req->token_id, IDBYTES_MAX);
blst_p1_compress(token->signature, &unblinded_signature);
return 0;
}
void bit_truncated_copy(byte* src, byte* dest, size_t destlen, int bits){
uint8_t bitmask;
int bytes;
bytes = (bits + 7) / 8;
memset(dest, 0, destlen); //clear out the destination
memcpy(dest, src, bytes); //copy over as many bytes as are needed for the number of bits
//bitmask out the high (fsp is little-endian) bits of the destination if bits is not a round number of bytes
bitmask = 0xFF >> (bits % 8);
//printf("mask: 0x%0x\n", bitmask);
dest[bytes - 1] = dest[bytes - 1] & bitmask;
}

View file

@ -3,16 +3,33 @@
#include "blst/blst.h"
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
// only used for debugging
#include <stdio.h>
#include "debugprint.h"
// Note: In constrained environments where the IDBITS and HASHBITS values of the tokens in use are known, such as in fsp, these can be set exactly.
// However, that may render some structs (such as the request descriptor) incompatible if moved to an environment where this is nonstandard.
#define IDBITS_DEFAULT 128
#define IDBITS_MAX 256
#define HASHBITS_DEFAULT 256
#define HASHBITS_MAX 256
#define IDBYTES_MAX (IDBITS_MAX + 7) / 8
#define HASHBYTES_MAX (HASHBITS_MAX + 7) / 8
#define FSTOKEN_DST ((byte*) "MY-DST")
// For efficiency's sake, structs intended for use with the FemtoStar Protocol must be strictly-aligned without padding, with little-endian byte ordering.
// This avoids wasting network capacity on padding, and avoids the performance impact of reordering on most modern platforms (most importantly x86 and RISC-V)
// while ensuring compatibility with strict-alignment architectures. Take note: This is NOT typical network-order!
// Additionally, note that most structs do not explicitly include what target they are for (even the target descriptor!). Keeping what is for what target is
// not a core part of fstoken, and is the responsibility of the software using fstoken (e.g. ctm or an implementation of fsp). This keeps tokens light in
// situations where which target is relevant is unambiguous (e.g. in the session setup with a satellite, where the satellite is always the same target).
// A target_descriptor is a stored description of a target, which can be used to request, verify, and sometimes sign tokens for it.
typedef struct{
#pragma scalar_storage_order little-endian
@ -22,9 +39,42 @@ typedef struct{
uint16_t hashbits;
byte sk[32];
byte pk[96];
} target_descriptor;
} fstoken_target_descriptor;
// A request descriptor is a structure held privately by the requester until they receive a signature for their request - it is then used to generate a token
typedef struct{
#pragma scalar_storage_order little-endian
byte blinding_factor[32];
byte token_id[IDBYTES_MAX];
} fstoken_request_descriptor;
// A request is a structure sent by the requester to the signer, to be signed and a signature returned. It need not be kept private.
typedef struct{
#pragma scalar_storage_order little-endian
byte id_blind[48];
} fstoken_request;
// I'm unsure if these "single-item structs" make any sense - signatures in blst are independent of system or scalar_storage_order endianness anyway
typedef struct{
#pragma scalar_storage_order little-endian
byte signature[48];
} fstoken_signature;
typedef struct{
#pragma scalar_storage_order little-endian
byte signature[48];
byte token_id[IDBYTES_MAX];
} fstoken_token;
void fstoken_keygen(byte* ikm, byte* sk_byte, byte* pk_byte);
void fstoken_get_pk_from_sk(byte* sk_byte, byte* pk_byte);
void bit_truncated_copy(byte* src, byte* dest, size_t destlen, int bits);
void fstoken_gen_request(fstoken_request* req, fstoken_request_descriptor* reqdesc, fstoken_target_descriptor* target, byte* token_id, byte* blinding_factor);
int fstoken_sign_request(fstoken_request* req, fstoken_signature* sig, fstoken_target_descriptor* targ);
int fstoken_gen_token(fstoken_request_descriptor* req, fstoken_signature* sig, fstoken_token* token);
#endif