Skip to content

Commit

Permalink
Enhance --export-header option for tr31-tool
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
leonlynch committed Oct 29, 2023
1 parent 3527f97 commit 3bd3f7b
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 40 deletions.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
154 changes: 115 additions & 39 deletions src/tr31-tool.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 3bd3f7b

Please sign in to comment.