Home | History | Annotate | Line # | Download | only in test
      1 #!/usr/bin/env python3
      2 #
      3 # Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
      4 #
      5 # Licensed under the Apache License 2.0 (the "License").  You may not use
      6 # this file except in compliance with the License.  You can obtain a copy
      7 # in the file LICENSE in the source distribution or at
      8 # https://www.openssl.org/source/license.html
      9 
     10 # A python program written to parse (version 42) of the ACVP test vectors for
     11 # SLH_DSA. The 3 files that can be processed by this utility can be downloaded
     12 # from
     13 #  https://github.com/usnistgov/ACVP-Server/blob/master/gen-val/json-files/SLH-DSA-keyGen-FIPS204/internalProjection.json
     14 #  https://github.com/usnistgov/ACVP-Server/blob/master/gen-val/json-files/SLH-DSA-sigGen-FIPS204/internalProjection.json
     15 #  https://github.com/usnistgov/ACVP-Server/blob/master/gen-val/json-files/SLH-DSA-sigVer-FIPS204/internalProjection.json
     16 # and output from this utility to
     17 #  test/recipes/30-test_evp_data/evppkey_slh_dsa_keygen.txt
     18 #  test/recipes/30-test_evp_data/evppkey_slh_dsa_siggen.txt
     19 #  test/recipes/30-test_evp_data/evppkey_slh_dsa_sigver.txt
     20 #
     21 # e.g. python3 slhdsa_parse.py ~/Downloads/keygen.json > ./test/recipes/30-test_evp_data/evppkey_slh_dsa_keygen.txt
     22 #
     23 import json
     24 import argparse
     25 import datetime
     26 import re
     27 import sys
     28 
     29 def eprint(*args, **kwargs):
     30     print(*args, file=sys.stderr, **kwargs)
     31 
     32 def print_label(label, value):
     33     print(label + " = " + value)
     34 
     35 def print_hexlabel(label, tag, value):
     36     print(label + " = hex" + tag + ":" + value)
     37 
     38 def parse_slh_dsa_key_gen(groups):
     39     for grp in groups:
     40         name = grp['parameterSet'].replace('-', '_')
     41         for tst in grp['tests']:
     42             print("");
     43             print_label("FIPSversion", ">=3.5.0")
     44             print_label("KeyGen", grp['parameterSet'])
     45             print_label("KeyName", "tcId" + str(tst['tcId']))
     46             print_hexlabel("Ctrl", "seed", tst['skSeed'] + tst['skPrf'] + tst['pkSeed'])
     47             print_hexlabel("CtrlOut", "pub", tst['pk'])
     48             print_hexlabel("CtrlOut", "priv", tst['sk'])
     49 
     50 def parse_slh_dsa_sig_gen(groups):
     51     coverage = set()
     52     for grp in groups:
     53         name = grp['parameterSet'].replace('-', '_')
     54         encoding = "1" if grp['preHash'] != 'none' else "0"
     55         deterministic = "1" if grp['deterministic'] else "0"
     56         for tst in grp['tests']:
     57             if tst['hashAlg'] != 'none':
     58                 continue
     59 
     60             # Check if there is a similar test already and skip if so
     61             # Signature generation tests are very expensive, so we need to
     62             # limit things substantially.  By default, we only test one of
     63             # the slow flavours and one of each kind of the fast ones.
     64             if name.find('f') >= 0:
     65                 context = "1" if 'context' in tst else "0"
     66                 coverage_name = name + "_" + encoding + deterministic + context
     67             else:
     68                 coverage_name = name
     69             extended_test = coverage_name in coverage
     70             coverage.add(coverage_name)
     71 
     72             # Emit test case
     73             testname = name + "_" + str(tst['tcId'])
     74             print("");
     75             print_label("PrivateKeyRaw", testname + ":" + grp['parameterSet'] + ":" + tst['sk'])
     76             print("");
     77             print_label("FIPSversion", ">=3.5.0")
     78             print_label("Sign-Message", grp['parameterSet'] + ":" + testname)
     79             if extended_test:
     80                 print_label("Extended-Test", "1")
     81             print_label("Input", tst['message'])
     82             print_label("Output", tst['signature'])
     83             if 'additionalRandomness' in tst:
     84                 print_hexlabel("Ctrl", "test-entropy", tst['additionalRandomness']);
     85             print_label("Ctrl", "deterministic:" + deterministic)
     86             print_label("Ctrl", "message-encoding:" + encoding)
     87             if 'context' in tst:
     88                 print_hexlabel("Ctrl", "context-string", tst["context"])
     89 
     90 def parse_slh_dsa_sig_ver(groups):
     91     for grp in groups:
     92         name = grp['parameterSet'].replace('-', '_')
     93         encoding = "1" if grp['preHash'] != 'none' else "0"
     94         for tst in grp['tests']:
     95             if tst['hashAlg'] != 'none':
     96                 continue
     97             testname = name + "_" + str(tst['tcId'])
     98             print("");
     99             print_label("PublicKeyRaw", testname + ":" + grp['parameterSet'] + ":" + tst['pk'])
    100             print("");
    101             if "reason" in tst:
    102                 print("# " + tst['reason'])
    103             print_label("FIPSversion", ">=3.5.0")
    104             print_label("Verify-Message-Public", grp['parameterSet'] + ":" + testname)
    105             print_label("Input", tst['message'])
    106             print_label("Output", tst['signature'])
    107             if 'additionalRandomness' in tst:
    108                 print_hexlabel("Ctrl", "test-entropy", tst['additionalRandomness']);
    109             print_label("Ctrl", "message-encoding:" + encoding)
    110             if 'context' in tst:
    111                 print_hexlabel("Ctrl", "context-string", tst["context"])
    112             if not tst['testPassed']:
    113                 print_label("Result", "VERIFY_ERROR")
    114 
    115 parser = argparse.ArgumentParser(description="")
    116 parser.add_argument('filename', type=str)
    117 args = parser.parse_args()
    118 
    119 # Open and read the JSON file
    120 with open(args.filename, 'r') as file:
    121     data = json.load(file)
    122 
    123 year = datetime.date.today().year
    124 version = data['vsId']
    125 algorithm = data['algorithm']
    126 mode = data['mode']
    127 
    128 print("# Copyright " + str(year) + " The OpenSSL Project Authors. All Rights Reserved.")
    129 print("#")
    130 print("# Licensed under the Apache License 2.0 (the \"License\").  You may not use")
    131 print("# this file except in compliance with the License.  You can obtain a copy")
    132 print("# in the file LICENSE in the source distribution or at")
    133 print("# https://www.openssl.org/source/license.html\n")
    134 print("# ACVP test data for " + algorithm + " " + mode + " generated from")
    135 print("# https://github.com/usnistgov/ACVP-Server/blob/master/gen-val/json-files/"
    136       "SLH-DSA-" + mode + "-FIPS204/internalProjection.json")
    137 print("# [version " + str(version) + "]")
    138 
    139 if algorithm == "SLH-DSA":
    140     if mode == 'sigVer':
    141         parse_slh_dsa_sig_ver(data['testGroups'])
    142     elif mode == 'sigGen':
    143         parse_slh_dsa_sig_gen(data['testGroups'])
    144     elif mode == 'keyGen':
    145         parse_slh_dsa_key_gen(data['testGroups'])
    146     else:
    147         eprint("Unsupported mode " + mode)
    148 else:
    149     eprint("Unsupported algorithm " + algorithm)
    150