diff --git a/sw/device/tests/crypto/BUILD b/sw/device/tests/crypto/BUILD index e5e5307cac037..45b5392c1a318 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 d15a867ce0242..256e45df557b0 100644 --- a/sw/device/tests/crypto/hmac_multistream_functest.c +++ b/sw/device/tests/crypto/hmac_multistream_functest.c @@ -7,6 +7,7 @@ #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 +26,123 @@ 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`. + * + * For streaming calls, the exact number of streaming segments can be up to 4. + * For fewer segments, a transition from `kHmacTestFeedSegmentX` to + * `kHmacTestFinalize` is allowed so long as all message bytes of the test is + * fed to HMAC core. + * + * The value of `kHmacTestFeedSegmentX` enum is arranged to match integer X, + * as it us used as an index to access `segment_idx` array defined in + * `hmac_extended_test_vector_t`. + */ +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 { + /* The pointer to the test vector coming from the autogenerated C header. */ + hmac_test_vector_t *hmac_test_vector; + /* The original index of this test vector in the autogenerated C header. */ + size_t original_idx; + /* The number of update calls for a streaming test. */ + size_t segment_count; + /* Randomly generated indices to break down the test message to segments. */ + size_t segment_idx[4]; + /* `progess` keeps track of how many message segments are streamed so far. */ + hmac_test_progress_t progress; + /* `hash_ctx` is used to store context during streaming. */ + otcrypto_hash_context_t hash_ctx; +} hmac_extended_test_vector_t; + +static hmac_extended_test_vector_t + kHmacExtendedTestVectors[2 * ARRAYSIZE(kHmacTestVectors)]; + +/** + * Populate `kHmacExtendedTestVectors` array with tests such that: + * i) There are mixture of oneshot and streaming HMAC operations. + * Each vector is run in 2 duplicates, one as a streaming and another + * as a oneshot call. + * ii) For streaming operations, messages are split into [1,4] + * segments with randomly chosen indices. + * iii) The order of tests is randomized. + */ +static void vectors_populate(void) { + hmac_extended_test_vector_t *cur_ext_vec; + hmac_test_vector_t *cur_vec; + // First add streaming calls to the test 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. The indices of a message + // looks like below: + // 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 of 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 and print + // how the new order maps to the original order from the test file. + rand_testutils_shuffle(kHmacExtendedTestVectors, + sizeof(hmac_extended_test_vector_t), + ARRAYSIZE(kHmacExtendedTestVectors)); + for (size_t i = 0; i < ARRAYSIZE(kHmacExtendedTestVectors); i++) { + cur_ext_vec = &kHmacExtendedTestVectors[i]; + cur_vec = cur_ext_vec->hmac_test_vector; + LOG_INFO("Vector order = %d, original_vector_id = %d", i, + cur_ext_vec->original_idx); + 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. * @@ -72,8 +190,6 @@ static status_t ctx_init(otcrypto_hash_context_t *hash_ctx, case kHmacTestOperationSha384: OT_FALLTHROUGH_INTENDED; case kHmacTestOperationSha512: - LOG_INFO("Invoking hash_init for %s.", - current_test_vector->vector_identifier); TRY(get_hash_mode(current_test_vector, &hash_mode)); TRY(otcrypto_hash_init(hash_ctx, hash_mode)); break; @@ -82,8 +198,6 @@ static status_t ctx_init(otcrypto_hash_context_t *hash_ctx, case kHmacTestOperationHmacSha384: OT_FALLTHROUGH_INTENDED; case kHmacTestOperationHmacSha512: - LOG_INFO("Invoking hmac_init for %s.", - current_test_vector->vector_identifier); TRY(otcrypto_hmac_init((otcrypto_hmac_context_t *)hash_ctx, ¤t_test_vector->key)); break; @@ -93,6 +207,55 @@ static status_t ctx_init(otcrypto_hash_context_t *hash_ctx, return OTCRYPTO_OK; } +/** + * Run the test given by `current_test_vector` as oneshot operation. + * + * @param current_test_vector Pointer to the test vector. + * @return The result of the operation. + */ +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; + } + LOG_INFO("Comparing result for %s.", current_test_vector->vector_identifier); + 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. @@ -102,31 +265,18 @@ static status_t ctx_init(otcrypto_hash_context_t *hash_ctx, * * @param hash_ctx Corresponding context for given `current_test_vector`. * @param current_test_vector Pointer to the hardcoded test vector. - * @param segment_index Determines whether the first or the second half of the - * message to be used to update the context. + * @param segment_start The starting index of the chosen segment. + * @param segment_len The byte length of the chosen segment. + * @return The result of the operation. */ 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; @@ -154,6 +304,7 @@ static status_t feed_msg(otcrypto_hash_context_t *hash_ctx, * * @param hash_ctx Corresponding context for given `current_test_vector`. * @param current_test_vector Pointer to the hardcoded test vector. + * @return The result of the operation. */ static status_t hmac_finalize(otcrypto_hash_context_t *hash_ctx, hmac_test_vector_t *current_test_vector) { @@ -170,8 +321,6 @@ static status_t hmac_finalize(otcrypto_hash_context_t *hash_ctx, .data = act_tag, .len = digest_len, }; - LOG_INFO("Invoking final call for %s.", - current_test_vector->vector_identifier); switch (current_test_vector->test_operation) { case kHmacTestOperationSha256: OT_FALLTHROUGH_INTENDED; @@ -192,32 +341,111 @@ static status_t hmac_finalize(otcrypto_hash_context_t *hash_ctx, return OTCRYPTO_BAD_ARGS; } LOG_INFO("Comparing result for %s.", current_test_vector->vector_identifier); - TRY_CHECK_ARRAYS_EQ(act_tag, current_test_vector->digest.data, digest_len); + CHECK_ARRAYS_EQ(act_tag, current_test_vector->digest.data, digest_len); return OTCRYPTO_OK; } /** - * 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. + * Process a message segment for the given test vector. + * + * If the given test vector is already run and completed, this function + * simply returns. + * + * If the given test vector is a oneshot call, then it is run and the + * result is compared. The process of the vector is already update to done. + * + * If the given test vector is a starming call, then this function calls + * the necessary init, update or final call. In the final call, the result + * is compared with the expected result. + * + * @param test_ext_vec The test vector to run. + * @return The result of the operation. + * */ -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); + 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) { + LOG_INFO("Initializing HMAC stream for vector #%d", + test_ext_vec->original_idx); + 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) { + LOG_INFO("Finalizing HMAC stream for vector #%d", + test_ext_vec->original_idx); + 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; + } + LOG_INFO("Streaming the last segment #%d of 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 = 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("Streaming segment #%d of 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) { + vectors_populate(); + + 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; } diff --git a/sw/device/tests/crypto/hmac_testvectors.h.tpl b/sw/device/tests/crypto/hmac_testvectors.h.tpl index e5c0654c455f4..d9d23dd8cdeac 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;