diff --git a/base64.c b/base64.c index 5162e8a..fac4c9b 100644 --- a/base64.c +++ b/base64.c @@ -3,6 +3,7 @@ This is derived from the libb64 project, which has been placed in the public dom For details, see http://sourceforge.net/projects/libb64 */ +#include #include "base64.h" int base64_decode_value(char value_in) @@ -211,4 +212,25 @@ void base64_decode_oneshot(char* input, int input_len, char* output){ base64_init_decodestate(&s); base64_decode_block(input, input_len, output, &s); +} + +// Provides the length of the binary you would get if you decoded base64 string b64, accounting for padding +int base64_binlength(char* b64){ + int length, padding; + length = strlen(b64); + + if(length % 4 != 0) return -1; // return -1 if length is not divisible by four (i.e. if invalid base64) + + // somewhat messy way to determine how many bytes of padding are at the end of a base64 string + if(b64[length - 1] == '=' && b64[length - 2] == '=' && b64[length - 3] == '='){padding = 3;} + else if(b64[length - 1] == '=' && b64[length - 2] == '='){padding = 2;} + else if(b64[length - 1] == '='){padding = 1;} + else{padding = 0;} + + return(((length / 4) * 3) - padding); +} + +// Provides the base64 length of a binary of length binlength bytes +int base64_base64length(int binlength){ + return(((binlength + 2) / 3) * 4); } \ No newline at end of file diff --git a/base64.h b/base64.h index 4f66413..f8bdd8e 100644 --- a/base64.h +++ b/base64.h @@ -48,5 +48,7 @@ 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); +int base64_binlength(char* b64); +int base64_base64length(int binlength); #endif diff --git a/ctm.c b/ctm.c index c773fd0..872d7ef 100644 --- a/ctm.c +++ b/ctm.c @@ -35,12 +35,16 @@ void print_help(char* 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); + printf("%s verify [targ] [token] - verify token [token] against target [targ], as base64 - returns 0 on verified\n", name); + printf("%s bverify [targ] - bulk-verify tokens against target [targ], accepting base64 tokens on stdin and outputting 0 (verified) or 1 to stdout\n", name); + printf("%s inspect [targ] [token] - inpect a token [token] for target [targ], to see its token ID and signature\n", name); + printf("%s shorten [targ] [token] - shorten a token [token] for target [targ], generating a symmetrically-verifiable short token\n", name); } int get_targ_path(char* target, char** targ_path){ int path_len; - path_len = strlen(token_path) + strlen(target) + 17; + path_len = strlen(token_path) + strlen(target) + 17; //string length of "/targets/", ".target", and null terminator *targ_path = malloc(path_len); if(*targ_path == NULL) return 1; @@ -53,7 +57,7 @@ int get_targ_path(char* target, char** targ_path){ return 0; } -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 +int get_target(char* targ_name, fstoken_target_descriptor* target){ FILE *targ_file; char *targ_path; @@ -81,6 +85,154 @@ int get_fixed_length(char* dest, size_t dest_size){ return 0; } +int decode_base64_token(fstoken_target_descriptor* target, fstoken_token* token, char* token_b64){ + int token_length, target_token_length; + + target_token_length = fstoken_target_token_length(target); + token_length = base64_binlength(token_b64); + + if(token_length == -1){ + fprintf(stderr, "Invalid base64 provided\n"); + return 3; // should be an enum + } + + if(token_length != target_token_length){ + fprintf(stderr, "Provided token (%i bytes) of incorrect length for specified target (%i bytes).\n", token_length, target_token_length); + return 4; + } + + base64_decode_oneshot(token_b64, strlen(token_b64), (char*) token); + + return 0; +} + +int inspect_token(char* targ_name, char* token_b64){ + fstoken_target_descriptor target; + fstoken_token token; + int targ_idbytes; + + if(!get_target(targ_name, &target)){ + printf("Invalid target %s\n", targ_name); + return -1; + } + + if(decode_base64_token(&target, &token, token_b64)){ + return 2; + } + + if(fstoken_verify_token(&token, &target) == 0){ + printf("Token is valid for target %s.\n", targ_name); + }else{ + printf("Token is not valid for target!\n"); + return 1; + } + + targ_idbytes = (target.idbits + 7) / 8; + + printf("\n"); + print_bytes("Signature (hex): ", token.signature, 48, ' '); + printf("Signature (base64): "); + print_base64(token.signature, 48); + printf("\n"); + print_bytes("Token ID (hex): ", token.token_id, targ_idbytes, ' '); + printf("Token ID (base64): "); + print_base64(token.token_id, targ_idbytes); + + return 0; +} + +// Receives tokens on stdin and provides 0 (valid), 1 (invalid), or 2/3/4 (various bad input) to stdout, with additional messages on stderr in case of error +int verify_bulk(char* targ_name){ + char token_b64[((((HASHBYTES_MAX + IDBYTES_MAX) + 2) / 3) * 4) + 2]; // maximum possible base64 length + 2 characters for get_fixed_length + fstoken_target_descriptor target; + fstoken_token token; + int target_token_b64_length, result; + + if(!get_target(targ_name, &target)){ + fprintf(stderr, "Invalid target %s\n", targ_name); + return -1; + } + + target_token_b64_length = base64_base64length(fstoken_target_token_length(&target)); + + printf("Target token length %i\n", target_token_b64_length); + + while(1){ + if(get_fixed_length(token_b64, target_token_b64_length + 2)){ // +2 because of get_fixed_length, I should probably change that to be not stupid + printf("2"); + fprintf(stderr, "Provided token base64 incorrect length for target (should be %i characters).\n", target_token_b64_length); + continue; + } + + result = decode_base64_token(&target, &token, token_b64); + + if(result){ + printf("%i", result); + continue; + } + + if(fstoken_verify_token(&token, &target) == 0){ + printf("0"); + }else{ + printf("1"); + } + } +} + +int verify_single(char* targ_name, char* token_b64){ + fstoken_target_descriptor target; + fstoken_token token; + + if(!get_target(targ_name, &target)){ + printf("Invalid target %s\n", targ_name); + return -1; + } + + decode_base64_token(&target, &token, token_b64); + + if(fstoken_verify_token(&token, &target) == 0){ + printf("Verified!\n"); + return 0; + }else{ + printf("Not verified!\n"); + return 1; + } +} + +int sign_bulk(char* targ_name){ + char req_b64[66]; + fstoken_request req; + fstoken_target_descriptor targ; + fstoken_signature sig; + + 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; + } + + fprintf(stderr, "Bulk signing started. Enter token requests\n"); + + while(1){ + if(get_fixed_length(req_b64, sizeof(req_b64))){ + fprintf(stderr, "Invalid request length\n"); + continue; + }; + + base64_decode_oneshot((char*) req_b64, 64, (char*) req.id_blind); + + //debug_print_bytes("Signature hex: ", req.id_blind, 48); + + if(fstoken_sign_request(&req, &sig, &targ)){ + fprintf(stderr, "Signing failed. Is the token request valid?\n"); + continue; + }; + + print_base64(sig.signature, 48); + } + + return 0; +} + int sign_single(char* targ_name, char* req_b64){ fstoken_request req; fstoken_target_descriptor targ; @@ -105,6 +257,19 @@ int sign_single(char* targ_name, char* req_b64){ return 0; } +/*int verify_token(char* targ_name, byte* token_bytes){ + fstoken_target_descriptor target; + fstoken_token token; + int token_length; + + if(!get_target(targ_name, &target)){ + printf("Invalid target %s\n", targ_name); + return 1; + } + + //base64_decode_oneshot(token_bytes, ) +}*/ + int interactive_request(char* targ_name, byte* token_id, byte* blinding_factor){ fstoken_request req; fstoken_request_descriptor reqdesc; @@ -150,6 +315,20 @@ int interactive_request(char* targ_name, byte* token_id, byte* blinding_factor){ printf("\nToken: "); print_base64(&token, token_length); + //token.token_id[3] = '\0'; + + switch(fstoken_verify_token(&token, &target)){ + case 0: + printf("Verified!\n"); + break; + case 1: + printf("Signature does not match Token ID for target. Did the signer sign for the wrong target or sign the wrong request?\n"); + return 1; + case 2: + printf("Invalid signature (not in G1 group - either something has gone very wrong or the signer is attempting to do something malicious)\n"); + return 1; + } + return 0; } @@ -500,6 +679,44 @@ int main(int argc, char *argv[]){ return 1; } - sign_single(argv[2], argv[3]); + return sign_single(argv[2], argv[3]); + }else if(strcmp(argv[1], "bsign") == 0){ + if(argc != 3){ + fprintf(stderr, "Please provide a target [targ]. See help.\n"); + return 1; + } + + return sign_bulk(argv[2]); + }else if(strcmp(argv[1], "verify") == 0){ + if(argc != 4){ + fprintf(stderr, "Please provide a target [targ] and a token [token]. See help.\n"); + return 1; + } + + if(strlen(argv[3]) < 68){ + fprintf(stderr, "Token must be at least 49 bytes (68 base64 characters).\n"); + return 1; + } + + return(verify_single(argv[2], argv[3])); + }else if(strcmp(argv[1], "bverify") == 0){ + if(argc != 3){ + fprintf(stderr, "Please provide a target [targ]. See help.\n"); + return 1; + } + + verify_bulk(argv[2]); + }else if(strcmp(argv[1], "inspect") == 0){ + if(argc != 4){ + fprintf(stderr, "Please provide a target [targ] and a token [token]. See help.\n"); + return 1; + } + + if(strlen(argv[3]) < 68){ + fprintf(stderr, "Token must be at least 49 bytes (68 base64 characters).\n"); + return 1; + } + + return(inspect_token(argv[2], argv[3])); } } \ No newline at end of file diff --git a/debugprint.c b/debugprint.c index 8f1be37..c36d9ff 100644 --- a/debugprint.c +++ b/debugprint.c @@ -20,6 +20,7 @@ void print_base64(void *toprint, int input_len){ 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 + if(output == NULL) return; base64_encode_oneshot(toprint, input_len, output); printf("%s\n", output); diff --git a/fstoken.c b/fstoken.c index 626e590..91ecbf2 100644 --- a/fstoken.c +++ b/fstoken.c @@ -117,6 +117,26 @@ int fstoken_gen_token(fstoken_request_descriptor* req, fstoken_signature* sig, f return 0; } +// 0 = valid token, 1 = signature check failed, 2 = signature point invalid +int fstoken_verify_token(fstoken_token* token, fstoken_target_descriptor* targ){ + int targ_idbytes; + blst_p1_affine sig; + blst_p2_affine pk; + + blst_p1_uncompress(&sig, token->signature); + blst_p2_uncompress(&pk, targ->pk); + + BLST_ERROR returned; + + if(!blst_p1_affine_in_g1(&sig)) return 2; + + targ_idbytes = (targ->idbits + 7) / 8; + + returned = blst_core_verify_pk_in_g2(&pk, &sig, 1, token->token_id, targ_idbytes, FSTOKEN_DST, strlen((char *) FSTOKEN_DST), targ->pk, 96); + + return (returned != BLST_SUCCESS); +} + void bit_truncated_copy(byte* src, byte* dest, size_t destlen, int bits){ uint8_t bitmask; int bytes; @@ -130,4 +150,9 @@ void bit_truncated_copy(byte* src, byte* dest, size_t destlen, int bits){ bitmask = 0xFF >> (bits % 8); //printf("mask: 0x%0x\n", bitmask); dest[bytes - 1] = dest[bytes - 1] & bitmask; +} + +// Helper function to get the length (in bytes) of an (unshortened) token for the target +int fstoken_target_token_length(fstoken_target_descriptor* target){ + return 48 + (target->idbits + 7) / 8; } \ No newline at end of file diff --git a/fstoken.h b/fstoken.h index a888ba3..536a747 100644 --- a/fstoken.h +++ b/fstoken.h @@ -25,7 +25,7 @@ // 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 +// Additionally, note that most structs do not explicitly include what target they are for (even the target descriptor!). Tracking 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). @@ -70,11 +70,21 @@ typedef struct{ byte token_id[IDBYTES_MAX]; } fstoken_token; +typedef struct{ + #pragma scalar_storage_order little-endian + + byte hash[HASHBYTES_MAX]; + byte token_id[IDBYTES_MAX]; +} fstoken_short_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); +int fstoken_verify_token(fstoken_token* token, fstoken_target_descriptor* targ); +int fstoken_shorten_token(fstoken_token* token, fstoken_short_token short_token); +int fstoken_target_token_length(fstoken_target_descriptor* target); #endif \ No newline at end of file