Move base64 code to separate file, work on implementing HTTP Signature validation

master
teknomunk 1 year ago
parent df589eb9e0
commit bc3b4daebc

@ -132,14 +132,6 @@ bool route_activity( struct ap_activity* act )
return false;
}
bool validate_signature( struct ap_envelope* env )
{
// TODO: validate signature
printf( "TODO: validate signature\n" );
return false;
}
bool process_one()
{
// Items requiring cleanup
@ -163,13 +155,13 @@ bool process_one()
printf( "Failed to parse envelope+activity for data/inbox/%d.json\n", id );
return false;
}
validate_signature(env);
// Validate signature
env->validated = http_signature_validate( env );
// Load activity
FILE* f = fmemopen( env->body, strlen(env->body), "r" );
act = ap_activity_from_FILE(f);
fclose(f);
if( !act ) { goto failed; }
// Discard delete requests

@ -10,6 +10,7 @@
// Model
#include "model/server.h"
#include "model/ap/account.h"
#include "model/crypto/keys.h"
// Stdlib
#include <stdio.h>
@ -127,11 +128,44 @@ void account_sync_from_acitvity_pub( unsigned int account_id )
a->server = server_name;
}
// Extract out the public key
char* id = strdup(ap->public_key.id);
char* key_id = NULL;
strtok_r( id, "#", &key_id );
snprintf( filename, sizeof(filename), "data/accounts/%d/%s.pem", a->id, key_id );
FILE* key_pem = fopen(filename,"w");
fprintf( key_pem, "%s", ap->public_key.pem );
fclose(key_pem);
account_save(a);
ap_account_free(ap);
account_free(a);
}
struct crypto_keys* account_get_public_key( struct account* a, const char* key_name )
{
char filename[512];
snprintf( filename, sizeof(filename), "data/accounts/%d/%s.pem", a->id, key_name );
FILE* f = fopen( filename, "r" );
if( !f ) {
printf( "Failed to open file %s\n", filename );
return NULL;
}
fclose(f);
struct crypto_keys* keys = crypto_keys_new();
if( crypto_keys_load_public( keys, filename ) ) {
return keys;
}
printf( "Failed to load public key from %s\n", filename );
crypto_keys_free(keys);
return NULL;
}
struct crypto_keys* account_get_private_key( struct account* a )
{
return NULL;
}
static void create_account_skeleton( int account_id )
{

@ -3,6 +3,8 @@
#include <stdio.h>
#include <stdbool.h>
struct crypto_keys;
enum account_type
{
at_owner = 1,
@ -38,6 +40,8 @@ struct account* account_new();
void account_free( struct account* a );
void account_save( struct account* a );
void account_sync_from_acitvity_pub( unsigned int id );
struct crypto_keys* account_get_public_key( struct account* a, const char* key_name );
struct crypto_keys* account_get_private_key( struct account* a );
void account_add_follower( struct account* a, struct account* follower );
void account_remove_follower( struct account* a, struct account* follower );

@ -0,0 +1,57 @@
#include "base64.h"
#include <stdint.h>
#include <stdlib.h>
char* base64_strict_encode( void* v_binary, size_t len )
{
// RFC 4648 - https://www.rfc-editor.org/rfc/rfc4648.html
static const char* alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
enum { pad = '=' };
uint8_t* binary = v_binary;
const int groups = ( len + 2 ) / 3;
const int padding = len % 3;
char* result = malloc( groups * 4 + 1 );
result[groups*4] = '\0';
uint32_t word;
char* out = result;
for( int remain = len; remain > 0; remain -= 3, binary += 3, out += 4 ) {
word = 0;
switch( remain ) {
default:
word |= binary[2] << (8*0);
case 2:
word |= binary[1] << (8*1);
case 1:
word |= binary[0] << (8*2);
}
switch( remain ) {
default:
out[3] = alphabet[ ( word >> (6*0) ) % 64 ];
case 2:
out[2] = alphabet[ ( word >> (6*1) ) % 64 ];
case 1:
out[1] = alphabet[ ( word >> (6*2) ) % 64 ];
out[0] = alphabet[ ( word >> (6*3) ) % 64 ];
}
switch( remain ) {
case 1:
out[3] = '=';
out[2] = '=';
break;
case 2:
out[3] = '=';
break;
}
}
return result;
}
bool base64_decode( const char* str, void** v_binary, size_t* len )
{
return false;
}

@ -0,0 +1,8 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
char* base64_strict_encode( void* v_binary, size_t len );
bool base64_decode( const char* str, void** v_binary, size_t* len );

@ -1,7 +1,10 @@
#include "http_sign.h"
#include "sha256/sha256.h"
#include "model/account.h"
#include "model/crypto/keys.h"
#include "model/crypto/base64.h"
#include "model/ap/inbox_envelope.h"
#include <stdlib.h>
#include <stdio.h>
@ -54,6 +57,212 @@ bool http_signature_make( const char* inbox, struct crypto_keys* keys, struct ht
return true;
}
static void print_downcase( FILE* f, const char* str )
{
for(;*str;++str) {
switch( *str ) {
case 'A' ... 'Z':
fputc( *str - 'A' + 'a', f );
break;
default:
fputc( *str, f );
break;
}
}
}
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;
}
}
fprintf( hl, "\n" );
}
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
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);
return result;
failed:
result = false;
goto cleanup;
}
bool http_signature_validate( struct ap_envelope* env )
{
char* signature_header = NULL;
char* date_header = NULL;
char* algorithm = NULL;
char* key_id = NULL;
char* headers = NULL;
char* signature = NULL;
bool result = false;
// 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;
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 ) { goto failed; }
if( !headers ) { goto failed; }
if( !key_id ) { goto failed; }
// Get actor URI
char* key_name = NULL;
char* actor_uri = strtok_r( key_id, "#", &key_name );
if( !actor_uri ) { goto failed; }
// Get the account
struct account* actor = account_fetch_from_uri(actor_uri);
if( !actor ) { printf( "failed to load account for %s\n", actor_uri ); goto failed; }
// Get the public key
struct crypto_keys* keys = account_get_public_key( actor, key_name );
if( !keys ) { goto failed; }
// Create the hash line
char* hash_line = NULL;
size_t hash_line_size = 0;
FILE* hl = open_memstream( &hash_line, &hash_line_size );
for( char* part = strtok_r( headers, " ", &rem ); part; part = strtok_r(NULL," ",&rem) ) {
printf( "header: %s\n", part );
if( 0 == strcmp(part,"(request-target)") ) {
fprintf( hl, "(request-target): post /inbox\n" );
} else if( 0 == strcmp(part,"digest") ) {
if( !validate_body_digest( env ) ) {
printf( "Digest validation failed\n" );
goto failed;
}
print_header_line( hl, env, part );
} else {
print_header_line( hl, env, part );
}
}
fclose(hl);
hash_line = realloc(hash_line, hash_line_size+1 );
hash_line[hash_line_size] = '\0';
printf( "\nhash_line:\n%s\n", hash_line );
char raw_hash[32];
sha256_easy_hash( hash_line, hash_line_size, raw_hash );
char* signature_bin = NULL;
size_t signature_len = 0;
if( !base64_decode( signature, (void**)&signature_bin, &signature_len ) ) {
printf( "Failed to decode base64 signature\n" );
goto failed;
}
printf( "TODO: validate HTTP signature - so far, so good\n" );
cleanup:
free(signature_header);
return result;
failed:
result = false;
goto cleanup;
}
void http_signature_free( struct http_signature* sign )
{

@ -4,6 +4,7 @@
struct crypto_keys;
struct ap_envelope;
struct http_signature
{
char* host;
@ -13,5 +14,5 @@ struct http_signature
bool http_signature_make( const char* inbox, struct crypto_keys* keys, struct http_signature* sign );
void http_signature_free( struct http_signature* sign );
bool http_signature_validate( struct http_signature* sign );
bool http_signature_validate( struct ap_envelope* env );

@ -1,5 +1,7 @@
#include "keys.h"
#include "model/crypto/base64.h"
#include <openssl/pem.h>
#include <openssl/evp.h>
#include <openssl/err.h>
@ -48,54 +50,20 @@ bool crypto_keys_load_private( struct crypto_keys* keys, const char* filename )
return !!keys->privkey;
}
char* base64_strict_encode( void* v_binary, size_t len )
bool crypto_keys_load_public( struct crypto_keys* keys, const char* filename )
{
// RFC 4648 - https://www.rfc-editor.org/rfc/rfc4648.html
static const char* alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
enum { pad = '=' };
uint8_t* binary = v_binary;
const int groups = ( len + 2 ) / 3;
const int padding = len % 3;
char* result = malloc( groups * 4 + 1 );
result[groups*4] = '\0';
uint32_t word;
char* out = result;
for( int remain = len; remain > 0; remain -= 3, binary += 3, out += 4 ) {
word = 0;
switch( remain ) {
default:
word |= binary[2] << (8*0);
case 2:
word |= binary[1] << (8*1);
case 1:
word |= binary[0] << (8*2);
}
switch( remain ) {
default:
out[3] = alphabet[ ( word >> (6*0) ) % 64 ];
case 2:
out[2] = alphabet[ ( word >> (6*1) ) % 64 ];
case 1:
out[1] = alphabet[ ( word >> (6*2) ) % 64 ];
out[0] = alphabet[ ( word >> (6*3) ) % 64 ];
}
switch( remain ) {
case 1:
out[3] = '=';
out[2] = '=';
break;
case 2:
out[3] = '=';
break;
}
if( keys->pubkey ) {
EVP_PKEY_free( keys->pubkey );
keys->pubkey = NULL;
}
FILE* f = fopen(filename,"r");
return result;
keys->pubkey = PEM_read_PUBKEY( f, NULL, NULL, NULL );
fclose(f);
return !!keys->pubkey;
}
char* crypto_keys_sign( struct crypto_keys* keys, void* data, unsigned int size )
{
char buffer[512];

Loading…
Cancel
Save