Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[hmac, dv] Extend multistream TLT #23780

Merged
merged 1 commit into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions sw/device/tests/crypto/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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",
],
Expand Down
314 changes: 271 additions & 43 deletions sw/device/tests/crypto/hmac_multistream_functest.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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.
*
Expand Down Expand Up @@ -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;
Expand All @@ -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,
&current_test_vector->key));
break;
Expand All @@ -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(&current_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(&current_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.
Expand All @@ -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 = &current_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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
Expand All @@ -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;
}
Expand Down
Loading