#include <string.h>
#include "hss_aux.h"
#include "hss_internal.h"
#include "common_defs.h"
#include "lm_common.h"
#include "endian.h"
#include "hash.h"
#include "hss_zeroize.h"
#define AUX_DATA_MARKER 0
#define NO_AUX_DATA 0x00
#define AUX_DATA_HASHES 4
static void compute_seed_derive( unsigned char *result, unsigned hash,
const unsigned char *seed, union hash_context *ctx);
static void compute_hmac( unsigned char *dest,
unsigned hash, unsigned size_hash,
union hash_context *ctx,
unsigned char *key,
const unsigned char *data, size_t len_data);
aux_level_t hss_optimal_aux_level( size_t max_length,
const param_set_t *lm_type,
const param_set_t *lm_ots_type,
size_t *actual_len ) {
unsigned h0;
unsigned size_hash;
LMS_UNUSED(lm_ots_type);
if (!lm_look_up_parameter_set(lm_type[0], NULL, &size_hash, &h0)) {
return 0;
}
if (max_length < AUX_DATA_HASHES + size_hash) {
if (actual_len) *actual_len = 1;
return 0;
}
size_t orig_max_length = max_length;
max_length -= AUX_DATA_HASHES + size_hash;
aux_level_t aux_level = 0;
unsigned subtree_size = hss_smallest_subtree_size(h0, 0, size_hash);
unsigned level = h0 % subtree_size;
if (level == 0) level = subtree_size;
for (; level < h0; level += subtree_size) {
size_t len_this_level = (size_t)size_hash<<level;
if (max_length >= len_this_level) {
max_length -= len_this_level;
aux_level |= 0x80000000UL | ((aux_level_t)1<<level);
} else {
break;
}
}
if (actual_len) *actual_len = orig_max_length - max_length;
return aux_level;
}
static unsigned memcmp_consttime( const void *a, const void *b, size_t n ) {
unsigned sum = 0;
const unsigned char *p = a;
const unsigned char *q = b;
while (n--) {
sum += *p++ ^ *q++;
}
return sum;
}
struct expanded_aux_data *hss_expand_aux_data( const unsigned char *aux_data,
size_t len_aux_data,
struct expanded_aux_data *temp, unsigned size_hash,
struct hss_working_key *w ) {
if (!aux_data || aux_data[AUX_DATA_MARKER] == NO_AUX_DATA) return 0;
const unsigned char *orig_aux_data = aux_data;
unsigned long aux_level = (unsigned long)get_bigendian( aux_data, 4 );
aux_data += 4;
aux_level &= 0x7ffffffffL;
unsigned h;
for (h = 0; h <= MAX_MERKLE_HEIGHT; h++, aux_level >>= 1) {
if (aux_level & 1) {
temp->data[h] = (void *)aux_data;
aux_data += (size_t)size_hash << h;
} else {
temp->data[h] = 0;
}
}
if (w) {
size_t expected_len = (aux_data - orig_aux_data) + size_hash;
if (expected_len > len_aux_data) {
return 0;
}
if (len_aux_data < 4 + size_hash) return 0;
union hash_context ctx;
unsigned char key[ MAX_HASH ];
compute_seed_derive( key, w->tree[0]->h, w->working_key_seed, &ctx );
unsigned char expected_mac[ MAX_HASH ];
compute_hmac( expected_mac, w->tree[0]->h, size_hash, &ctx, key,
orig_aux_data, aux_data - orig_aux_data );
hss_zeroize( key, size_hash );
hss_zeroize( &ctx, sizeof ctx );
if (0 != memcmp_consttime( expected_mac, aux_data, size_hash)) {
return 0;
}
}
return temp;
}
size_t hss_get_aux_data_len(size_t max_length,
unsigned levels,
const param_set_t *lm_type,
const param_set_t *lm_ots_type) {
size_t len = 0;
LMS_UNUSED(levels);
if (!hss_optimal_aux_level( max_length, lm_type, lm_ots_type, &len )) {
return 1;
}
return len;
}
void hss_store_aux_marker( unsigned char *aux_data, aux_level_t aux_level ) {
if (aux_level == 0) {
aux_data[AUX_DATA_MARKER] = NO_AUX_DATA;
} else {
put_bigendian( &aux_data[AUX_DATA_MARKER], aux_level, 4 );
}
}
void hss_save_aux_data( struct expanded_aux_data *data, unsigned level,
unsigned size_hash, merkle_index_t q,
const unsigned char *cur_val ) {
if (!data) return;
if (!data->data[level]) return;
memcpy( data->data[level] + size_hash * q, cur_val, size_hash );
}
static void compute_seed_derive( unsigned char *result, unsigned hash,
const unsigned char *seed, union hash_context *ctx) {
hss_init_hash_context( hash, ctx );
unsigned char prefix[ DAUX_PREFIX_LEN ];
memset( prefix, 0, DAUX_D );
SET_D( prefix + DAUX_D, D_DAUX );
hss_update_hash_context( hash, ctx, prefix, sizeof prefix );
hss_update_hash_context( hash, ctx, seed, SEED_LEN );
hss_finalize_hash_context( hash, ctx, result );
hss_zeroize( &ctx, sizeof ctx );
}
static void xor_key( unsigned char *key, unsigned xor_val, unsigned len_key) {
unsigned i;
for (i = 0; i<len_key; i++) {
key[i] ^= xor_val;
}
}
#define IPAD 0x36
#define OPAD 0x5c
static void compute_hmac( unsigned char *dest,
unsigned hash, unsigned size_hash,
union hash_context *ctx,
unsigned char *key,
const unsigned char *data, size_t len_data) {
unsigned block_size = hss_hash_blocksize(hash);
hss_init_hash_context( hash, ctx );
xor_key( key, IPAD, size_hash );
hss_update_hash_context( hash, ctx, key, size_hash );
unsigned j;
for (j = size_hash; j<block_size; j++) {
const unsigned char ipad = IPAD;
hss_update_hash_context( hash, ctx, &ipad, 1 );
}
hss_update_hash_context( hash, ctx, data, len_data );
hss_finalize_hash_context( hash, ctx, dest );
hss_init_hash_context( hash, ctx );
xor_key( key, IPAD^OPAD, size_hash );
hss_update_hash_context( hash, ctx, key, size_hash );
for (j = size_hash; j<block_size; j++) {
const unsigned char opad = OPAD;
hss_update_hash_context( hash, ctx, &opad, 1 );
}
hss_update_hash_context( hash, ctx, dest, size_hash );
hss_finalize_hash_context( hash, ctx, dest );
xor_key( key, OPAD, size_hash );
}
void hss_finalize_aux_data( struct expanded_aux_data *data,
unsigned size_hash, unsigned hash,
const unsigned char *seed) {
if (!data) return;
union hash_context ctx;
unsigned char aux_seed[ MAX_HASH ];
compute_seed_derive( aux_seed, hash, seed, &ctx );
unsigned char *aux = 0;
size_t total_length = 4;
unsigned i;
for (i = 0; i<MAX_MERKLE_HEIGHT+1; i++) {
if (data->data[i]) {
total_length += (size_t)size_hash << i;
if (!aux) {
aux = data->data[i] - 4;
}
}
}
if (aux) {
compute_hmac( aux+total_length, hash, size_hash, &ctx, aux_seed,
aux, total_length );
}
hss_zeroize( &ctx, sizeof ctx );
hss_zeroize( aux_seed, size_hash );
}
bool hss_extract_aux_data(const struct expanded_aux_data *aux, unsigned level,
const struct hss_working_key *w, unsigned char *dest,
merkle_index_t node_offset,
merkle_index_t node_count) {
if (!aux) return false;
if (!aux->data[level]) return false;
unsigned hash_size = w->tree[0]->hash_size;
memcpy( dest,
aux->data[level] + node_offset*hash_size,
node_count * hash_size );
return true;
}