Add LMS evp_test using NIST ACVP test data.

This covers all LMS algorithm parameter sets.

The following changes were done to handle the tests:
 (1) Changed LMS to use OSSL_PKEY_PARAM_PUB_KEY instead of
OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY for import/export.
(There is no reason to have the encoded form for verify operations).
 (2) Fixed a bug for W=1 with truncated digests. The checksum was using
a value of 8-w, which was off by 1 for this case. A value was added to
the ots parameters that represents this value.
 (3) A check in evp_test for a NID was removed since LMS does not have
OIDS (HSS does).
 (4) the unused PROPERTIES param was removed from the LMS keymanager.

Reviewed-by: Viktor Dukhovni <viktor@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Paul Dale <ppzgs1@gmail.com>
(Merged from https://github.com/openssl/openssl/pull/27885)
This commit is contained in:
slontis
2025-07-02 18:21:39 +10:00
committed by Pauli
parent d3081a52e8
commit e6c8110483
11 changed files with 2524 additions and 31 deletions

View File

@@ -13,24 +13,24 @@
/* Refer to SP800-208 Section 4 LM-OTS parameter sets */
static const LM_OTS_PARAMS lm_ots_params[] = {
{ OSSL_LM_OTS_TYPE_SHA256_N32_W1, 32, 1, 265, "SHA256"},
{ OSSL_LM_OTS_TYPE_SHA256_N32_W2, 32, 2, 133, "SHA256"},
{ OSSL_LM_OTS_TYPE_SHA256_N32_W4, 32, 4, 67, "SHA256"},
{ OSSL_LM_OTS_TYPE_SHA256_N32_W8, 32, 8, 34, "SHA256"},
{ OSSL_LM_OTS_TYPE_SHA256_N24_W1, 24, 1, 200, "SHA256-192"},
{ OSSL_LM_OTS_TYPE_SHA256_N24_W2, 24, 2, 101, "SHA256-192"},
{ OSSL_LM_OTS_TYPE_SHA256_N24_W4, 24, 4, 51, "SHA256-192"},
{ OSSL_LM_OTS_TYPE_SHA256_N24_W8, 24, 8, 26, "SHA256-192"},
{ OSSL_LM_OTS_TYPE_SHAKE_N32_W1, 32, 1, 265, "SHAKE-256"},
{ OSSL_LM_OTS_TYPE_SHAKE_N32_W2, 32, 2, 133, "SHAKE-256"},
{ OSSL_LM_OTS_TYPE_SHAKE_N32_W4, 32, 4, 67, "SHAKE-256"},
{ OSSL_LM_OTS_TYPE_SHAKE_N32_W8, 32, 8, 34, "SHAKE-256"},
{ OSSL_LM_OTS_TYPE_SHA256_N32_W1, 32, 1, 265, 7, "SHA256"},
{ OSSL_LM_OTS_TYPE_SHA256_N32_W2, 32, 2, 133, 6, "SHA256"},
{ OSSL_LM_OTS_TYPE_SHA256_N32_W4, 32, 4, 67, 4, "SHA256"},
{ OSSL_LM_OTS_TYPE_SHA256_N32_W8, 32, 8, 34, 0, "SHA256"},
{ OSSL_LM_OTS_TYPE_SHA256_N24_W1, 24, 1, 200, 8, "SHA256-192"},
{ OSSL_LM_OTS_TYPE_SHA256_N24_W2, 24, 2, 101, 6, "SHA256-192"},
{ OSSL_LM_OTS_TYPE_SHA256_N24_W4, 24, 4, 51, 4, "SHA256-192"},
{ OSSL_LM_OTS_TYPE_SHA256_N24_W8, 24, 8, 26, 0, "SHA256-192"},
{ OSSL_LM_OTS_TYPE_SHAKE_N32_W1, 32, 1, 265, 7, "SHAKE-256"},
{ OSSL_LM_OTS_TYPE_SHAKE_N32_W2, 32, 2, 133, 6, "SHAKE-256"},
{ OSSL_LM_OTS_TYPE_SHAKE_N32_W4, 32, 4, 67, 4, "SHAKE-256"},
{ OSSL_LM_OTS_TYPE_SHAKE_N32_W8, 32, 8, 34, 0, "SHAKE-256"},
/* SHAKE-256/192 - OpenSSL does not support this as a name */
{ OSSL_LM_OTS_TYPE_SHAKE_N24_W1, 24, 1, 200, "SHAKE-256"},
{ OSSL_LM_OTS_TYPE_SHAKE_N24_W2, 24, 2, 101, "SHAKE-256"},
{ OSSL_LM_OTS_TYPE_SHAKE_N24_W4, 24, 4, 51, "SHAKE-256"},
{ OSSL_LM_OTS_TYPE_SHAKE_N24_W8, 24, 8, 26, "SHAKE-256"},
{ 0, 0, 0, 0, NULL },
{ OSSL_LM_OTS_TYPE_SHAKE_N24_W1, 24, 1, 200, 8, "SHAKE-256"},
{ OSSL_LM_OTS_TYPE_SHAKE_N24_W2, 24, 2, 101, 6, "SHAKE-256"},
{ OSSL_LM_OTS_TYPE_SHAKE_N24_W4, 24, 4, 51, 4, "SHAKE-256"},
{ OSSL_LM_OTS_TYPE_SHAKE_N24_W8, 24, 8, 26, 0, "SHAKE-256"},
{ 0, 0, 0, 0, 0, NULL },
};
/**
@@ -62,5 +62,5 @@ uint16_t ossl_lm_ots_params_checksum(const LM_OTS_PARAMS *params,
for (i = 0; i < bytes; ++i)
sum += end - coef(S, i, params->w);
return (sum << (8 - params->w));
return (sum << params->ls);
}

View File

@@ -125,7 +125,7 @@ int ossl_lms_pubkey_from_params(const OSSL_PARAM params[], LMS_KEY *lmskey)
{
const OSSL_PARAM *p = NULL;
p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY);
p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_PUB_KEY);
if (p != NULL) {
if (p->data == NULL
|| p->data_type != OSSL_PARAM_OCTET_STRING

View File

@@ -20,7 +20,7 @@ LMS keymanager for import and export.
=over 4
=item "encoded-pub-key" (B<OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY>) <octet string>
=item "pub" (B<OSSL_PKEY_PARAM_PUB_KEY>) <octet string>
Used for getting and setting the encoding of an LMS public key. The public key
is expected to be in XDR format.

View File

@@ -98,6 +98,12 @@ typedef struct lm_ots_params_st {
* One of (200, 101, 51, 26) for n = 24, for w=1,2,4,8
*/
uint32_t p;
/*
* The size of the shift needed to move the checksum so
* that it appears in the checksum digits.
* See RFC 8554 Appendix B. LM-OTS Parameter Options
*/
uint32_t ls;
const char *digestname; /* Hash Name */
} LM_OTS_PARAMS;

View File

@@ -493,7 +493,7 @@ static int self_test_LMS(OSSL_SELF_TEST *st, OSSL_LIB_CTX *libctx)
OSSL_SELF_TEST_onbegin(st, OSSL_SELF_TEST_TYPE_KAT_SIGNATURE,
OSSL_SELF_TEST_DESC_SIGN_LMS);
pm[0] = OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY,
pm[0] = OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_PUB_KEY,
(unsigned char *)t->pub,
t->publen);
pm[1] = OSSL_PARAM_construct_end();

View File

@@ -79,8 +79,7 @@ static int lms_import(void *keydata, int selection, const OSSL_PARAM params[])
}
static const OSSL_PARAM lms_key_types[] = {
OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY, NULL, 0),
OSSL_PARAM_utf8_string(OSSL_PKEY_PARAM_PROPERTIES, NULL, 0),
OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PUB_KEY, NULL, 0),
OSSL_PARAM_END
};
static const OSSL_PARAM *lms_imexport_types(int selection)
@@ -109,7 +108,7 @@ static int lms_export(void *keydata, int selection, OSSL_CALLBACK *param_cb,
return 0;
if (!ossl_param_build_set_octet_string(tmpl, params,
OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY,
OSSL_PKEY_PARAM_PUB_KEY,
lmskey->pub.encoded,
lmskey->pub.encodedlen))
goto err;

View File

@@ -5278,7 +5278,6 @@ start:
char *strnid = NULL, *keydata = NULL;
unsigned char *keybin;
size_t keylen;
int nid;
if (strcmp(pp->key, "PrivateKeyRaw") == 0)
klist = &private_keys;
@@ -5297,11 +5296,6 @@ start:
return 0;
}
nid = OBJ_txt2nid(strnid);
if (nid == NID_undef) {
TEST_info("Unrecognised algorithm NID");
return 0;
}
if (!parse_bin(keydata, &keybin, &keylen)) {
TEST_info("Failed to create binary key");
return 0;

79
test/lms_parse.py Normal file
View File

@@ -0,0 +1,79 @@
#!/usr/bin/env python
# Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
#
# Licensed under the Apache License 2.0 (the "License"). You may not use
# this file except in compliance with the License. You can obtain a copy
# in the file LICENSE in the source distribution or at
# https://www.openssl.org/source/license.html
# A python program written to parse (version 1.0) of the ACVP test vectors for
# LMS. The file that can be processed by this utility can be downloaded from
# https://raw.githubusercontent.com/usnistgov/ACVP-Server/refs/heads/master/gen-val/json-files/LMS-sigVer-1.0/internalProjection.json
# and output from this utility to
# test/recipes/30-test_evp_data/evppkey_lms.txt
#
# e.g. python3 mldsa_parse.py ~/Downloads/internalProjection.json > ./test/recipes/30-test_evp_data/evppkey_lms.txt
#
import json
import argparse
import datetime
def print_label(label, value):
print(label + " = " + value)
def print_hexlabel(label, tag, value):
print(label + " = hex" + tag + ":" + value)
def parse_lms_sig_ver(groups):
for grp in groups:
lmsmode = grp["lmsMode"]
lmotsmode = grp["lmOtsMode"]
name = lmsmode + "_" + str(grp["tgId"])
pubkey = grp["publicKey"]
if grp["testType"] != "AFT":
continue
print_label("Title", lmsmode + " " + lmotsmode)
print("");
print_label("PublicKeyRaw", name + ":" + "LMS" + ":" + pubkey)
for tst in grp['tests']:
testname = lmsmode + "_" + str(tst['tcId'])
print("");
if "reason" in tst:
print("# " + tst['reason'])
print_label("FIPSversion", ">=3.6.0")
print_label("Verify-Message-Public", "LMS:" + name)
print_label("Input", tst['message'])
print_label("Output", tst['signature'])
if not tst['testPassed']:
print_label("Result", "VERIFY_ERROR")
parser = argparse.ArgumentParser(description="")
parser.add_argument('filename', type=str)
args = parser.parse_args()
# Open and read the JSON file
with open(args.filename, 'r') as file:
data = json.load(file)
year = datetime.date.today().year
version = data['vsId']
revision = data['revision']
algorithm = data['algorithm']
print("# Copyright " + str(year) + " The OpenSSL Project Authors. All Rights Reserved.")
print("#")
print("# Licensed under the Apache License 2.0 (the \"License\"). You may not use")
print("# this file except in compliance with the License. You can obtain a copy")
print("# in the file LICENSE in the source distribution or at")
print("# https://www.openssl.org/source/license.html\n")
print("# ACVP test data for " + algorithm + " generated from")
print("# https://raw.githubusercontent.com/usnistgov/ACVP-Server/refs/heads/master/gen-val/json-files/LMS-sigVer-1.0/internalProjection.json")
print("# [version " + str(version) + " : revision " + str(revision) + "]")
print("")
if algorithm == "LMS":
parse_lms_sig_ver(data['testGroups'])
else:
print("Unsupported algorithm " + algorithm)

View File

@@ -34,7 +34,7 @@ static EVP_PKEY *lms_pubkey_from_data(const unsigned char *data, size_t datalen)
EVP_PKEY *key = NULL;
OSSL_PARAM params[2];
params[0] = OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY,
params[0] = OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_PUB_KEY,
(unsigned char *)data, datalen);
params[1] = OSSL_PARAM_construct_end();
ret = TEST_ptr(ctx = EVP_PKEY_CTX_new_from_name(libctx, "LMS", propq))

View File

@@ -34,6 +34,7 @@ my $no_siv = disabled("siv");
my $no_argon2 = disabled("argon2");
my $no_ml_dsa = disabled("ml-dsa");
my $no_ml_kem = disabled("ml-kem");
my $no_lms = disabled("lms");
# Default config depends on if the legacy module is built or not
my $defaultcnf = $no_legacy ? 'default.cnf' : 'default-and-legacy.cnf';
@@ -124,6 +125,9 @@ push @files, qw(
evppkey_ml_kem_keygen.txt
evppkey_ml_kem_encap_decap.txt
) unless $no_ml_kem;
push @files, qw(
evppkey_lms_sigver.txt
) unless $no_lms;
# A list of tests that only run with the default provider
# (i.e. The algorithms are not present in the fips provider)

File diff suppressed because one or more lines are too long