From: slontis Date: Tue, 1 Oct 2024 02:35:43 +0000 (+1000) Subject: Add LMS Signature verification. X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=7be3137fb5dd44c97def5d1d6279ffa7218dc643;p=thirdparty%2Fopenssl.git Add LMS Signature verification. Reviewed-by: Viktor Dukhovni Reviewed-by: Matt Caswell Reviewed-by: Paul Dale (Merged from https://github.com/openssl/openssl/pull/27885) --- diff --git a/crypto/lms/build.info b/crypto/lms/build.info index e6bf504e19c..068f3d7d85a 100644 --- a/crypto/lms/build.info +++ b/crypto/lms/build.info @@ -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 diff --git a/crypto/lms/lm_ots_params.c b/crypto/lms/lm_ots_params.c index 51d8485809b..87d98ada28c 100644 --- a/crypto/lms/lm_ots_params.c +++ b/crypto/lms/lm_ots_params.c @@ -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 index 00000000000..8038a5c8d0d --- /dev/null +++ b/crypto/lms/lm_ots_verify.c @@ -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 +#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; +} diff --git a/crypto/lms/lms_key.c b/crypto/lms/lms_key.c index 6d90fa7ee78..f2cfe697007 100644 --- a/crypto/lms/lms_key.c +++ b/crypto/lms/lms_key.c @@ -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 index 00000000000..7c1c8b226c4 --- /dev/null +++ b/crypto/lms/lms_sig.c @@ -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 index 00000000000..1cb758aed84 --- /dev/null +++ b/crypto/lms/lms_sig_decoder.c @@ -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 index 00000000000..f604f4a1fd8 --- /dev/null +++ b/crypto/lms/lms_verify.c @@ -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; +} diff --git a/include/crypto/lms.h b/include/crypto/lms.h index 563c33ff076..244a734c375 100644 --- a/include/crypto/lms.h +++ b/include/crypto/lms.h @@ -18,6 +18,7 @@ # ifndef OPENSSL_NO_LMS # include "types.h" # include +# 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 index 00000000000..1b2cbf909bb --- /dev/null +++ b/include/crypto/lms_sig.h @@ -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 */ diff --git a/include/crypto/lms_util.h b/include/crypto/lms_util.h index 4f2bbb63661..2cced539991 100644 --- a/include/crypto/lms_util.h +++ b/include/crypto/lms_util.h @@ -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 +#include +#include /* * This LMS implementation assumes that the hash algorithm must be the same for @@ -20,44 +23,44 @@ #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); } diff --git a/providers/defltprov.c b/providers/defltprov.c index c5e11393320..7c880b0873a 100644 --- a/providers/defltprov.c +++ b/providers/defltprov.c @@ -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 }, diff --git a/providers/implementations/include/prov/implementations.h b/providers/implementations/include/prov/implementations.h index 31d81c361d7..faf32e789e2 100644 --- a/providers/implementations/include/prov/implementations.h +++ b/providers/implementations/include/prov/implementations.h @@ -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[]; diff --git a/providers/implementations/signature/build.info b/providers/implementations/signature/build.info index 0fd39841f34..87270d7dab0 100644 --- a/providers/implementations/signature/build.info +++ b/providers/implementations/signature/build.info @@ -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 index 00000000000..1e641a70847 --- /dev/null +++ b/providers/implementations/signature/lms_sig.c @@ -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 +#include +#include +#include +#include +#include +#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 +}; diff --git a/test/lms_test.c b/test/lms_test.c index f1cb57d2999..ef6ab7d0dac 100644 --- a/test/lms_test.c +++ b/test/lms_test.c @@ -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; }