From 4616280d69f89a9f3613a51077b809b46930b2dd Mon Sep 17 00:00:00 2001 From: Ryan Torok Date: Thu, 8 Feb 2024 14:18:11 +0000 Subject: [PATCH] [cryptotest] Parser for NIST SHA2/3, SHAKE test vectors Signed-off-by: Ryan Torok --- sw/host/cryptotest/testvectors/data/BUILD | 54 +++++++++ sw/host/cryptotest/testvectors/parsers/BUILD | 9 ++ .../parsers/nist_cavp_hash_parser.py | 107 ++++++++++++++++++ 3 files changed, 170 insertions(+) create mode 100755 sw/host/cryptotest/testvectors/parsers/nist_cavp_hash_parser.py diff --git a/sw/host/cryptotest/testvectors/data/BUILD b/sw/host/cryptotest/testvectors/data/BUILD index a9ea64d164ff4..c52e47d85c486 100644 --- a/sw/host/cryptotest/testvectors/data/BUILD +++ b/sw/host/cryptotest/testvectors/data/BUILD @@ -44,3 +44,57 @@ genrule( ("ecdsa_secp384r1_sha384_test.json", "wycheproof_ecdsa_p384_sha384"), ] ] + +[ + run_binary( + name = "nist_cavp_{}_{}_{}_json".format( + src_repo, + algorithm.lower(), + msg_type.lower(), + ), + srcs = [ + "@nist_cavp_{}//:{}{}.rsp".format(src_repo, algorithm, msg_type), + "//sw/host/cryptotest/testvectors/data/schemas:hash_schema.json", + ], + outs = [":nist_{}_{}.json".format( + algorithm.lower(), + msg_type.lower(), + )], + args = [ + "--src", + "$(location @nist_cavp_{}//:{}{}.rsp)".format(src_repo, algorithm, msg_type), + "--dst", + "$(location :nist_{}_{}.json)".format( + algorithm.lower(), + msg_type.lower(), + ), + "--schema", + "$(location //sw/host/cryptotest/testvectors/data/schemas:hash_schema.json)", + "--algorithm", + algorithm, + ], + tool = "//sw/host/cryptotest/testvectors/parsers:nist_cavp_hash_parser", + ) + for algorithm, src_repo, extra_msg_types in [ + ("SHA256", "sha2_fips_180_4", []), + ("SHA384", "sha2_fips_180_4", []), + ("SHA512", "sha2_fips_180_4", []), + ("SHA3_256", "sha3_fips_202", []), + ("SHA3_384", "sha3_fips_202", []), + ("SHA3_512", "sha3_fips_202", []), + ( + "SHAKE128", + "shake_fips_202", + ["VariableOut"], + ), + ( + "SHAKE256", + "shake_fips_202", + ["VariableOut"], + ), + ] + for msg_type in [ + "ShortMsg", + "LongMsg", + ] + extra_msg_types +] diff --git a/sw/host/cryptotest/testvectors/parsers/BUILD b/sw/host/cryptotest/testvectors/parsers/BUILD index bed04ebabf65c..86dfe3486ed65 100644 --- a/sw/host/cryptotest/testvectors/parsers/BUILD +++ b/sw/host/cryptotest/testvectors/parsers/BUILD @@ -41,6 +41,15 @@ py_binary( ], ) +py_binary( + name = "nist_cavp_hash_parser", + srcs = ["nist_cavp_hash_parser.py"], + deps = [ + ":cryptotest_util", + requirement("jsonschema"), + ], +) + py_binary( name = "nist_cavp_hmac_parser", srcs = ["nist_cavp_hmac_parser.py"], diff --git a/sw/host/cryptotest/testvectors/parsers/nist_cavp_hash_parser.py b/sw/host/cryptotest/testvectors/parsers/nist_cavp_hash_parser.py new file mode 100755 index 0000000000000..3a418c499d13e --- /dev/null +++ b/sw/host/cryptotest/testvectors/parsers/nist_cavp_hash_parser.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +# Copyright lowRISC contributors. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +"""Parser for converting NIST CAVP Hash Function test vectors to JSON. + +""" + +import argparse +import sys +import json +import jsonschema + +from cryptotest_util import parse_rsp, str_to_byte_array + +# Map hash function names as formatted in the test vectors to their +# JSON schema names +HASH_FUNCTION_NAME_MAPPING = { + "SHA256": "sha-256", + "SHA384": "sha-384", + "SHA512": "sha-512", + "SHA3_256": "sha3-256", + "SHA3_384": "sha3-384", + "SHA3_512": "sha3-512", + "SHAKE128": "shake-128", + "SHAKE256": "shake-256", +} + + +def parse_testcases(args) -> None: + raw_testcases = parse_rsp(args.src) + test_cases = list() + algorithm = HASH_FUNCTION_NAME_MAPPING[args.algorithm] + # The test vectors for the SHA functions use "MD" as the key for + # the message digest. SHAKE functions use "Output". + if algorithm.startswith("shake"): + digest_key = "Output" + else: + digest_key = "MD" + for section_name in raw_testcases.keys(): + count = 1 + for test_vec in raw_testcases[section_name]: + # The test vector includes a single placeholder zero byte if its + # intended message length is 0, so we need to ignore that byte. + if "Len" in test_vec and int(test_vec["Len"]) == 0: + message = [] + else: + message = str_to_byte_array(test_vec["Msg"]) + if "COUNT" in test_vec: + test_case_id = int(test_vec["COUNT"]) + else: + test_case_id = count + test_case = { + "vendor": "nist", + "test_case_id": test_case_id, + "algorithm": algorithm, + "message": message, + "digest": str_to_byte_array(test_vec[digest_key]), + # All NIST hash test vectors are expected to have the + # right message digest + "result": True, + } + + test_cases.append(test_case) + count += 1 + + json_filename = f"{args.dst}" + with open(json_filename, "w") as file: + json.dump(test_cases, file, indent=4) + + # Validate generated JSON + with open(args.schema) as schema_file: + schema = json.load(schema_file) + jsonschema.validate(test_cases, schema) + + +def main() -> int: + parser = argparse.ArgumentParser( + description="Parsing utility for NIST CAVP Hash Function test vectors.") + + parser.add_argument( + "--src", + help="Source file to import." + ) + parser.add_argument( + "--dst", + help="Destination of the output file." + ) + parser.add_argument( + "--schema", + type = str, + help = "Test vector schema file" + ) + parser.add_argument( + "--algorithm", + type = str, + help = "Hash algorithm the test vectors are testing" + ) + args = parser.parse_args() + parse_testcases(args) + + return 0 + + +if __name__ == "__main__": + sys.exit(main())