/* * 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" #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; OPENSSL_store_u16_be(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 */ OPENSSL_store_u32_be(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 (!lms_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; OPENSSL_store_u32_be(qbuf, node_num); OPENSSL_store_u16_be(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; }