Commit 3d82b990d1 for openssl.org
commit 3d82b990d1fdd7b8af23d69f140cd4aff56ee6ad
Author: slontis <shane.lontis@oracle.com>
Date: Fri Dec 12 13:52:25 2025 +1100
Added LMS support for OpenSSL commandline signature verification using pkeyutl.
Added LMS 'SubjectPublicKeyInfo' encoder/decoder support.
Modified LMS keymanager and signature code to work with pkey and
pkeyutl.
Test data for public keys and signatures were generated by modifying
BouncyCastle code tests.
Reviewed-by: Viktor Dukhovni <viktor@openssl.org>
Reviewed-by: Paul Dale <paul.dale@oracle.com>
Reviewed-by: Matt Caswell <matt@openssl.org>
MergeDate: Fri Feb 27 14:40:27 2026
(Merged from https://github.com/openssl/openssl/pull/29381)
diff --git a/CHANGES.md b/CHANGES.md
index 34b959f152..51e2eb5dda 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -32,6 +32,12 @@ OpenSSL 4.0
### Changes between 3.6 and 4.0 [xx XXX xxxx]
+ * Added LMS support for signature verification to `pkeyutl' command.
+ To enable this, LMS 'SubjectPublicKeyInfo' encoder and decoders were
+ added, and the LMS keymanager and signature code were updated.
+
+ *Shane Lontis*
+
* New `SSL_get0_sigalg()` and `SSL_get0_shared_sigalg()` functions report the
TLS signature algorithm name and codepoint for the peer advertised and shared
algorithms respectively. These supersede the existing `SSL_get_sigalgs()` and
diff --git a/crypto/lms/lms_key.c b/crypto/lms/lms_key.c
index a5d87ad845..752ffea519 100644
--- a/crypto/lms/lms_key.c
+++ b/crypto/lms/lms_key.c
@@ -106,3 +106,25 @@ int ossl_lms_key_has(const LMS_KEY *key, int selection)
return 0;
return 1;
}
+
+/* Returns the public key data or NULL if there is no public key */
+const uint8_t *ossl_lms_key_get_pub(const LMS_KEY *key)
+{
+ return key->pub.encoded;
+}
+
+/* The encoded public key size */
+size_t ossl_lms_key_get_pub_len(const LMS_KEY *key)
+{
+ return 24 + key->lms_params->n;
+}
+
+size_t ossl_lms_key_get_collision_strength_bits(const LMS_KEY *key)
+{
+ return key->lms_params->n * 8;
+}
+
+size_t ossl_lms_key_get_sig_len(const LMS_KEY *key)
+{
+ return 12 + key->lms_params->n * (1 + key->ots_params->p + key->lms_params->h);
+}
diff --git a/crypto/lms/lms_params.c b/crypto/lms/lms_params.c
index 6ac6dcdb81..006a5d3768 100644
--- a/crypto/lms/lms_params.c
+++ b/crypto/lms/lms_params.c
@@ -11,27 +11,27 @@
/* Refer to SP800-208 Section 4 LMS Parameter Sets */
static const LMS_PARAMS lms_params[] = {
- { OSSL_LMS_TYPE_SHA256_N32_H5, "SHA256", 32, 5 },
- { OSSL_LMS_TYPE_SHA256_N32_H10, "SHA256", 32, 10 },
- { OSSL_LMS_TYPE_SHA256_N32_H15, "SHA256", 32, 15 },
- { OSSL_LMS_TYPE_SHA256_N32_H20, "SHA256", 32, 20 },
- { OSSL_LMS_TYPE_SHA256_N32_H25, "SHA256", 32, 25 },
- { OSSL_LMS_TYPE_SHA256_N24_H5, "SHA256-192", 24, 5 },
- { OSSL_LMS_TYPE_SHA256_N24_H10, "SHA256-192", 24, 10 },
- { OSSL_LMS_TYPE_SHA256_N24_H15, "SHA256-192", 24, 15 },
- { OSSL_LMS_TYPE_SHA256_N24_H20, "SHA256-192", 24, 20 },
- { OSSL_LMS_TYPE_SHA256_N24_H25, "SHA256-192", 24, 25 },
- { OSSL_LMS_TYPE_SHAKE_N32_H5, "SHAKE-256", 32, 5 },
- { OSSL_LMS_TYPE_SHAKE_N32_H10, "SHAKE-256", 32, 10 },
- { OSSL_LMS_TYPE_SHAKE_N32_H15, "SHAKE-256", 32, 15 },
- { OSSL_LMS_TYPE_SHAKE_N32_H20, "SHAKE-256", 32, 20 },
- { OSSL_LMS_TYPE_SHAKE_N32_H25, "SHAKE-256", 32, 25 },
+ { OSSL_LMS_TYPE_SHA256_N32_H5, "SHA256", 32, 5, 128 },
+ { OSSL_LMS_TYPE_SHA256_N32_H10, "SHA256", 32, 10, 128 },
+ { OSSL_LMS_TYPE_SHA256_N32_H15, "SHA256", 32, 15, 128 },
+ { OSSL_LMS_TYPE_SHA256_N32_H20, "SHA256", 32, 20, 128 },
+ { OSSL_LMS_TYPE_SHA256_N32_H25, "SHA256", 32, 25, 128 },
+ { OSSL_LMS_TYPE_SHA256_N24_H5, "SHA256-192", 24, 5, 96 },
+ { OSSL_LMS_TYPE_SHA256_N24_H10, "SHA256-192", 24, 10, 96 },
+ { OSSL_LMS_TYPE_SHA256_N24_H15, "SHA256-192", 24, 15, 96 },
+ { OSSL_LMS_TYPE_SHA256_N24_H20, "SHA256-192", 24, 20, 96 },
+ { OSSL_LMS_TYPE_SHA256_N24_H25, "SHA256-192", 24, 25, 96 },
+ { OSSL_LMS_TYPE_SHAKE_N32_H5, "SHAKE-256", 32, 5, 256 },
+ { OSSL_LMS_TYPE_SHAKE_N32_H10, "SHAKE-256", 32, 10, 256 },
+ { OSSL_LMS_TYPE_SHAKE_N32_H15, "SHAKE-256", 32, 15, 256 },
+ { OSSL_LMS_TYPE_SHAKE_N32_H20, "SHAKE-256", 32, 20, 256 },
+ { OSSL_LMS_TYPE_SHAKE_N32_H25, "SHAKE-256", 32, 25, 256 },
/* SHAKE-256/192 */
- { OSSL_LMS_TYPE_SHAKE_N24_H5, "SHAKE-256", 24, 5 },
- { OSSL_LMS_TYPE_SHAKE_N24_H10, "SHAKE-256", 24, 10 },
- { OSSL_LMS_TYPE_SHAKE_N24_H15, "SHAKE-256", 24, 15 },
- { OSSL_LMS_TYPE_SHAKE_N24_H20, "SHAKE-256", 24, 20 },
- { OSSL_LMS_TYPE_SHAKE_N24_H25, "SHAKE-256", 24, 25 },
+ { OSSL_LMS_TYPE_SHAKE_N24_H5, "SHAKE-256", 24, 5, 192 },
+ { OSSL_LMS_TYPE_SHAKE_N24_H10, "SHAKE-256", 24, 10, 192 },
+ { OSSL_LMS_TYPE_SHAKE_N24_H15, "SHAKE-256", 24, 15, 192 },
+ { OSSL_LMS_TYPE_SHAKE_N24_H20, "SHAKE-256", 24, 20, 192 },
+ { OSSL_LMS_TYPE_SHAKE_N24_H25, "SHAKE-256", 24, 25, 192 },
{ 0, NULL, 0, 0 }
};
diff --git a/doc/man7/EVP_PKEY-LMS.pod b/doc/man7/EVP_PKEY-LMS.pod
index a51e9be436..4a5323e2aa 100644
--- a/doc/man7/EVP_PKEY-LMS.pod
+++ b/doc/man7/EVP_PKEY-LMS.pod
@@ -27,6 +27,16 @@ is expected to be in XDR format.
=back
+The following parameters is gettable using EVP_PKEY_get_utf8_string_param().
+
+=over 4
+
+=item "mandatory-digest" (B<OSSL_PKEY_PARAM_MANDATORY_DIGEST>) <UTF8 string>
+
+The empty string, signifying that no digest may be specified.
+
+=back
+
=head1 CONFORMING TO
=over 4
@@ -91,6 +101,8 @@ L<provider-keymgmt(7)>
=head1 HISTORY
This functionality was added in OpenSSL 3.6.
+The gettable "mandatory-digest" and support for loading LMS public keys in
+SubjectPublicKeyInfo format was added in OpenSSL 4.0.
=head1 COPYRIGHT
diff --git a/doc/man7/EVP_SIGNATURE-LMS.pod b/doc/man7/EVP_SIGNATURE-LMS.pod
index bd936bfbfe..7bffe8b912 100644
--- a/doc/man7/EVP_SIGNATURE-LMS.pod
+++ b/doc/man7/EVP_SIGNATURE-LMS.pod
@@ -24,6 +24,9 @@ that specify the digest name are not necessary.
LMS support is disabled by default at compile-time.
To enable, specify the B<enable-lms> build configuration option.
+For backwards compatibility reasons EVP_DigestVerifyInit_ex() and
+EVP_DigestVerify() may also be used, but the digest passed in I<mdname> must be NULL.
+
LMS should only be used for older deployments.
New deployments should use either L<EVP_SIGNATURE-ML-DSA(7)>
or <L/EVP_SIGNATURE-SLH-DSA(7)>.
@@ -56,6 +59,8 @@ L<provider-signature(7)>,
=head1 HISTORY
This functionality was added in OpenSSL 3.6.
+Support for EVP_DigestVerifyInit_ex() and EVP_DigestVerify() was added in
+OpenSSL 4.0.
=head1 COPYRIGHT
diff --git a/doc/man7/OSSL_PROVIDER-base.pod b/doc/man7/OSSL_PROVIDER-base.pod
index fdc7d0e54e..ca8416f755 100644
--- a/doc/man7/OSSL_PROVIDER-base.pod
+++ b/doc/man7/OSSL_PROVIDER-base.pod
@@ -132,6 +132,10 @@ are also available in the default provider.
=item SLH-DSA-SHAKE-256f
+=item LMS
+
+Private keys are not supported for LMS.
+
=back
In addition to this provider, all of these encoding algorithms are also
@@ -202,6 +206,10 @@ combination with the FIPS provider.
=item SLH-DSA-SHAKE-256f
+=item LMS
+
+Private keys are not supported for LMS.
+
=back
In addition to this provider, all of these decoding algorithms are also
@@ -230,7 +238,10 @@ L<openssl-core_dispatch.h(7)>, L<provider(7)>
This functionality was added in OpenSSL 3.0.
-Support for B<ML-DSA> and <ML-KEM> was added in OpenSSL 3.5.
+Support for B<ML-DSA> and B<ML-KEM> was added in OpenSSL 3.5.
+
+Support for B<LMS> Public Key (SubjectPublicKeyInfo) encoders and decoders
+was added in OpenSSL 4.0.
=head1 COPYRIGHT
diff --git a/doc/man7/OSSL_PROVIDER-default.pod b/doc/man7/OSSL_PROVIDER-default.pod
index d49502b5fd..1c98bb787f 100644
--- a/doc/man7/OSSL_PROVIDER-default.pod
+++ b/doc/man7/OSSL_PROVIDER-default.pod
@@ -444,6 +444,10 @@ are also available in the base provider.
=item SLH-DSA-SHAKE-256f
+=item LMS
+
+Private keys are not supported for LMS.
+
=back
In addition to this provider, all of these encoding algorithms are also
@@ -512,6 +516,10 @@ combination with the FIPS provider.
=item SLH-DSA-SHAKE-256f
+=item LMS
+
+Private keys are not supported for LMS.
+
=back
In addition to this provider, all of these decoding algorithms are also
@@ -538,6 +546,9 @@ L<OSSL_PROVIDER-base(7)>
=head1 HISTORY
+Support for B<LMS> Public Key (SubjectPublicKeyInfo) encoders and decoders
+was added in OpenSSL 4.0.
+
The RIPEMD160 digest was added to the default provider in OpenSSL 3.0.7.
The HKDF-SHA256, HKDF-SHA384 and HKDF-SHA512 algorithms were added in OpenSSL 3.6.
diff --git a/include/crypto/lms.h b/include/crypto/lms.h
index 6fd2835c29..ffb856893f 100644
--- a/include/crypto/lms.h
+++ b/include/crypto/lms.h
@@ -117,6 +117,7 @@ typedef struct lms_params_st {
const char *digestname; /* One of SHA256, SHA256-192, or SHAKE256 */
uint32_t n; /* The Digest size (either 24 or 32), Useful for setting up SHAKE */
uint32_t h; /* The height of a LMS tree which is one of 5, 10, 15, 20, 25) */
+ size_t bit_strength;
} LMS_PARAMS;
typedef struct lms_pub_key_st {
@@ -156,5 +157,10 @@ int ossl_lms_pubkey_decode(const unsigned char *pub, size_t publen,
LMS_KEY *lmskey);
size_t ossl_lms_pubkey_length(const unsigned char *data, size_t datalen);
+const uint8_t *ossl_lms_key_get_pub(const LMS_KEY *key);
+size_t ossl_lms_key_get_pub_len(const LMS_KEY *key);
+size_t ossl_lms_key_get_collision_strength_bits(const LMS_KEY *key);
+size_t ossl_lms_key_get_sig_len(const LMS_KEY *key);
+
#endif /* OPENSSL_NO_LMS */
#endif /* OSSL_CRYPTO_LMS_H */
diff --git a/providers/implementations/encode_decode/build.info b/providers/implementations/encode_decode/build.info
index 09c2d8c435..2347ef865f 100644
--- a/providers/implementations/encode_decode/build.info
+++ b/providers/implementations/encode_decode/build.info
@@ -20,7 +20,7 @@ ENDIF
DEPEND[encode_key2any.o]=../../common/include/prov/der_rsa.h
IF[{- !$disabled{lms} -}]
- SOURCE[$DECODER_GOAL]=decode_lmsxdr2key.c
+ SOURCE[$DECODER_GOAL]=decode_lmsxdr2key.c lms_codecs.c
ENDIF
IF[{- !$disabled{'ml-dsa'} -}]
diff --git a/providers/implementations/encode_decode/decode_der2key.c b/providers/implementations/encode_decode/decode_der2key.c
index b75d73b0fd..3458fd324a 100644
--- a/providers/implementations/encode_decode/decode_der2key.c
+++ b/providers/implementations/encode_decode/decode_der2key.c
@@ -44,6 +44,7 @@
#include "internal/nelem.h"
#include "prov/ml_dsa_codecs.h"
#include "prov/ml_kem_codecs.h"
+#include "prov/lms_codecs.h"
#include "providers/implementations/encode_decode/decode_der2key.inc"
#ifndef OPENSSL_NO_SLH_DSA
@@ -1001,6 +1002,25 @@ static ossl_inline void *ml_dsa_d2i_PUBKEY(const uint8_t **der, long der_len,
/* ---------------------------------------------------------------------- */
+#ifndef OPENSSL_NO_LMS
+#define lms_evp_type EVP_PKEY_HSS_LMS
+#define lms_free (free_key_fn *)ossl_lms_key_free
+#define lms_check NULL
+#define lms_adjust NULL
+
+static ossl_inline void *lms_d2i_PUBKEY(const uint8_t **der, long der_len,
+ struct der2key_ctx_st *ctx)
+{
+ LMS_KEY *key;
+
+ key = ossl_lms_d2i_PUBKEY(*der, der_len, ctx->provctx);
+ if (key != NULL)
+ *der += der_len;
+ return key;
+}
+#endif
+/* ---------------------------------------------------------------------- */
+
/*
* The DO_ macros help define the selection mask and the method functions
* for each kind of object we want to decode.
@@ -1303,3 +1323,7 @@ MAKE_DECODER("ML-DSA-65", ml_dsa_65, ml_dsa_65, SubjectPublicKeyInfo);
MAKE_DECODER("ML-DSA-87", ml_dsa_87, ml_dsa_87, PrivateKeyInfo);
MAKE_DECODER("ML-DSA-87", ml_dsa_87, ml_dsa_87, SubjectPublicKeyInfo);
#endif
+
+#ifndef OPENSSL_NO_LMS
+MAKE_DECODER("LMS", lms, lms, SubjectPublicKeyInfo);
+#endif
diff --git a/providers/implementations/encode_decode/encode_key2any.c b/providers/implementations/encode_decode/encode_key2any.c
index b4deb9b59b..396c017998 100644
--- a/providers/implementations/encode_decode/encode_key2any.c
+++ b/providers/implementations/encode_decode/encode_key2any.c
@@ -41,6 +41,7 @@
#include "prov/endecoder_local.h"
#include "prov/ml_dsa_codecs.h"
#include "prov/ml_kem_codecs.h"
+#include "prov/lms_codecs.h"
#include "providers/implementations/encode_decode/encode_key2any.inc"
#include <crypto/asn1.h>
@@ -1107,6 +1108,19 @@ static int slh_dsa_pki_priv_to_der(const void *vkey, unsigned char **pder,
#define slh_dsa_shake_256f_pem_type "SLH-DSA-SHAKE-256f"
#endif /* OPENSSL_NO_SLH_DSA */
+#ifndef OPENSSL_NO_LMS
+static int lms_spki_pub_to_der(const void *vkey, unsigned char **pder,
+ ossl_unused void *ctx)
+{
+ return ossl_lms_i2d_pubkey(vkey, pder);
+}
+
+#define prepare_lms_params NULL
+#define lms_check_key_type NULL
+#define lms_evp_type EVP_PKEY_HSS_LMS
+#define lms_pem_type "LMS"
+#endif /* OPENSSL_NO_LMS */
+
/* ---------------------------------------------------------------------- */
static OSSL_FUNC_decoder_newctx_fn key2any_newctx;
@@ -1768,3 +1782,8 @@ MAKE_ENCODER(ml_dsa_87, ml_dsa, PrivateKeyInfo, pem);
MAKE_ENCODER(ml_dsa_87, ml_dsa, SubjectPublicKeyInfo, der);
MAKE_ENCODER(ml_dsa_87, ml_dsa, SubjectPublicKeyInfo, pem);
#endif /* OPENSSL_NO_ML_DSA */
+
+#ifndef OPENSSL_NO_LMS
+MAKE_ENCODER(lms, lms, SubjectPublicKeyInfo, der);
+MAKE_ENCODER(lms, lms, SubjectPublicKeyInfo, pem);
+#endif
diff --git a/providers/implementations/encode_decode/encode_key2text.c b/providers/implementations/encode_decode/encode_key2text.c
index 20b26e7024..8f6aabc452 100644
--- a/providers/implementations/encode_decode/encode_key2text.c
+++ b/providers/implementations/encode_decode/encode_key2text.c
@@ -33,6 +33,7 @@
#include "prov/endecoder_local.h"
#include "prov/ml_dsa_codecs.h"
#include "prov/ml_kem_codecs.h"
+#include "prov/lms_codecs.h"
DEFINE_SPECIAL_STACK_OF_CONST(BIGNUM_const, BIGNUM)
@@ -621,6 +622,14 @@ static int ml_dsa_to_text(BIO *out, const void *key, int selection)
return ossl_ml_dsa_key_to_text(out, (const ML_DSA_KEY *)key, selection);
}
#endif /* OPENSSL_NO_ML_DSA */
+
+#ifndef OPENSSL_NO_LMS
+static int lms_to_text(BIO *out, const void *key, int selection)
+{
+ return ossl_lms_key_to_text(out, (LMS_KEY *)key, selection);
+}
+#endif /* OPENSSL_NO_LMS */
+
/* ---------------------------------------------------------------------- */
static void *key2text_newctx(void *provctx)
@@ -743,3 +752,7 @@ MAKE_TEXT_ENCODER(slh_dsa_shake_192f, slh_dsa);
MAKE_TEXT_ENCODER(slh_dsa_shake_256s, slh_dsa);
MAKE_TEXT_ENCODER(slh_dsa_shake_256f, slh_dsa);
#endif
+
+#ifndef OPENSSL_NO_LMS
+MAKE_TEXT_ENCODER(lms, lms);
+#endif
diff --git a/providers/implementations/encode_decode/lms_codecs.c b/providers/implementations/encode_decode/lms_codecs.c
new file mode 100644
index 0000000000..1d4a446622
--- /dev/null
+++ b/providers/implementations/encode_decode/lms_codecs.c
@@ -0,0 +1,191 @@
+/*
+ * 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
+ */
+
+#include <string.h>
+#include <openssl/byteorder.h>
+#include <openssl/err.h>
+#include <openssl/proverr.h>
+#include <openssl/x509.h>
+#include <openssl/core_names.h>
+#include "internal/encoder.h"
+#include "internal/nelem.h"
+#include "internal/packet.h"
+#include "prov/lms_codecs.h"
+
+/*-
+ * The DER ASN.1 encoding of LMS public keys prepends 20 bytes
+ * to the encoded public key:
+ *
+ * - 2 byte outer sequence tag and length
+ * - 2 byte algorithm sequence tag and length
+ * - 2 byte algorithm OID tag and length
+ * - 11 byte algorithm OID (from NIST CSOR OID arc)
+ * - 2 byte bit string tag and length
+ * - 1 bitstring lead byte
+ *
+ * A HSS key with a single tree also represents LMS public key.
+ * This has 4 extra bytes after the above data with the value 0x00, 0x00, 0x00, 0x01
+ *
+ * The LMS public key consists of
+ * 4 byte LMS type
+ * 4 byte OTS type
+ * 16 byte Id
+ * n bytes of K where n = 32 or 24.
+ * i.e. 24 + n bytes
+ */
+
+#define LMS_SPKI_OVERHEAD 20
+#define HSS_HEADER 4
+#define HSS_LMS_SPKI_OVERHEAD (LMS_SPKI_OVERHEAD + HSS_HEADER)
+#define HSS_LMS_HEADER(n) { \
+ 0x30, 0x2E + n, 0x30, 0x0d, \
+ 0x06, 0x0b, \
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x03, 0x11, \
+ 0x03, 0x1D + n, \
+ 0x00, \
+ 0x00, 0x00, 0x00, 0x01 \
+}
+
+typedef struct {
+ const uint8_t header[HSS_LMS_SPKI_OVERHEAD];
+} LMS_SPKI_FMT;
+
+static const LMS_SPKI_FMT hss_lms_32_spkifmt = {
+ HSS_LMS_HEADER(32)
+};
+static const LMS_SPKI_FMT hss_lms_24_spkifmt = {
+ HSS_LMS_HEADER(24)
+};
+
+typedef struct {
+ const LMS_SPKI_FMT *spkifmt;
+} LMS_CODEC;
+
+static const LMS_CODEC codecs[2] = {
+ { &hss_lms_32_spkifmt },
+ { &hss_lms_24_spkifmt }
+};
+
+static const LMS_SPKI_FMT *find_spkifmt(const uint8_t *pk, int pk_len)
+{
+ size_t i;
+
+ if (pk_len <= HSS_LMS_SPKI_OVERHEAD)
+ return NULL;
+
+ for (i = 0; i < OSSL_NELEM(codecs); ++i) {
+ if (memcmp(pk, codecs[i].spkifmt->header, HSS_LMS_SPKI_OVERHEAD) == 0)
+ return codecs[i].spkifmt;
+ }
+ return NULL;
+}
+
+LMS_KEY *
+ossl_lms_d2i_PUBKEY(const uint8_t *pk, int pk_len, PROV_CTX *provctx)
+{
+ OSSL_LIB_CTX *libctx = PROV_LIBCTX_OF(provctx);
+ LMS_KEY *ret;
+ const LMS_SPKI_FMT *spkifmt;
+
+ spkifmt = find_spkifmt(pk, pk_len);
+ if (spkifmt == NULL)
+ return NULL;
+
+ if ((ret = ossl_lms_key_new(libctx)) == NULL)
+ return NULL;
+
+ pk += sizeof(spkifmt->header);
+ pk_len -= sizeof(spkifmt->header);
+
+ if (!ossl_lms_pubkey_decode(pk, (size_t)pk_len, ret)) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_ENCODING,
+ "error parsing LMS public key from input SPKI");
+ ossl_lms_key_free(ret);
+ return NULL;
+ }
+
+ return ret;
+}
+
+int ossl_lms_i2d_pubkey(const LMS_KEY *key, unsigned char **out)
+{
+ if (key->pub.encoded == NULL || key->pub.encodedlen == 0) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_NOT_A_PUBLIC_KEY,
+ "no LMS public key data available");
+ return 0;
+ }
+ if (out != NULL) {
+ WPACKET pkt;
+ size_t sz = HSS_HEADER + key->pub.encodedlen;
+ uint8_t *buf = OPENSSL_malloc(sz);
+ int ret;
+
+ if (buf == NULL)
+ return 0;
+ ret = WPACKET_init_static_len(&pkt, buf, sz, 0)
+ /* Output HSS format which has a 4 byte value (L = 1) */
+ && WPACKET_memcpy(&pkt, hss_lms_32_spkifmt.header + sizeof(hss_lms_32_spkifmt.header) - HSS_HEADER, HSS_HEADER)
+ /* Output the LMS encoded public key */
+ && WPACKET_memcpy(&pkt, key->pub.encoded, key->pub.encodedlen);
+ WPACKET_cleanup(&pkt);
+ if (ret == 0) {
+ OPENSSL_free(buf);
+ return 0;
+ }
+ *out = buf;
+ }
+ return (int)key->pub.encodedlen + HSS_HEADER;
+}
+
+static const char *get_digest(const char *name)
+{
+ if (strcmp(name, "SHAKE-256") == 0)
+ return "SHAKE";
+ return strcmp(name, "SHA256-192") == 0 ? "SHA256" : name;
+}
+
+int ossl_lms_key_to_text(BIO *out, const LMS_KEY *key, int selection)
+{
+ const LMS_PARAMS *lms_params = key->lms_params;
+ const LM_OTS_PARAMS *ots_params = key->ots_params;
+
+ if (out == NULL || key == NULL) {
+ ERR_raise(ERR_LIB_PROV, ERR_R_PASSED_NULL_PARAMETER);
+ return 0;
+ }
+ if (key->pub.encoded == NULL || key->pub.encodedlen == 0) {
+ /* Regardless of the |selection|, there must be a public key */
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_MISSING_KEY,
+ "no LMS key material available");
+ return 0;
+ }
+ if (BIO_printf(out, "lms-type: %s-N%d-H%d (0x%x)\n",
+ get_digest(lms_params->digestname),
+ (int)lms_params->n, (int)lms_params->h, (int)lms_params->lms_type)
+ <= 0)
+ return 0;
+ if (BIO_printf(out, "lm-ots-type: %s-N%d-W%d (0x%x)\n",
+ get_digest(ots_params->digestname),
+ (int)ots_params->n, (int)ots_params->w, (int)ots_params->lm_ots_type)
+ <= 0)
+ return 0;
+ if (!ossl_bio_print_labeled_buf(out, "Id:", key->Id, 16))
+ return 0;
+ if ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) != 0) {
+ /* Private keys are not supported */
+ } else if ((selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) != 0) {
+ if (BIO_printf(out, "LMS Public-Key:\n") <= 0)
+ return 0;
+ }
+ if (!ossl_bio_print_labeled_buf(out, "pub:", key->pub.encoded, key->pub.encodedlen))
+ return 0;
+ if (!ossl_bio_print_labeled_buf(out, "K:", key->pub.K, lms_params->n))
+ return 0;
+ return 1;
+}
diff --git a/providers/implementations/include/prov/implementations.h b/providers/implementations/include/prov/implementations.h
index 86f876a0db..7ac15cf0d0 100644
--- a/providers/implementations/include/prov/implementations.h
+++ b/providers/implementations/include/prov/implementations.h
@@ -748,6 +748,10 @@ extern const OSSL_DISPATCH ossl_slh_dsa_shake_192f_to_text_encoder_functions[];
extern const OSSL_DISPATCH ossl_slh_dsa_shake_256s_to_text_encoder_functions[];
extern const OSSL_DISPATCH ossl_slh_dsa_shake_256f_to_text_encoder_functions[];
+extern const OSSL_DISPATCH ossl_lms_to_SubjectPublicKeyInfo_der_encoder_functions[];
+extern const OSSL_DISPATCH ossl_lms_to_SubjectPublicKeyInfo_pem_encoder_functions[];
+extern const OSSL_DISPATCH ossl_lms_to_text_encoder_functions[];
+
/* Decoders */
extern const OSSL_DISPATCH ossl_PrivateKeyInfo_der_to_dh_decoder_functions[];
extern const OSSL_DISPATCH ossl_SubjectPublicKeyInfo_der_to_dh_decoder_functions[];
@@ -864,6 +868,7 @@ extern const OSSL_DISPATCH ossl_file_store_functions[];
extern const OSSL_DISPATCH ossl_winstore_store_functions[];
extern const OSSL_DISPATCH ossl_xdr_to_lms_decoder_functions[];
+extern const OSSL_DISPATCH ossl_SubjectPublicKeyInfo_der_to_lms_decoder_functions[];
extern const OSSL_DISPATCH ossl_PrivateKeyInfo_der_to_ml_dsa_44_decoder_functions[];
extern const OSSL_DISPATCH ossl_SubjectPublicKeyInfo_der_to_ml_dsa_44_decoder_functions[];
diff --git a/providers/implementations/include/prov/lms_codecs.h b/providers/implementations/include/prov/lms_codecs.h
new file mode 100644
index 0000000000..d8dc96045e
--- /dev/null
+++ b/providers/implementations/include/prov/lms_codecs.h
@@ -0,0 +1,25 @@
+/*
+ * 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
+ */
+
+#ifndef PROV_LMS_CODECS_H
+#define PROV_LMS_CODECS_H
+#pragma once
+
+#ifndef OPENSSL_NO_LMS
+#include <openssl/e_os2.h>
+#include "crypto/lms.h"
+#include "prov/provider_ctx.h"
+
+__owur LMS_KEY *
+ossl_lms_d2i_PUBKEY(const uint8_t *pubenc, int publen, PROV_CTX *provctx);
+__owur int ossl_lms_i2d_pubkey(const LMS_KEY *key, unsigned char **out);
+__owur int ossl_lms_key_to_text(BIO *out, const LMS_KEY *key, int selection);
+
+#endif /* OPENSSL_NO_LMS */
+#endif /* PROV_LMS_CODECS_H */
diff --git a/providers/implementations/include/prov/names.h b/providers/implementations/include/prov/names.h
index 8bfed63ff5..a7006be4be 100644
--- a/providers/implementations/include/prov/names.h
+++ b/providers/implementations/include/prov/names.h
@@ -416,7 +416,7 @@
#define PROV_DESCS_SM2 "OpenSSL SM2 implementation"
#define PROV_NAMES_curveSM2 "curveSM2"
#define PROV_DESCS_curveSM2 "OpenSSL curveSM2 implementation"
-#define PROV_NAMES_LMS "LMS"
+#define PROV_NAMES_LMS "LMS:id-alg-hss-lms-hashsig:1.2.840.113549.1.9.16.3.17"
#define PROV_DESCS_LMS "OpenSSL LMS implementation"
#define PROV_NAMES_ML_DSA_44 "ML-DSA-44:MLDSA44:2.16.840.1.101.3.4.3.17:id-ml-dsa-44"
#define PROV_DESCS_ML_DSA_44 "OpenSSL ML-DSA-44 implementation"
diff --git a/providers/implementations/keymgmt/lms_kmgmt.c b/providers/implementations/keymgmt/lms_kmgmt.c
index 8d7cfcc6f4..af1075b414 100644
--- a/providers/implementations/keymgmt/lms_kmgmt.c
+++ b/providers/implementations/keymgmt/lms_kmgmt.c
@@ -28,6 +28,8 @@ static OSSL_FUNC_keymgmt_export_fn lms_export;
static OSSL_FUNC_keymgmt_import_types_fn lms_imexport_types;
static OSSL_FUNC_keymgmt_export_types_fn lms_imexport_types;
static OSSL_FUNC_keymgmt_load_fn lms_load;
+static OSSL_FUNC_keymgmt_gettable_params_fn lms_gettable_params;
+static OSSL_FUNC_keymgmt_get_params_fn lms_get_params;
#define LMS_POSSIBLE_SELECTIONS (OSSL_KEYMGMT_SELECT_PUBLIC_KEY)
@@ -49,7 +51,7 @@ static int lms_has(const void *keydata, int selection)
if (!ossl_prov_is_running() || key == NULL)
return 0;
- if ((selection & LMS_POSSIBLE_SELECTIONS) == 0)
+ if ((selection & OSSL_KEYMGMT_SELECT_KEYPAIR) == 0)
return 1; /* the selection is not missing */
return ossl_lms_key_has(key, selection);
@@ -150,6 +152,50 @@ static void *lms_load(const void *reference, size_t reference_sz)
return NULL;
}
+static const OSSL_PARAM *lms_gettable_params(void *provctx)
+{
+ return lms_get_params_list;
+}
+
+static int lms_get_params(void *keydata, OSSL_PARAM params[])
+{
+ LMS_KEY *key = keydata;
+ const uint8_t *d;
+ size_t len;
+ struct lms_get_params_st p;
+
+ if (key == NULL || !lms_get_params_decoder(params, &p))
+ return 0;
+
+ if (p.bits != NULL
+ && !OSSL_PARAM_set_size_t(p.bits, 8 * ossl_lms_key_get_pub_len(key)))
+ return 0;
+
+ if (p.secbits != NULL
+ && !OSSL_PARAM_set_size_t(p.secbits, ossl_lms_key_get_collision_strength_bits(key)))
+ return 0;
+
+ if (p.maxsize != NULL
+ && !OSSL_PARAM_set_size_t(p.maxsize, ossl_lms_key_get_sig_len(key)))
+ return 0;
+
+ if (p.pubkey != NULL) {
+ d = ossl_lms_key_get_pub(key);
+ if (d != NULL) {
+ len = ossl_lms_key_get_pub_len(key);
+ if (!OSSL_PARAM_set_octet_string(p.pubkey, d, len))
+ return 0;
+ }
+ }
+ /*
+ * This allows apps to use an empty digest, so that the old API
+ * for digest signing can be used.
+ */
+ if (p.dgstp != NULL && !OSSL_PARAM_set_utf8_string(p.dgstp, ""))
+ return 0;
+ return 1;
+}
+
const OSSL_DISPATCH ossl_lms_keymgmt_functions[] = {
{ OSSL_FUNC_KEYMGMT_NEW, (void (*)(void))lms_new_key },
{ OSSL_FUNC_KEYMGMT_FREE, (void (*)(void))lms_free_key },
@@ -161,5 +207,7 @@ const OSSL_DISPATCH ossl_lms_keymgmt_functions[] = {
{ OSSL_FUNC_KEYMGMT_EXPORT, (void (*)(void))lms_export },
{ OSSL_FUNC_KEYMGMT_EXPORT_TYPES, (void (*)(void))lms_imexport_types },
{ OSSL_FUNC_KEYMGMT_LOAD, (void (*)(void))lms_load },
+ { OSSL_FUNC_KEYMGMT_GET_PARAMS, (void (*)(void))lms_get_params },
+ { OSSL_FUNC_KEYMGMT_GETTABLE_PARAMS, (void (*)(void))lms_gettable_params },
OSSL_DISPATCH_END
};
diff --git a/providers/implementations/keymgmt/lms_kmgmt.inc.in b/providers/implementations/keymgmt/lms_kmgmt.inc.in
index 59e1ed9f53..1e34a1eabf 100644
--- a/providers/implementations/keymgmt/lms_kmgmt.inc.in
+++ b/providers/implementations/keymgmt/lms_kmgmt.inc.in
@@ -14,3 +14,11 @@ use OpenSSL::paramnames qw(produce_param_decoder);
{- produce_param_decoder('lms_import',
(['OSSL_PKEY_PARAM_PUB_KEY', 'pub', 'octet_string'],
)); -}
+
+{- produce_param_decoder('lms_get_params',
+ (['OSSL_PKEY_PARAM_BITS', 'bits', 'int'],
+ ['OSSL_PKEY_PARAM_SECURITY_BITS', 'secbits', 'int'],
+ ['OSSL_PKEY_PARAM_MAX_SIZE', 'maxsize', 'int'],
+ ['OSSL_PKEY_PARAM_MANDATORY_DIGEST', 'dgstp', 'utf8_string'],
+ ['OSSL_PKEY_PARAM_PUB_KEY', 'pubkey', 'octet_string'],
+ )); -}
diff --git a/providers/implementations/signature/lms_signature.c b/providers/implementations/signature/lms_signature.c
index 65ebe68c00..b88b9a3a96 100644
--- a/providers/implementations/signature/lms_signature.c
+++ b/providers/implementations/signature/lms_signature.c
@@ -24,6 +24,8 @@ static OSSL_FUNC_signature_newctx_fn lms_newctx;
static OSSL_FUNC_signature_freectx_fn lms_freectx;
static OSSL_FUNC_signature_verify_message_init_fn lms_verify_msg_init;
static OSSL_FUNC_signature_verify_fn lms_verify;
+static OSSL_FUNC_signature_digest_verify_init_fn lms_digest_verify_init;
+static OSSL_FUNC_signature_digest_verify_fn lms_digest_verify;
typedef struct {
OSSL_LIB_CTX *libctx;
@@ -130,11 +132,37 @@ static int lms_verify(void *vctx, const unsigned char *sigbuf, size_t sigbuf_len
return ret;
}
+static int lms_digest_verify_init(void *vctx, const char *mdname, void *vkey,
+ const OSSL_PARAM params[])
+{
+ PROV_LMS_CTX *ctx = (PROV_LMS_CTX *)vctx;
+
+ if (mdname != NULL && mdname[0] != '\0') {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_DIGEST,
+ "Explicit digest not supported for LMS operations");
+ return 0;
+ }
+ if (vkey == NULL && ctx->key != NULL)
+ return 1; /* lms_set_ctx_params(ctx, params); */
+
+ return lms_verify_msg_init(vctx, vkey, params);
+}
+
+static int lms_digest_verify(void *vctx, const uint8_t *sig, size_t siglen,
+ const uint8_t *tbs, size_t tbslen)
+{
+ return lms_verify(vctx, sig, siglen, tbs, tbslen);
+}
+
const OSSL_DISPATCH ossl_lms_signature_functions[] = {
{ OSSL_FUNC_SIGNATURE_NEWCTX, (void (*)(void))lms_newctx },
{ OSSL_FUNC_SIGNATURE_FREECTX, (void (*)(void))lms_freectx },
{ OSSL_FUNC_SIGNATURE_VERIFY_MESSAGE_INIT,
(void (*)(void))lms_verify_msg_init },
{ OSSL_FUNC_SIGNATURE_VERIFY, (void (*)(void))lms_verify },
+ { OSSL_FUNC_SIGNATURE_DIGEST_VERIFY_INIT,
+ (void (*)(void))lms_digest_verify_init },
+ { OSSL_FUNC_SIGNATURE_DIGEST_VERIFY,
+ (void (*)(void))lms_digest_verify },
OSSL_DISPATCH_END
};
diff --git a/test/lms_test.c b/test/lms_test.c
index 579da37276..82af0a2f06 100644
--- a/test/lms_test.c
+++ b/test/lms_test.c
@@ -281,15 +281,20 @@ static int lms_digest_verify_fail_test(void)
LMS_ACVP_TEST_DATA *td = &lms_testdata[0];
EVP_PKEY *pub = NULL;
EVP_MD_CTX *vctx = NULL;
+ int expected = 1;
if (!TEST_ptr(pub = lms_pubkey_from_data(td->pub, td->publen)))
return 0;
if (!TEST_ptr(vctx = EVP_MD_CTX_new()))
goto err;
- /* Only one shot mode is supported, streaming fails to initialise */
+ /* Prior to 4.0 EVP_DigestVerifyInit_ex is not supported */
+ if (OSSL_PROVIDER_available(libctx, "fips")
+ && fips_provider_version_match(libctx, "<4.0.0"))
+ expected = 0;
+
if (!TEST_int_eq(EVP_DigestVerifyInit_ex(vctx, NULL, NULL, libctx, NULL,
pub, NULL),
- 0))
+ expected))
goto err;
ret = 1;
err:
diff --git a/test/recipes/15-test_lms_codecs.t b/test/recipes/15-test_lms_codecs.t
new file mode 100644
index 0000000000..7f2d046dc5
--- /dev/null
+++ b/test/recipes/15-test_lms_codecs.t
@@ -0,0 +1,62 @@
+#! /usr/bin/env perl
+# 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
+
+use strict;
+use warnings;
+
+use File::Spec;
+use File::Copy;
+use File::Compare qw/compare_text compare/;
+use IO::File;
+use OpenSSL::Glob;
+use OpenSSL::Test qw/:DEFAULT data_file srctop_file bldtop_dir/;
+use OpenSSL::Test::Utils;
+
+setup("test_lms_codecs");
+
+# The test vectors were generated using modified Bouncy Castle tests
+# from core/src/test/java/org/bouncycastle/pqc/crypto/test/LMSTest.java
+my @algs = qw(sha256_n24_w1 shake_n24_w1 shake_n24_w2 shake_n24_w4 shake_n24_w8 shake_n32_w1 shake_n32_w8);
+
+plan skip_all => "LMS isn't supported in this build"
+ if disabled("lms");
+
+plan tests => @algs * 7;
+
+foreach my $alg (@algs) {
+ my $pubpem =data_file(sprintf("%s_pub.pem", $alg));
+ my $pubder = data_file(sprintf("%s_pub.der", $alg));
+ my $pubtxt = data_file(sprintf("%s_pub.txt", $alg));
+ my $msg = data_file(sprintf("%s_msg.bin", $alg));
+ my $sig = data_file(sprintf("%s_sig.bin", $alg));
+ my $outpubder = sprintf("%s_pubout.der", $alg);
+ my $outpubpem = sprintf("%s_pubout.pem", $alg);
+ my $outpubtxt = sprintf("%s_pubout.txt", $alg);
+
+ # Load Public PEM and generate Public DER
+ ok(run(app([qw(openssl pkey -pubin -outform DER -in),
+ $pubpem, '-out', $outpubder])));
+ ok(!compare($pubder, $outpubder),
+ sprintf("pubkey DER match: %s", $alg));
+
+ # Load Public DER and generate Public PEM
+ ok(run(app([qw(openssl pkey -pubin -inform DER -outform PEM -in),
+ $pubder, '-out', $outpubpem])));
+ ok(!compare($pubpem, $outpubpem),
+ sprintf("pubkey PEM match: %s", $alg));
+
+ # Check text encoding
+ ok(run(app([qw(openssl pkey -pubin -noout -text -in),
+ $pubpem, '-out', $outpubtxt])));
+ ok(!compare_text($pubtxt, $outpubtxt),
+ sprintf("pubkey TEXT match: %s", $alg));
+
+ # Perform verify
+ ok(run(app([qw(openssl pkeyutl -verify -rawin -pubin -inkey),
+ $pubpem, '-in', $msg, '-sigfile', $sig])));
+}
diff --git a/test/recipes/15-test_lms_codecs_data/sha256_n24_w1_msg.bin b/test/recipes/15-test_lms_codecs_data/sha256_n24_w1_msg.bin
new file mode 100644
index 0000000000..aa52d7284f
Binary files /dev/null and b/test/recipes/15-test_lms_codecs_data/sha256_n24_w1_msg.bin differ
diff --git a/test/recipes/15-test_lms_codecs_data/sha256_n24_w1_pub.der b/test/recipes/15-test_lms_codecs_data/sha256_n24_w1_pub.der
new file mode 100644
index 0000000000..aa33e95560
Binary files /dev/null and b/test/recipes/15-test_lms_codecs_data/sha256_n24_w1_pub.der differ
diff --git a/test/recipes/15-test_lms_codecs_data/sha256_n24_w1_pub.pem b/test/recipes/15-test_lms_codecs_data/sha256_n24_w1_pub.pem
new file mode 100644
index 0000000000..9525959d86
--- /dev/null
+++ b/test/recipes/15-test_lms_codecs_data/sha256_n24_w1_pub.pem
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MEYwDQYLKoZIhvcNAQkQAxEDNQAAAAABAAAACgAAAAW7nBtfzxKGxB99zen7U6U5
+x+Pl7vX6uHi9NsBOq1Te98wljDKoQsT9
+-----END PUBLIC KEY-----
diff --git a/test/recipes/15-test_lms_codecs_data/sha256_n24_w1_pub.txt b/test/recipes/15-test_lms_codecs_data/sha256_n24_w1_pub.txt
new file mode 100644
index 0000000000..57dc596690
--- /dev/null
+++ b/test/recipes/15-test_lms_codecs_data/sha256_n24_w1_pub.txt
@@ -0,0 +1,12 @@
+lms-type: SHA256-N24-H5 (0xa)
+lm-ots-type: SHA256-N24-W1 (0x5)
+Id:
+ bb:9c:1b:5f:cf:12:86:c4:1f:7d:cd:e9:fb:53:a5:39
+LMS Public-Key:
+pub:
+ 00:00:00:0a:00:00:00:05:bb:9c:1b:5f:cf:12:86:c4:
+ 1f:7d:cd:e9:fb:53:a5:39:c7:e3:e5:ee:f5:fa:b8:78:
+ bd:36:c0:4e:ab:54:de:f7:cc:25:8c:32:a8:42:c4:fd
+K:
+ c7:e3:e5:ee:f5:fa:b8:78:bd:36:c0:4e:ab:54:de:f7:
+ cc:25:8c:32:a8:42:c4:fd
diff --git a/test/recipes/15-test_lms_codecs_data/sha256_n24_w1_sig.bin b/test/recipes/15-test_lms_codecs_data/sha256_n24_w1_sig.bin
new file mode 100644
index 0000000000..573353d7f5
Binary files /dev/null and b/test/recipes/15-test_lms_codecs_data/sha256_n24_w1_sig.bin differ
diff --git a/test/recipes/15-test_lms_codecs_data/shake_n24_w1_msg.bin b/test/recipes/15-test_lms_codecs_data/shake_n24_w1_msg.bin
new file mode 100644
index 0000000000..289557b085
Binary files /dev/null and b/test/recipes/15-test_lms_codecs_data/shake_n24_w1_msg.bin differ
diff --git a/test/recipes/15-test_lms_codecs_data/shake_n24_w1_pub.der b/test/recipes/15-test_lms_codecs_data/shake_n24_w1_pub.der
new file mode 100644
index 0000000000..db738593cc
Binary files /dev/null and b/test/recipes/15-test_lms_codecs_data/shake_n24_w1_pub.der differ
diff --git a/test/recipes/15-test_lms_codecs_data/shake_n24_w1_pub.pem b/test/recipes/15-test_lms_codecs_data/shake_n24_w1_pub.pem
new file mode 100644
index 0000000000..61a6b6c7a0
--- /dev/null
+++ b/test/recipes/15-test_lms_codecs_data/shake_n24_w1_pub.pem
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MEYwDQYLKoZIhvcNAQkQAxEDNQAAAAABAAAAGAAAAA3hiMFvJkCjYSJ8Y1MrEAPo
+BgOLGt8IGv2dTCxXArrkJ9yn/UDYE1gw
+-----END PUBLIC KEY-----
diff --git a/test/recipes/15-test_lms_codecs_data/shake_n24_w1_pub.txt b/test/recipes/15-test_lms_codecs_data/shake_n24_w1_pub.txt
new file mode 100644
index 0000000000..74d176b71d
--- /dev/null
+++ b/test/recipes/15-test_lms_codecs_data/shake_n24_w1_pub.txt
@@ -0,0 +1,12 @@
+lms-type: SHAKE-N24-H25 (0x18)
+lm-ots-type: SHAKE-N24-W1 (0xd)
+Id:
+ e1:88:c1:6f:26:40:a3:61:22:7c:63:53:2b:10:03:e8
+LMS Public-Key:
+pub:
+ 00:00:00:18:00:00:00:0d:e1:88:c1:6f:26:40:a3:61:
+ 22:7c:63:53:2b:10:03:e8:06:03:8b:1a:df:08:1a:fd:
+ 9d:4c:2c:57:02:ba:e4:27:dc:a7:fd:40:d8:13:58:30
+K:
+ 06:03:8b:1a:df:08:1a:fd:9d:4c:2c:57:02:ba:e4:27:
+ dc:a7:fd:40:d8:13:58:30
diff --git a/test/recipes/15-test_lms_codecs_data/shake_n24_w1_sig.bin b/test/recipes/15-test_lms_codecs_data/shake_n24_w1_sig.bin
new file mode 100644
index 0000000000..9b8a45e6db
Binary files /dev/null and b/test/recipes/15-test_lms_codecs_data/shake_n24_w1_sig.bin differ
diff --git a/test/recipes/15-test_lms_codecs_data/shake_n24_w2_msg.bin b/test/recipes/15-test_lms_codecs_data/shake_n24_w2_msg.bin
new file mode 100644
index 0000000000..5028abb9de
Binary files /dev/null and b/test/recipes/15-test_lms_codecs_data/shake_n24_w2_msg.bin differ
diff --git a/test/recipes/15-test_lms_codecs_data/shake_n24_w2_pub.der b/test/recipes/15-test_lms_codecs_data/shake_n24_w2_pub.der
new file mode 100644
index 0000000000..041cc4518c
Binary files /dev/null and b/test/recipes/15-test_lms_codecs_data/shake_n24_w2_pub.der differ
diff --git a/test/recipes/15-test_lms_codecs_data/shake_n24_w2_pub.pem b/test/recipes/15-test_lms_codecs_data/shake_n24_w2_pub.pem
new file mode 100644
index 0000000000..bd75db43ba
--- /dev/null
+++ b/test/recipes/15-test_lms_codecs_data/shake_n24_w2_pub.pem
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MEYwDQYLKoZIhvcNAQkQAxEDNQAAAAABAAAAFwAAAA5asYCHy4y+LHiEhTrF+45u
+WeqnTn3NQY/AtFRb8S/IlLFjxDjHuZPS
+-----END PUBLIC KEY-----
diff --git a/test/recipes/15-test_lms_codecs_data/shake_n24_w2_pub.txt b/test/recipes/15-test_lms_codecs_data/shake_n24_w2_pub.txt
new file mode 100644
index 0000000000..59572f28b0
--- /dev/null
+++ b/test/recipes/15-test_lms_codecs_data/shake_n24_w2_pub.txt
@@ -0,0 +1,12 @@
+lms-type: SHAKE-N24-H20 (0x17)
+lm-ots-type: SHAKE-N24-W2 (0xe)
+Id:
+ 5a:b1:80:87:cb:8c:be:2c:78:84:85:3a:c5:fb:8e:6e
+LMS Public-Key:
+pub:
+ 00:00:00:17:00:00:00:0e:5a:b1:80:87:cb:8c:be:2c:
+ 78:84:85:3a:c5:fb:8e:6e:59:ea:a7:4e:7d:cd:41:8f:
+ c0:b4:54:5b:f1:2f:c8:94:b1:63:c4:38:c7:b9:93:d2
+K:
+ 59:ea:a7:4e:7d:cd:41:8f:c0:b4:54:5b:f1:2f:c8:94:
+ b1:63:c4:38:c7:b9:93:d2
diff --git a/test/recipes/15-test_lms_codecs_data/shake_n24_w2_sig.bin b/test/recipes/15-test_lms_codecs_data/shake_n24_w2_sig.bin
new file mode 100644
index 0000000000..a6c8730b73
Binary files /dev/null and b/test/recipes/15-test_lms_codecs_data/shake_n24_w2_sig.bin differ
diff --git a/test/recipes/15-test_lms_codecs_data/shake_n24_w4_msg.bin b/test/recipes/15-test_lms_codecs_data/shake_n24_w4_msg.bin
new file mode 100644
index 0000000000..76372b99fb
Binary files /dev/null and b/test/recipes/15-test_lms_codecs_data/shake_n24_w4_msg.bin differ
diff --git a/test/recipes/15-test_lms_codecs_data/shake_n24_w4_pub.der b/test/recipes/15-test_lms_codecs_data/shake_n24_w4_pub.der
new file mode 100644
index 0000000000..5a00b90eaa
Binary files /dev/null and b/test/recipes/15-test_lms_codecs_data/shake_n24_w4_pub.der differ
diff --git a/test/recipes/15-test_lms_codecs_data/shake_n24_w4_pub.pem b/test/recipes/15-test_lms_codecs_data/shake_n24_w4_pub.pem
new file mode 100644
index 0000000000..2b1aca7f66
--- /dev/null
+++ b/test/recipes/15-test_lms_codecs_data/shake_n24_w4_pub.pem
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MEYwDQYLKoZIhvcNAQkQAxEDNQAAAAABAAAAFQAAAA8FRHkVfAnrfjSnkWv0wDTi
+TwIBexRlvoUYM8uY201/i9qtrkT/IHG7
+-----END PUBLIC KEY-----
diff --git a/test/recipes/15-test_lms_codecs_data/shake_n24_w4_pub.txt b/test/recipes/15-test_lms_codecs_data/shake_n24_w4_pub.txt
new file mode 100644
index 0000000000..d95cd00a15
--- /dev/null
+++ b/test/recipes/15-test_lms_codecs_data/shake_n24_w4_pub.txt
@@ -0,0 +1,12 @@
+lms-type: SHAKE-N24-H10 (0x15)
+lm-ots-type: SHAKE-N24-W4 (0xf)
+Id:
+ 05:44:79:15:7c:09:eb:7e:34:a7:91:6b:f4:c0:34:e2
+LMS Public-Key:
+pub:
+ 00:00:00:15:00:00:00:0f:05:44:79:15:7c:09:eb:7e:
+ 34:a7:91:6b:f4:c0:34:e2:4f:02:01:7b:14:65:be:85:
+ 18:33:cb:98:db:4d:7f:8b:da:ad:ae:44:ff:20:71:bb
+K:
+ 4f:02:01:7b:14:65:be:85:18:33:cb:98:db:4d:7f:8b:
+ da:ad:ae:44:ff:20:71:bb
diff --git a/test/recipes/15-test_lms_codecs_data/shake_n24_w4_sig.bin b/test/recipes/15-test_lms_codecs_data/shake_n24_w4_sig.bin
new file mode 100644
index 0000000000..f2b7cce9af
Binary files /dev/null and b/test/recipes/15-test_lms_codecs_data/shake_n24_w4_sig.bin differ
diff --git a/test/recipes/15-test_lms_codecs_data/shake_n24_w8_msg.bin b/test/recipes/15-test_lms_codecs_data/shake_n24_w8_msg.bin
new file mode 100644
index 0000000000..4116f7fe2a
Binary files /dev/null and b/test/recipes/15-test_lms_codecs_data/shake_n24_w8_msg.bin differ
diff --git a/test/recipes/15-test_lms_codecs_data/shake_n24_w8_pub.der b/test/recipes/15-test_lms_codecs_data/shake_n24_w8_pub.der
new file mode 100644
index 0000000000..55d9a499ca
Binary files /dev/null and b/test/recipes/15-test_lms_codecs_data/shake_n24_w8_pub.der differ
diff --git a/test/recipes/15-test_lms_codecs_data/shake_n24_w8_pub.pem b/test/recipes/15-test_lms_codecs_data/shake_n24_w8_pub.pem
new file mode 100644
index 0000000000..b9d91ba840
--- /dev/null
+++ b/test/recipes/15-test_lms_codecs_data/shake_n24_w8_pub.pem
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MEYwDQYLKoZIhvcNAQkQAxEDNQAAAAABAAAAFgAAABBKkKgaFDJ5ZZeXtXKGbK0k
+Wz5VJo3faGque7ml53MEoMgiDstCvQCI
+-----END PUBLIC KEY-----
diff --git a/test/recipes/15-test_lms_codecs_data/shake_n24_w8_pub.txt b/test/recipes/15-test_lms_codecs_data/shake_n24_w8_pub.txt
new file mode 100644
index 0000000000..e1d1669135
--- /dev/null
+++ b/test/recipes/15-test_lms_codecs_data/shake_n24_w8_pub.txt
@@ -0,0 +1,12 @@
+lms-type: SHAKE-N24-H15 (0x16)
+lm-ots-type: SHAKE-N24-W8 (0x10)
+Id:
+ 4a:90:a8:1a:14:32:79:65:97:97:b5:72:86:6c:ad:24
+LMS Public-Key:
+pub:
+ 00:00:00:16:00:00:00:10:4a:90:a8:1a:14:32:79:65:
+ 97:97:b5:72:86:6c:ad:24:5b:3e:55:26:8d:df:68:6a:
+ ae:7b:b9:a5:e7:73:04:a0:c8:22:0e:cb:42:bd:00:88
+K:
+ 5b:3e:55:26:8d:df:68:6a:ae:7b:b9:a5:e7:73:04:a0:
+ c8:22:0e:cb:42:bd:00:88
diff --git a/test/recipes/15-test_lms_codecs_data/shake_n24_w8_sig.bin b/test/recipes/15-test_lms_codecs_data/shake_n24_w8_sig.bin
new file mode 100644
index 0000000000..2705ff88c1
Binary files /dev/null and b/test/recipes/15-test_lms_codecs_data/shake_n24_w8_sig.bin differ
diff --git a/test/recipes/15-test_lms_codecs_data/shake_n32_w1_msg.bin b/test/recipes/15-test_lms_codecs_data/shake_n32_w1_msg.bin
new file mode 100644
index 0000000000..c7066c1358
Binary files /dev/null and b/test/recipes/15-test_lms_codecs_data/shake_n32_w1_msg.bin differ
diff --git a/test/recipes/15-test_lms_codecs_data/shake_n32_w1_pub.der b/test/recipes/15-test_lms_codecs_data/shake_n32_w1_pub.der
new file mode 100644
index 0000000000..0474e535d4
Binary files /dev/null and b/test/recipes/15-test_lms_codecs_data/shake_n32_w1_pub.der differ
diff --git a/test/recipes/15-test_lms_codecs_data/shake_n32_w1_pub.pem b/test/recipes/15-test_lms_codecs_data/shake_n32_w1_pub.pem
new file mode 100644
index 0000000000..745d68e8f1
--- /dev/null
+++ b/test/recipes/15-test_lms_codecs_data/shake_n32_w1_pub.pem
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+ME4wDQYLKoZIhvcNAQkQAxEDPQAAAAABAAAAEQAAAAl0e74SphV4ef9P0i/rzbVX
+ZhA1zoQ7v9BdHYPpf5Y1di1uJ0J48CEp4+bi4BKFnx4=
+-----END PUBLIC KEY-----
diff --git a/test/recipes/15-test_lms_codecs_data/shake_n32_w1_pub.txt b/test/recipes/15-test_lms_codecs_data/shake_n32_w1_pub.txt
new file mode 100644
index 0000000000..685066ea94
--- /dev/null
+++ b/test/recipes/15-test_lms_codecs_data/shake_n32_w1_pub.txt
@@ -0,0 +1,13 @@
+lms-type: SHAKE-N32-H15 (0x11)
+lm-ots-type: SHAKE-N32-W1 (0x9)
+Id:
+ 74:7b:be:12:a6:15:78:79:ff:4f:d2:2f:eb:cd:b5:57
+LMS Public-Key:
+pub:
+ 00:00:00:11:00:00:00:09:74:7b:be:12:a6:15:78:79:
+ ff:4f:d2:2f:eb:cd:b5:57:66:10:35:ce:84:3b:bf:d0:
+ 5d:1d:83:e9:7f:96:35:76:2d:6e:27:42:78:f0:21:29:
+ e3:e6:e2:e0:12:85:9f:1e
+K:
+ 66:10:35:ce:84:3b:bf:d0:5d:1d:83:e9:7f:96:35:76:
+ 2d:6e:27:42:78:f0:21:29:e3:e6:e2:e0:12:85:9f:1e
diff --git a/test/recipes/15-test_lms_codecs_data/shake_n32_w1_sig.bin b/test/recipes/15-test_lms_codecs_data/shake_n32_w1_sig.bin
new file mode 100644
index 0000000000..80da90a09d
Binary files /dev/null and b/test/recipes/15-test_lms_codecs_data/shake_n32_w1_sig.bin differ
diff --git a/test/recipes/15-test_lms_codecs_data/shake_n32_w8_msg.bin b/test/recipes/15-test_lms_codecs_data/shake_n32_w8_msg.bin
new file mode 100644
index 0000000000..637df1950f
Binary files /dev/null and b/test/recipes/15-test_lms_codecs_data/shake_n32_w8_msg.bin differ
diff --git a/test/recipes/15-test_lms_codecs_data/shake_n32_w8_pub.der b/test/recipes/15-test_lms_codecs_data/shake_n32_w8_pub.der
new file mode 100644
index 0000000000..95bd5af430
Binary files /dev/null and b/test/recipes/15-test_lms_codecs_data/shake_n32_w8_pub.der differ
diff --git a/test/recipes/15-test_lms_codecs_data/shake_n32_w8_pub.pem b/test/recipes/15-test_lms_codecs_data/shake_n32_w8_pub.pem
new file mode 100644
index 0000000000..4ffcce60c6
--- /dev/null
+++ b/test/recipes/15-test_lms_codecs_data/shake_n32_w8_pub.pem
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+ME4wDQYLKoZIhvcNAQkQAxEDPQAAAAABAAAAEwAAAAyXL+d31A/h79l2A6YnM80c
+0tbxPFSghfIwF7vUaWXAhTq8Nj+rztkRmiYzejBqe4g=
+-----END PUBLIC KEY-----
diff --git a/test/recipes/15-test_lms_codecs_data/shake_n32_w8_pub.txt b/test/recipes/15-test_lms_codecs_data/shake_n32_w8_pub.txt
new file mode 100644
index 0000000000..bd82d8cf90
--- /dev/null
+++ b/test/recipes/15-test_lms_codecs_data/shake_n32_w8_pub.txt
@@ -0,0 +1,13 @@
+lms-type: SHAKE-N32-H25 (0x13)
+lm-ots-type: SHAKE-N32-W8 (0xc)
+Id:
+ 97:2f:e7:77:d4:0f:e1:ef:d9:76:03:a6:27:33:cd:1c
+LMS Public-Key:
+pub:
+ 00:00:00:13:00:00:00:0c:97:2f:e7:77:d4:0f:e1:ef:
+ d9:76:03:a6:27:33:cd:1c:d2:d6:f1:3c:54:a0:85:f2:
+ 30:17:bb:d4:69:65:c0:85:3a:bc:36:3f:ab:ce:d9:11:
+ 9a:26:33:7a:30:6a:7b:88
+K:
+ d2:d6:f1:3c:54:a0:85:f2:30:17:bb:d4:69:65:c0:85:
+ 3a:bc:36:3f:ab:ce:d9:11:9a:26:33:7a:30:6a:7b:88
diff --git a/test/recipes/15-test_lms_codecs_data/shake_n32_w8_sig.bin b/test/recipes/15-test_lms_codecs_data/shake_n32_w8_sig.bin
new file mode 100644
index 0000000000..39a2faf9d1
Binary files /dev/null and b/test/recipes/15-test_lms_codecs_data/shake_n32_w8_sig.bin differ