From c7a776961c0fd6f41879667a9cb03491d7cfdb93 Mon Sep 17 00:00:00 2001 From: Hanno Becker Date: Sun, 22 Dec 2024 07:13:32 +0000 Subject: [PATCH] Tests: Re-introduce ACVP client as standalone Python script The ACVP client was originally introduced as a standalone script but later added to `tests`, imposing the test infrastructure's dependencies. This commit re-introduces the original standalone script and adds it to `make quickcheck`. The ACVP test within the test scripts remains for now, but should likely be reconciled. Signed-off-by: Hanno Becker --- Makefile | 5 +- test/acvp_client.py | 116 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 test/acvp_client.py diff --git a/Makefile b/Makefile index de56c52d2..cee9d27e4 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ quickcheck: checkall buildall: mlkem nistkat kat acvp $(Q)echo " Everything builds fine!" -checkall: buildall check_kat check_nistkat check_func +checkall: buildall check_kat check_nistkat check_func check_acvp $(Q)echo " Everything checks fine!" check_kat: buildall @@ -33,6 +33,9 @@ check_func: buildall $(MLKEM768_DIR)/bin/test_mlkem768 $(MLKEM1024_DIR)/bin/test_mlkem1024 +check_acvp: buildall + python3 ./test/acvp_client.py + lib: $(BUILD_DIR)/libmlkem.a mlkem: \ diff --git a/test/acvp_client.py b/test/acvp_client.py new file mode 100644 index 000000000..e0080dd68 --- /dev/null +++ b/test/acvp_client.py @@ -0,0 +1,116 @@ +# Copyright (c) 2024 The mlkem-native project authors +# SPDX-License-Identifier: Apache-2.0 + +# ACVP client for ML-KEM +# +# Processes 'internalProjection.json' files from +# https://github.com/usnistgov/ACVP-Server/blob/master/gen-val/json-files +# +# Invokes `acvp_mlkem{lvl}` under the hood. + +import json +import subprocess + +acvp_dir = "test/acvp_data" +acvp_keygen_json = f"{acvp_dir}/acvp_keygen_internalProjection.json" +acvp_encapDecap_json = f"{acvp_dir}/acvp_encapDecap_internalProjection.json" + +with open(acvp_keygen_json, "r") as f: + acvp_keygen_data = json.load(f) + +with open(acvp_encapDecap_json, "r") as f: + acvp_encapDecap_data = json.load(f) + + +def get_acvp_binary(tg): + """Convert JSON dict for ACVP test group to suitable ACVP binary.""" + parameterSetToLevel = { + "ML-KEM-512": 512, + "ML-KEM-768": 768, + "ML-KEM-1024": 1024, + } + level = parameterSetToLevel[tg["parameterSet"]] + basedir = f"./test/build/mlkem{level}/bin" + acvp_bin = f"acvp_mlkem{level}" + return f"{basedir}/{acvp_bin}" + + +def run_encapDecap_test(tg, tc): + print(f"Running encapDecap test case {tc['tcId']} ({tg['function']}) ... ", end="") + if tg["function"] == "encapsulation": + acvp_bin = get_acvp_binary(tg) + acvp_call = [ + acvp_bin, + "encapDecap", + "AFT", + "encapsulation", + f"ek={tc['ek']}", + f"m={tc['m']}", + ] + result = subprocess.run(acvp_call, encoding="utf-8", capture_output=True) + if result.returncode != 0: + print("FAIL!") + print(f"{acvp_call} failed with error code {result.returncode}") + print(result.stderr) + exit(1) + # Extract results and compare to expected data + for l in result.stdout.splitlines(): + (k, v) = l.split("=") + if v != tc[k]: + print("FAIL!") + print(f"Mismatching result for {k}: expected {tc[k]}, got {v}") + exit(1) + print("OK") + elif tg["function"] == "decapsulation": + acvp_bin = get_acvp_binary(tg) + acvp_call = [ + acvp_bin, + "encapDecap", + "VAL", + "decapsulation", + f"dk={tg['dk']}", + f"c={tc['c']}", + ] + result = subprocess.run(acvp_call, encoding="utf-8", capture_output=True) + if result.returncode != 0: + print("FAIL!") + print(f"{acvp_call} failed with error code {result.returncode}") + print(result.stderr) + exit(1) + # Extract results and compare to expected data + for l in result.stdout.splitlines(): + (k, v) = l.split("=") + if v != tc[k]: + print("FAIL!") + print(f"Mismatching result for {k}: expected {tc[k]}, got {v}") + exit(1) + print("OK") + + +def run_keyGen_test(tg, tc): + print(f"Running keyGen test case {tc['tcId']} ... ", end="") + acvp_bin = get_acvp_binary(tg) + acvp_call = [acvp_bin, "keyGen", "AFT", f"z={tc['z']}", f"d={tc['d']}"] + result = subprocess.run(acvp_call, encoding="utf-8", capture_output=True) + if result.returncode != 0: + print("FAIL!") + print(f"{acvp_call} failed with error code {result.returncode}") + print(result.stderr) + exit(1) + # Extract results and compare to expected data + for l in result.stdout.splitlines(): + (k, v) = l.split("=") + if v != tc[k]: + print("FAIL!") + print(f"Mismatching result for {k}: expected {tc[k]}, got {v}") + exit(1) + print("OK") + + +for tg in acvp_encapDecap_data["testGroups"]: + for tc in tg["tests"]: + run_encapDecap_test(tg, tc) + +for tg in acvp_keygen_data["testGroups"]: + for tc in tg["tests"]: + run_keyGen_test(tg, tc)