]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
Add LMS Signature verification.
authorslontis <shane.lontis@oracle.com>
Tue, 1 Oct 2024 02:35:43 +0000 (12:35 +1000)
committerPauli <ppzgs1@gmail.com>
Thu, 10 Jul 2025 09:03:46 +0000 (19:03 +1000)
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)

15 files changed:
crypto/lms/build.info
crypto/lms/lm_ots_params.c
crypto/lms/lm_ots_verify.c [new file with mode: 0644]
crypto/lms/lms_key.c
crypto/lms/lms_sig.c [new file with mode: 0644]
crypto/lms/lms_sig_decoder.c [new file with mode: 0644]
crypto/lms/lms_verify.c [new file with mode: 0644]
include/crypto/lms.h
include/crypto/lms_sig.h [new file with mode: 0644]
include/crypto/lms_util.h
providers/defltprov.c
providers/implementations/include/prov/implementations.h
providers/implementations/signature/build.info
providers/implementations/signature/lms_sig.c [new file with mode: 0644]
test/lms_test.c

index e6bf504e19c968ee9d71a9d68218df37c5dc4d6b..068f3d7d85aab170d8cbe0c7fda273eecfe6a857 100644 (file)
@@ -1,6 +1,7 @@
 LIBS=../../libcrypto
 
-$COMMON=lms_params.c lms_pubkey_decode.c lms_key.c lm_ots_params.c
+$COMMON=lms_params.c lms_pubkey_decode.c lms_key.c lm_ots_params.c \
+        lm_ots_verify.c lms_sig.c lms_sig_decoder.c lms_verify.c
 
 IF[{- !$disabled{'lms'} -}]
 SOURCE[../../libcrypto]=$COMMON
index 51d8485809babe3ac47f0d5852ae2ef6080a42eb..87d98ada28ca60a8e175c0fda741a4c32dce1403 100644 (file)
@@ -8,6 +8,8 @@
  */
 
 #include "crypto/lms.h"
+#include "crypto/lms_sig.h"
+#include "crypto/lms_util.h"
 
 /* Refer to SP800-208 Section 4 LM-OTS parameter sets */
 static const LM_OTS_PARAMS lm_ots_params[] = {
@@ -47,3 +49,18 @@ const LM_OTS_PARAMS *ossl_lm_ots_params_get(uint32_t ots_type)
             return p;
     return NULL;
 }
+
+/* See RFC 8554 Section 4.4 Checksum */
+uint16_t ossl_lm_ots_params_checksum(const LM_OTS_PARAMS *params,
+                                     const unsigned char *S)
+{
+    uint16_t sum = 0;
+    uint16_t i;
+    /* Largest size is 8 * 32 / 1 = 256 (which doesnt quite fit into 8 bits) */
+    uint16_t bytes = (8 * params->n / params->w);
+    uint16_t end = (1 << params->w) - 1;
+
+    for (i = 0; i < bytes; ++i)
+        sum += end - coef(S, i, params->w);
+    return (sum << (8 - params->w));
+}
diff --git a/crypto/lms/lm_ots_verify.c b/crypto/lms/lm_ots_verify.c
new file mode 100644 (file)
index 0000000..8038a5c
--- /dev/null
@@ -0,0 +1,156 @@
+/*
+ * 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 <openssl/evp.h>
+#include "crypto/lms_sig.h"
+#include "crypto/lms_util.h"
+
+static int lm_ots_compute_pubkey_final(EVP_MD_CTX *ctx, EVP_MD_CTX *ctxIq,
+                                       const LM_OTS_SIG *sig, unsigned char *Kc);
+
+/**
+ * @brief OTS Signature verification.
+ *
+ * See RFC 8554 Section 4.6 Signature Verification
+ * Algorithm 4b: Compute a Public Key Candidate |Kc| from a signature, message
+ * and public key parameters.
+ *
+ * @param ctx A temporary working EVP_MD_CTX object.
+ * @param ctxIq A EVP_MD_CTX object that has been initialised with a digest,
+ *              that returns a non finalized value of H(I || q)
+ * @param sig An LM_OTS_SIG object that contains C and y
+ * @param pub The public key LM_OTS_PARAMS
+ * @param Id A 16 byte indentifier (I) associated with a LMS tree
+ * @param q The leaf index of the LMS tree.
+ * @param msg A message to verify
+ * @param msglen The size of |msg|
+ * @param Kc The computed public key candidate. It is assumed the size is n.
+ * @returns 1 on success, or 0 otherwise.
+ */
+int ossl_lm_ots_compute_pubkey(EVP_MD_CTX *ctx, EVP_MD_CTX *ctxIq,
+                               const LM_OTS_SIG *sig, const LM_OTS_PARAMS *pub,
+                               const unsigned char *Id, uint32_t q,
+                               const unsigned char *msg, size_t msglen,
+                               unsigned char *Kc)
+{
+    int ret = 0;
+    unsigned char qbuf[LMS_SIZE_q];
+    unsigned char d_mesg[sizeof(uint16_t)];
+
+    if (sig->params != pub)
+        return 0;
+
+    U32STR(qbuf, q);
+    U16STR(d_mesg, OSSL_LMS_D_MESG);
+
+    if (!EVP_DigestUpdate(ctxIq, Id, LMS_SIZE_I)
+            || !EVP_DigestUpdate(ctxIq, qbuf, sizeof(qbuf))
+            || !EVP_MD_CTX_copy_ex(ctx, ctxIq)
+            /* Q = H(I || u32str(q) || u16str(D_MESG) || C || msg) */
+            || !EVP_DigestUpdate(ctx, d_mesg, sizeof(d_mesg))
+            || !EVP_DigestUpdate(ctx, sig->C, sig->params->n)
+            || !EVP_DigestUpdate(ctx, msg, msglen)
+            || !lm_ots_compute_pubkey_final(ctx, ctxIq, sig, Kc))
+        goto err;
+    ret = 1;
+err:
+    return ret;
+}
+
+/**
+ * @brief simple function to increment a 16 bit counter by 1.
+ * It assumes checking for overflow is not required.
+ */
+static ossl_inline void INC16(unsigned char *tag)
+{
+    if (++(tag[1]) == 0)
+        ++*tag;
+}
+
+/*
+ * @brief OTS signature verification final phase
+ * See RFC 8554 Section 4.3 Signature Verification
+ * Algorithm 4b - Part 3
+ * Step 3 (Finalizes Q) and 4
+ *
+ * @param ctx A EVP_MD_CTX object that contains a non finalized value of
+ *            Q = H(I || u32str(q) || u16str(D_MESG) || C || msg)
+ *            This ctx is reused for other calculations.
+ * @param ctxIq A EVP_MD_CTX object that contains a non finalized value of H(I || q).
+ * @param sig An object that containing LM_OTS signature data.
+ * @param Kc The computed public key. It is assumed the size is n.
+ * @returns 1 on success, or 0 otherwise.
+ */
+static int lm_ots_compute_pubkey_final(EVP_MD_CTX *ctx, EVP_MD_CTX *ctxIq,
+                                       const LM_OTS_SIG *sig, unsigned char *Kc)
+{
+    int ret = 0, i;
+    EVP_MD_CTX *ctxKc = NULL;
+    unsigned char tag[2 + 1], *tag2 = &tag[2];
+    unsigned char Q[LMS_MAX_DIGEST_SIZE + LMS_SIZE_QSUM], *Qsum;
+    unsigned char z[LMS_MAX_DIGEST_SIZE];
+    unsigned char d_pblc[sizeof(uint16_t)];
+    uint16_t sum;
+    const LM_OTS_PARAMS *params = sig->params;
+    int n = params->n;
+    int p = params->p;
+    uint8_t j, w = params->w, end = (1 << w) - 1;
+    int a;
+    unsigned char *y;
+
+    if (!EVP_DigestFinal_ex(ctx, Q, NULL))
+        goto err;
+
+    ctxKc = EVP_MD_CTX_create();
+    if (ctxKc == NULL)
+        goto err;
+    sum = ossl_lm_ots_params_checksum(params, Q);
+    Qsum = Q + n;
+    /* Q || Cksm(Q) */
+    U16STR(Qsum, sum);
+    U16STR(d_pblc, OSSL_LMS_D_PBLC);
+
+    if (!(EVP_MD_CTX_copy_ex(ctxKc, ctxIq))
+            || !EVP_DigestUpdate(ctxKc, d_pblc, sizeof(d_pblc)))
+        goto err;
+
+    y = sig->y;
+    tag[0] = 0;
+    tag[1] = 0;
+
+    /*
+     * Depending on the lm_ots_type (see lm_ots_params[])
+     * The outer loop |p| ranges from 26...265 iterations and
+     * the inner loop |end| is in the range 0...(2^w)-1
+     */
+    for (i = 0; i < p; ++i) {
+        a = coef(Q, i, w);
+        memcpy(z, y, n);
+        y += n;
+        for (j = a; j < end; ++j) {
+            *tag2 = (j & 0xFF);
+            if (!(EVP_MD_CTX_copy_ex(ctx, ctxIq))
+                    || !EVP_DigestUpdate(ctx, tag, sizeof(tag))
+                    || !EVP_DigestUpdate(ctx, z, n)
+                    || !EVP_DigestFinal_ex(ctx, z, NULL))
+                goto err;
+        }
+        INC16(tag);
+        if (!EVP_DigestUpdate(ctxKc, z, n))
+            goto err;
+    }
+
+    /* Kc = H(I || u32str(q) || u16str(D_PBLC) || z[0] || ... || z[p-1]) */
+    if (!EVP_DigestFinal(ctxKc, Kc, NULL))
+        goto err;
+    ret = 1;
+err:
+    EVP_MD_CTX_free(ctxKc);
+    return ret;
+}
index 6d90fa7ee78b5557d01c2b290e4e305f2824d11c..f2cfe697007cb3718555ba450eee868dd080e22c 100644 (file)
@@ -21,8 +21,13 @@ LMS_KEY *ossl_lms_key_new(OSSL_LIB_CTX *libctx)
 {
     LMS_KEY *ret = OPENSSL_zalloc(sizeof(LMS_KEY));
 
-    if (ret != NULL)
+    if (ret != NULL) {
+        if (!CRYPTO_NEW_REF(&ret->references, 1)) {
+            OPENSSL_free(ret);
+            return NULL;
+        }
         ret->libctx = libctx;
+    }
     return ret;
 }
 
@@ -32,16 +37,39 @@ LMS_KEY *ossl_lms_key_new(OSSL_LIB_CTX *libctx)
 void ossl_lms_key_free(LMS_KEY *lmskey)
 {
     LMS_PUB_KEY *pub;
+    int i;
 
     if (lmskey == NULL)
         return;
 
+    CRYPTO_DOWN_REF(&lmskey->references, &i);
+    REF_PRINT_COUNT("LMS_KEY", i, lmskey);
+    if (i > 0)
+        return;
+    REF_ASSERT_ISNT(i < 0);
+
     pub = &lmskey->pub;
-    if (pub->allocated)
-        OPENSSL_free(pub->encoded);
+    OPENSSL_free(pub->encoded);
+    CRYPTO_FREE_REF(&lmskey->references);
     OPENSSL_free(lmskey);
 }
 
+/*
+ * @brief Increase the reference count for a LMS_KEY object.
+ * @returns 1 on success or 0 otherwise.
+ */
+int ossl_lms_key_up_ref(LMS_KEY *key)
+{
+    int i;
+
+    if (CRYPTO_UP_REF(&key->references, &i) <= 0)
+        return 0;
+
+    REF_PRINT_COUNT("LMS_KEY", i, key);
+    REF_ASSERT_ISNT(i < 2);
+    return ((i > 1) ? 1 : 0);
+}
+
 /**
  * @brief Are 2 LMS public keys equal?
  *
@@ -84,9 +112,9 @@ int ossl_lms_key_valid(const LMS_KEY *key, int selection)
         return 0;
 
     if ((selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) != 0)
-        if (key->pub.encoded == NULL || key->pub.encodedlen == 0)
-            return 0;
-    /* There is no private key currently */
+        return key->pub.encoded != NULL && key->pub.encodedlen != 0;
+    if ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) != 0)
+        return 0; /* a private key that doesn't exist can't be valid */
     return 1;
 }
 
@@ -99,10 +127,10 @@ int ossl_lms_key_valid(const LMS_KEY *key, int selection)
  */
 int ossl_lms_key_has(const LMS_KEY *key, int selection)
 {
-    int ok = 1;
-
     if ((selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) != 0)
-        ok = (key != NULL && key->pub.K != NULL);
+        return (key != NULL) && (key->pub.K != NULL);
     /* There is no private key currently */
-    return ok;
+    if ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) != 0)
+        return 0;
+    return 1;
 }
diff --git a/crypto/lms/lms_sig.c b/crypto/lms/lms_sig.c
new file mode 100644 (file)
index 0000000..7c1c8b2
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * 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 "crypto/lms_sig.h"
+
+/**
+ * @brief Create a new LMS_SIG object
+ */
+LMS_SIG *ossl_lms_sig_new(void)
+{
+    return OPENSSL_zalloc(sizeof(LMS_SIG));
+}
+
+/**
+ * @brief Destroy an existing LMS_SIG object
+ */
+void ossl_lms_sig_free(LMS_SIG *sig)
+{
+    OPENSSL_free(sig);
+}
diff --git a/crypto/lms/lms_sig_decoder.c b/crypto/lms/lms_sig_decoder.c
new file mode 100644 (file)
index 0000000..1cb758a
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * 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 "crypto/lms_sig.h"
+#include "crypto/lms_util.h"
+
+/**
+ * @brief Decode a byte array containing XDR signature data into a LMS_SIG object.
+ *
+ * This is used for LMS Signature Verification.
+ * This function may be called multiple times.
+ * See RFC 8554 Algorithm 6a: Steps 1 and 2.
+ * It uses shallow copies for C, y and path.
+ *
+ * @param pkt Contains the signature data to decode. There may still be data
+ *            remaining in pkt after decoding.
+ * @param pub A  public key that contains LMS_PARAMS and LM_OTS_PARAMS associated
+ *            with the signature.
+ * @returns The created LMS_SIG object is successful, or NULL on failure. A
+ *          failure may occur if the passed in LMS public key |pub| is not
+ *          compatible with the decoded LMS_SIG object,
+ */
+LMS_SIG *ossl_lms_sig_from_pkt(PACKET *pkt, const LMS_KEY *pub)
+{
+    uint32_t sig_ots_type = 0, sig_lms_type = 0;
+    const LMS_PARAMS *lparams = pub->lms_params;
+    const LM_OTS_PARAMS *pub_ots_params = pub->ots_params;
+    const LM_OTS_PARAMS *sig_params;
+    LMS_SIG *lsig = NULL;
+
+    lsig = ossl_lms_sig_new();
+    if (lsig == NULL)
+        return NULL;
+
+    if (!PACKET_get_4_len(pkt, &lsig->q)    /* q = Leaf Index */
+            || !PACKET_get_4_len(pkt, &sig_ots_type)
+            || pub_ots_params->lm_ots_type != sig_ots_type)
+        goto err;
+    sig_params = pub_ots_params;
+    lsig->sig.params = sig_params;
+    lsig->params = lparams;
+
+    if (!PACKET_get_bytes(pkt, (const unsigned char **)&lsig->sig.C,
+                          sig_params->n)
+            || !PACKET_get_bytes(pkt, (const unsigned char **)&lsig->sig.y,
+                                 sig_params->p * sig_params->n)
+            || !PACKET_get_4_len(pkt, &sig_lms_type)
+            || (lparams->lms_type != sig_lms_type)
+            || HASH_NOT_MATCHED(lparams, sig_params)
+            || lsig->q >= (uint32_t)(1 << lparams->h)
+            || !PACKET_get_bytes(pkt, (const unsigned char **)&lsig->paths,
+                                 lparams->h * lparams->n)
+            || PACKET_remaining(pkt) > 0)
+        goto err;
+    return lsig;
+err:
+    ossl_lms_sig_free(lsig);
+    return NULL;
+}
+
+/**
+ * @brief Decode a byte array of LMS signature data.
+ *
+ * This function does not duplicate any of the byte data contained within
+ * |sig|. So it is expected that |sig| will exist for the duration of the
+ * returned signature |out|.
+ *
+ * @param out Used to return the LMS_SIG object.
+ * @param pub The root public LMS key
+ * @param sig A input byte array of signature data.
+ * @param siglen The size of sig.
+ * @returns 1 if the signature is successfully decoded,
+ *          otherwise it returns 0.
+ */
+int ossl_lms_sig_decode(LMS_SIG **out, LMS_KEY *pub,
+                        const unsigned char *sig, size_t siglen)
+{
+    PACKET pkt;
+    LMS_SIG *s = NULL;
+
+    if (pub == NULL)
+        return 0;
+
+    if (!PACKET_buf_init(&pkt, sig, siglen))
+        return 0;
+
+    s = ossl_lms_sig_from_pkt(&pkt, pub);
+    if (s == NULL)
+        return 0;
+
+    /* Fail if there are trailing bytes */
+    if (PACKET_remaining(&pkt) > 0)
+        goto err;
+    *out = s;
+    return 1;
+err:
+    ossl_lms_sig_free(s);
+    return 0;
+}
diff --git a/crypto/lms/lms_verify.c b/crypto/lms/lms_verify.c
new file mode 100644 (file)
index 0000000..f604f4a
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+ * 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 "crypto/lms_sig.h"
+#include "crypto/lms_util.h"
+#include "internal/common.h"
+
+/*
+ * Constants used for obtaining unique inputs for different hashing operations
+ * e.g H(I || q || OSSL_LMS_D_LEAF || ... )
+ */
+const uint16_t OSSL_LMS_D_PBLC          = 0x8080;
+const uint16_t OSSL_LMS_D_MESG          = 0x8181;
+const uint16_t OSSL_LMS_D_LEAF          = 0x8282;
+const uint16_t OSSL_LMS_D_INTR          = 0x8383;
+
+/*
+ * @brief Compute the candidate LMS root value Tc
+ *
+ * @param paths An array of bytes representing the hash values associated with
+ *              the path through the tree from the leaf associated with the
+ *              LM-OTS signature to the root public key node.
+ * @param n The hash output size (The size of each path in |paths|)
+ * @param nodenum The leaf index node number. The root node had a value of 1
+ *                Each subsequent level has nodes in the range 2^h...2^(h+1)-1
+ * @param ctx A EVP_MD_CTX object used for calculations
+ * @param ctxI A EVP_MD_CTX object containing an unfinalised H(I)
+ * @param Tc Contains H(I || u32str(node_num) || u16str(D_LEAF) || Kc) on input,
+ *           and on output returns the calculated candidate public key.
+ * @returns 1 on success, or 0 otherwise.
+ */
+static
+int lms_sig_compute_tc_from_path(const unsigned char *paths, uint32_t n,
+                                 uint32_t node_num,
+                                 EVP_MD_CTX *ctx, EVP_MD_CTX *ctxI,
+                                 unsigned char *Tc)
+{
+    int ret = 0;
+    unsigned char qbuf[4];
+    unsigned char d_intr[sizeof(uint16_t)];
+    const unsigned char *path = paths;
+
+    U16STR(d_intr, OSSL_LMS_D_INTR);
+
+    /*
+     * Calculate the public key Tc using the path
+     * The root hash is the hash of its 2 childrens Hash values.
+     * A child hash for each level is passed in by paths, and we have
+     * a leaf value that can be used with the path to calculate the parent
+     * hash.
+     */
+    while (node_num > 1) {
+        /* At each level the path contains either the left or right child */
+        int odd = node_num & 1;
+
+        node_num = node_num >> 1; /* get the parent node_num */
+        U32STR(qbuf, node_num);
+
+        /*
+         * Calculate Tc as either
+         *   Tc(parent) = H(I || node_q || 0x8383 || paths[i][n] || Tc(right) OR
+         *   Tc(parent) = H(I || node_q || 0x8383 || Tc(left) || paths[i][n])
+         */
+        if (!EVP_MD_CTX_copy_ex(ctx, ctxI)
+                || !EVP_DigestUpdate(ctx, qbuf, sizeof(qbuf))
+                || !EVP_DigestUpdate(ctx, d_intr, sizeof(d_intr)))
+            goto err;
+
+        if (odd) {
+            if (!EVP_DigestUpdate(ctx, path, n)
+                || !EVP_DigestUpdate(ctx, Tc, n))
+                goto err;
+        } else {
+            if (!EVP_DigestUpdate(ctx, Tc, n)
+                || !EVP_DigestUpdate(ctx, path, n))
+                goto err;
+        }
+        /*
+         * Tc = parent Hash, which is either the left or right child for the next
+         * level up (node_num determines if it is left or right).
+         */
+        if (!EVP_DigestFinal_ex(ctx, Tc, NULL))
+            goto err;
+        path += n;
+    }
+    ret = 1;
+err:
+    return ret;
+}
+
+/*
+ * @brief LMS signature verification.
+ * See RFC 8554 Section 5.4.2. Algorithm 6: Steps 3 & 4
+ *
+ * @param lms_sig Is a valid decoded LMS_SIG signature object.
+ * @param pub Is a valid LMS public key object.
+ * @param md Contains the fetched digest to be used for Hash operations
+ * @param msg A message to verify
+ * @param msglen The size of |msg|
+ * @returns 1 if the verification succeeded, or 0 otherwise.
+ */
+int ossl_lms_sig_verify(const LMS_SIG *lms_sig, const LMS_KEY *pub,
+                        const EVP_MD *md,
+                        const unsigned char *msg, size_t msglen)
+{
+    int ret = 0;
+    EVP_MD_CTX *ctx = NULL, *ctxIq = NULL;
+    EVP_MD_CTX *ctxI;
+    unsigned char Kc[LMS_MAX_DIGEST_SIZE];
+    unsigned char Tc[LMS_MAX_DIGEST_SIZE];
+    unsigned char qbuf[4];
+    unsigned char d_leaf[sizeof(uint16_t)];
+    const LMS_PARAMS *lms_params = pub->lms_params;
+    uint32_t n = lms_params->n;
+    uint32_t node_num;
+
+    ctx = EVP_MD_CTX_create();
+    ctxIq = EVP_MD_CTX_create();
+    if (ctx == NULL || ctxIq == NULL)
+        goto err;
+
+    if (!evp_md_ctx_init(ctxIq, md, lms_sig->params))
+        goto err;
+    /*
+     * Algorithm 6a: Step 3.
+     * Calculate a candidate public key |Kc| using the lmots_signature, message,
+     * and the identifiers I, q
+     */
+    if (!ossl_lm_ots_compute_pubkey(ctx, ctxIq, &lms_sig->sig,
+                                    pub->ots_params, pub->Id,
+                                    lms_sig->q, msg, msglen, Kc))
+        goto err;
+
+    /*
+     * Algorithm 6a: Step 4
+     * Compute the candidate LMS root value Tc
+     */
+    if (!ossl_assert(lms_sig->q < (uint32_t)(1 << lms_params->h)))
+        return 0;
+    node_num = (1 << lms_params->h) + lms_sig->q;
+
+    U32STR(qbuf, node_num);
+    U16STR(d_leaf, OSSL_LMS_D_LEAF);
+    ctxI = ctxIq;
+    /*
+     * Tc = H(I || u32str(node_num) || u16str(D_LEAF) || Kc)
+     *
+     * ctx is left initialised with the md from ossl_lm_ots_compute_pubkey,
+     * so there is no need to reinitialise it here.
+     */
+    if (!EVP_DigestInit_ex2(ctx, NULL, NULL)
+            || !EVP_DigestUpdate(ctx, pub->Id, LMS_SIZE_I)
+            || !EVP_MD_CTX_copy_ex(ctxI, ctx)
+            || !EVP_DigestUpdate(ctx, qbuf, sizeof(qbuf))
+            || !EVP_DigestUpdate(ctx, d_leaf, sizeof(d_leaf))
+            || !EVP_DigestUpdate(ctx, Kc, n)
+            || !EVP_DigestFinal_ex(ctx, Tc, NULL)
+            || !lms_sig_compute_tc_from_path(lms_sig->paths, n, node_num,
+                                             ctx, ctxI, Tc))
+        goto err;
+    /* Algorithm 6: Step 4 */
+    ret = (memcmp(pub->pub.K, Tc, n) == 0);
+err:
+    EVP_MD_CTX_free(ctxIq);
+    EVP_MD_CTX_free(ctx);
+    return ret;
+}
index 563c33ff0761065dad9e0c9b77161a77e618d6fe..244a734c375e7a405ba3495e299d1d30b229d56c 100644 (file)
@@ -18,6 +18,7 @@
 # ifndef OPENSSL_NO_LMS
 #  include "types.h"
 #  include <openssl/params.h>
+#  include "internal/refcount.h"
 
 /*
  * Numeric identifiers associated with Leighton-Micali Signatures (LMS)
@@ -63,6 +64,9 @@
 #  define OSSL_LM_OTS_TYPE_SHAKE_N24_W4  0x0000000F
 #  define OSSL_LM_OTS_TYPE_SHAKE_N24_W8  0x00000010
 
+/* Constants used for verifying */
+#  define LMS_SIZE_q 4
+
 /* XDR sizes when encoding and decoding */
 #  define LMS_SIZE_I 16
 #  define LMS_SIZE_LMS_TYPE 4
@@ -123,7 +127,6 @@ typedef struct lms_pub_key_st {
      * It is a pointer into the encoded buffer
      */
     unsigned char *K;
-    uint32_t allocated;             /* If 1 then encoded needs to be freed */
 } LMS_PUB_KEY;
 
 struct lms_key_st {
@@ -132,12 +135,14 @@ struct lms_key_st {
     OSSL_LIB_CTX *libctx;
     unsigned char *Id;        /* A pointer to 16 bytes (I[16]) */
     LMS_PUB_KEY pub;
+    CRYPTO_REF_COUNT references;
 };
 
 const LMS_PARAMS *ossl_lms_params_get(uint32_t lms_type);
 const LM_OTS_PARAMS *ossl_lm_ots_params_get(uint32_t ots_type);
 
 LMS_KEY *ossl_lms_key_new(OSSL_LIB_CTX *libctx);
+int ossl_lms_key_up_ref(LMS_KEY *key);
 void ossl_lms_key_free(LMS_KEY *lmskey);
 int ossl_lms_key_equal(const LMS_KEY *key1, const LMS_KEY *key2, int selection);
 int ossl_lms_key_valid(const LMS_KEY *key, int selection);
diff --git a/include/crypto/lms_sig.h b/include/crypto/lms_sig.h
new file mode 100644 (file)
index 0000000..1b2cbf9
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * 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
+ */
+
+/*
+ * Internal LMS/LM_OTS functions for other submodules,
+ * not for application use
+ *
+ * Refer to RFC 8554 Sections 5.4 & 4.5 for information related to
+ * LMS Signatures & LM_OTS Signature Generation respectively.
+ */
+
+#ifndef OSSL_CRYPTO_LMS_SIG_H
+# define OSSL_CRYPTO_LMS_SIG_H
+# pragma once
+# ifndef OPENSSL_NO_LMS
+#  include "lms.h"
+#  include "internal/packet.h"
+
+/* The values defined for 8 byte TAGS */
+extern const uint16_t OSSL_LMS_D_PBLC;      /* 8080 */
+extern const uint16_t OSSL_LMS_D_MESG;      /* 8181 */
+extern const uint16_t OSSL_LMS_D_LEAF;      /* 8282 */
+extern const uint16_t OSSL_LMS_D_INTR;      /* 8383 */
+
+/* Used by OTS signature when calculating Q || Cksm(Q) */
+#  define LMS_SIZE_CHECKSUM 2
+#  define LMS_SIZE_QSUM 2
+
+/*
+ * An object for storing a One-Time Signature
+ * See RFC 8554 Section 4.5
+ */
+typedef struct lm_ots_sig_st {
+    const LM_OTS_PARAMS *params;
+    /* For verify operations the following pointers are not allocated */
+    unsigned char *C; /* A salt value of size n */
+    unsigned char *y; /* The trailing part of a signature of size p * n */
+} LM_OTS_SIG;
+
+/*
+ * An object for storing a LMS signature
+ * See RFC 8554 Section 5.4
+ */
+typedef struct lms_signature_st {
+    uint32_t q;
+    LM_OTS_SIG sig;
+    const LMS_PARAMS *params; /* contains the LMS type */
+    unsigned char *paths; /* size is h * m */
+} LMS_SIG;
+
+LMS_SIG *ossl_lms_sig_new(void);
+void ossl_lms_sig_free(LMS_SIG *sig);
+LMS_SIG *ossl_lms_sig_from_pkt(PACKET *pkt, const LMS_KEY *pub);
+int ossl_lms_sig_decode(LMS_SIG **out, LMS_KEY *pub,
+                        const unsigned char *sig, size_t siglen);
+int ossl_lms_sig_verify(const LMS_SIG *lms_sig, const LMS_KEY *pub,
+                        const EVP_MD *md,
+                        const unsigned char *msg, size_t msglen);
+
+int ossl_lm_ots_compute_pubkey(EVP_MD_CTX *ctx, EVP_MD_CTX *ctxIq,
+                               const LM_OTS_SIG *sig, const LM_OTS_PARAMS *pub,
+                               const unsigned char *Id, uint32_t q,
+                               const unsigned char *msg, size_t msglen,
+                               unsigned char *Kc);
+uint16_t ossl_lm_ots_params_checksum(const LM_OTS_PARAMS *params,
+                                     const unsigned char *S);
+
+# endif /* OPENSSL_NO_LMS */
+#endif /* OSSL_CRYPTO_LMS_SIG_H */
index 4f2bbb636617e08796c040faa3d9c3dd10157096..2cced53999141968e4f47ecdedd8f287554e3ed9 100644 (file)
@@ -7,9 +7,12 @@
  * https://www.openssl.org/source/license.html
  */
 
-/* @brief Internal LMS internal helper functions */
+/* @brief Internal LMS helper functions */
 
 #include "internal/packet.h"
+#include <openssl/params.h>
+#include <openssl/core_names.h>
+#include <openssl/evp.h>
 
 /*
  * This LMS implementation assumes that the hash algorithm must be the same for
 #define HASH_NOT_MATCHED(a, b) \
     (a)->n != (b)->n || (strcmp((a)->digestname, (b)->digestname) != 0)
 
-/**
- * @brief Helper function to return a ptr to a pkt buffer and move forward.
- * Used when decoding byte array XDR data.
- *
- * @param pkt A PACKET object that needs to have at least len bytes remaining.
- * @param out The returned ptr to the current position in the pkt buffer.
- * @param len The amount that we will move forward in the pkt buffer.
- * @returns 1 if there is enough bytes remaining to be able to skip forward,
- *          or 0 otherwise.
+/* Convert a 32 bit value |in| to 4 bytes |out| */
+#define U32STR(out, in)                             \
+    (out)[0] = (unsigned char)(((in) >> 24) & 0xff); \
+    (out)[1] = (unsigned char)(((in) >> 16) & 0xff); \
+    (out)[2] = (unsigned char)(((in) >> 8) & 0xff);  \
+    (out)[3] = (unsigned char)((in) & 0xff)
+
+/* Convert a 16 bit value |in| to 2 bytes |out| */
+#define U16STR(out, in)                             \
+    (out)[0] = (unsigned char)(((in) >> 8) & 0xff); \
+    (out)[1] = (unsigned char)((in) & 0xff)
+
+/*
+ * See RFC 8554 Section 3.1.3: Strings of w-bit Elements
+ * w: Is one of {1,2,4,8}
  */
 static ossl_unused ossl_inline
-int PACKET_get_bytes_shallow(PACKET *pkt, unsigned char **out, size_t len)
+uint8_t coef(const unsigned char *S, uint16_t i, uint8_t w)
 {
-    const unsigned char **data = (const unsigned char **)out;
+    uint8_t bitmask = (1 << w) - 1;
+    uint8_t shift = 8 - (w * (i % (8 / w)) + w);
+    int id = (i * w) / 8;
 
-    if (!PACKET_peek_bytes(pkt, data, len))
-        return 0;
-
-    packet_forward(pkt, len);
-
-    return 1;
+    return (S[id] >> shift) & bitmask;
 }
 
-/**
- * @brief Get 4 bytes in network order from |pkt| and store the value in |*data|
- * Similar to PACKET_get_net_4() except the data is uint32_t
- *
- * @param pkt Contains a buffer to read from
- * @param data The object to write the data to.
- * @returns 1 on success, or 0 otherwise.
- */
 static ossl_unused ossl_inline
-int PACKET_get_4_len(PACKET *pkt, uint32_t *data)
+int evp_md_ctx_init(EVP_MD_CTX *ctx, const EVP_MD *md,
+                    const LMS_PARAMS *lms_params)
 {
-    size_t i = 0;
-    int ret = PACKET_get_net_4_len(pkt, &i);
+    OSSL_PARAM params[2] = { OSSL_PARAM_END, OSSL_PARAM_END };
+    OSSL_PARAM *p = NULL;
 
-    if (ret)
-        *data = (uint32_t)i;
-    return ret;
+    /* The OpenSSL SHAKE implementation requires the xoflen to be set */
+    if (strncmp(lms_params->digestname, "SHAKE", 5) == 0) {
+        params[0] = OSSL_PARAM_construct_uint32(OSSL_DIGEST_PARAM_XOFLEN,
+                                                (uint32_t *)&lms_params->n);
+        p = params;
+    }
+    return EVP_DigestInit_ex2(ctx, md, p);
 }
index c5e113933201c40b4c40d3127adc2054141542da..7c880b0873a7e02dd45acebfca183ae8a90738fb 100644 (file)
@@ -483,6 +483,9 @@ static const OSSL_ALGORITHM deflt_signature[] = {
 #ifndef OPENSSL_NO_CMAC
     { PROV_NAMES_CMAC, "provider=default", ossl_mac_legacy_cmac_signature_functions },
 #endif
+#ifndef OPENSSL_NO_LMS
+    { PROV_NAMES_LMS, "provider=default", ossl_lms_signature_functions },
+#endif
 #ifndef OPENSSL_NO_SLH_DSA
     { PROV_NAMES_SLH_DSA_SHA2_128S, "provider=default",
       ossl_slh_dsa_sha2_128s_signature_functions, PROV_DESCS_SLH_DSA_SHA2_128S },
index 31d81c361d77553d8b0e464f53450f3fd7e08fb4..faf32e789e247b75238c277cd6869cb4d28dbf31 100644 (file)
@@ -437,6 +437,7 @@ extern const OSSL_DISPATCH ossl_mac_legacy_siphash_signature_functions[];
 extern const OSSL_DISPATCH ossl_mac_legacy_poly1305_signature_functions[];
 extern const OSSL_DISPATCH ossl_mac_legacy_cmac_signature_functions[];
 extern const OSSL_DISPATCH ossl_sm2_signature_functions[];
+extern const OSSL_DISPATCH ossl_lms_signature_functions[];
 extern const OSSL_DISPATCH ossl_ml_dsa_44_signature_functions[];
 extern const OSSL_DISPATCH ossl_ml_dsa_65_signature_functions[];
 extern const OSSL_DISPATCH ossl_ml_dsa_87_signature_functions[];
index 0fd39841f3400ee8dc5f8b851e5a037686470859..87270d7dab083f86751c2df5bbb1cb6ea758f71d 100644 (file)
@@ -6,6 +6,7 @@ $EC_GOAL=../../libdefault.a ../../libfips.a
 $MAC_GOAL=../../libdefault.a ../../libfips.a
 $RSA_GOAL=../../libdefault.a ../../libfips.a
 $SM2_GOAL=../../libdefault.a
+$LMS_GOAL=../../libdefault.a
 $ML_DSA_GOAL=../../libdefault.a ../../libfips.a
 $SLH_DSA_GOAL=../../libdefault.a ../../libfips.a
 
@@ -36,6 +37,10 @@ DEPEND[sm2_sig.o]=../../common/include/prov/der_sm2.h
 
 SOURCE[$MAC_GOAL]=mac_legacy_sig.c
 
+IF[{- !$disabled{lms} -}]
+  SOURCE[$LMS_GOAL]=lms_sig.c
+ENDIF
+
 IF[{- !$disabled{'ml-dsa'} -}]
   SOURCE[$ML_DSA_GOAL]=ml_dsa_sig.c
 ENDIF
diff --git a/providers/implementations/signature/lms_sig.c b/providers/implementations/signature/lms_sig.c
new file mode 100644 (file)
index 0000000..1e641a7
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2024 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 <openssl/core.h>
+#include <openssl/core_dispatch.h>
+#include <openssl/core_names.h>
+#include <openssl/proverr.h>
+#include <openssl/params.h>
+#include <openssl/evp.h>
+#include "prov/providercommon.h"
+#include "prov/provider_ctx.h"
+#include "prov/implementations.h"
+#include "crypto/lms_sig.h"
+
+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;
+
+typedef struct {
+    OSSL_LIB_CTX *libctx;
+    char *propq;
+    LMS_KEY *key;
+    EVP_MD *md;
+} PROV_LMS_CTX;
+
+static void *lms_newctx(void *provctx, const char *propq)
+{
+    PROV_LMS_CTX *ctx;
+
+    if (!ossl_prov_is_running())
+        return NULL;
+
+    ctx = OPENSSL_zalloc(sizeof(PROV_LMS_CTX));
+    if (ctx == NULL)
+        return NULL;
+
+    if (propq != NULL && (ctx->propq = OPENSSL_strdup(propq)) == NULL)
+        goto err;
+    ctx->libctx = PROV_LIBCTX_OF(provctx);
+    return ctx;
+err:
+    OPENSSL_free(ctx);
+    return NULL;
+}
+
+static void lms_freectx(void *vctx)
+{
+    PROV_LMS_CTX *ctx = (PROV_LMS_CTX *)vctx;
+
+    if (ctx == NULL)
+        return;
+    ossl_lms_key_free(ctx->key);
+    OPENSSL_free(ctx->propq);
+    EVP_MD_free(ctx->md);
+    OPENSSL_free(ctx);
+}
+
+static int setdigest(PROV_LMS_CTX *ctx, const char *digestname)
+{
+    /*
+     * Assume that only one digest can be used by LMS.
+     * Set the digest to the one contained in the public key.
+     * If the optional digestname passed in by the user is different
+     * then return an error.
+     */
+    LMS_KEY *key = ctx->key;
+    const char *pub_digestname = key->ots_params->digestname;
+
+    if (ctx->md != NULL) {
+        if (EVP_MD_is_a(ctx->md, pub_digestname))
+            goto end;
+        EVP_MD_free(ctx->md);
+    }
+    ctx->md = EVP_MD_fetch(ctx->libctx, pub_digestname, ctx->propq);
+    if (ctx->md == NULL)
+        return 0;
+end:
+    return digestname == NULL || EVP_MD_is_a(ctx->md, digestname);
+}
+
+static int lms_verify_msg_init(void *vctx, void *vkey, const OSSL_PARAM params[])
+{
+    PROV_LMS_CTX *ctx = (PROV_LMS_CTX *)vctx;
+    LMS_KEY *key = (LMS_KEY *)vkey;
+
+    if (!ossl_prov_is_running() || ctx == NULL)
+        return 0;
+
+    if (key == NULL && ctx->key == NULL) {
+        ERR_raise(ERR_LIB_PROV, PROV_R_NO_KEY_SET);
+        return 0;
+    }
+
+    if (!ossl_lms_key_up_ref(key))
+        return 0;
+    ossl_lms_key_free(ctx->key);
+    ctx->key = key;
+    if (!setdigest(ctx, NULL))
+        return 0;
+    return 1;
+}
+
+static int lms_verify(void *vctx, const unsigned char *sigbuf, size_t sigbuf_len,
+                      const unsigned char *msg, size_t msglen)
+{
+    int ret = 0;
+    PROV_LMS_CTX *ctx = (PROV_LMS_CTX *)vctx;
+    LMS_KEY *pub = ctx->key;
+    LMS_SIG *sig = NULL;
+
+    /* A root public key is required to perform a verify operation */
+    if (pub == NULL)
+        return 0;
+
+    /* Decode the LMS signature data into a LMS_SIG object */
+    if (!ossl_lms_sig_decode(&sig, pub, sigbuf, sigbuf_len))
+        return 0;
+
+    ret = ossl_lms_sig_verify(sig, pub, ctx->md, msg, msglen);
+    ossl_lms_sig_free(sig);
+    return ret;
+}
+
+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_DISPATCH_END
+};
index f1cb57d2999a0dc42d66388f48f600c6a332602f..ef6ab7d0dac52b5a97cae06d50bf33b0a02e7354 100644 (file)
@@ -239,6 +239,228 @@ end:
     return ret;
 }
 
+static int lms_verify_test(int tst)
+{
+    int ret = 0;
+    LMS_ACVP_TEST_DATA *td = &lms_testdata[tst];
+    EVP_PKEY_CTX *ctx = NULL;
+    EVP_PKEY *pkey = NULL;
+    EVP_SIGNATURE *sig = NULL;
+
+    ret = TEST_ptr(pkey = lms_pubkey_from_data(td->pub, td->publen))
+        && TEST_ptr(sig = EVP_SIGNATURE_fetch(libctx, "LMS", NULL))
+        && TEST_ptr(ctx = EVP_PKEY_CTX_new_from_pkey(libctx, pkey, NULL))
+        && TEST_int_eq(EVP_PKEY_verify_message_init(ctx, sig, NULL), 1)
+        && TEST_int_eq(EVP_PKEY_verify(ctx, td->sig, td->siglen,
+                                       td->msg, td->msglen), 1);
+
+    EVP_PKEY_free(pkey);
+    EVP_PKEY_CTX_free(ctx);
+    EVP_SIGNATURE_free(sig);
+    return ret;
+}
+
+static int lms_digest_verify_fail_test(void)
+{
+    int ret = 0;
+    LMS_ACVP_TEST_DATA *td = &lms_testdata[0];
+    EVP_PKEY *pub = NULL;
+    EVP_MD_CTX *vctx = NULL;
+
+    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 */
+    if (!TEST_int_eq(EVP_DigestVerifyInit_ex(vctx, NULL, NULL, libctx, NULL,
+                                             pub, NULL), 0))
+        goto err;
+    ret = 1;
+ err:
+    EVP_PKEY_free(pub);
+    EVP_MD_CTX_free(vctx);
+    return ret;
+}
+
+static int lms_verify_fail_test(void)
+{
+    int ret = 0;
+    LMS_ACVP_TEST_DATA *td = &lms_testdata[0];
+    EVP_PKEY_CTX *ctx = NULL;
+    EVP_PKEY *pkey = NULL;
+
+    if (!TEST_ptr(pkey = lms_pubkey_from_data(td->pub, td->publen))
+            || !TEST_ptr(ctx = EVP_PKEY_CTX_new_from_pkey(libctx, pkey, NULL)))
+        goto end;
+    /* Only one shot mode is supported, streaming fails to initialise */
+    if (!TEST_int_eq(EVP_PKEY_verify_init(ctx), -2))
+        goto end;
+    ret = 1;
+end:
+    EVP_PKEY_CTX_free(ctx);
+    EVP_PKEY_free(pkey);
+    return ret;
+}
+
+static int lms_verify_bad_sig_test(void)
+{
+    int ret = 0, i = 0;
+    LMS_ACVP_TEST_DATA *td = &lms_testdata[1];
+    EVP_PKEY *pkey = NULL;
+    EVP_SIGNATURE *sig = NULL;
+    EVP_PKEY_CTX *ctx = NULL;
+    unsigned char *sig_data = NULL, corrupt_mask = 0x01;
+    /*
+     * Corrupt every 3rd byte to run less tests. The smallest element of an XDR
+     * encoding is 4 bytes, so this will corrupt every element.
+     * Memory sanitisation is slow, so a larger step size is used for this.
+     */
+#if defined(OSSL_SANITIZE_MEMORY)
+    const int step = (int)(td->siglen >> 1);
+#else
+    const int step = 3;
+#endif
+
+    /* Copy the signature so that we can corrupt it */
+    sig_data = OPENSSL_memdup(td->sig, td->siglen);
+    if (sig_data == NULL)
+        return 0;
+
+    if (!TEST_ptr(pkey = lms_pubkey_from_data(td->pub, td->publen))
+            || !TEST_ptr(sig = EVP_SIGNATURE_fetch(libctx, "LMS", NULL))
+            || !TEST_ptr(ctx = EVP_PKEY_CTX_new_from_pkey(libctx, pkey, NULL)))
+        goto end;
+
+    if (!TEST_int_eq(EVP_PKEY_verify_message_init(ctx, sig, NULL), 1))
+        goto end;
+
+    for (i = 0; i < (int)td->siglen; i += step) {
+        sig_data[i] ^= corrupt_mask; /* corrupt a byte */
+        if (i > 0)
+            sig_data[i - step] ^= corrupt_mask; /* Reset the previously corrupt byte */
+
+        if (!TEST_int_eq(EVP_PKEY_verify(ctx, sig_data, td->siglen,
+                                         td->msg, td->msglen), 0)) {
+            TEST_note("Incorrectly passed when %dth byte of signature"
+                      " was corrupted", i);
+            goto end;
+        }
+    }
+
+    ret = 1;
+end:
+    EVP_SIGNATURE_free(sig);
+    EVP_PKEY_CTX_free(ctx);
+    EVP_PKEY_free(pkey);
+    OPENSSL_free(sig_data);
+
+    return ret;
+}
+
+/*
+ * Test that using the incorrect signature lengths (both shorter and longer)
+ * fail.
+ * NOTE: It does not get an out of bounds read due to the signature
+ * knowing how large it should be
+ */
+static int lms_verify_bad_sig_len_test(void)
+{
+    int ret = 0;
+    LMS_ACVP_TEST_DATA *td = &lms_testdata[1];
+    EVP_PKEY *pkey = NULL;
+    EVP_SIGNATURE *sig = NULL;
+    EVP_PKEY_CTX *ctx = NULL;
+    size_t siglen = 0;
+    const int step = 3;
+    unsigned char sigdata[4096];
+
+    if (!TEST_ptr(pkey = lms_pubkey_from_data(td->pub, td->publen))
+            || !TEST_ptr(sig = EVP_SIGNATURE_fetch(libctx, "LMS", NULL))
+            || !TEST_ptr(ctx = EVP_PKEY_CTX_new_from_pkey(libctx, pkey, NULL)))
+        goto end;
+
+    if (!TEST_size_t_le(td->siglen + 16, sizeof(sigdata)))
+        goto end;
+
+    OPENSSL_cleanse(sigdata, sizeof(sigdata));
+    memcpy(sigdata, td->sig, td->siglen);
+
+    ret = 0;
+    for (siglen = 0; siglen < td->siglen + 16; siglen += step) {
+        if (siglen == td->siglen)   /* ignore the size that should pass */
+            continue;
+        if (!TEST_int_eq(EVP_PKEY_verify_message_init(ctx, sig, NULL), 1))
+            goto end;
+        if (!TEST_int_eq(EVP_PKEY_verify(ctx, sigdata, siglen,
+                                         td->msg, td->msglen), 0)) {
+            TEST_note("Incorrectly accepted signature key of length"
+                      " %u (expected %u)", (unsigned)siglen, (unsigned)td->siglen);
+            goto end;
+        }
+    }
+
+    ret = 1;
+end:
+    EVP_SIGNATURE_free(sig);
+    EVP_PKEY_CTX_free(ctx);
+    EVP_PKEY_free(pkey);
+
+    return ret;
+}
+
+static int lms_verify_bad_pub_sig_test(void)
+{
+    LMS_ACVP_TEST_DATA *td = &lms_testdata[1];
+    int ret = 0, i = 0;
+    EVP_PKEY *pkey = NULL;
+    EVP_SIGNATURE *sig = NULL;
+    EVP_PKEY_CTX *ctx = NULL;
+    unsigned char *pub = NULL;
+    const int step = 1;
+
+    /* Copy the public key data so that we can corrupt it */
+    if (!TEST_ptr(pub = OPENSSL_memdup(td->pub, td->publen)))
+        return 0;
+
+    if (!TEST_ptr(sig = EVP_SIGNATURE_fetch(libctx, "LMS", NULL)))
+        goto end;
+
+    for (i = 0; i < (int)td->publen; i += step) {
+        if (i > 0) {
+            EVP_PKEY_free(pkey);
+            EVP_PKEY_CTX_free(ctx);
+            pkey = NULL;
+            ctx = NULL;
+            pub[i - step] ^= 1;
+        }
+        pub[i] ^= 1;
+        /* Corrupting the public key may cause the key load to fail */
+        pkey = lms_pubkey_from_data(pub, td->publen);
+        if (pkey == NULL)
+            continue;
+        if (!TEST_ptr(ctx = EVP_PKEY_CTX_new_from_pkey(libctx, pkey, NULL)))
+            continue;
+        if (!TEST_int_eq(EVP_PKEY_verify_message_init(ctx, sig, NULL), 1))
+            continue;
+        /* We expect the verify to fail */
+        if (!TEST_int_eq(EVP_PKEY_verify(ctx, td->sig, td->siglen,
+                                         td->msg, td->msglen), 0)) {
+            TEST_note("Incorrectly passed when byte %d of the public key"
+                      " was corrupted", i);
+            goto end;
+        }
+    }
+
+    ret = 1;
+end:
+    EVP_SIGNATURE_free(sig);
+    EVP_PKEY_CTX_free(ctx);
+    EVP_PKEY_free(pkey);
+    OPENSSL_free(pub);
+
+    return ret;
+}
+
 int setup_tests(void)
 {
     ADD_TEST(lms_bad_pub_len_test);
@@ -247,5 +469,12 @@ int setup_tests(void)
     ADD_TEST(lms_key_decode_test);
     ADD_TEST(lms_pubkey_decoder_test);
     ADD_TEST(lms_pubkey_decoder_fail_test);
+    ADD_ALL_TESTS(lms_verify_test, OSSL_NELEM(lms_testdata));
+    ADD_TEST(lms_verify_fail_test);
+    ADD_TEST(lms_digest_verify_fail_test);
+    ADD_TEST(lms_verify_bad_sig_test);
+    ADD_TEST(lms_verify_bad_sig_len_test);
+    ADD_TEST(lms_verify_bad_pub_sig_test);
+
     return 1;
 }