Skip to content

Commit

Permalink
[cryptolib, testing] Add testing support to HMAC streaming
Browse files Browse the repository at this point in the history
This PR adds a big testing harness for HMAC.

Namely, it brings:
* A py script to generate a HMAC/SHA2 vector for varying sizes (using Crypto.Hash lib)
* Another py script to determine random parameters and run the prev script for to obtain 20 vecs
* tpl/set scripts to conver generated hjson headers to C headers
* hmac_functest to drive all variants of SHA2/HMAC functions from the generated vectors.

Signed-off-by: Fatih Balli <[email protected]>
  • Loading branch information
ballifatih committed May 27, 2024
1 parent 3f703b6 commit 8e4b002
Show file tree
Hide file tree
Showing 8 changed files with 581 additions and 0 deletions.
36 changes: 36 additions & 0 deletions sw/device/tests/crypto/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,13 @@ autogen_cryptotest_header(
tool = "//sw/device/tests/crypto:kdf_set_testvectors",
)

autogen_cryptotest_header(
name = "hmac_testvectors_random_header",
hjson = "//sw/device/tests/crypto/testvectors:hmac_testvectors_random",
template = ":hmac_testvectors.h.tpl",
tool = "//sw/device/tests/crypto:hmac_set_testvectors",
)

opentitan_test(
name = "kmac_functest_hardcoded",
srcs = ["kmac_functest.c"],
Expand Down Expand Up @@ -396,6 +403,24 @@ opentitan_test(
],
)

opentitan_test(
name = "hmac_functest",
srcs = ["hmac_functest.c"],
exec_env = EARLGREY_TEST_ENVS,
verilator = verilator_params(
timeout = "long",
),
deps = [
":hmac_testvectors_random_header",
"//sw/device/lib/base:macros",
"//sw/device/lib/crypto/drivers:hmac",
"//sw/device/lib/crypto/impl:hash",
"//sw/device/lib/crypto/impl:mac",
"//sw/device/lib/testing/test_framework:check",
"//sw/device/lib/testing/test_framework:ottf_main",
],
)

opentitan_test(
name = "kdf_kmac_sideload_functest_hardcoded",
srcs = ["kdf_kmac_sideload_functest.c"],
Expand Down Expand Up @@ -870,6 +895,17 @@ py_binary(
],
)

py_binary(
name = "hmac_set_testvectors",
srcs = ["hmac_set_testvectors.py"],
imports = ["."],
deps = [
"//util/design/lib:common",
requirement("hjson"),
requirement("mako"),
],
)

opentitan_test(
name = "sha256_functest",
srcs = ["sha256_functest.c"],
Expand Down
127 changes: 127 additions & 0 deletions sw/device/tests/crypto/hmac_functest.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// Copyright lowRISC contributors (OpenTitan project).
// 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/drivers/hmac.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/test_framework/check.h"
#include "sw/device/lib/testing/test_framework/ottf_main.h"

// The autogen rule that creates this header creates it in a directory named
// after the rule, then manipulates the include path in the
// cc_compilation_context to include that directory, so the compiler will find
// the version of this file matching the Bazel rule under test.
#include "hmac_testvectors.h"

// Module ID for status codes.
#define MODULE_ID MAKE_MODULE_ID('h', 'm', 'a')

// Global pointer to the current test vector.
static hmac_test_vector_t *current_test_vector = NULL;

/**
* Determines `hash_mode` for given SHA-2 test vectors.
*
* Note that for HMAC operations, mode information is part of the key struct,
* hence this function is only used for hash vectors.
*
* @param test_vec The pointer to the test vector.
* @param[out] hash_mode The determined hash_mode of the given test vector.
*/
static status_t get_hash_mode(hmac_test_vector_t *test_vec,
otcrypto_hash_mode_t *hash_mode) {
switch (test_vec->test_operation) {
case kHmacTestOperationSha256:
*hash_mode = kOtcryptoHashModeSha256;
return OTCRYPTO_OK;
case kHmacTestOperationSha384:
*hash_mode = kOtcryptoHashModeSha384;
return OTCRYPTO_OK;
case kHmacTestOperationSha512:
*hash_mode = kOtcryptoHashModeSha512;
return OTCRYPTO_OK;
default:
return OTCRYPTO_BAD_ARGS;
}
}

/**
* Run the test pointed to by `current_test_vector`.
*/
static status_t run_test_vector(void) {
// Populate `checksum` and `config.security_level` fields.
current_test_vector->key.checksum =
integrity_blinded_checksum(&current_test_vector->key);

otcrypto_hmac_context_t hmac_ctx;
otcrypto_hash_context_t hash_ctx;
// 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.
uint32_t act_tag[kHmacMaxDigestBits];
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,
};
size_t break_index = current_test_vector->message.len / 2;
otcrypto_const_byte_buf_t msg_part1 = {
.data = current_test_vector->message.data,
.len = break_index,
};
otcrypto_const_byte_buf_t msg_part2 = {
.data = &current_test_vector->message.data[break_index],
.len = current_test_vector->message.len - break_index,
};
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_init(&hash_ctx, hash_digest.mode));
TRY(otcrypto_hash_update(&hash_ctx, msg_part1));
TRY(otcrypto_hash_update(&hash_ctx, msg_part2));
TRY(otcrypto_hash_final(&hash_ctx, hash_digest));
break;
case kHmacTestOperationHmacSha256:
OT_FALLTHROUGH_INTENDED;
case kHmacTestOperationHmacSha384:
OT_FALLTHROUGH_INTENDED;
case kHmacTestOperationHmacSha512:
TRY(otcrypto_hmac_init(&hmac_ctx, &current_test_vector->key));
TRY(otcrypto_hmac_update(&hmac_ctx, msg_part1));
TRY(otcrypto_hmac_update(&hmac_ctx, msg_part2));
TRY(otcrypto_hmac_final(&hmac_ctx, tag_buf));
break;
default:
return OTCRYPTO_BAD_ARGS;
}
TRY_CHECK_ARRAYS_EQ(act_tag, current_test_vector->digest.data, digest_len);
return OTCRYPTO_OK;
}

OTTF_DEFINE_TEST_CONFIG();
bool test_main(void) {
LOG_INFO("Testing cryptolib HMAC driver.");
CHECK_STATUS_OK(entropy_complex_init());
status_t test_result = OK_STATUS();
for (size_t i = 0; i < ARRAYSIZE(kHmacTestVectors); i++) {
current_test_vector = &kHmacTestVectors[i];
LOG_INFO("Running test %d of %d, test vector identifier: %s", i + 1,
ARRAYSIZE(kHmacTestVectors),
current_test_vector->vector_identifier);
EXECUTE_TEST(test_result, run_test_vector);
}
return status_ok(test_result);
}
59 changes: 59 additions & 0 deletions sw/device/tests/crypto/hmac_gen_random_testvectors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/usr/bin/env python3
# Copyright lowRISC contributors (OpenTitan project).
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0

import argparse
import random
import sys

import hjson
import hmac_gen_single_testvector
'''
Generate multiple test vectos
'''


def gen_random_test(idx):
random_instance = random.Random(idx)
operation = random_instance.choice(["SHA256", "HMAC256", "SHA384", "HMAC384",
"SHA512", "HMAC512"])
input_msg_len = 8 * random_instance.randint(0, 1000)
# Ensure that the key length is larger or equal to security parameter.
# Also, KMAC HWIP only supports a discrete set of key lengths.
if operation == "HMAC256":
key_len = 256
elif operation == "HMAC384":
key_len = 384
elif operation == "HMAC512":
key_len = 512
else:
key_len = 0

return hmac_gen_single_testvector.gen_random_test(idx, operation, input_msg_len, key_len)


def main():
parser = argparse.ArgumentParser()
parser.add_argument('n',
type=int,
help='Number of random test vectors to generate.')
parser.add_argument('outfile',
metavar='FILE',
type=argparse.FileType('w'),
help='Write output to this file.')

args = parser.parse_args()

testvecs = []
for i in range(args.n):
testvecs.append(gen_random_test(i))

hjson.dump(testvecs, args.outfile)
args.outfile.close()

return 0


if __name__ == '__main__':
sys.exit(main())
109 changes: 109 additions & 0 deletions sw/device/tests/crypto/hmac_gen_single_testvector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#!/usr/bin/env python3
# Copyright lowRISC contributors (OpenTitan project).
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0

import argparse
import random
import sys

import hjson
from Crypto.Hash import HMAC, SHA256, SHA384, SHA512

'''
Generate a HMAC/SHA2 vectors for specified sizes.
'''


def validate_inputs(operation, input_msg_len, key_len):
if operation in ["SHA256", "SHA384", "SHA512"] and key_len != 0:
raise ValueError("SHA operations should have no key_len inputs")
if operation in ["HMAC256", "HMAC384", "HMAC512"] and key_len == 0:
raise ValueError("key_len needs to be larger than 0 for HMAC")
if input_msg_len % 8 != 0:
raise ValueError("input_msg_len needs to be divisible by 8")


# Generate a HMAC test vector and return it as string in hjson format.
# The input length arguments are all in bit size.
def gen_random_test(seed, operation, input_msg_len, key_len):

validate_inputs(operation, input_msg_len, key_len)

random_instance = random.Random(seed)
random_instance.seed(seed)
input_msg = random_instance.randbytes(input_msg_len // 8)
key = random_instance.randbytes(key_len // 8)

if operation == "HMAC256":
mac = HMAC.new(key=key, digestmod=SHA256)
elif operation == "HMAC384":
mac = HMAC.new(key=key, digestmod=SHA384)
elif operation == "HMAC512":
mac = HMAC.new(key=key, digestmod=SHA512)
elif operation == "SHA256":
mac = SHA256.new()
elif operation == "SHA384":
mac = SHA384.new()
elif operation == "SHA512":
mac = SHA512.new()

mac.update(input_msg)
digest = mac.hexdigest()
vector_identifier = \
"./sw/device/tests/crypto/hmac_gen_single_testvector.py "\
"--seed={} --key_len={} --operation={} --input_msg_len={} "\
"<output-file>"\
.format(seed, key_len, operation, input_msg_len)

print(vector_identifier)
testvec = {
'vector_identifier': vector_identifier,
'operation': operation,
'input_msg': '0x' + input_msg.hex(),
'digest': '0x' + digest,
}

if operation in ["HMAC256", "HMAC384", "HMAC512"]:
testvec['key'] = '0x' + key.hex()
return testvec


def main():
parser = argparse.ArgumentParser()
parser.add_argument('--seed',
required=True,
type=int,
help='Seed for randomness.')
parser.add_argument('--key_len',
required=True,
type=int,
choices=[0, 128, 256, 384, 512, 1024],
default=0,
help='Key length (in bits).')
parser.add_argument('--operation',
required=True,
choices=['SHA256', 'SHA384', 'SHA512', 'HMAC256', 'HMAC384', 'HMAC512'],
help='SHA/HMAC mode (e.g. SHA256 or HMAC256).')
parser.add_argument('--input_msg_len',
required=True,
type=int,
help='Input message length (in bits).')
parser.add_argument('outfile',
metavar='FILE',
type=argparse.FileType('w'),
help='Write output to this file.')

args = parser.parse_args()

testvecs = gen_random_test(args.seed, args.operation, args.input_msg_len,
args.key_len)

hjson.dump(testvecs, args.outfile)
args.outfile.close()

return 0


if __name__ == '__main__':
sys.exit(main())
Loading

0 comments on commit 8e4b002

Please sign in to comment.