From 3bd3f7bed05d6776bf012d16b2eba1787da4af73 Mon Sep 17 00:00:00 2001 From: Leon Lynch Date: Sun, 29 Oct 2023 11:19:16 +0100 Subject: [PATCH] Enhance --export-header option for tr31-tool * If the header (and optional blocks) are not already a multiple of the encryption block size, add fake optional block padding. * Currently this enhancement only supports an optional block count of at most 8 to avoid having to parse the optional block count digits. This does not impact tr31_import() in any way though and is purely a tr31-tool limitation. * The fake optional block padding is removed once tr31_import() succeeds such that tr31_export() can apply new optional block padding as required. * The processing of an export header is now encapsulated in tr31-tool as tr31_init_from_header() such that it can eventually be reused for other purposes too, like parsing verbatim optional block strings, or moved into the TR-31 library. --- README.md | 9 ++- src/CMakeLists.txt | 9 +++ src/tr31-tool.c | 154 +++++++++++++++++++++++++++++++++------------ 3 files changed, 132 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index e9b3600..9489ce3 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,14 @@ either a combination of the `--export-format-version B`, `--export-key-algorithm` and `--export-template` options, or using the `--export-header` option. For example: ``` -tr31-tool --kbpk AB2E09DB3EF0BA71E0CE6CD755C23A3B --export BF82DAC6A33DF92CE66E15B70E5DCEB6 --export-header B0128B1TX00N0300KS18FFFF00A0200001E00000KC0C000169E3KP0C00ECAD62 +tr31-tool --kbpk AB2E09DB3EF0BA71E0CE6CD755C23A3B --export BF82DAC6A33DF92CE66E15B70E5DCEB6 --export-header B0000B1TX00N0200KS18FFFF00A0200001E00000KC0C000169E3 +``` + +Individual optional blocks can also be added when exporting a TR-31 key block +by using the various `--export-opt-block-XX` functions, where `XX` is the +optional block identifier. For example: +``` +tr31-tool --kbpk AB2E09DB3EF0BA71E0CE6CD755C23A3B --export BF82DAC6A33DF92CE66E15B70E5DCEB6 --export-header B0000B1TX00N0000 --export-opt-block-KS FFFF00A0200001E00000 --export-opt-block-KC ``` Roadmap diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b31abf8..52f19ef 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -688,4 +688,13 @@ if(TARGET tr31-tool AND BUILD_TESTING) PROPERTIES PASS_REGULAR_EXPRESSION "^D0144M7HG00N0300HM0621LB09MyKeyPB11" ) + + # test export header containing optional blocks but insufficient padding + add_test(NAME tr31_tool_test42 + COMMAND tr31-tool --kbpk 88E1AB2A2E3DD38C1FA039A536500CC8A87AB9D62DC92C01058FA79F44657DE6 --export 3F419E1CB7079442AA37474C2EFBF8B8 --export-header D1234M7HG00N0100HM0621 + ) + set_tests_properties(tr31_tool_test42 + PROPERTIES + PASS_REGULAR_EXPRESSION "^D0128M7HG00N0200HM0621PB0A" + ) endif() diff --git a/src/tr31-tool.c b/src/tr31-tool.c index 4bad11f..c288b48 100644 --- a/src/tr31-tool.c +++ b/src/tr31-tool.c @@ -96,7 +96,9 @@ static error_t argp_parser_helper(int key, char* arg, struct argp_state* state); static void* read_file(FILE* file, size_t* len); 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 { @@ -657,6 +659,117 @@ 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) { @@ -909,45 +1022,8 @@ static int populate_tr31_from_header(const struct tr31_tool_options_t* options, { int r; - // determine fake key block length to allow parsing of header - size_t export_header_len = strlen(options->export_header); - size_t tmp_key_block_len = export_header_len; - switch (options->export_header[0]) { - case 'A': - case 'C': - tmp_key_block_len += 32 + 8; - break; - - case 'B': - tmp_key_block_len += 32 + 16; - break; - - case 'D': - case 'E': - tmp_key_block_len += 48 + 16; - break; - - default: - fprintf(stderr, "Unsupported key block format version\n"); - return 1; - } - if (tmp_key_block_len > 9999) { - fprintf(stderr, "Export header too large\n"); - return 1; - } - - // build fake key block to allow parsing of header - char tmp_keyblock[tmp_key_block_len]; - memcpy(tmp_keyblock, options->export_header, export_header_len); - memset(tmp_keyblock + export_header_len, '0', sizeof(tmp_keyblock) - export_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); + // parse export header + r = tr31_init_from_header(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;