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";