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
*/
#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[] = {
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));
+}
--- /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 <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;
+}
{
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;
}
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?
*
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;
}
*/
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;
}
--- /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 "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);
+}
--- /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 "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;
+}
--- /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 "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;
+}
# ifndef OPENSSL_NO_LMS
# include "types.h"
# include <openssl/params.h>
+# include "internal/refcount.h"
/*
* Numeric identifiers associated with Leighton-Micali Signatures (LMS)
# 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
* 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 {
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);
--- /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
+ */
+
+/*
+ * 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 */
* 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);
}
#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 },
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[];
$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
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
--- /dev/null
+/*
+ * 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
+};
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);
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;
}