From 409087993ce98dbab3360a8e69913dacba0fcd84 Mon Sep 17 00:00:00 2001 From: Fatih Balli Date: Sat, 22 Jun 2024 18:30:18 +0200 Subject: [PATCH] [hmac, dv] Extend multistream TLT Related to #23507. Extends the multistream HMAC test so that: * One-shot and streaming calls are mixed. * Streaming calls are divided into random number of message segments [1, 4]. * The order of test vectors is randomized. Signed-off-by: Fatih Balli --- sw/device/tests/crypto/BUILD | 1 + .../tests/crypto/hmac_multistream_functest.c | 274 +++++++++++++++--- sw/device/tests/crypto/hmac_testvectors.h.tpl | 2 +- 3 files changed, 242 insertions(+), 35 deletions(-) diff --git a/sw/device/tests/crypto/BUILD b/sw/device/tests/crypto/BUILD index e5e5307cac0370..45b5392c1a3188 100644 --- a/sw/device/tests/crypto/BUILD +++ b/sw/device/tests/crypto/BUILD @@ -473,6 +473,7 @@ opentitan_test( "//sw/device/lib/base:macros", "//sw/device/lib/crypto/impl:hash", "//sw/device/lib/crypto/impl:mac", + "//sw/device/lib/testing:rand_testutils", "//sw/device/lib/testing/test_framework:check", "//sw/device/lib/testing/test_framework:ottf_main", ], diff --git a/sw/device/tests/crypto/hmac_multistream_functest.c b/sw/device/tests/crypto/hmac_multistream_functest.c index d15a867ce0242f..91383c8d21249c 100644 --- a/sw/device/tests/crypto/hmac_multistream_functest.c +++ b/sw/device/tests/crypto/hmac_multistream_functest.c @@ -2,11 +2,13 @@ // Licensed under the Apache License, Version 2.0, see LICENSE for details. // SPDX-License-Identifier: Apache-2.0 +#include "sw/device/lib/crypto/drivers/entropy.h" #include "sw/device/lib/crypto/impl/integrity.h" #include "sw/device/lib/crypto/include/datatypes.h" #include "sw/device/lib/crypto/include/hash.h" #include "sw/device/lib/crypto/include/mac.h" #include "sw/device/lib/runtime/log.h" +#include "sw/device/lib/testing/rand_testutils.h" #include "sw/device/lib/testing/test_framework/check.h" #include "sw/device/lib/testing/test_framework/ottf_main.h" @@ -25,6 +27,115 @@ static_assert(sizeof(otcrypto_hash_context_t) == sizeof(otcrypto_hmac_context_t), "Hash and Hmac contexts are expected to be of the same length"); +/** + * This enum defines the different stages of a test vector during streaming + * or oneshot HMAC calls. + * + * Oneshot calls transition from `kHmacTestNotStarted` to `kHmacTestDone`. + * `kHmacTestFeedSegmentX` indices are arranged to match the integer X, + * because they are used as indices to access segment indices defined in + * `hmac_extended_test_vector_t`. + * + * For streaming calls, the exact number of streaming segments must not + * necessarily be 4. In that case, a transition from `kHmacTestFeedSegmentX` to + * `kHmacTestFinalize` is allowed so long as all message bytes of the test is + * fed to HMAC core. + */ +typedef enum hmac_test_progress { + /* The oneshot or streaming test has not been initialized.*/ + kHmacTestNotStarted = 0, + /* The streaming test is initialized, but no message segment is fed.*/ + kHmacTestFeedSegment1 = 1, + /* The streaming test is initialized and the first message segment is fed.*/ + kHmacTestFeedSegment2 = 2, + /* The streaming test is initialized and the first two message segments are + fed.*/ + kHmacTestFeedSegment3 = 3, + /* The streaming test is initialized and the first three message segments are + fed.*/ + kHmacTestFeedSegment4 = 4, + /* All message bytes are fed but the final is not called.*/ + kHmacTestFinalize = 5, + /* The test is completed and the result is verified.*/ + kHmacTestDone = 6, +} hmac_test_progress_t; + +/** + * Extend `hmac_test_vector_t` with metadata fields useful for streaming. + */ +typedef struct hmac_extended_test_vector { + hmac_test_vector_t *hmac_test_vector; + /* The original index of this test vector from the hardcoded test file .*/ + size_t original_idx; + /* The number of update calls for a streaming test. */ + size_t segment_count; + /** + * Candidate indices that are starting point for message segments. + * Only `segment_count` of them are used. + */ + size_t segment_idx[4]; + /** + * Use `progess` to keep track of how many message segments are streamed + * so far. + */ + hmac_test_progress_t progress; + otcrypto_hash_context_t hash_ctx; +} hmac_extended_test_vector_t; + +static hmac_extended_test_vector_t + kHmacExtendedTestVectors[2* ARRAYSIZE(kHmacTestVectors)]; + +static void prepare_vectors(void) { + hmac_extended_test_vector_t *cur_ext_vec; + hmac_test_vector_t *cur_vec; + // First add streaming calls to the array. + for (size_t i = 0; i < ARRAYSIZE(kHmacTestVectors); i++) { + cur_ext_vec = &kHmacExtendedTestVectors[i]; + cur_vec = &kHmacTestVectors[i]; + cur_ext_vec->hmac_test_vector = cur_vec; + cur_ext_vec->original_idx = i; + cur_ext_vec->segment_count = rand_testutils_gen32_range(1, 4); + // Pick three indicies to break down message into 4 parts, but we only + // use the first `segment_count` of them. + // ix[0] = 0 ... idx[1] ... idx[2] ... idx[3] ... message_len-1 + cur_ext_vec->segment_idx[0] = 0; + cur_ext_vec->segment_idx[2] = + rand_testutils_gen32_range(0, cur_vec->message.len); + cur_ext_vec->segment_idx[1] = + rand_testutils_gen32_range(0, cur_ext_vec->segment_idx[2]); + cur_ext_vec->segment_idx[3] = rand_testutils_gen32_range( + cur_ext_vec->segment_idx[2], cur_vec->message.len); + cur_ext_vec->progress = kHmacTestNotStarted; + } + // Add oneshot calls for the same vectors. + for (size_t i = 0; i < ARRAYSIZE(kHmacTestVectors); i++) { + cur_ext_vec = &kHmacExtendedTestVectors[ARRAYSIZE(kHmacTestVectors) + i]; + cur_vec = &kHmacTestVectors[i]; + cur_ext_vec->hmac_test_vector = cur_vec; + cur_ext_vec->original_idx = i; + cur_ext_vec->segment_count = 0; + cur_ext_vec->progress = kHmacTestNotStarted; + } + // Now we shuffle the order of vectors. + rand_testutils_shuffle(kHmacExtendedTestVectors, + sizeof(hmac_extended_test_vector_t), ARRAYSIZE(kHmacExtendedTestVectors)); + LOG_INFO("random_val = %h", rand_testutils_gen32()); + for (size_t i = 0; i < ARRAYSIZE(kHmacTestVectors); i++) { + cur_ext_vec = &kHmacExtendedTestVectors[i]; + cur_vec = cur_ext_vec->hmac_test_vector; + LOG_INFO( + "vector_idx = %d, original_vector_idx = %d, vector_identifier = %s", i, + cur_ext_vec->original_idx, + cur_ext_vec->hmac_test_vector->vector_identifier); + LOG_INFO( + "segment_count = %d, segment_idx = [%d, %d, %d, %d], message_len = %d", + cur_ext_vec->segment_count, cur_ext_vec->segment_idx[0], + cur_ext_vec->segment_idx[1], cur_ext_vec->segment_idx[2], + cur_ext_vec->segment_idx[3], + cur_ext_vec->hmac_test_vector->message.len); + } +} + /** * Determines `hash_mode` for given SHA-2 test vectors. * @@ -93,6 +204,51 @@ static status_t ctx_init(otcrypto_hash_context_t *hash_ctx, return OTCRYPTO_OK; } +/** + * Run the test pointed to by `current_test_vector`. + */ +static status_t hmac_oneshot(hmac_test_vector_t *current_test_vector) { + // Populate `checksum` and `config.security_level` fields. + current_test_vector->key.checksum = + integrity_blinded_checksum(¤t_test_vector->key); + + // The test vectors already have the correct digest sizes hardcoded. + size_t digest_len = current_test_vector->digest.len; + // Allocate the buffer for the maximum digest size (which comes from SHA-512). + uint32_t act_tag[kSha512DigestWords]; + otcrypto_word32_buf_t tag_buf = { + .data = act_tag, + .len = digest_len, + }; + otcrypto_hash_digest_t hash_digest = { + // .mode is to be determined below in switch-case block. + .data = act_tag, + .len = digest_len, + }; + switch (current_test_vector->test_operation) { + case kHmacTestOperationSha256: + OT_FALLTHROUGH_INTENDED; + case kHmacTestOperationSha384: + OT_FALLTHROUGH_INTENDED; + case kHmacTestOperationSha512: + TRY(get_hash_mode(current_test_vector, &hash_digest.mode)); + TRY(otcrypto_hash(current_test_vector->message, hash_digest)); + break; + case kHmacTestOperationHmacSha256: + OT_FALLTHROUGH_INTENDED; + case kHmacTestOperationHmacSha384: + OT_FALLTHROUGH_INTENDED; + case kHmacTestOperationHmacSha512: + TRY(otcrypto_hmac(¤t_test_vector->key, current_test_vector->message, + tag_buf)); + break; + default: + return OTCRYPTO_BAD_ARGS; + } + TRY_CHECK_ARRAYS_EQ(act_tag, current_test_vector->digest.data, digest_len); + return OTCRYPTO_OK; +} + /** * Feed messages to the streaming hash/hmac operation with context `hash_ctx`. * `current_test_vector` is used to determine message bytes to be used. @@ -107,26 +263,12 @@ static status_t ctx_init(otcrypto_hash_context_t *hash_ctx, */ static status_t feed_msg(otcrypto_hash_context_t *hash_ctx, hmac_test_vector_t *current_test_vector, - size_t segment_index) { - size_t segment_start; - size_t segment_length; - if (segment_index == 0) { - segment_start = 0; - segment_length = current_test_vector->message.len / 2; - } else if (segment_index == 1) { - segment_start = current_test_vector->message.len / 2; - segment_length = - current_test_vector->message.len - current_test_vector->message.len / 2; - } else { - return OTCRYPTO_BAD_ARGS; - } - + size_t segment_start, size_t segment_len) { otcrypto_const_byte_buf_t msg = { .data = ¤t_test_vector->message.data[segment_start], - .len = segment_length, + .len = segment_len, }; - LOG_INFO("Feeding message part %d for %s.", segment_index, - current_test_vector->vector_identifier); + switch (current_test_vector->test_operation) { case kHmacTestOperationSha256: OT_FALLTHROUGH_INTENDED; @@ -197,33 +339,97 @@ static status_t hmac_finalize(otcrypto_hash_context_t *hash_ctx, } /** - * Run all vectors specified in `kHmacTestVectors` in parallel. Namely: - * i) Each stream is instantiated (by init call). - * ii) Each stream receives the first half its message. - * iii) Each stream receives the second half of its message. - * iv) Each stream is concluded with a final call and the digest result is - * compared with the expected valud. + * Feed a message segment to HMAC core. + * + * */ -static status_t run_test(void) { - // hash_contexts are also used as hmac_contexts, since their size is equal. - otcrypto_hash_context_t hash_contexts[ARRAYSIZE(kHmacTestVectors)]; - for (size_t i = 0; i < ARRAYSIZE(kHmacTestVectors); i++) { - TRY(ctx_init(&hash_contexts[i], &kHmacTestVectors[i])); +static status_t process_segment(hmac_extended_test_vector_t *test_ext_vec) { + // If `test_ext_vec` is done, simply return. + if (test_ext_vec->progress == kHmacTestDone) { + return OTCRYPTO_OK; } - for (size_t i = 0; i < ARRAYSIZE(kHmacTestVectors); i++) { - TRY(feed_msg(&hash_contexts[i], &kHmacTestVectors[i], /*segment_index=*/0)); + + // If `test_ext_vec` is one-shot, we need to call one-shot API. + if (test_ext_vec->segment_count == 0 && + test_ext_vec->progress == kHmacTestNotStarted) { + LOG_INFO("Invoking oneshot HMAC for vector #%d", + test_ext_vec->original_idx); + LOG_INFO("vector_identifier = %s", + test_ext_vec->hmac_test_vector->vector_identifier); + hmac_oneshot(test_ext_vec->hmac_test_vector); + // Mark this test as complete + test_ext_vec->progress = kHmacTestDone; + return OTCRYPTO_OK; } - for (size_t i = 0; i < ARRAYSIZE(kHmacTestVectors); i++) { - TRY(feed_msg(&hash_contexts[i], &kHmacTestVectors[i], /*segment_index=*/1)); + + // A sanity check: oneshot calls should not arrive here and `segment_count` + // should be valid. + if (test_ext_vec->segment_count == 0 || test_ext_vec->segment_count > 4) { + return OTCRYPTO_BAD_ARGS; } - for (size_t i = 0; i < ARRAYSIZE(kHmacTestVectors); i++) { - TRY(hmac_finalize(&hash_contexts[i], &kHmacTestVectors[i])); + + // If `test_ext_vec` is streaming, and this is the first call, then call init. + if (test_ext_vec->progress == kHmacTestNotStarted) { + ctx_init(&test_ext_vec->hash_ctx, test_ext_vec->hmac_test_vector); + test_ext_vec->progress = kHmacTestFeedSegment1; + return OTCRYPTO_OK; + } + + if (test_ext_vec->progress == kHmacTestFinalize) { + hmac_finalize(&test_ext_vec->hash_ctx, test_ext_vec->hmac_test_vector); + test_ext_vec->progress = kHmacTestDone; + return OTCRYPTO_OK; + } + + // Handle the last message specially + if (test_ext_vec->progress == test_ext_vec->segment_count) { + size_t segment_start = + test_ext_vec->segment_idx[test_ext_vec->progress - 1]; + size_t segment_len = + test_ext_vec->hmac_test_vector->message.len - segment_start; + // Sanity check + if (segment_start > test_ext_vec->hmac_test_vector->message.len) { + return OTCRYPTO_BAD_ARGS; + } + feed_msg(&test_ext_vec->hash_ctx, test_ext_vec->hmac_test_vector, + segment_start, segment_len); + test_ext_vec->progress = kHmacTestFinalize; + return OTCRYPTO_OK; + } + + if (test_ext_vec->progress < test_ext_vec->segment_count) { + size_t segment_start = + test_ext_vec->segment_idx[test_ext_vec->progress - 1]; + size_t segment_len = + test_ext_vec->segment_idx[test_ext_vec->progress] - segment_start; + LOG_INFO("Feeding message part %d for vector #%d.", test_ext_vec->progress, + test_ext_vec->original_idx); + feed_msg(&test_ext_vec->hash_ctx, test_ext_vec->hmac_test_vector, + segment_start, segment_len); + test_ext_vec->progress++; + return OTCRYPTO_OK; + } + return OTCRYPTO_BAD_ARGS; +} + +/** + * Run all vectors specified in `kHmacExtendedTestVectors` in parallel step + * by step. + */ +static status_t run_test(void) { + prepare_vectors(); + + for (size_t i = kHmacTestNotStarted; i < kHmacTestDone; i++) { + for (size_t j = 0; j < ARRAYSIZE(kHmacExtendedTestVectors); j++) { + TRY(process_segment(&kHmacExtendedTestVectors[j])); + } } return OTCRYPTO_OK; } OTTF_DEFINE_TEST_CONFIG(); bool test_main(void) { + CHECK_STATUS_OK(entropy_complex_init()); LOG_INFO("Testing cryptolib SHA-2/HMAC with parallel multiple streams."); status_t test_result = OK_STATUS(); EXECUTE_TEST(test_result, run_test); diff --git a/sw/device/tests/crypto/hmac_testvectors.h.tpl b/sw/device/tests/crypto/hmac_testvectors.h.tpl index e5c0654c455f44..d9d23dd8cdeac9 100644 --- a/sw/device/tests/crypto/hmac_testvectors.h.tpl +++ b/sw/device/tests/crypto/hmac_testvectors.h.tpl @@ -30,7 +30,7 @@ typedef enum hmac_test_operation { kHmacTestOperationHmacSha512, } hmac_test_operation_t; -typedef struct kdf_test_vector { +typedef struct hmac_test_vector { char* vector_identifier; hmac_test_operation_t test_operation; otcrypto_blinded_key_t key;