Skip to content

Commit

Permalink
Refactor parsing, validation and decoding of optional block DA/WP
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
leonlynch committed Oct 5, 2023
1 parent 45043c5 commit cdac71c
Show file tree
Hide file tree
Showing 5 changed files with 237 additions and 25 deletions.
2 changes: 1 addition & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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]"
Expand Down
49 changes: 44 additions & 5 deletions src/tr31-tool.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down
106 changes: 102 additions & 4 deletions src/tr31.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand Down
67 changes: 66 additions & 1 deletion src/tr31.h
Original file line number Diff line number Diff line change
Expand Up @@ -224,13 +224,36 @@ 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.
size_t kcv_len; ///< Length of @ref tr31_opt_blk_kcv_data_t.kcv in bytes. Must be at most 3 bytes for legacy KCV or at most 5 bytes for CMAC KCV (according to ANSI X9.24-1)
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; ///<Wrapping Pedigree (WP) format version
/// Wrapping Pedigree (WP) version 0
struct v0_t {
uint8_t wrapping_pedigree; ///< Wrapping Pedigree value
} v0; ///< Wrapping Pedigree (WP) version 0. Valid if @ref tr31_opt_blk_wp_data_t.version is @ref TR31_OPT_BLOCK_WP_VERSION_0
};

/**
* @brief TR-31 context object
* This object is typically populated by @ref tr31_import().
Expand Down Expand Up @@ -535,6 +558,33 @@ int tr31_opt_block_add_DA(
size_t da_len
);

/**
* Decode optional block 'DA' for Derivation(s) Allowed for Derivation Keys.
* The caller is responsible for computing the length of the output data and
* allocating a suitable buffer. The length can be calculated using:
* @code
* if (opt_ctx->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.
Expand Down Expand Up @@ -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.
*
Expand All @@ -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.
*
Expand Down
38 changes: 24 additions & 14 deletions src/tr31_strings.c
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down

0 comments on commit cdac71c

Please sign in to comment.