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 4090879
Show file tree
Hide file tree
Showing 3 changed files with 242 additions and 35 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
274 changes: 240 additions & 34 deletions sw/device/tests/crypto/hmac_multistream_functest.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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.
*
Expand Down Expand Up @@ -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(&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;
}
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.
Expand All @@ -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 = &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 @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion sw/device/tests/crypto/hmac_testvectors.h.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 4090879

Please sign in to comment.