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)
### 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
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);
+}
/* 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 }
};
=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
=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
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)>.
=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
=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
=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
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
=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
=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
=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.
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 {
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 */
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'} -}]
#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
/* ---------------------------------------------------------------------- */
+#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.
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
#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>
#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;
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
#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)
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)
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
--- /dev/null
+/*
+ * 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;
+}
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[];
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[];
--- /dev/null
+/*
+ * 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 */
#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"
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)
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);
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 },
{ 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
};
{- 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'],
+ )); -}
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;
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
};
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:
--- /dev/null
+#! /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])));
+}
--- /dev/null
+-----BEGIN PUBLIC KEY-----
+MEYwDQYLKoZIhvcNAQkQAxEDNQAAAAABAAAACgAAAAW7nBtfzxKGxB99zen7U6U5
+x+Pl7vX6uHi9NsBOq1Te98wljDKoQsT9
+-----END PUBLIC KEY-----
--- /dev/null
+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
--- /dev/null
+7\96\æ7\8a\ e¬_ô\18
+þM\b¢0Gùx\15@&?¼T\88òv\r"G\ 3('ûEÖh\83p»ò_¡©Ô\ 15\ eãÖ7\14\ 4^\91¢½ \ e~hQSº´ÓN=±\14\1e1\8c\19\9f·âzô\97qß_u\ 1·ûv9÷ë\86µýÕ\91\17\83$È\98¦íCK9ÌÿÕË\99©&Ã|>ãj=Ø\9bçä\9fWi
\ No newline at end of file
--- /dev/null
+-----BEGIN PUBLIC KEY-----
+MEYwDQYLKoZIhvcNAQkQAxEDNQAAAAABAAAAGAAAAA3hiMFvJkCjYSJ8Y1MrEAPo
+BgOLGt8IGv2dTCxXArrkJ9yn/UDYE1gw
+-----END PUBLIC KEY-----
--- /dev/null
+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
--- /dev/null
+é^/\rB\9aÁ¢(ßáôÑ\16uV*\9dáª"®Ççiø`\9e$g\b;É8 \17û:ÿR±¹\1f}ì1¶ \9fo?Z\85\ 6
+v+º|\99\13Sáÿ_à\ 3Ì\7fö\99Ü<0(¸>\12$uú²xI\b\9bª;í\97ñG\16;\82Ùr^\8f\10§°¼\17¹Gõ/A÷\r\1d\ 1\93Õ\95-S]ïð«};\ eR$
\ No newline at end of file
--- /dev/null
+-----BEGIN PUBLIC KEY-----
+MEYwDQYLKoZIhvcNAQkQAxEDNQAAAAABAAAAFwAAAA5asYCHy4y+LHiEhTrF+45u
+WeqnTn3NQY/AtFRb8S/IlLFjxDjHuZPS
+-----END PUBLIC KEY-----
--- /dev/null
+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
--- /dev/null
+-----BEGIN PUBLIC KEY-----
+MEYwDQYLKoZIhvcNAQkQAxEDNQAAAAABAAAAFQAAAA8FRHkVfAnrfjSnkWv0wDTi
+TwIBexRlvoUYM8uY201/i9qtrkT/IHG7
+-----END PUBLIC KEY-----
--- /dev/null
+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
--- /dev/null
+\v*6\ 4Y\9f&uä\10\98ßNð¥p'\ fØl51ã?lzó\82\ f\8bâÁÐô\13\86W$NÊ
+°,ó,Χ½\ eìV\1eÉߪ\1f°\8d)¨i0&\11\ 3Ä®õ³]}-¢LßP\93Ì\95"]³6\9cæ\86~êÁ9´¢|lV\90}\86ùdÊ\eÁ#ò\ 4O^\94àêBË$Ö\87¶\8e\8a½\13ÀÉt\80p2
\ No newline at end of file
--- /dev/null
+-----BEGIN PUBLIC KEY-----
+MEYwDQYLKoZIhvcNAQkQAxEDNQAAAAABAAAAFgAAABBKkKgaFDJ5ZZeXtXKGbK0k
+Wz5VJo3faGque7ml53MEoMgiDstCvQCI
+-----END PUBLIC KEY-----
--- /dev/null
+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
--- /dev/null
+Ý«å¸ÁHiä¹\12Ï«èy\98Ö&´~`\8dbP\94lÖÕ\81]µó\8dÄ×÷{ÿl\12Ð,\86Ì\9a\9bo3ê÷n\ 2_\90º<Õ'ä%Ó\18\96j\86¬j\1a\1cd\8fD(\13\ f\1aÉ7wy\92ªî\17Ç; øÞô\\vD\95%ßGO\ 4\12\15\ 1ÝØ\ f5>ÿ\89\98012)ºùøÝº\8a¿\80\1cú<\9ezg\13
\ No newline at end of file
--- /dev/null
+-----BEGIN PUBLIC KEY-----
+ME4wDQYLKoZIhvcNAQkQAxEDPQAAAAABAAAAEQAAAAl0e74SphV4ef9P0i/rzbVX
+ZhA1zoQ7v9BdHYPpf5Y1di1uJ0J48CEp4+bi4BKFnx4=
+-----END PUBLIC KEY-----
--- /dev/null
+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
--- /dev/null
+\90\1fK*ÙÇDã\81\85\19½\93ÀW\8aɪoâ³¼?ã^¥i\84|\1fç\97Ò@ØF¿\1f×*xOFó\93±Á\ 3Qp® ¥ie\ré\86ç\e\ eùÕ`°'r\19\8d\e\1a\1e\82Y&¥ñH,P\9f\8a\18½÷\82gJ_OÛÈ=Y\83\9eô7;¿\ 1a\8dj-¥"\r´Å\ÅO%u\92\97É\97ìçsz\e\86H
\ No newline at end of file
--- /dev/null
+-----BEGIN PUBLIC KEY-----
+ME4wDQYLKoZIhvcNAQkQAxEDPQAAAAABAAAAEwAAAAyXL+d31A/h79l2A6YnM80c
+0tbxPFSghfIwF7vUaWXAhTq8Nj+rztkRmiYzejBqe4g=
+-----END PUBLIC KEY-----
--- /dev/null
+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