From cdac71cae3ce3bdeb6b3b2788544a8a6939ce3db Mon Sep 17 00:00:00 2001 From: Leon Lynch Date: Thu, 5 Oct 2023 20:14:22 +0200 Subject: [PATCH] Refactor parsing, validation and decoding of optional block DA/WP This commit refactors the processing of these optional blocks: * Optional block DA * Optional block WP The intention is for parsing, validation and decoding of optional blocks to be separate activities. Therefore these optional blocks are no longer decoded immediately during import and are instead decoded by new dedicated decoding API functions. This change will eventually make it possible to disable key block validation during import for key blocks that use standardised key block IDs for proprietary purposes. --- src/CMakeLists.txt | 2 +- src/tr31-tool.c | 49 ++++++++++++++++++--- src/tr31.c | 106 +++++++++++++++++++++++++++++++++++++++++++-- src/tr31.h | 67 +++++++++++++++++++++++++++- src/tr31_strings.c | 38 ++++++++++------ 5 files changed, 237 insertions(+), 25 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c3c3360..5602b24 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -552,7 +552,7 @@ if(TARGET tr31-tool AND BUILD_TESTING) "Key version: Unused[\r\n]" "Key exportability: \\[E\\] Exportable in a trusted key block only[\r\n]" "Optional blocks \\[5\\]:[\r\n]" - "\t\\[DA\\] Derivation\\(s\\) Allowed for Derivation Keys: D2ABND3AEN[\r\n]" + "\t\\[DA\\] Derivation\\(s\\) Allowed for Derivation Keys: D2ABN,D3AEN[\r\n]" "\t\\[LB\\] Label: \"My Great Key\"[\r\n]" "\t\\[PK\\] Protection Key Check Value \\(KCV\\) of export KBPK: 9876543210 \\(CMAC based KCV\\)[\r\n]" "\t\\[WP\\] Wrapping Pedigree: 3 \\(Asymmetric key at risk of quantum computing and symmetric key of lesser effective strength\\)[\r\n]" diff --git a/src/tr31-tool.c b/src/tr31-tool.c index cc1bf77..fdc3d0b 100644 --- a/src/tr31-tool.c +++ b/src/tr31-tool.c @@ -795,6 +795,40 @@ static int do_tr31_import(const struct tr31_tool_options_t* options) break; } + case TR31_OPT_BLOCK_DA: { + size_t da_attr_count; + size_t da_data_len; + struct tr31_opt_blk_da_data_t* da_data; + if (tr31_ctx.opt_blocks[i].data_length < 2) { + // invalid; print as string + print_str(tr31_ctx.opt_blocks[i].data, tr31_ctx.opt_blocks[i].data_length); + break; + } + da_attr_count = (tr31_ctx.opt_blocks[i].data_length - 2) / 5; + da_data_len = sizeof(struct tr31_opt_blk_da_attr_t) + * da_attr_count + + sizeof(struct tr31_opt_blk_da_data_t); + da_data = malloc(da_data_len); + r = tr31_opt_block_decode_DA(&tr31_ctx.opt_blocks[i], da_data, da_data_len); + if (r) { + // invalid; print as string + print_str(tr31_ctx.opt_blocks[i].data, tr31_ctx.opt_blocks[i].data_length); + free(da_data); + break; + } + for (size_t i = 0; i < da_attr_count; ++i) { + printf("%s%s%c%c%c", + i == 0 ? "" : ",", + tr31_get_key_usage_ascii(da_data->attr[i].key_usage, ascii_buf, sizeof(ascii_buf)), + da_data->attr[i].algorithm, + da_data->attr[i].mode_of_use, + da_data->attr[i].exportability + ); + } + free(da_data); + break; + } + case TR31_OPT_BLOCK_HM: { uint8_t hash_algorithm; r = tr31_opt_block_decode_HM(&tr31_ctx.opt_blocks[i], &hash_algorithm); @@ -849,13 +883,18 @@ static int do_tr31_import(const struct tr31_tool_options_t* options) break; } - case TR31_OPT_BLOCK_DA: - case TR31_OPT_BLOCK_WP: - // for some optional blocks, skip the first two bytes - if (tr31_ctx.opt_blocks[i].data_length > 2) { - print_str(tr31_ctx.opt_blocks[i].data + 2, tr31_ctx.opt_blocks[i].data_length - 2); + case TR31_OPT_BLOCK_WP: { + struct tr31_opt_blk_wp_data_t wp_data; + r = tr31_opt_block_decode_WP(&tr31_ctx.opt_blocks[i], &wp_data); + if (r || wp_data.version != TR31_OPT_BLOCK_WP_VERSION_0) { + // invalid; print as string + print_str(tr31_ctx.opt_blocks[i].data, tr31_ctx.opt_blocks[i].data_length); + break; } + // valid; assume version 00 and print wrapping pedigree digit + print_str(tr31_ctx.opt_blocks[i].data + 2, 1); break; + } case TR31_OPT_BLOCK_CT: // for certificates and certificate chains, skip the first two bytes and use quotes diff --git a/src/tr31.c b/src/tr31.c index ade9069..eb68883 100644 --- a/src/tr31.c +++ b/src/tr31.c @@ -1219,7 +1219,6 @@ int tr31_opt_block_add_DA( return TR31_ERROR_INVALID_OPTIONAL_BLOCK_DATA; } - // NOTE: tr31_opt_block_export() copies this optional block verbatim opt_ctx = tr31_opt_block_alloc(ctx, TR31_OPT_BLOCK_DA, da_len + 2); if (!opt_ctx) { return -2; @@ -1230,6 +1229,63 @@ int tr31_opt_block_add_DA( return r; } +int tr31_opt_block_decode_DA( + const struct tr31_opt_ctx_t* opt_ctx, + struct tr31_opt_blk_da_data_t* da_data, + size_t da_data_len +) +{ + size_t count; + const uint8_t* da_attr; + + if (!opt_ctx || !da_data || !da_data_len) { + return -1; + } + + if (opt_ctx->id != TR31_OPT_BLOCK_DA) { + return -2; + } + + // decode optional block DA version + // see ANSI X9.143:2021, 6.3.6.1, table 8 + if (opt_ctx->data_length < 2) { + return TR31_ERROR_INVALID_OPTIONAL_BLOCK_DATA; + } + da_data->version = hex_to_int(opt_ctx->data, 2); + if (da_data->version != TR31_OPT_BLOCK_DA_VERSION_1) { + // unsupported DA version + return TR31_ERROR_INVALID_OPTIONAL_BLOCK_DATA; + } + + // validate optional block length + if (opt_ctx->data_length < 7 || + (opt_ctx->data_length - 2) % 5 != 0 + ) { + return TR31_ERROR_INVALID_OPTIONAL_BLOCK_DATA; + } + count = (opt_ctx->data_length - 2) / 5; + + // validate output data length + if (da_data_len != sizeof(struct tr31_opt_blk_da_attr_t) * count + sizeof(struct tr31_opt_blk_da_data_t)) { + return -3; + } + + // decode optional block DA version 1 + // see ANSI X9.143:2021, 6.3.6.1, table 8 + da_attr = opt_ctx->data + 2; + for (size_t i = 0; i < count; ++i) { + uint16_t key_usage_raw = da_attr[0]; + key_usage_raw += da_attr[1] << 8; + da_data->attr[i].key_usage = ntohs(key_usage_raw); + da_data->attr[i].algorithm = da_attr[2]; + da_data->attr[i].mode_of_use = da_attr[3]; + da_data->attr[i].exportability = da_attr[4]; + da_attr += 5; + } + + return 0; +} + static int tr31_opt_block_validate_hash_algorithm(uint8_t hash_algorithm) { // validate hash algorithm @@ -1654,15 +1710,57 @@ int tr31_opt_block_add_WP( } // assume wrapping pedigree optional block version 00 - // unfortunately optional block WP carries an odd number of hex digits and - // therefore the digits must be encoded here instead of in - // tr31_opt_block_export() // see ANSI X9.143:2021, 6.3.6.15, table 23 int_to_hex(TR31_OPT_BLOCK_WP_VERSION_0, buf, 2); int_to_hex(wrapping_pedigree, buf + 2, 1); return tr31_opt_block_add(ctx, TR31_OPT_BLOCK_WP, buf, sizeof(buf)); } +int tr31_opt_block_decode_WP( + const struct tr31_opt_ctx_t* opt_ctx, + struct tr31_opt_blk_wp_data_t* wp_data +) +{ + int r; + + if (!opt_ctx || !wp_data) { + return -1; + } + + if (opt_ctx->id != TR31_OPT_BLOCK_WP) { + return -2; + } + + // decode optional block data and validate + // see ANSI X9.143:2021, 6.3.6.15, table 23 + if (opt_ctx->data_length < 2) { + return TR31_ERROR_INVALID_OPTIONAL_BLOCK_DATA; + } + r = hex_to_bin(opt_ctx->data, 2, &wp_data->version, sizeof(wp_data->version)); + if (r) { + return TR31_ERROR_INVALID_OPTIONAL_BLOCK_DATA; + } + if (wp_data->version == TR31_OPT_BLOCK_WP_VERSION_0) { + uint8_t wrapping_pedigree; + + // decode WP optional block version 00 + if (opt_ctx->data_length != 3) { + return TR31_ERROR_INVALID_OPTIONAL_BLOCK_DATA; + } + wrapping_pedigree = hex_to_int(opt_ctx->data + 2, 1); + if (wrapping_pedigree > 3) { + return TR31_ERROR_INVALID_OPTIONAL_BLOCK_DATA; + } + wp_data->v0.wrapping_pedigree = wrapping_pedigree; + + } else { + // unsupported AKL version + return TR31_ERROR_INVALID_OPTIONAL_BLOCK_DATA; + } + + return 0; +} + int tr31_import( const char* key_block, const struct tr31_key_t* kbpk, diff --git a/src/tr31.h b/src/tr31.h index ca4cb9d..d831fc8 100644 --- a/src/tr31.h +++ b/src/tr31.h @@ -224,6 +224,20 @@ struct tr31_opt_blk_bdkid_data_t { uint8_t bdkid[5]; ///< Key Set ID (KSI) or Base Derivation Key ID (BDK ID) }; +/// Decoded Derivation Allowed (DA) attributes +struct tr31_opt_blk_da_attr_t { + unsigned int key_usage; ///< Derivation Allowed: key usage + unsigned int algorithm; ///< Derivation Allowed: key algorithm + unsigned int mode_of_use; ///< Derivation Allowed: mode of use + unsigned int exportability; ///< Derivation Allowed: exportability +}; + +/// Decoded Derivation(s) Allowed (DA) data +struct tr31_opt_blk_da_data_t { + unsigned int version; ///< Derivation(s) Allowed (DA) version + struct tr31_opt_blk_da_attr_t attr[]; ///< Derivation Allowed (DA) array +}; + /// Decoded optional block Key Check Value (KCV) data struct tr31_opt_blk_kcv_data_t { uint8_t kcv_algorithm; ///< KCV algorithm output. Either @ref TR31_OPT_BLOCK_KCV_LEGACY or @ref TR31_OPT_BLOCK_KCV_CMAC. @@ -231,6 +245,15 @@ struct tr31_opt_blk_kcv_data_t { uint8_t kcv[5]; ///< Key Check Value (KCV) }; +/// Decoded optional block Wrapping Pedigree (WP) data +struct tr31_opt_blk_wp_data_t { + uint8_t version; ///data_length > 2) { + * da_attr_count = (opt_ctx->data_length - 2) / 5; + * da_data_len = sizeof(struct tr31_opt_blk_da_attr_t) * da_attr_count + * + sizeof(struct tr31_opt_blk_da_data_t); + * da_data = malloc(da_data_len); + * } + * @endcode + * + * @note This function complies with ANSI X9.143 and will fail for + * non-compliant encodings of this optional block. + * + * @param opt_ctx TR-31 optional block context object + * @param da_data Decoded Derivation(s) Allowed (DA) data output + * @param da_data_len Length of @p da_data in bytes. See function description. + * @return Zero for success. Less than zero for internal error. Greater than zero for data error. See @ref tr31_error_t + */ +int tr31_opt_block_decode_DA( + const struct tr31_opt_ctx_t* opt_ctx, + struct tr31_opt_blk_da_data_t* da_data, + size_t da_data_len +); + /** * Add optional block 'HM' for HMAC hash algorithm of wrapped key to TR-31 * context object. @@ -788,7 +838,7 @@ int tr31_opt_block_add_TS( ); /** - * Add optional block 'WP' for wrapping pedigree to TR-31 context object. + * Add optional block 'WP' for Wrapping Pedigree to TR-31 context object. * * @note This function requires an initialised TR-31 context object to be provided. * @@ -801,6 +851,21 @@ int tr31_opt_block_add_WP( uint8_t wrapping_pedigree ); +/** + * Decode optional block 'WP' for Wrapping Pedigree. + * + * @note This function complies with ANSI X9.143 and will fail for + * non-compliant encodings of this optional block. + * + * @param opt_ctx TR-31 optional block context object + * @param wp_data Decoded Wrapping Pedigree (WP) data output + * @return Zero for success. Less than zero for internal error. Greater than zero for data error. See @ref tr31_error_t + */ +int tr31_opt_block_decode_WP( + const struct tr31_opt_ctx_t* opt_ctx, + struct tr31_opt_blk_wp_data_t* wp_data +); + /** * Import TR-31 key block. This function will also decrypt the key data if possible. * diff --git a/src/tr31_strings.c b/src/tr31_strings.c index 9c7e465..b3ecb20 100644 --- a/src/tr31_strings.c +++ b/src/tr31_strings.c @@ -362,28 +362,38 @@ static int tr31_opt_block_iso8601_get_string(const struct tr31_opt_ctx_t* opt_bl static const char* tr31_opt_block_wrapping_pedigree_get_string(const struct tr31_opt_ctx_t* opt_block) { - const uint8_t* data; - if (!opt_block || - opt_block->id != TR31_OPT_BLOCK_WP || - opt_block->data_length != 3 - ) { + int r; + struct tr31_opt_blk_wp_data_t wp_data; + + // Use canary value to know whether WP version was decoded + wp_data.version = 0xFF; + r = tr31_opt_block_decode_WP(opt_block, &wp_data); + if (r < 0) { return NULL; } - data = opt_block->data; + if (r > 0) { + // If at least the WP version is available, report it as unknown + if (wp_data.version != 0xFF && + wp_data.version != TR31_OPT_BLOCK_WP_VERSION_0 + ) { + return "Unknown wrapping pedigree version"; + } else { + // Invalid + return NULL; + } + } - if (data[0] != '0' || data[1] != '0') { + if (wp_data.version != TR31_OPT_BLOCK_WP_VERSION_0) { return "Unknown wrapping pedigree version"; } // See ANSI X9.143:2021, 6.3.6.15, table 23 - if ((data[2] & 0xF0) == 0x30) { // ASCII number - switch (data[2] & 0x0F) { - case TR31_OPT_BLOCK_WP_EQ_GT: return "Equal or greater effective strength"; - case TR31_OPT_BLOCK_WP_LT: return "Lesser effective strength"; - case TR31_OPT_BLOCK_WP_ASYMMETRIC: return "Asymmetric key at risk of quantum computing"; - case TR31_OPT_BLOCK_WP_ASYMMETRIC_LT: return "Asymmetric key at risk of quantum computing and symmetric key of lesser effective strength"; - } + switch (wp_data.v0.wrapping_pedigree) { + case TR31_OPT_BLOCK_WP_EQ_GT: return "Equal or greater effective strength"; + case TR31_OPT_BLOCK_WP_LT: return "Lesser effective strength"; + case TR31_OPT_BLOCK_WP_ASYMMETRIC: return "Asymmetric key at risk of quantum computing"; + case TR31_OPT_BLOCK_WP_ASYMMETRIC_LT: return "Asymmetric key at risk of quantum computing and symmetric key of lesser effective strength"; } return "Unknown";