Skip to content

Commit

Permalink
[hmac, dv] Extend multistream TLT
Browse files Browse the repository at this point in the history
Related to lowRISC#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 <[email protected]>
  • Loading branch information
ballifatih committed Jun 22, 2024
1 parent de38ce3 commit 452cf6f
Show file tree
Hide file tree
Showing 3 changed files with 273 additions and 44 deletions.
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

0 comments on commit 452cf6f

Please sign in to comment.