Skip to content

Commit

Permalink
Reimplement tr31_init_from_header() inside TR-31 library
Browse files Browse the repository at this point in the history
This eliminates the need for tr31-tool to create fake key blocks with
fake optional block padding for the purpose of simply parsing a key
block header.
  • Loading branch information
leonlynch committed Nov 11, 2023
1 parent c9c2d66 commit 0d55d20
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 113 deletions.
114 changes: 1 addition & 113 deletions src/tr31-tool.c
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@ static int parse_hex(const char* hex, void* bin, size_t bin_len);
static void print_hex(const void* buf, size_t length);
static void print_str(const void* buf, size_t length);
static void print_str_with_quotes(const void* buf, size_t length);
static int tr31_init_from_header(const char* header, struct tr31_ctx_t* tr31_ctx);

// argp option keys
enum tr31_tool_option_keys_t {
Expand Down Expand Up @@ -659,117 +658,6 @@ static void print_str_with_quotes(const void* buf, size_t length)
printf("\"");
}

static int tr31_init_from_header(const char* header, struct tr31_ctx_t* tr31_ctx)
{
int r;
size_t header_len;
size_t enc_block_size;
size_t payload_and_authenticator_len;
char* padded_header = NULL;
size_t key_block_len;

header_len = strlen(header);
if (header_len < 16) {
return TR31_ERROR_INVALID_LENGTH_FIELD;
}

// determine encryption block size and payload+authenticator length
switch (header[0]) {
case 'A':
case 'C':
enc_block_size = 8; // DES block size
payload_and_authenticator_len = 32 + 8;
break;

case 'B':
enc_block_size = 8; // DES block size
payload_and_authenticator_len = 32 + 16;
break;

case 'D':
case 'E':
enc_block_size = 16; // AES block size
payload_and_authenticator_len = 48 + 16;
break;

default:
return TR31_ERROR_UNSUPPORTED_VERSION;
}

// ensure that header length is a multiple of encryption block size
// and add fake optional block padding if necessary
if (header_len & (enc_block_size-1)) {
unsigned int pb_len = 4; // minimum length of optional block PB

if (header[12] != '0' || header[13] > '8') {
// only support single digit optional block counts below 9 for now
return TR31_ERROR_INVALID_NUMBER_OF_OPTIONAL_BLOCKS_FIELD;
}

// compute required padding length
if ((header_len + pb_len) & (enc_block_size-1)) { // if further padding is required
pb_len = ((header_len + 4 + enc_block_size) & ~(enc_block_size-1)) - header_len;
}

// sanity check
if (pb_len < 4 || pb_len > 15) {
return -1;
}

// build new header
header_len = header_len + pb_len;
padded_header = malloc(header_len + 1);
snprintf(
padded_header,
header_len + 1,
"%sPB%02X%.*s",
header,
pb_len,
pb_len - 4,
"000000000000000"
);
padded_header[13]++; // increment optional block count
header = padded_header;
}

// determine fake key block length to allow parsing of header
key_block_len = header_len + payload_and_authenticator_len;
if (key_block_len > 9999) {
fprintf(stderr, "Export header too large\n");
return TR31_ERROR_INVALID_LENGTH_FIELD;
}

// build fake key block to allow parsing of header
char tmp_keyblock[key_block_len];
memcpy(tmp_keyblock, header, header_len);
memset(tmp_keyblock + header_len, '0', sizeof(tmp_keyblock) - header_len);

// fix length field to allow parsing of header
char tmp[5];
snprintf(tmp, sizeof(tmp), "%04zu", sizeof(tmp_keyblock));
memcpy(tmp_keyblock + 1, tmp, 4);

// misuse TR-31 import function to parse header into TR-31 context object
r = tr31_import(tmp_keyblock, sizeof(tmp_keyblock), NULL, tr31_ctx);
if (r) {
return r;
}

// cleanup padded header and remove fake optional block PB from context object
if (padded_header) {
free(padded_header);
if (tr31_ctx->opt_blocks_count) {
tr31_ctx->opt_blocks_count -= 1;
if (tr31_ctx->opt_blocks[tr31_ctx->opt_blocks_count].data) {
free(tr31_ctx->opt_blocks[tr31_ctx->opt_blocks_count].data);
tr31_ctx->opt_blocks[tr31_ctx->opt_blocks_count].data = NULL;
}
}
}

return 0;
}

// TR-31 KBPK populating helper function
static int populate_kbpk(const struct tr31_tool_options_t* options, unsigned int format_version, struct tr31_key_t* kbpk)
{
Expand Down Expand Up @@ -1023,7 +911,7 @@ static int populate_tr31_from_header(const struct tr31_tool_options_t* options,
int r;

// parse export header
r = tr31_init_from_header(options->export_header, tr31_ctx);
r = tr31_init_from_header(options->export_header, strlen(options->export_header), tr31_ctx);
if (r) {
fprintf(stderr, "Error while parsing export header; error %d: %s\n", r, tr31_get_error_string(r));
return 1;
Expand Down
104 changes: 104 additions & 0 deletions src/tr31.c
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,108 @@ int tr31_init(
return 0;
}

int tr31_init_from_header(
const char* key_block_header,
size_t key_block_header_len,
struct tr31_ctx_t* ctx
)
{
int r;
const struct tr31_header_t* header;
const void* ptr;

if (!key_block_header || !ctx) {
return -1;
}

// NOTE: the implementation of this function should be kept up to date
// with the implementation of tr31_import()

// validate minimum key block header length
if (key_block_header_len < sizeof(struct tr31_header_t)) {
return TR31_ERROR_INVALID_LENGTH;
}

// validate key block header as printable ASCII (format PA)
r = tr31_validate_format_pa(key_block_header, key_block_header_len);
if (r) {
return TR31_ERROR_INVALID_KEY_BLOCK_STRING;
}

// initialise TR-31 context object
header = (const struct tr31_header_t*)key_block_header;
r = tr31_init(header->version_id, NULL, ctx);
if (r) {
// return error value as-is
return r;
}

// decode header fields associated with wrapped key
r = tr31_key_init(
ntohs(header->key_usage),
header->algorithm,
header->mode_of_use,
header->key_version,
header->exportability,
NULL,
0,
&ctx->key
);
if (r) {
// return error value as-is
return r;
}

// decode number of optional blocks field
int opt_blocks_count = dec_to_int(header->opt_blocks_count, sizeof(header->opt_blocks_count));
if (opt_blocks_count < 0) {
return TR31_ERROR_INVALID_NUMBER_OF_OPTIONAL_BLOCKS_FIELD;
}
ctx->opt_blocks_count = opt_blocks_count;

// decode optional blocks
// see ANSI X9.143:2021, 6.3.6
ptr = header + 1; // optional blocks, if any, are after the header
if (ctx->opt_blocks_count) {
ctx->opt_blocks = calloc(ctx->opt_blocks_count, sizeof(ctx->opt_blocks[0]));
}
for (int i = 0; i < opt_blocks_count; ++i) {
// ensure that current pointer is valid for minimal optional block
if (ptr + sizeof(struct tr31_opt_blk_t) - (void*)header > key_block_header_len) {
r = TR31_ERROR_INVALID_LENGTH;
goto error;
}

// copy optional block field
size_t opt_blk_len;
r = tr31_opt_block_parse(
ptr,
(void*)key_block_header + key_block_header_len - ptr,
&opt_blk_len,
&ctx->opt_blocks[i]
);
if (r) {
// return error value as-is
goto error;
}

// advance current pointer
ptr += opt_blk_len;
}

// NOTE: the total optional block length is intentially ignored and not
// validated against the encryption block length

// success
r = 0;
goto exit;

error:
tr31_release(ctx);
exit:
return r;
}

static struct tr31_opt_ctx_t* tr31_opt_block_alloc(
struct tr31_ctx_t* ctx,
unsigned int id,
Expand Down Expand Up @@ -1520,6 +1622,7 @@ int tr31_import(
return -1;
}

// success
r = 0;
goto exit;

Expand Down Expand Up @@ -1803,6 +1906,7 @@ int tr31_export(
goto error;
}

// success
r = 0;
goto exit;

Expand Down
24 changes: 24 additions & 0 deletions src/tr31.h
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,30 @@ int tr31_init(
struct tr31_ctx_t* ctx
);

/**
* Initialise TR-31 context object from key block header. The header may also
* include optional blocks.
*
* @note The length specified in the key block header will be ignored.
*
* @note The total length of all optional blocks will not be validated against
* the encryption block length and it is therefore not necessary, but
* also not prohibited, to include optional block 'PB' for padding.
*
* @note Use @ref tr31_release() to release internal resources when done.
*
* @param key_block_header Key block header. If present, key block payload and
* authenticator will be ignored.
* @param key_block_header_len Length of @p key_block_header in bytes. Must be
* at least 16 bytes.
* @param ctx TR-31 context object output
*/
int tr31_init_from_header(
const char* key_block_header,
size_t key_block_header_len,
struct tr31_ctx_t* ctx
);

/**
* Add optional block to TR-31 context object
*
Expand Down

0 comments on commit 0d55d20

Please sign in to comment.