#include "http_sign.h" #include "sha256/sha256.h" #include "http/server/header.h" #include "util/format.h" #include "model/account.h" #include "model/crypto/keys.h" #include "model/crypto/base64.h" #include "model/inbox_envelope.h" #include #include #include #include #include bool http_signature_make( const char* inbox, struct crypto_keys* keys, struct http_signature* sign, const char* postdata ) { memset(sign,0,sizeof(*sign)); if( 0 != strncmp( "https://", inbox, 8 ) ) { printf( "! Invalid inbox: %s\n", inbox ); return false; } // Separate host and path from inbox char* path = index( &inbox[8], '/' ); char* host = sign->host = strndup( &inbox[8], path - &inbox[8] ); // Build HTTP date - TODO: move to separate file time_t utc_time = time(NULL); struct tm gmtime_data; gmtime_r( &utc_time, &gmtime_data ); static const char* day_of_week[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; static const char* month_of_year[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; char date[32]; snprintf( date, sizeof(date), "%s, %02d %s %d %02d:%02d:%02d GMT", day_of_week[ gmtime_data.tm_wday ], gmtime_data.tm_mday, month_of_year[ gmtime_data.tm_mon ], gmtime_data.tm_year + 1900, gmtime_data.tm_hour, gmtime_data.tm_min, gmtime_data.tm_sec ); sign->date = strdup(date); // Calculate Digest unsigned char raw_hash[32]; sign->content_length = strlen(postdata); sha256_easy_hash( postdata, sign->content_length, raw_hash ); char* base64_hash = base64_strict_encode(raw_hash,32); sign->digest = aformat( "SHA-256=%s", base64_hash ); free(base64_hash); // Build hash line char hash_line[512]; snprintf( hash_line, 512, "(request-target): post %s\nhost: %s\ndate: %s\ncontent-length: %d\ndigest: %s", path, host, date, sign->content_length, sign->digest ); printf( "\nbuilding hash_line = %s|\n\n", hash_line ); // sign sign->signature = crypto_keys_sign( keys, hash_line, strlen(hash_line) ); printf( "Signature: %s\n", sign->signature ); return true; } void print_header_line( FILE* hl, struct ap_envelope* env, const char* header_name ) { fprintf( hl, "%s: ", header_name ); bool first = true; for( int i = 0; i < env->headers.count; ++i ) { if( 0 == strcasecmp(header_name,env->headers.items[i].key ) ) { if( !first ) { fprintf( hl, "," ); } fprintf( hl, "%s", env->headers.items[i].value ); first = false; } } } static bool validate_body_digest( struct ap_envelope* env ) { // Per: https://datatracker.ietf.org/doc/html/rfc3230 char* digest_header = NULL; char* content_length_header = NULL; char* hash = NULL; bool result = false; // Get the Digest header for( int i = 0; i < env->headers.count; ++i ) { char* key = env->headers.items[i].key; char* value = env->headers.items[i].value; if( 0 == strcasecmp( "Digest", key ) ) { free(digest_header); digest_header = strdup(value); } else if( 0 == strcasecmp( "Content-Length", key ) ) { free(content_length_header); content_length_header = strdup(value); } } if( !digest_header ) { goto failed; } if( !content_length_header ) { goto failed; } // Validate the body length against the Content-Length header int content_length = -1; sscanf( content_length_header, "%d", &content_length ); if( strlen(env->body) != content_length ) { printf( "Content-Length mismatch\n" ); goto failed; } char* digest = NULL; char* algo = strtok_r(digest_header, "=", &digest ); // Only implementing SHA-256 if( 0 != strcmp( algo, "SHA-256") ) { goto failed; } // Calculate sha-256 hash unsigned char raw_hash[32]; sha256_easy_hash( env->body, content_length, raw_hash ); hash = base64_strict_encode(raw_hash,32); result = ( 0 == strcmp(digest,hash) ); cleanup: free(hash); free(digest_header); free(content_length_header); return result; failed: result = false; goto cleanup; } bool http_signature_validate( struct ap_envelope* env, const char* request_target, const char* expected_actor ) { char* signature_header = NULL; char* date_header = NULL; struct crypto_keys* keys = NULL; bool result = false; char* hash_line = NULL; struct account* actor = NULL; // Get the Signature header for( int i = 0; i < env->headers.count; ++i ) { char* key = env->headers.items[i].key; char* value = env->headers.items[i].value; if( 0 == strcasecmp( "Signature", key ) ) { free(signature_header); signature_header = strdup(value); } else if( 0 == strcasecmp( "Date", key ) ) { free(date_header); date_header = strdup(value); } } if( !signature_header ) { return false; } //printf( "Found Signature: %s\n", signature_header ); // Validate time if( date_header ) { printf( "TODO: validate received within window\n" ); } // Break apart the header char* rem = NULL; //char* algorithm = NULL; char* key_id = NULL; char* headers = NULL; char* signature = NULL; for( char* part = strtok_r(signature_header,",",&rem); part; part = strtok_r(NULL,",",&rem) ) { // Split apart the key="value" pair char* key; char* value; key = strtok_r( part, "=", &value ); // Strip the "" off if( value[0] != '\"' ) { goto failed; } value = &value[1]; int len = strlen(value); if( value[len-1] != '\"' ) { goto failed; } value[len-1] = '\0'; // Handle parts if( 0 == strcmp(key,"keyId") ) { key_id = value; /* } else if( 0 == strcmp(key,"algorithm") ) { algorithm = value; */ } else if( 0 == strcmp(key,"headers") ) { headers = value; } else if( 0 == strcmp(key,"signature") ) { signature = value; } //printf( "\t%s = %s\n", key, value ); } // Make sure we got all the required information if( !signature ) { printf( "! No signature\n" ); goto failed; } if( !headers ) { printf( "! No headers\n" ); goto failed; } if( !key_id ) { printf( "! no key_id\n" ); goto failed; } // Get actor URI if( 0 == strcmp(key_id,"Test") ) { keys = crypto_keys_new(); if( !crypto_keys_load_public( keys, "assets/test.public.pem" ) ) { printf( "! Failed to load public key\n" ); goto failed; } } else { char* key_name = NULL; char* actor_uri = strtok_r( key_id, "#", &key_name ); if( !actor_uri ) { printf( "! Failed to get actor\n" ); goto failed; } if( 0 != strcmp( actor_uri, expected_actor ) ) { printf( "! Signature doesn't match actor (Expected %s, got %s)\n", expected_actor, actor_uri ); goto failed; } // Get the account actor = account_from_uri_or_fetch(actor_uri); if( !actor ) { printf( "! failed to load account for %s\n", actor_uri ); goto failed; } // Get the public key keys = account_get_public_key( actor, key_name ); if( !keys ) { goto failed; } } // Create the hash line size_t hash_line_size = 0; FILE* hl = open_memstream( &hash_line, &hash_line_size ); bool is_first = true; for( char* part = strtok_r( headers, " ", &rem ); part; part = strtok_r(NULL," ",&rem) ) { //printf( "header: %s\n", part ); if( !is_first ) { fprintf( hl, "\n" ); } if( 0 == strcmp(part,"(request-target)") ) { fprintf( hl, "(request-target): %s", request_target ); } else if( 0 == strcmp(part,"digest") ) { if( !validate_body_digest( env ) ) { printf( "! Digest validation failed" ); goto failed; } print_header_line( hl, env, part ); } else { print_header_line( hl, env, part ); } is_first = false; } fclose(hl); hash_line = realloc(hash_line, hash_line_size+1 ); hash_line[hash_line_size] = '\0'; //printf( "\nhash_line:\n%s|\n\n", hash_line ); //result = crypto_keys_verify( keys, raw_hash, 32, signature ); result = crypto_keys_verify( keys, hash_line, hash_line_size, signature ); if( !result ) { printf( "! Signature is not valid\n" ); goto failed; } result = true; cleanup: crypto_keys_free(keys); account_free(actor); free(signature_header); free(date_header); free(hash_line); return result; failed: result = false; goto cleanup; } void http_signature_free( struct http_signature* sign ) { free(sign->host); free(sign->date); free(sign->signature); free(sign->digest); }