Skip to content

Commit

Permalink
Refactor tr31_import() not to require null-termination
Browse files Browse the repository at this point in the history
It is convenient for the caller to be able to pass a key block that was
received in some kind of message format (eg ISO 8583) directly to
tr31_import() without having to copy it out to add null-termination.
However, this requires an additional parameter to tr31_import() for the
caller to indicate the exact key block length.

Note that this API change is not backward compatible due to the
additional parameter for tr31_import() as well as the renumbering of the
error enum. The intention is for the next release to update the SONAME
version anyway.

In addition, tr31_import() will now also validate the key block buffer
as printable ASCII as early as possible. This ensures that there aren't
any illegal characters or NULL values in the key block buffer.
  • Loading branch information
leonlynch committed Oct 13, 2023
1 parent 76b2a31 commit 3730179
Show file tree
Hide file tree
Showing 6 changed files with 44 additions and 37 deletions.
19 changes: 9 additions & 10 deletions src/tr31-tool.c
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ static error_t argp_parser_helper(int key, char* arg, struct argp_state* state)

} else {
// Copy argument
buf_len = strlen(arg) + 1;
buf_len = strlen(arg);
buf = malloc(buf_len);
memcpy(buf, arg, buf_len);
}
Expand Down Expand Up @@ -716,10 +716,10 @@ static int do_tr31_import(const struct tr31_tool_options_t* options)

if (options->kbpk) { // if key block protection key was provided
// parse and decrypt TR-31 key block
r = tr31_import(options->key_block, &kbpk, &tr31_ctx);
r = tr31_import(options->key_block, options->key_block_len, &kbpk, &tr31_ctx);
} else { // else if no key block protection key was provided
// parse TR-31 key block
r = tr31_import(options->key_block, NULL, &tr31_ctx);
r = tr31_import(options->key_block, options->key_block_len, NULL, &tr31_ctx);
}
// check for errors
if (r) {
Expand Down Expand Up @@ -912,16 +912,16 @@ static int populate_tr31_from_header(const struct tr31_tool_options_t* options,
switch (options->export_header[0]) {
case 'A':
case 'C':
tmp_key_block_len += 32 + 8 + 1;
tmp_key_block_len += 32 + 8;
break;

case 'B':
tmp_key_block_len += 32 + 16 + 1;
tmp_key_block_len += 32 + 16;
break;

case 'D':
case 'E':
tmp_key_block_len += 48 + 16 + 1;
tmp_key_block_len += 48 + 16;
break;

default:
Expand All @@ -936,16 +936,15 @@ static int populate_tr31_from_header(const struct tr31_tool_options_t* options,
// 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 - 1);
tmp_keyblock[sizeof(tmp_keyblock) - 1] = 0;
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", tmp_key_block_len - 1);
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, NULL, tr31_ctx);
r = tr31_import(tmp_keyblock, sizeof(tmp_keyblock), NULL, 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
13 changes: 9 additions & 4 deletions src/tr31.c
Original file line number Diff line number Diff line change
Expand Up @@ -1163,12 +1163,12 @@ int tr31_opt_block_add_WP(

int tr31_import(
const char* key_block,
size_t key_block_len,
const struct tr31_key_t* kbpk,
struct tr31_ctx_t* ctx
)
{
int r;
size_t key_block_len;
const struct tr31_header_t* header;
size_t opt_blk_len_total = 0;
unsigned int enc_block_size;
Expand All @@ -1178,15 +1178,19 @@ int tr31_import(
return -1;
}

key_block_len = strlen(key_block);
header = (const struct tr31_header_t*)key_block;

// validate minimum length
if (key_block_len < TR31_MIN_KEY_BLOCK_LENGTH) {
return TR31_ERROR_INVALID_LENGTH;
}

// validate key block as printable ASCII (format PA)
r = tr31_validate_format_pa(key_block, key_block_len);
if (r) {
return TR31_ERROR_INVALID_KEY_BLOCK_STRING;
}

// initialise TR-31 context object
header = (const struct tr31_header_t*)key_block;
r = tr31_init(header->version_id, NULL, ctx);
if (r) {
// return error value as-is
Expand Down Expand Up @@ -2721,6 +2725,7 @@ const char* tr31_get_error_string(enum tr31_error_t error)

switch (error) {
case TR31_ERROR_INVALID_LENGTH: return "Invalid key block length";
case TR31_ERROR_INVALID_KEY_BLOCK_STRING: return "Invalid key block string";
case TR31_ERROR_UNSUPPORTED_VERSION: return "Unsupported key block format version";
case TR31_ERROR_INVALID_LENGTH_FIELD: return "Invalid key block length field";
case TR31_ERROR_UNSUPPORTED_KEY_USAGE: return "Unsupported key usage";
Expand Down
5 changes: 4 additions & 1 deletion src/tr31.h
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ struct tr31_ctx_t {
/// TR-31 library errors
enum tr31_error_t {
TR31_ERROR_INVALID_LENGTH = 1, ///< Invalid key block length
TR31_ERROR_INVALID_KEY_BLOCK_STRING, ///< Invalid key block string
TR31_ERROR_UNSUPPORTED_VERSION, ///< Unsupported key block format version
TR31_ERROR_INVALID_LENGTH_FIELD, ///< Invalid key block length field
TR31_ERROR_UNSUPPORTED_KEY_USAGE, ///< Unsupported key usage
Expand Down Expand Up @@ -626,13 +627,15 @@ int tr31_opt_block_add_WP(
* @note This function will populate a new TR-31 context object.
* Use @ref tr31_release() to release internal resources when done.
*
* @param key_block TR-31 key block. Null terminated. At least the header must be ASCII encoded.
* @param key_block TR-31 key block. Must contain printable ASCII characters. Null-termination not required.
* @param key_block_len Length of TR-31 key block in bytes, excluding null-termination.
* @param kbpk TR-31 key block protection key. NULL if not available or decryption is not required.
* @param ctx TR-31 context object output
* @return Zero for success. Less than zero for internal error. Greater than zero for data error. See @ref tr31_error_t
*/
int tr31_import(
const char* key_block,
size_t key_block_len,
const struct tr31_key_t* kbpk,
struct tr31_ctx_t* ctx
);
Expand Down
8 changes: 4 additions & 4 deletions test/tr31_decode_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ int main(void)

// test key block decoding for format version B with optional block KS
printf("Test 1 (Format version B with optional block KS)...\n");
r = tr31_import(test1_tr31_ascii, NULL, &test_tr31);
r = tr31_import(test1_tr31_ascii, strlen(test1_tr31_ascii), NULL, &test_tr31);
if (r) {
fprintf(stderr, "tr31_import() error %d: %s\n", r, tr31_get_error_string(r));
goto exit;
Expand Down Expand Up @@ -76,7 +76,7 @@ int main(void)

// test key block decoding for format version D containing TDES key
printf("Test 2 (Format version D containing TDES key)...\n");
r = tr31_import(test2_tr31_ascii, NULL, &test_tr31);
r = tr31_import(test2_tr31_ascii, strlen(test2_tr31_ascii), NULL, &test_tr31);
if (r) {
fprintf(stderr, "tr31_import() error %d: %s\n", r, tr31_get_error_string(r));
goto exit;
Expand Down Expand Up @@ -104,7 +104,7 @@ int main(void)

// test key block decoding for format version D containing AES key
printf("Test 3 (Format version D containing AES key)...\n");
r = tr31_import(test3_tr31_ascii, NULL, &test_tr31);
r = tr31_import(test3_tr31_ascii, strlen(test3_tr31_ascii), NULL, &test_tr31);
if (r) {
fprintf(stderr, "tr31_import() error %d: %s\n", r, tr31_get_error_string(r));
goto exit;
Expand Down Expand Up @@ -132,7 +132,7 @@ int main(void)

// test key block decoding for format version B with optional blocks KS, KC, and KP
printf("Test 4 (Format version B with optional block KS, KC, KP)...\n");
r = tr31_import(test4_tr31_ascii, NULL, &test_tr31);
r = tr31_import(test4_tr31_ascii, strlen(test4_tr31_ascii), NULL, &test_tr31);
if (r) {
fprintf(stderr, "tr31_import() error %d: %s\n", r, tr31_get_error_string(r));
goto exit;
Expand Down
34 changes: 17 additions & 17 deletions test/tr31_decrypt_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ int main(void)

// test key block decryption for format version A
printf("Test 1 (Basic format version A)...\n");
r = tr31_import(test1_tr31_format_a, &test_kbpk, &test_tr31);
r = tr31_import(test1_tr31_format_a, strlen(test1_tr31_format_a), &test_kbpk, &test_tr31);
if (r) {
fprintf(stderr, "tr31_import() error %d: %s\n", r, tr31_get_error_string(r));
goto exit;
Expand Down Expand Up @@ -262,7 +262,7 @@ int main(void)

// test key block decryption for format version B
printf("Test 1 (Basic format version B)...\n");
r = tr31_import(test1_tr31_format_b, &test_kbpk, &test_tr31);
r = tr31_import(test1_tr31_format_b, strlen(test1_tr31_format_b), &test_kbpk, &test_tr31);
if (r) {
fprintf(stderr, "tr31_import() error %d: %s\n", r, tr31_get_error_string(r));
goto exit;
Expand Down Expand Up @@ -302,7 +302,7 @@ int main(void)

// test key block decryption for format version C
printf("Test 1 (Basic format version C)...\n");
r = tr31_import(test1_tr31_format_c, &test_kbpk, &test_tr31);
r = tr31_import(test1_tr31_format_c, strlen(test1_tr31_format_c), &test_kbpk, &test_tr31);
if (r) {
fprintf(stderr, "tr31_import() error %d: %s\n", r, tr31_get_error_string(r));
goto exit;
Expand Down Expand Up @@ -348,7 +348,7 @@ int main(void)
test_kbpk.mode_of_use = TR31_KEY_MODE_OF_USE_ENC_DEC;
test_kbpk.length = sizeof(test2_kbpk);
test_kbpk.data = (void*)test2_kbpk;
r = tr31_import(test2_tr31_ascii, &test_kbpk, &test_tr31);
r = tr31_import(test2_tr31_ascii, strlen(test2_tr31_ascii), &test_kbpk, &test_tr31);
if (r) {
fprintf(stderr, "tr31_import() error %d: %s\n", r, tr31_get_error_string(r));
goto exit;
Expand Down Expand Up @@ -394,7 +394,7 @@ int main(void)
test_kbpk.mode_of_use = TR31_KEY_MODE_OF_USE_ENC_DEC;
test_kbpk.length = sizeof(test3_kbpk);
test_kbpk.data = (void*)test3_kbpk;
r = tr31_import(test3_tr31_ascii, &test_kbpk, &test_tr31);
r = tr31_import(test3_tr31_ascii, strlen(test3_tr31_ascii), &test_kbpk, &test_tr31);
if (r) {
fprintf(stderr, "tr31_import() error %d: %s\n", r, tr31_get_error_string(r));
goto exit;
Expand Down Expand Up @@ -440,7 +440,7 @@ int main(void)
test_kbpk.mode_of_use = TR31_KEY_MODE_OF_USE_ENC_DEC;
test_kbpk.length = sizeof(test4_kbpk);
test_kbpk.data = (void*)test4_kbpk;
r = tr31_import(test4_tr31_ascii, &test_kbpk, &test_tr31);
r = tr31_import(test4_tr31_ascii, strlen(test4_tr31_ascii), &test_kbpk, &test_tr31);
if (r) {
fprintf(stderr, "tr31_import() error %d: %s\n", r, tr31_get_error_string(r));
goto exit;
Expand Down Expand Up @@ -490,7 +490,7 @@ int main(void)
test_kbpk.mode_of_use = TR31_KEY_MODE_OF_USE_ENC_DEC;
test_kbpk.length = sizeof(test5_kbpk);
test_kbpk.data = (void*)test5_kbpk;
r = tr31_import(test5_tr31_ascii, &test_kbpk, &test_tr31);
r = tr31_import(test5_tr31_ascii, strlen(test5_tr31_ascii), &test_kbpk, &test_tr31);
if (r) {
fprintf(stderr, "tr31_import() error %d: %s\n", r, tr31_get_error_string(r));
goto exit;
Expand Down Expand Up @@ -540,7 +540,7 @@ int main(void)
test_kbpk.mode_of_use = TR31_KEY_MODE_OF_USE_ENC_DEC;
test_kbpk.length = sizeof(test6_kbpk);
test_kbpk.data = (void*)test6_kbpk;
r = tr31_import(test6_tr31_ascii, &test_kbpk, &test_tr31);
r = tr31_import(test6_tr31_ascii, strlen(test6_tr31_ascii), &test_kbpk, &test_tr31);
if (r) {
fprintf(stderr, "tr31_import() error %d: %s\n", r, tr31_get_error_string(r));
goto exit;
Expand Down Expand Up @@ -586,7 +586,7 @@ int main(void)
test_kbpk.mode_of_use = TR31_KEY_MODE_OF_USE_ENC_DEC;
test_kbpk.length = sizeof(test7_kbpk);
test_kbpk.data = (void*)test7_kbpk;
r = tr31_import(test7_tr31_ascii, &test_kbpk, &test_tr31);
r = tr31_import(test7_tr31_ascii, strlen(test7_tr31_ascii), &test_kbpk, &test_tr31);
if (r) {
fprintf(stderr, "tr31_import() error %d: %s\n", r, tr31_get_error_string(r));
goto exit;
Expand Down Expand Up @@ -632,7 +632,7 @@ int main(void)
test_kbpk.mode_of_use = TR31_KEY_MODE_OF_USE_ENC_DEC;
test_kbpk.length = sizeof(test8_kbpk);
test_kbpk.data = (void*)test8_kbpk;
r = tr31_import(test8_tr31_ascii, &test_kbpk, &test_tr31);
r = tr31_import(test8_tr31_ascii, strlen(test8_tr31_ascii), &test_kbpk, &test_tr31);
if (r) {
fprintf(stderr, "tr31_import() error %d: %s\n", r, tr31_get_error_string(r));
goto exit;
Expand Down Expand Up @@ -678,7 +678,7 @@ int main(void)
test_kbpk.mode_of_use = TR31_KEY_MODE_OF_USE_ENC_DEC;
test_kbpk.length = sizeof(test9_kbpk);
test_kbpk.data = (void*)test9_kbpk;
r = tr31_import(test9_tr31_ascii, &test_kbpk, &test_tr31);
r = tr31_import(test9_tr31_ascii, strlen(test9_tr31_ascii), &test_kbpk, &test_tr31);
if (r) {
fprintf(stderr, "tr31_import() error %d: %s\n", r, tr31_get_error_string(r));
goto exit;
Expand Down Expand Up @@ -724,7 +724,7 @@ int main(void)
test_kbpk.mode_of_use = TR31_KEY_MODE_OF_USE_ENC_DEC;
test_kbpk.length = sizeof(test10_kbpk);
test_kbpk.data = (void*)test10_kbpk;
r = tr31_import(test10_tr31_ascii, &test_kbpk, &test_tr31);
r = tr31_import(test10_tr31_ascii, strlen(test10_tr31_ascii), &test_kbpk, &test_tr31);
if (r) {
fprintf(stderr, "tr31_import() error %d: %s\n", r, tr31_get_error_string(r));
goto exit;
Expand Down Expand Up @@ -770,7 +770,7 @@ int main(void)
test_kbpk.mode_of_use = TR31_KEY_MODE_OF_USE_ENC_DEC;
test_kbpk.length = sizeof(test11_kbpk);
test_kbpk.data = (void*)test11_kbpk;
r = tr31_import(test11_tr31_ascii, &test_kbpk, &test_tr31);
r = tr31_import(test11_tr31_ascii, strlen(test11_tr31_ascii), &test_kbpk, &test_tr31);
if (r) {
fprintf(stderr, "tr31_import() error %d: %s\n", r, tr31_get_error_string(r));
goto exit;
Expand Down Expand Up @@ -816,7 +816,7 @@ int main(void)
test_kbpk.mode_of_use = TR31_KEY_MODE_OF_USE_ENC_DEC;
test_kbpk.length = sizeof(test15_kbpk);
test_kbpk.data = (void*)test15_kbpk;
r = tr31_import(test15_tr31_ascii, &test_kbpk, &test_tr31);
r = tr31_import(test15_tr31_ascii, strlen(test15_tr31_ascii), &test_kbpk, &test_tr31);
if (r) {
fprintf(stderr, "tr31_import() error %d: %s\n", r, tr31_get_error_string(r));
goto exit;
Expand Down Expand Up @@ -866,7 +866,7 @@ int main(void)
test_kbpk.mode_of_use = TR31_KEY_MODE_OF_USE_ENC_DEC;
test_kbpk.length = sizeof(test16_kbpk);
test_kbpk.data = (void*)test16_kbpk;
r = tr31_import(test16_tr31_ascii, &test_kbpk, &test_tr31);
r = tr31_import(test16_tr31_ascii, strlen(test16_tr31_ascii), &test_kbpk, &test_tr31);
if (r) {
fprintf(stderr, "tr31_import() error %d: %s\n", r, tr31_get_error_string(r));
goto exit;
Expand Down Expand Up @@ -916,7 +916,7 @@ int main(void)
test_kbpk.mode_of_use = TR31_KEY_MODE_OF_USE_ENC_DEC;
test_kbpk.length = sizeof(test17_kbpk);
test_kbpk.data = (void*)test17_kbpk;
r = tr31_import(test17_tr31_ascii, &test_kbpk, &test_tr31);
r = tr31_import(test17_tr31_ascii, strlen(test17_tr31_ascii), &test_kbpk, &test_tr31);
if (r) {
fprintf(stderr, "tr31_import() error %d: %s\n", r, tr31_get_error_string(r));
goto exit;
Expand Down Expand Up @@ -962,7 +962,7 @@ int main(void)
test_kbpk.mode_of_use = TR31_KEY_MODE_OF_USE_ENC_DEC;
test_kbpk.length = sizeof(test18_kbpk);
test_kbpk.data = (void*)test18_kbpk;
r = tr31_import(test18_tr31_ascii, &test_kbpk, &test_tr31);
r = tr31_import(test18_tr31_ascii, strlen(test18_tr31_ascii), &test_kbpk, &test_tr31);
if (r) {
fprintf(stderr, "tr31_import() error %d: %s\n", r, tr31_get_error_string(r));
goto exit;
Expand Down
2 changes: 1 addition & 1 deletion test/tr31_export_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -907,7 +907,7 @@ int main(void)
tr31_release(&test_tr31);

// Import and decrypt key block
r = tr31_import(key_block, &test[i].kbpk, &test_tr31);
r = tr31_import(key_block, strlen(key_block), &test[i].kbpk, &test_tr31);
if (r) {
fprintf(stderr, "tr31_import() error %d: %s\n", r, tr31_get_error_string(r));
goto exit;
Expand Down

0 comments on commit 3730179

Please sign in to comment.