Start test suite, implement base64_decode, validate http signatures

master
teknomunk 1 year ago
parent bc3b4daebc
commit b5a229267b

@ -0,0 +1,7 @@
The private/public key pair included here is for the test suite, and is publicly available
at the following location:
https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-08#appendix-C
Messages about these two keys being checked into the source repository will be ignored.

@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXgIBAAKBgQDCFENGw33yGihy92pDjZQhl0C36rPJj+CvfSC8+q28hxA161QF
NUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6Z4UMR7EOcpfdUE9Hf3m/hs+F
UR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJwoYi+1hqp1fIekaxsyQIDAQAB
AoGBAJR8ZkCUvx5kzv+utdl7T5MnordT1TvoXXJGXK7ZZ+UuvMNUCdN2QPc4sBiA
QWvLw1cSKt5DsKZ8UETpYPy8pPYnnDEz2dDYiaew9+xEpubyeW2oH4Zx71wqBtOK
kqwrXa/pzdpiucRRjk6vE6YY7EBBs/g7uanVpGibOVAEsqH1AkEA7DkjVH28WDUg
f1nqvfn2Kj6CT7nIcE3jGJsZZ7zlZmBmHFDONMLUrXR/Zm3pR5m0tCmBqa5RK95u
412jt1dPIwJBANJT3v8pnkth48bQo/fKel6uEYyboRtA5/uHuHkZ6FQF7OUkGogc
mSJluOdc5t6hI1VsLn0QZEjQZMEOWr+wKSMCQQCC4kXJEsHAve77oP6HtG/IiEn7
kpyUXRNvFsDE0czpJJBvL/aRFUJxuRK91jhjC68sA7NsKMGg5OXb5I5Jj36xAkEA
gIT7aFOYBFwGgQAQkWNKLvySgKbAZRTeLBacpHMuQdl1DfdntvAyqpAZ0lY0RKmW
G6aFKaqQfOXKCyWoUiVknQJAXrlgySFci/2ueKlIE1QqIiLSZ8V8OlpFLRnb1pzI
7U1yQXnTAEFYM560yJlzUpOb1V4cScGd365tiSMvxLOvTA==
-----END RSA PRIVATE KEY-----

@ -0,0 +1,6 @@
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCFENGw33yGihy92pDjZQhl0C3
6rPJj+CvfSC8+q28hxA161QFNUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6
Z4UMR7EOcpfdUE9Hf3m/hs+FUR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJw
oYi+1hqp1fIekaxsyQIDAQAB
-----END PUBLIC KEY-----

@ -157,7 +157,7 @@ bool process_one()
}
// Validate signature
env->validated = http_signature_validate( env );
env->validated = http_signature_validate( env, "post /inbox" );
// Load activity
FILE* f = fmemopen( env->body, strlen(env->body), "r" );

@ -0,0 +1,10 @@
#include "test.h"
#include "test/crypto.h"
#include <stdio.h>
void built_in_test()
{
if( !test_crypto() ) { printf( "[FAIL] test_crypto\n" ); }
}

@ -0,0 +1,4 @@
#pragma once
void built_in_test();

@ -0,0 +1,8 @@
src/controller/test/.crypt-f203bfe5.o: src/controller/test/crypt.c \
/usr/include/stdc-predef.h src/controller/test/crypto.h \
/usr/lib/gcc/x86_64-pc-linux-gnu/12.2.0/include/stdbool.h \
src/model/crypto/http_sign.h
/usr/include/stdc-predef.h:
src/controller/test/crypto.h:
/usr/lib/gcc/x86_64-pc-linux-gnu/12.2.0/include/stdbool.h:
src/model/crypto/http_sign.h:

@ -0,0 +1,79 @@
#include "crypto.h"
#include "http_server/header.h"
#include "model/ap/inbox_envelope.h"
#include "model/crypto/keys.h"
#include "model/crypto/http_sign.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static bool test_signatures()
{
struct crypto_keys* keys = crypto_keys_new();
if( !crypto_keys_load_public( keys, "assets/test.public.pem" ) ) {
printf( "[FAIL] unable to load assets/test.public.pem\n" );
return false;
}
if( !crypto_keys_load_private( keys, "assets/test.private.pem" ) ) {
printf( "[FAIL] unable to load assets/test.private.pem\n" );
return false;
}
char* data = "This is a test of the emergency broadcast system.";
char* sign = crypto_keys_sign( keys, data, strlen(data) );
if( !sign ) {
printf( "[FAIL] unable to sign data\n" );
return false;
}
free(sign);
crypto_keys_free(keys);
return true;
}
static bool test_http_signature()
{
// https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-08#C.3 All Headers Test
struct http_header headers[] = {
{ .key = "Host", .value = "example.com" },
{ .key = "Date", .value = "Thu, 05 Jan 2014 21:31:40 GMT" },
{ .key = "Content-Type", .value = "application/json" },
{ .key = "Digest", .value = "SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=" },
{ .key = "Content-Length", .value = "18" },
{ .key = "Signature", .value =
"keyId=\"Test\",algorithm=\"rsa-sha256\","
"headers=\"(request-target) host date content-type digest content-length\","
"signature=\"Ef7MlxLXoBovhil3AlyjtBwAL9g4TN3tibLj7uuNB3CROat/9Kae"
"Q4hW2NiJ+pZ6HQEOx9vYZAyi+7cmIkmJszJCut5kQLAwuX+Ms/mUFvpKlSo9StS"
"2bMXDBNjOh4Auj774GFj4gwjS+3NhFeoqyr/MuN6HsEnkvn6zdgfE2i0=\""
},
};
struct ap_envelope env = {
.when = "1388957500000000000",
.headers = {
.items = headers,
.count = sizeof(headers) / sizeof(headers[0]),
},
.validated = false,
.body = "{\"hello\": \"world\"}",
};
return http_signature_validate( &env, "post /foo?param=value&pet=dog" );
}
bool test_crypto()
{
bool result = true;
if( !test_signatures() ) { printf( "[FAIL] test_signatures()\n" ); return false; }
if( !test_http_signature() ) { printf( "[FAIL] test_http_signature()\n" ); return false; }
return true;
}

@ -0,0 +1,6 @@
#pragma once
#include <stdbool.h>
bool test_crypto();

@ -1 +1 @@
Subproject commit 84141b7260faf667d733d571ea0e81ac42fb0907
Subproject commit 5205ad49d932341983ef5c8ef5b0c09433f8e06f

@ -13,6 +13,7 @@
#include "controller/main.h"
#include "controller/inbox.h"
#include "controller/outbox.h"
#include "controller/test.h"
#include <curl/curl.h>
@ -75,8 +76,9 @@ int main( int argc, char* argv[] )
int section = 0;
sscanf(argv[1],"--section=%d",&section);
switch( section ) {
case 1: process_inbox(); return 0;
case 2: process_outbox(); return 0;
case 1: process_inbox(); goto exit;
case 2: process_outbox(); goto exit;
case 3: built_in_test(); goto exit;
case 100: develop(); return 0;
}
@ -100,6 +102,7 @@ int main( int argc, char* argv[] )
int code = 0;
if( !run_webserver(args) ) { code = 1; }
exit:
app_args_release(args);
return code;

@ -2,11 +2,14 @@
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
static const char* alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
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;
@ -52,6 +55,55 @@ char* base64_strict_encode( void* v_binary, size_t len )
}
bool base64_decode( const char* str, void** v_binary, size_t* len )
{
return false;
int input_length = strlen(str);
uint8_t* data = malloc( ( input_length * 6 + 7 ) / 8 );
*v_binary = data;
*len = 0;
uint32_t buffer = 0;
int bits = 0;
uint8_t part;
for(;;) {
//printf( " - Input %02X (%c)\n", *str, *str );
switch( *str ) {
case 'A' ... 'Z': part = *str - 'A'; break;
case 'a' ... 'z': part = *str - 'a' + 26; break;
case '0' ... '9': part = *str - '0' + 52; break;
case '+': part = 62; break;
case '/': part = 63; break;
case '=': goto finish;
case '\0':
goto done;
default:
free( *v_binary );
*len = 0;
return false;
};
++str;
buffer = ( buffer << 6 ) | part;
bits += 6;
//printf( "buffer=%08X, bits=%d\n", buffer, bits );
if( bits > 8 ) {
uint8_t byte = buffer >> ( bits % 8 );
*data = byte;
//printf( " - Output: %02X (%c)\n", byte, byte );
buffer &= ( 1 << (bits%8) ) - 1;
*len += 1;
++data;
bits -= 8;
}
}
done:
*len += 1;
finish:
uint8_t byte = buffer >> ( bits % 8 );
*data = byte;
++data;
bits = 0;
return true;
}

@ -17,7 +17,7 @@ bool http_signature_make( const char* inbox, struct crypto_keys* keys, struct ht
memset(sign,0,sizeof(*sign));
if( 0 != strncmp( "https://", inbox, 8 ) ) {
printf( "Invalid inbox: %s\n", inbox );
printf( "! Invalid inbox: %s\n", inbox );
return false;
}
@ -85,8 +85,6 @@ void print_header_line( FILE* hl, struct ap_envelope* env, const char* header_na
first = false;
}
}
fprintf( hl, "\n" );
}
static bool validate_body_digest( struct ap_envelope* env )
@ -137,20 +135,18 @@ static bool validate_body_digest( struct ap_envelope* env )
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 )
bool http_signature_validate( struct ap_envelope* env, const char* request_target )
{
char* signature_header = NULL;
char* date_header = NULL;
char* algorithm = NULL;
char* key_id = NULL;
char* headers = NULL;
char* signature = NULL;
struct crypto_keys* keys = NULL;
bool result = false;
// Get the Signature header
@ -166,7 +162,7 @@ bool http_signature_validate( struct ap_envelope* env )
}
}
if( !signature_header ) { return false; }
printf( "Found Signature: %s\n", signature_header );
//printf( "Found Signature: %s\n", signature_header );
// Validate time
if( date_header ) {
@ -175,6 +171,10 @@ bool http_signature_validate( struct ap_envelope* env )
// 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;
@ -199,65 +199,84 @@ bool http_signature_validate( struct ap_envelope* env )
signature = value;
}
printf( "\t%s = %s\n", key, 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; }
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
char* key_name = NULL;
char* actor_uri = strtok_r( key_id, "#", &key_name );
if( !actor_uri ) { goto failed; }
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;
}
// 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 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; }
// Get the public key
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 );
bool is_first = true;
for( char* part = strtok_r( headers, " ", &rem ); part; part = strtok_r(NULL," ",&rem) ) {
printf( "header: %s\n", part );
//printf( "header: %s\n", part );
if( !is_first ) {
fprintf( hl, "\n" );
}
if( 0 == strcmp(part,"(request-target)") ) {
fprintf( hl, "(request-target): post /inbox\n" );
fprintf( hl, "(request-target): %s", request_target );
} else if( 0 == strcmp(part,"digest") ) {
if( !validate_body_digest( env ) ) {
printf( "Digest validation failed\n" );
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", hash_line );
//printf( "\nhash_line:\n%s|\n\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" );
//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;
}
printf( "TODO: validate HTTP signature - so far, so good\n" );
result = true;
cleanup:
crypto_keys_free(keys);
free(signature_header);
free(date_header);
free(hash_line);
return result;
failed:
result = false;

@ -14,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 ap_envelope* env );
bool http_signature_validate( struct ap_envelope* env, const char* request_target );

@ -1,6 +1,7 @@
#include "keys.h"
#include "model/crypto/base64.h"
#include "sha256/sha256.h"
#include <openssl/pem.h>
#include <openssl/evp.h>
@ -44,6 +45,7 @@ bool crypto_keys_load_private( struct crypto_keys* keys, const char* filename )
}
FILE* f = fopen(filename,"r");
if( !f ) { return false; }
keys->privkey = PEM_read_PrivateKey( f, NULL, NULL, NULL );
fclose(f);
@ -57,6 +59,7 @@ bool crypto_keys_load_public( struct crypto_keys* keys, const char* filename )
keys->pubkey = NULL;
}
FILE* f = fopen(filename,"r");
if( !f ) { return false; }
keys->pubkey = PEM_read_PUBKEY( f, NULL, NULL, NULL );
fclose(f);
@ -66,13 +69,16 @@ bool crypto_keys_load_public( struct crypto_keys* keys, const char* filename )
char* crypto_keys_sign( struct crypto_keys* keys, void* data, unsigned int size )
{
char buffer[512];
char* result = NULL;
char* sign_binary = NULL;
EVP_PKEY_CTX* ctx = NULL;
if( !keys->privkey ) { return NULL; }
// hash data with SHA-256
char hash[32];
sha256_easy_hash( data, size, hash );
// Setup for signature
// Code based on https://www.openssl.org/docs/man3.1/man3/EVP_PKEY_sign.html
ctx = EVP_PKEY_CTX_new(keys->privkey, NULL /* no engine */);
@ -88,10 +94,11 @@ char* crypto_keys_sign( struct crypto_keys* keys, void* data, unsigned int size
sign_binary = malloc(siglen);
if( !sign_binary ) { goto failed; }
int retcode = EVP_PKEY_sign(ctx, sign_binary, &siglen, data, size);
int retcode = EVP_PKEY_sign(ctx, sign_binary, &siglen, hash, 32);
if( retcode <= 0 ) {
char buffer[512];
ERR_error_string_n( retcode, buffer, sizeof(buffer) );
printf( "Failed to create signature: %s\n", buffer );
printf( "! Failed to create signature: %s\n", buffer );
goto failed;
}
@ -105,3 +112,52 @@ failed:
goto cleanup;
}
bool crypto_keys_verify( struct crypto_keys* keys, void* data, unsigned int size, char* signature )
{
bool result = false;
char* sign_binary = NULL;
EVP_PKEY_CTX* ctx = NULL;
char* signature_bin = NULL;
if( !keys->pubkey ) { goto failed; }
// hash data with SHA-256
char hash[32];
sha256_easy_hash( data, size, hash );
// Decode the signature
size_t signature_len = 0;
if( !base64_decode( signature, (void**)&signature_bin, &signature_len ) ) {
printf( "! Failed to decode base64 signature\n" );
goto failed;
}
// Setup for signature
// Code based on https://www.openssl.org/docs/man3.1/man3/EVP_PKEY_verify.html
ctx = EVP_PKEY_CTX_new(keys->pubkey, NULL /* no engine */);
if( !ctx ) { goto failed; }
if( EVP_PKEY_verify_init(ctx) <= 0 ) { goto failed; }
if( EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0 ) { goto failed; }
if( EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha256()) <= 0 ) { goto failed; }
int retcode = EVP_PKEY_verify(ctx, signature_bin, signature_len, hash, 32 );
if( retcode < 0 ) {
char buffer[512];
ERR_error_string_n( retcode, buffer, sizeof(buffer) );
printf( "! Failed to verify signature: %s\n", buffer );
goto failed;
}
if( retcode == 1 ) {
result = true;
}
cleanup:
free(signature_bin);
EVP_PKEY_CTX_free(ctx);
return result;
failed:
result = false;
goto cleanup;
}

@ -12,7 +12,7 @@ bool crypto_keys_load_public ( struct crypto_keys* keys, const char* filename );
bool crypto_keys_load_private( struct crypto_keys* keys, const char* filename );
char* crypto_keys_sign( struct crypto_keys* keys, void* data, unsigned int size );
bool crypto_keys_verify( struct crypto_keys* keys, void* data, unsigned int size );
bool crypto_keys_verify( struct crypto_keys* keys, void* data, unsigned int size, char* signature );
char* base64_strict_encode( void* v_binary, size_t len );

Loading…
Cancel
Save