diff --git a/certs/include.am b/certs/include.am index 0b17390c884..6afa36129be 100644 --- a/certs/include.am +++ b/certs/include.am @@ -162,7 +162,6 @@ include certs/falcon/include.am include certs/rsapss/include.am include certs/slhdsa/include.am include certs/lms/include.am -include certs/xmss/include.am include certs/rpk/include.am include certs/acert/include.am include certs/mldsa/include.am diff --git a/certs/lms/bc_hss_L2_H5_W8_root.der b/certs/lms/bc_hss_L2_H5_W8_root.der deleted file mode 100644 index 824d5664af8..00000000000 Binary files a/certs/lms/bc_hss_L2_H5_W8_root.der and /dev/null differ diff --git a/certs/lms/bc_hss_L3_H5_W4_root.der b/certs/lms/bc_hss_L3_H5_W4_root.der deleted file mode 100644 index 43904f7ae7a..00000000000 Binary files a/certs/lms/bc_hss_L3_H5_W4_root.der and /dev/null differ diff --git a/certs/lms/bc_lms_chain_ca.der b/certs/lms/bc_lms_chain_ca.der deleted file mode 100644 index e3cebcccbb1..00000000000 Binary files a/certs/lms/bc_lms_chain_ca.der and /dev/null differ diff --git a/certs/lms/bc_lms_chain_leaf.der b/certs/lms/bc_lms_chain_leaf.der deleted file mode 100644 index 4346b7aa159..00000000000 Binary files a/certs/lms/bc_lms_chain_leaf.der and /dev/null differ diff --git a/certs/lms/bc_lms_sha256_h10_w8_root.der b/certs/lms/bc_lms_sha256_h10_w8_root.der deleted file mode 100644 index c9ca9bf226c..00000000000 Binary files a/certs/lms/bc_lms_sha256_h10_w8_root.der and /dev/null differ diff --git a/certs/lms/bc_lms_sha256_h5_w4_root.der b/certs/lms/bc_lms_sha256_h5_w4_root.der deleted file mode 100644 index ef4941ee8c0..00000000000 Binary files a/certs/lms/bc_lms_sha256_h5_w4_root.der and /dev/null differ diff --git a/certs/lms/include.am b/certs/lms/include.am index f9e39c1d04d..d5b89871e96 100644 --- a/certs/lms/include.am +++ b/certs/lms/include.am @@ -2,11 +2,10 @@ # All paths should be given relative to the root # +# bc_lms_native_bc_root.der is stock Bouncy Castle output (RFC 9802-aligned +# HSS/LMS) kept as the cross-implementation interop anchor. wolfSSL's own +# LMS certificate/CSR/chain generation is exercised in-process by the +# test_rfc9802_lms_x509_gen unit test, so no other committed fixtures are +# needed here. EXTRA_DIST += \ - certs/lms/bc_lms_sha256_h5_w4_root.der \ - certs/lms/bc_lms_sha256_h10_w8_root.der \ - certs/lms/bc_hss_L2_H5_W8_root.der \ - certs/lms/bc_hss_L3_H5_W4_root.der \ - certs/lms/bc_lms_chain_ca.der \ - certs/lms/bc_lms_chain_leaf.der \ certs/lms/bc_lms_native_bc_root.der diff --git a/certs/xmss/bc_xmss_chain_ca.der b/certs/xmss/bc_xmss_chain_ca.der deleted file mode 100644 index 31c8690e5b5..00000000000 Binary files a/certs/xmss/bc_xmss_chain_ca.der and /dev/null differ diff --git a/certs/xmss/bc_xmss_chain_leaf.der b/certs/xmss/bc_xmss_chain_leaf.der deleted file mode 100644 index cf168ee3e2b..00000000000 Binary files a/certs/xmss/bc_xmss_chain_leaf.der and /dev/null differ diff --git a/certs/xmss/bc_xmss_sha2_10_256_root.der b/certs/xmss/bc_xmss_sha2_10_256_root.der deleted file mode 100644 index 12d70a002ef..00000000000 Binary files a/certs/xmss/bc_xmss_sha2_10_256_root.der and /dev/null differ diff --git a/certs/xmss/bc_xmss_sha2_16_256_root.der b/certs/xmss/bc_xmss_sha2_16_256_root.der deleted file mode 100644 index 91f3bf55466..00000000000 Binary files a/certs/xmss/bc_xmss_sha2_16_256_root.der and /dev/null differ diff --git a/certs/xmss/bc_xmssmt_sha2_20_2_256_root.der b/certs/xmss/bc_xmssmt_sha2_20_2_256_root.der deleted file mode 100644 index 24b47019e1b..00000000000 Binary files a/certs/xmss/bc_xmssmt_sha2_20_2_256_root.der and /dev/null differ diff --git a/certs/xmss/bc_xmssmt_sha2_20_4_256_root.der b/certs/xmss/bc_xmssmt_sha2_20_4_256_root.der deleted file mode 100644 index b3037316d2b..00000000000 Binary files a/certs/xmss/bc_xmssmt_sha2_20_4_256_root.der and /dev/null differ diff --git a/certs/xmss/bc_xmssmt_sha2_40_8_256_root.der b/certs/xmss/bc_xmssmt_sha2_40_8_256_root.der deleted file mode 100644 index 870faa2c18b..00000000000 Binary files a/certs/xmss/bc_xmssmt_sha2_40_8_256_root.der and /dev/null differ diff --git a/certs/xmss/include.am b/certs/xmss/include.am deleted file mode 100644 index ff3bfbee4df..00000000000 --- a/certs/xmss/include.am +++ /dev/null @@ -1,12 +0,0 @@ -# vim:ft=automake -# All paths should be given relative to the root -# - -EXTRA_DIST += \ - certs/xmss/bc_xmss_sha2_10_256_root.der \ - certs/xmss/bc_xmss_sha2_16_256_root.der \ - certs/xmss/bc_xmssmt_sha2_20_2_256_root.der \ - certs/xmss/bc_xmssmt_sha2_20_4_256_root.der \ - certs/xmss/bc_xmssmt_sha2_40_8_256_root.der \ - certs/xmss/bc_xmss_chain_ca.der \ - certs/xmss/bc_xmss_chain_leaf.der diff --git a/tests/api/test_lms_xmss.c b/tests/api/test_lms_xmss.c index 2dc3ff21589..f3f2d27a423 100644 --- a/tests/api/test_lms_xmss.c +++ b/tests/api/test_lms_xmss.c @@ -30,6 +30,9 @@ #include #include +#ifdef HAVE_ECC +#include +#endif #include #include #include @@ -230,11 +233,14 @@ int test_wc_LmsKey_reload_cache(void) * standard ISARA OIDs and wraps the raw RFC 8391 pub key in an OCTET * STRING, so the fixtures were produced with a small generator that * overrides the AlgorithmIdentifier and SPKI to match RFC 9802. */ -#if (defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS)) && \ - !defined(NO_FILESYSTEM) && !defined(NO_CERTS) -/* Sanity bound on a test fixture cert. The largest BC-generated - * fixture we ship (XMSS^MT 40/8) is ~19 KiB; 1 MiB is well above - * any realistic RFC 9802 cert and catches a wild XFTELL. Typed as +/* Only the LMS interop-anchor verification still loads a committed fixture + * (bc_lms_native_bc_root.der); everything else is generated in-process. Gate + * these file helpers on exactly that call site to avoid an unused-function + * warning in XMSS-only or truncated-hash builds. */ +#if defined(WOLFSSL_HAVE_LMS) && !defined(NO_FILESYSTEM) && \ + !defined(NO_CERTS) && !defined(WOLFSSL_NO_LMS_SHA256_256) +/* Sanity bound on a test fixture cert. 1 MiB is well above any realistic + * RFC 9802 cert and catches a wild XFTELL. Typed as * long to match XFTELL's return so the size comparison below isn't * a mixed long-vs-int compare. */ #define RFC9802_TEST_MAX_CERT_SIZE ((long)(1L << 20)) @@ -640,295 +646,607 @@ static int rfc9802_xmss_import_negative(void) } #endif -/* Walk the AlgorithmIdentifier SEQUENCE that begins at sigIndex and - * locate the byte offset of the last byte of its OID content. Handles - * both short-form (length < 128) and long-form DER length encodings, - * so a future fixture-regenerator that emits longer OIDs / SEQUENCEs - * still drives this test rather than tripping the loud-fail branch. - * - * Returns 0 on success with *oidLastByte set; returns -1 on any DER - * shape mismatch. */ -#if defined(WOLFSSL_HAVE_XMSS) && !defined(NO_FILESYSTEM) && !defined(NO_CERTS) -static int rfc9802_find_sig_alg_oid_last_byte(const byte* buf, word32 bufLen, - word32 sigIndex, word32* oidLastByte) +/* Collect the byte offset of the final sub-identifier of every + * 1.3.6.1.5.5.7.6. OID in a DER cert (XMSS ends 0x22, XMSS^MT ends + * 0x23). RFC 9802 reuses the same OID for the SubjectPublicKeyInfo algorithm, + * the TBS signatureAlgorithm and the outer signatureAlgorithm, so a conformant + * XMSS/XMSS^MT cert contains exactly three, in TBS-signature / SPKI-key / + * outer-signature order. Returns the number of occurrences found. */ +#if defined(WOLFSSL_ASN_TEMPLATE) && defined(WOLFSSL_HAVE_XMSS) && \ + !defined(WOLFSSL_XMSS_VERIFY_ONLY) && defined(WOLFSSL_CERT_GEN) && \ + !defined(NO_FILESYSTEM) && !defined(NO_CERTS) +static int rfc9802_collect_hbs_oid_offsets(const byte* der, word32 derSz, + byte lastByte, word32* offsets, int maxOff) { - word32 idx = sigIndex; - word32 oidContentLen = 0; - - /* AlgorithmIdentifier ::= SEQUENCE { algorithm OID, ... } */ - if (idx >= bufLen || buf[idx] != 0x30) - return -1; - idx++; - /* Skip SEQUENCE length (short or long form). */ - if (idx >= bufLen) - return -1; - if (buf[idx] < 0x80) { - idx++; - } - else { - word32 nbytes = (word32)(buf[idx] & 0x7F); - if (nbytes == 0 || nbytes > 4 || idx + 1 + nbytes > bufLen) - return -1; - idx += 1 + nbytes; - } - /* algorithm OID tag. */ - if (idx >= bufLen || buf[idx] != 0x06) - return -1; - idx++; - /* OID length (short or long form). */ - if (idx >= bufLen) - return -1; - if (buf[idx] < 0x80) { - oidContentLen = buf[idx]; - idx++; - } - else { - word32 nbytes = (word32)(buf[idx] & 0x7F); - word32 i; - if (nbytes == 0 || nbytes > 4 || idx + 1 + nbytes > bufLen) - return -1; - for (i = 0; i < nbytes; i++) - oidContentLen = (oidContentLen << 8) | buf[idx + 1 + i]; - idx += 1 + nbytes; + /* OID body for 1.3.6.1.5.5.7.6: 2B 06 01 05 05 07 06, then . */ + static const byte pfx[] = { 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x06 }; + int n = 0; + word32 i; + + for (i = 0; (word32)(i + sizeof(pfx)) < derSz; i++) { + if (XMEMCMP(der + i, pfx, sizeof(pfx)) == 0 && + der[i + sizeof(pfx)] == lastByte) { + if (n < maxOff) + offsets[n] = i + (word32)sizeof(pfx); + n++; + } } - if (oidContentLen == 0 || idx + oidContentLen > bufLen) - return -1; - *oidLastByte = idx + oidContentLen - 1; - return 0; + return n; } +#endif -/* Helper: load fixture, locate last byte of outer signatureAlgorithm - * OID, patch it from `expected` to `swap`, and assert that verifying - * the patched cert against itself as a trust anchor fails. */ -static int rfc9802_assert_oid_patch_breaks_verify(const char* path, - byte expectedLastByte, byte patchedLastByte) +int test_rfc9802_lms_x509_verify(void) { EXPECT_DECLS; - byte* buf = NULL; - int bytes = 0; - DecodedCert cert; - WOLFSSL_CERT_MANAGER* cm = NULL; - word32 sigIndex = 0; - word32 lastOidByte = 0; - - ExpectIntEQ(rfc9802_load_file(path, &buf, &bytes), TEST_SUCCESS); - if (buf == NULL) - return TEST_FAIL; - - wc_InitDecodedCert(&cert, buf, (word32)bytes, NULL); - ExpectIntEQ(wc_ParseCert(&cert, CERT_TYPE, NO_VERIFY, NULL), 0); - sigIndex = cert.sigIndex; - wc_FreeDecodedCert(&cert); - - ExpectIntEQ(rfc9802_find_sig_alg_oid_last_byte(buf, (word32)bytes, - sigIndex, &lastOidByte), 0); - /* Sanity-check the fixture matches the family the caller asserted, - * so a future regenerator swapping fixtures fails loudly here - * rather than silently testing the wrong direction. */ - ExpectIntEQ((int)buf[lastOidByte], (int)expectedLastByte); - - if (lastOidByte < (word32)bytes && - buf[lastOidByte] == expectedLastByte) { - buf[lastOidByte] = patchedLastByte; - ExpectNotNull(cm = wolfSSL_CertManagerNew()); - /* After the patch the cert's outer signatureAlgorithm and SPKI - * disagree. Verification must fail somewhere (at parse, at - * load, or at ConfirmSignature). The load is best-effort - - * some shape changes get caught there, others only at verify. */ - (void)wolfSSL_CertManagerLoadCABuffer(cm, buf, (long)bytes, - WOLFSSL_FILETYPE_ASN1); - ExpectIntNE(wolfSSL_CertManagerVerifyBuffer(cm, buf, - (long)bytes, WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); - if (cm != NULL) { - wolfSSL_CertManagerFree(cm); - cm = NULL; - } - } - - XFREE(buf, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#if defined(WOLFSSL_HAVE_LMS) +#if !defined(NO_FILESYSTEM) && !defined(NO_CERTS) && \ + !defined(WOLFSSL_NO_LMS_SHA256_256) + /* Cross-implementation interop gate. bc_lms_native_bc_root.der is + * generated through Bouncy Castle's stock JcaContentSignerBuilder("LMS") + * + JcaX509v3CertificateBuilder with no overrides; BC's native LMS X.509 + * path is RFC 9802-compliant for HSS/LMS, so wolfSSL must accept it + * end-to-end. This is the one fixture from an independent implementation + * that we keep; wolfSSL's own generation is exercised by + * test_rfc9802_lms_x509_gen instead of committed wolfSSL fixtures. */ + ExpectIntEQ(rfc9802_verify_one_cert("./certs/lms/bc_lms_native_bc_root.der", + HSS_LMSk, CTC_HSS_LMS), TEST_SUCCESS); +#endif /* !NO_FILESYSTEM && !NO_CERTS && !WOLFSSL_NO_LMS_SHA256_256 */ + /* Pure wolfCrypt-level negative tests don't need filesystem or cert + * support, so they run for any LMS-enabled build. */ + ExpectIntEQ(rfc9802_lms_import_negative(), TEST_SUCCESS); +#endif return EXPECT_RESULT(); } -/* X.509-level negative: swap the outer signatureAlgorithm OID byte so - * the cert declares XMSS where the SPKI is XMSS^MT, and vice versa. - * SigOidMatchesKeyOid must reject both directions before any crypto. */ -static int rfc9802_xmss_sig_oid_mismatch(void) +int test_rfc9802_xmss_x509_verify(void) { EXPECT_DECLS; - /* XMSS sigOID ends 0x22; XMSS^MT sigOID ends 0x23. Patch each - * direction so the asymmetric-key path is exercised both ways - - * a regression that only stripped the check from one branch of - * SigOidMatchesKeyOid would otherwise be missed. */ - ExpectIntEQ(rfc9802_assert_oid_patch_breaks_verify( - "./certs/xmss/bc_xmss_sha2_10_256_root.der", - /* expected XMSS */ 0x22, /* patched to XMSS^MT */ 0x23), - TEST_SUCCESS); - ExpectIntEQ(rfc9802_assert_oid_patch_breaks_verify( - "./certs/xmss/bc_xmssmt_sha2_20_2_256_root.der", - /* expected XMSS^MT */ 0x23, /* patched to XMSS */ 0x22), - TEST_SUCCESS); +#if defined(WOLFSSL_HAVE_XMSS) + /* No independent (RFC 9802-aligned) third-party XMSS X.509 implementation + * exists to interop against - OpenSSL has no XMSS cert signing and Bouncy + * Castle's XMSS encoding is not yet aligned with the final RFC - so there + * is no committed interop fixture here. wolfSSL's own XMSS/XMSS^MT cert + * generation, chain signing and the X.509-level signatureAlgorithm/SPKI + * mismatch rejection are exercised in test_rfc9802_xmss_x509_gen. + * + * Pure wolfCrypt-level negative tests run for any XMSS-enabled build. */ + ExpectIntEQ(rfc9802_xmss_import_negative(), TEST_SUCCESS); +#endif return EXPECT_RESULT(); } -#endif -/* Exercise a real CA -> leaf certificate chain, not just self-signed. - * Loads the CA as a trust anchor and verifies the leaf against it. */ -#if defined(WOLFSSL_HAVE_LMS) && !defined(NO_FILESYSTEM) && !defined(NO_CERTS) -static int rfc9802_lms_chain_verify(void) +/* RFC 9802 certificate/CSR GENERATION tests. + * + * These exercise the cert-gen path (wc_MakeCert_ex / wc_SignCert_ex and + * wc_MakeCertReq_ex) with a freshly generated LMS or XMSS key, then feed + * the result back through the existing verification path to prove the + * generated SubjectPublicKeyInfo, signatureAlgorithm and signature are + * RFC 9802-compliant and self-consistent. */ +/* RFC 9802 cert/CSR generation is only wired into the ASN.1 template + * implementation (the original/non-template path has no LMS/XMSS support), + * so all of these tests require WOLFSSL_ASN_TEMPLATE. */ +#if defined(WOLFSSL_ASN_TEMPLATE) && defined(WOLFSSL_CERT_GEN) && \ + !defined(NO_FILESYSTEM) && !defined(NO_CERTS) && \ + ((defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY)) || \ + (defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY))) +/* Populate a minimal self-consistent subject/issuer name. */ +static void rfc9802_gen_set_names(Cert* cert) { - EXPECT_DECLS; - byte* caBuf = NULL; - byte* leafBuf = NULL; - int caLen = 0; - int leafLen = 0; - WOLFSSL_CERT_MANAGER* cm = NULL; + XSTRNCPY(cert->subject.country, "US", CTC_NAME_SIZE); + XSTRNCPY(cert->subject.state, "OR", CTC_NAME_SIZE); + XSTRNCPY(cert->subject.locality, "Portland", CTC_NAME_SIZE); + XSTRNCPY(cert->subject.org, "wolfSSL", CTC_NAME_SIZE); + XSTRNCPY(cert->subject.unit, "Testing", CTC_NAME_SIZE); + XSTRNCPY(cert->subject.commonName, "RFC9802 Gen Root CA", CTC_NAME_SIZE); +} - ExpectIntEQ(rfc9802_load_file("./certs/lms/bc_lms_chain_ca.der", - &caBuf, &caLen), TEST_SUCCESS); - ExpectIntEQ(rfc9802_load_file("./certs/lms/bc_lms_chain_leaf.der", - &leafBuf, &leafLen), TEST_SUCCESS); +/* Verify a self-signed DER cert by loading it as its own CA. */ +static int rfc9802_gen_verify_selfsigned(const byte* der, int derSz) +{ + EXPECT_DECLS; + WOLFSSL_CERT_MANAGER* cm = NULL; ExpectNotNull(cm = wolfSSL_CertManagerNew()); - /* Only the CA is a trust anchor; the leaf is verified against it. */ - ExpectIntEQ(wolfSSL_CertManagerLoadCABuffer(cm, caBuf, (long)caLen, + ExpectIntEQ(wolfSSL_CertManagerLoadCABuffer(cm, der, (long)derSz, WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); - ExpectIntEQ(wolfSSL_CertManagerVerifyBuffer(cm, leafBuf, (long)leafLen, + ExpectIntEQ(wolfSSL_CertManagerVerifyBuffer(cm, der, (long)derSz, WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); - - /* Without loading the CA the leaf must NOT verify. */ - if (cm != NULL) { + if (cm != NULL) wolfSSL_CertManagerFree(cm); - cm = NULL; + return EXPECT_RESULT(); +} + +#ifdef WOLFSSL_CERT_REQ +/* Parse a generated CSR and confirm its proof-of-possession signature. */ +static int rfc9802_gen_verify_csr(const byte* der, int derSz) +{ + EXPECT_DECLS; + DecodedCert dc; + + wc_InitDecodedCert(&dc, der, (word32)derSz, NULL); + ExpectIntEQ(wc_ParseCert(&dc, CERTREQ_TYPE, VERIFY, NULL), 0); + wc_FreeDecodedCert(&dc); + return EXPECT_RESULT(); +} +#endif /* WOLFSSL_CERT_REQ */ + +/* Generate a self-signed root CA (and, when CSRs are enabled, a PKCS#10 + * request) for an already-made key, then feed each back through the + * verification path. keyType is the wc_MakeCert_ex/wc_SignCert_ex selector + * (LMS_TYPE / XMSS_TYPE / XMSSMT_TYPE) and sigType the matching CTC_ OID. + * key is void* to mirror the public wc_MakeCert_ex API; callers must pass a + * key object whose type matches keyType. */ +static int rfc9802_gen_roundtrip(void* key, int keyType, int sigType, + WC_RNG* rng, word32 derCap) +{ + EXPECT_DECLS; + byte* der = NULL; + int derSz; + + ExpectNotNull(der = (byte*)XMALLOC(derCap, NULL, DYNAMIC_TYPE_TMP_BUFFER)); + + /* Self-signed root CA: generate -> sign -> verify round trip. */ + if (EXPECT_SUCCESS() && der != NULL) { + Cert cert; + ExpectIntEQ(wc_InitCert(&cert), 0); + rfc9802_gen_set_names(&cert); + cert.sigType = sigType; + cert.isCA = 1; + cert.selfSigned = 1; + cert.daysValid = 365; + ExpectIntGT(wc_MakeCert_ex(&cert, der, derCap, keyType, key, rng), 0); + ExpectIntGT(derSz = wc_SignCert_ex(cert.bodySz, cert.sigType, der, + derCap, keyType, key, rng), 0); + ExpectIntEQ(rfc9802_gen_verify_selfsigned(der, derSz), TEST_SUCCESS); } - ExpectNotNull(cm = wolfSSL_CertManagerNew()); - ExpectIntNE(wolfSSL_CertManagerVerifyBuffer(cm, leafBuf, (long)leafLen, - WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); - if (cm != NULL) { - wolfSSL_CertManagerFree(cm); - cm = NULL; + +#ifdef WOLFSSL_CERT_REQ + /* PKCS#10 CSR: generate -> self-sign proof-of-possession -> parse. */ + if (EXPECT_SUCCESS() && der != NULL) { + Cert cert; + ExpectIntEQ(wc_InitCert(&cert), 0); + rfc9802_gen_set_names(&cert); + cert.sigType = sigType; + ExpectIntGT(wc_MakeCertReq_ex(&cert, der, derCap, keyType, key), 0); + ExpectIntGT(derSz = wc_SignCert_ex(cert.bodySz, cert.sigType, der, + derCap, keyType, key, rng), 0); + ExpectIntEQ(rfc9802_gen_verify_csr(der, derSz), TEST_SUCCESS); } +#endif /* WOLFSSL_CERT_REQ */ - XFREE(leafBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER); - XFREE(caBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); return EXPECT_RESULT(); } -#endif -/* Mirror of rfc9802_lms_chain_verify but for an XMSS CA -> leaf pair. */ -#if defined(WOLFSSL_HAVE_XMSS) && !defined(NO_FILESYSTEM) && !defined(NO_CERTS) -static int rfc9802_xmss_chain_verify(void) +/* wc_ecc_make_key is available with HAVE_ECC; HAVE_ECC_KEY_EXPORT is needed + * for the leaf SPKI and !WC_NO_RNG for key generation. */ +#if defined(HAVE_ECC) && defined(HAVE_ECC_KEY_EXPORT) && !defined(WC_NO_RNG) +/* Subject name for the generated leaf (distinct from the CA subject). */ +static void rfc9802_gen_set_leaf_names(Cert* cert) +{ + XSTRNCPY(cert->subject.country, "US", CTC_NAME_SIZE); + XSTRNCPY(cert->subject.state, "OR", CTC_NAME_SIZE); + XSTRNCPY(cert->subject.locality, "Portland", CTC_NAME_SIZE); + XSTRNCPY(cert->subject.org, "wolfSSL", CTC_NAME_SIZE); + XSTRNCPY(cert->subject.unit, "Testing", CTC_NAME_SIZE); + XSTRNCPY(cert->subject.commonName, "RFC9802 Gen Leaf", CTC_NAME_SIZE); +} + +/* Generate a self-signed LMS/XMSS CA, then an ECC leaf issued and signed by + * that CA, and confirm the leaf chains to the CA (and fails without it). This + * is the real RFC 9802 use case - a hash-based CA signing another cert - that + * self-signed roots and CSRs don't cover. caKey is the already-made CA key; + * caKeyType/caSigType select its algorithm. */ +static int rfc9802_gen_chain(void* caKey, int caKeyType, int caSigType, + WC_RNG* rng, word32 derCap) { EXPECT_DECLS; - byte* caBuf = NULL; - byte* leafBuf = NULL; - int caLen = 0; - int leafLen = 0; - WOLFSSL_CERT_MANAGER* cm = NULL; + ecc_key leafKey; + int leafKeyInit = 0; + byte* caDer = NULL; + byte* leafDer = NULL; + int caSz = 0; + int leafSz = 0; + WOLFSSL_CERT_MANAGER* cm = NULL; - ExpectIntEQ(rfc9802_load_file("./certs/xmss/bc_xmss_chain_ca.der", - &caBuf, &caLen), TEST_SUCCESS); - ExpectIntEQ(rfc9802_load_file("./certs/xmss/bc_xmss_chain_leaf.der", - &leafBuf, &leafLen), TEST_SUCCESS); + ExpectNotNull(caDer = (byte*)XMALLOC(derCap, NULL, DYNAMIC_TYPE_TMP_BUFFER)); + ExpectNotNull(leafDer = (byte*)XMALLOC(derCap, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + ExpectIntEQ(wc_ecc_init(&leafKey), 0); + leafKeyInit = 1; + ExpectIntEQ(wc_ecc_make_key(rng, 32, &leafKey), 0); + + /* Self-signed CA root. */ + if (EXPECT_SUCCESS() && caDer != NULL) { + Cert ca; + ExpectIntEQ(wc_InitCert(&ca), 0); + rfc9802_gen_set_names(&ca); + ca.sigType = caSigType; + ca.isCA = 1; + ca.selfSigned = 1; + ca.daysValid = 365; + ExpectIntGT(wc_MakeCert_ex(&ca, caDer, derCap, caKeyType, caKey, rng), + 0); + ExpectIntGT(caSz = wc_SignCert_ex(ca.bodySz, caSigType, caDer, derCap, + caKeyType, caKey, rng), 0); + } - ExpectNotNull(cm = wolfSSL_CertManagerNew()); - ExpectIntEQ(wolfSSL_CertManagerLoadCABuffer(cm, caBuf, (long)caLen, - WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); - ExpectIntEQ(wolfSSL_CertManagerVerifyBuffer(cm, leafBuf, (long)leafLen, - WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + /* ECC leaf, issued by the CA's subject and signed with the CA key. */ + if (EXPECT_SUCCESS() && leafDer != NULL && caSz > 0) { + Cert leaf; + ExpectIntEQ(wc_InitCert(&leaf), 0); + rfc9802_gen_set_leaf_names(&leaf); + leaf.sigType = caSigType; + leaf.daysValid = 365; + ExpectIntEQ(wc_SetIssuerBuffer(&leaf, caDer, caSz), 0); + ExpectIntGT(wc_MakeCert_ex(&leaf, leafDer, derCap, ECC_TYPE, &leafKey, + rng), 0); + ExpectIntGT(leafSz = wc_SignCert_ex(leaf.bodySz, caSigType, leafDer, + derCap, caKeyType, caKey, rng), 0); + } - if (cm != NULL) { - wolfSSL_CertManagerFree(cm); - cm = NULL; + /* Leaf verifies only when the CA is the trust anchor. */ + if (EXPECT_SUCCESS() && leafSz > 0) { + ExpectNotNull(cm = wolfSSL_CertManagerNew()); + ExpectIntEQ(wolfSSL_CertManagerLoadCABuffer(cm, caDer, (long)caSz, + WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CertManagerVerifyBuffer(cm, leafDer, (long)leafSz, + WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + if (cm != NULL) { + wolfSSL_CertManagerFree(cm); + cm = NULL; + } + ExpectNotNull(cm = wolfSSL_CertManagerNew()); + ExpectIntNE(wolfSSL_CertManagerVerifyBuffer(cm, leafDer, (long)leafSz, + WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + if (cm != NULL) { + wolfSSL_CertManagerFree(cm); + cm = NULL; + } } - ExpectNotNull(cm = wolfSSL_CertManagerNew()); - ExpectIntNE(wolfSSL_CertManagerVerifyBuffer(cm, leafBuf, (long)leafLen, - WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); - if (cm != NULL) { - wolfSSL_CertManagerFree(cm); - cm = NULL; + + /* Negative: corrupt the leaf's signature (last byte of the DER, in the + * signatureValue) and confirm verification fails even with the CA loaded. + * This proves the CA's hash-based signature is cryptographically checked, + * not accepted on issuer-name chaining alone. */ + if (EXPECT_SUCCESS() && leafSz > 0) { + byte saved = leafDer[leafSz - 1]; + leafDer[leafSz - 1] ^= 0xFF; + ExpectNotNull(cm = wolfSSL_CertManagerNew()); + ExpectIntEQ(wolfSSL_CertManagerLoadCABuffer(cm, caDer, (long)caSz, + WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + ExpectIntNE(wolfSSL_CertManagerVerifyBuffer(cm, leafDer, (long)leafSz, + WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + if (cm != NULL) { + wolfSSL_CertManagerFree(cm); + cm = NULL; + } + leafDer[leafSz - 1] = saved; } - XFREE(leafBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER); - XFREE(caBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (leafKeyInit) + wc_ecc_free(&leafKey); + XFREE(leafDer, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(caDer, NULL, DYNAMIC_TYPE_TMP_BUFFER); return EXPECT_RESULT(); } +#endif /* HAVE_ECC && HAVE_ECC_KEY_EXPORT */ +#endif /* gen test support */ + +#if defined(WOLFSSL_ASN_TEMPLATE) && defined(WOLFSSL_HAVE_LMS) && \ + !defined(WOLFSSL_LMS_VERIFY_ONLY) && \ + defined(WOLFSSL_CERT_GEN) && !defined(NO_FILESYSTEM) && \ + !defined(NO_CERTS) && !defined(WOLFSSL_NO_LMS_SHA256_256) +/* Init an LMS key with the shared persistence callbacks and given params. */ +static int rfc9802_gen_lms_init(LmsKey* key, int levels, int height, int win) +{ + int ret = wc_LmsKey_Init(key, NULL, INVALID_DEVID); + if (ret == 0) + ret = wc_LmsKey_SetParameters(key, levels, height, win); + if (ret == 0) + ret = wc_LmsKey_SetWriteCb(key, test_lms_write_key); + if (ret == 0) + ret = wc_LmsKey_SetReadCb(key, test_lms_read_key); + if (ret == 0) + ret = wc_LmsKey_SetContext(key, (void*)LMS_TEST_PRIV_KEY_FILE); + return ret; +} #endif -int test_rfc9802_lms_x509_verify(void) +int test_rfc9802_lms_x509_gen(void) { EXPECT_DECLS; -#if defined(WOLFSSL_HAVE_LMS) -#if !defined(NO_FILESYSTEM) && !defined(NO_CERTS) && \ - !defined(WOLFSSL_NO_LMS_SHA256_256) - /* Mixed single-level LMS and multi-level HSS fixtures. The HSS - * public key carries only the top-level LMS/LM-OTS types, so - * wc_LmsKey_ImportPubRaw's auto-derive path searches the map - * by (levels, lmsType, lmOtsType). The bc_lms_native_bc_root - * fixture is generated through Bouncy Castle's stock - * JcaContentSignerBuilder("LMS") + JcaX509v3CertificateBuilder - * with no overrides; including it here is the cross-impl interop - * gate (BC's native LMS X.509 path is RFC 9802-compliant for HSS/ - * LMS, so wolfSSL must accept it end-to-end). - * - * All fixtures use the SHA-256/M32 family, so the whole block - * is gated on that family being compiled in. Truncated SHA-256/192 - * or SHAKE-only builds skip this block. */ - static const char* const lmsFiles[] = { - "./certs/lms/bc_lms_sha256_h5_w4_root.der", -#if !defined(WOLFSSL_LMS_MAX_HEIGHT) || (WOLFSSL_LMS_MAX_HEIGHT >= 10) - "./certs/lms/bc_lms_sha256_h10_w8_root.der", +#if defined(WOLFSSL_ASN_TEMPLATE) && defined(WOLFSSL_HAVE_LMS) && \ + !defined(WOLFSSL_LMS_VERIFY_ONLY) && \ + defined(WOLFSSL_CERT_GEN) && !defined(NO_FILESYSTEM) && \ + !defined(NO_CERTS) && !defined(WOLFSSL_NO_LMS_SHA256_256) + LmsKey key; + WC_RNG rng; + + ExpectIntEQ(wc_InitRng(&rng), 0); + + /* Single-level LMS (L1-H5-W8). */ + remove(LMS_TEST_PRIV_KEY_FILE); + ExpectIntEQ(rfc9802_gen_lms_init(&key, 1, 5, 8), 0); + ExpectIntEQ(wc_LmsKey_MakeKey(&key, &rng), 0); + ExpectIntEQ(rfc9802_gen_roundtrip(&key, LMS_TYPE, CTC_HSS_LMS, &rng, 8192), + TEST_SUCCESS); + + /* Negative: signing an LMS key with a non-LMS signature OID must be + * rejected rather than emit a cert whose signatureAlgorithm contradicts + * its public key. The check fires before any signature is produced, so + * the key's one-time signatures are not consumed. */ + if (EXPECT_SUCCESS()) { + Cert cert; + byte* tmp = NULL; + ExpectNotNull(tmp = (byte*)XMALLOC(8192, NULL, DYNAMIC_TYPE_TMP_BUFFER)); + ExpectIntEQ(wc_InitCert(&cert), 0); + rfc9802_gen_set_names(&cert); + cert.sigType = CTC_HSS_LMS; + cert.isCA = 1; + cert.selfSigned = 1; + cert.daysValid = 365; + if (tmp != NULL) { + ExpectIntGT(wc_MakeCert_ex(&cert, tmp, 8192, LMS_TYPE, &key, + &rng), 0); + ExpectIntEQ(wc_SignCert_ex(cert.bodySz, CTC_XMSS, tmp, 8192, + LMS_TYPE, &key, &rng), WC_NO_ERR_TRACE(ALGO_ID_E)); + } + XFREE(tmp, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + +#if defined(HAVE_ECC) && defined(HAVE_ECC_KEY_EXPORT) && !defined(WC_NO_RNG) + /* Real CA use case: the LMS CA signs an ECC leaf; the leaf must chain to + * the CA. Reuses the L1 key (plenty of one-time signatures remain). */ + ExpectIntEQ(rfc9802_gen_chain(&key, LMS_TYPE, CTC_HSS_LMS, &rng, 8192), + TEST_SUCCESS); #endif + + wc_LmsKey_Free(&key); + remove(LMS_TEST_PRIV_KEY_FILE); + #if !defined(WOLFSSL_LMS_MAX_LEVELS) || (WOLFSSL_LMS_MAX_LEVELS >= 2) - "./certs/lms/bc_hss_L2_H5_W8_root.der", + /* Multi-level HSS (L2-H5-W8): the signature embeds a lower-level LMS + * public key + signature, exercising the larger, multi-level encoding. */ + remove(LMS_TEST_PRIV_KEY_FILE); + ExpectIntEQ(rfc9802_gen_lms_init(&key, 2, 5, 8), 0); + ExpectIntEQ(wc_LmsKey_MakeKey(&key, &rng), 0); + ExpectIntEQ(rfc9802_gen_roundtrip(&key, LMS_TYPE, CTC_HSS_LMS, &rng, 8192), + TEST_SUCCESS); + wc_LmsKey_Free(&key); + remove(LMS_TEST_PRIV_KEY_FILE); #endif + #if !defined(WOLFSSL_LMS_MAX_LEVELS) || (WOLFSSL_LMS_MAX_LEVELS >= 3) - "./certs/lms/bc_hss_L3_H5_W4_root.der", + /* Three-level HSS with Winternitz 4 (L3-H5-W4): exercises the deepest + * multi-level encoding and a different Winternitz parameter than the + * W8 cases above. */ + remove(LMS_TEST_PRIV_KEY_FILE); + ExpectIntEQ(rfc9802_gen_lms_init(&key, 3, 5, 4), 0); + ExpectIntEQ(wc_LmsKey_MakeKey(&key, &rng), 0); + ExpectIntEQ(rfc9802_gen_roundtrip(&key, LMS_TYPE, CTC_HSS_LMS, &rng, 8192), + TEST_SUCCESS); + wc_LmsKey_Free(&key); + remove(LMS_TEST_PRIV_KEY_FILE); #endif - "./certs/lms/bc_lms_native_bc_root.der", - }; - size_t i; - for (i = 0; i < sizeof(lmsFiles) / sizeof(lmsFiles[0]); i++) { - ExpectIntEQ(rfc9802_verify_one_cert(lmsFiles[i], - HSS_LMSk, CTC_HSS_LMS), TEST_SUCCESS); - } - ExpectIntEQ(rfc9802_lms_chain_verify(), TEST_SUCCESS); -#endif /* !NO_FILESYSTEM && !NO_CERTS && !WOLFSSL_NO_LMS_SHA256_256 */ - /* Pure wolfCrypt-level negative tests don't need filesystem or cert - * support, so they run for any LMS-enabled build. */ - ExpectIntEQ(rfc9802_lms_import_negative(), TEST_SUCCESS); + + wc_FreeRng(&rng); #endif return EXPECT_RESULT(); } -int test_rfc9802_xmss_x509_verify(void) +#if defined(WOLFSSL_ASN_TEMPLATE) && defined(WOLFSSL_HAVE_XMSS) && \ + !defined(WOLFSSL_XMSS_VERIFY_ONLY) && \ + defined(WOLFSSL_CERT_GEN) && !defined(NO_FILESYSTEM) && !defined(NO_CERTS) +#define XMSS_GEN_TEST_PRIV_KEY_FILE "/tmp/wolfssl_test_xmss_gen.key" +static enum wc_XmssRc xmss_gen_write_key(const byte* priv, word32 privSz, + void* context) +{ + FILE* f = fopen((const char*)context, "wb"); + enum wc_XmssRc ret = WC_XMSS_RC_SAVED_TO_NV_MEMORY; + if (f == NULL) + return WC_XMSS_RC_WRITE_FAIL; + if (fwrite(priv, 1, privSz, f) != privSz) + ret = WC_XMSS_RC_WRITE_FAIL; + fclose(f); + return ret; +} +static enum wc_XmssRc xmss_gen_read_key(byte* priv, word32 privSz, + void* context) +{ + FILE* f = fopen((const char*)context, "rb"); + enum wc_XmssRc ret = WC_XMSS_RC_READ_TO_MEMORY; + if (f == NULL) + return WC_XMSS_RC_READ_FAIL; + if (fread(priv, 1, privSz, f) != privSz) + ret = WC_XMSS_RC_READ_FAIL; + fclose(f); + return ret; +} + +/* Init an XMSS/XMSS^MT key with the shared persistence callbacks. */ +static int rfc9802_gen_xmss_init(XmssKey* key, const char* paramStr) +{ + int ret = wc_XmssKey_Init(key, NULL, INVALID_DEVID); + if (ret == 0) + ret = wc_XmssKey_SetParamStr(key, paramStr); + if (ret == 0) + ret = wc_XmssKey_SetWriteCb(key, xmss_gen_write_key); + if (ret == 0) + ret = wc_XmssKey_SetReadCb(key, xmss_gen_read_key); + if (ret == 0) + ret = wc_XmssKey_SetContext(key, (void*)XMSS_GEN_TEST_PRIV_KEY_FILE); + return ret; +} + +/* X.509-level negative tests on a wolfSSL-generated XMSS/XMSS^MT cert, run + * against the already-made key (no extra keygen). oidLast is the cert's true + * final OID byte (XMSS 0x22, XMSS^MT 0x23) and oidSwap the other family's: + * + * (a) flip only the outer signatureAlgorithm OID -> it no longer equals the + * TBS signatureAlgorithm, which the generic X.509 algId-consistency check + * rejects (ASN_SIG_OID_E at parse); + * (b) flip both signatureAlgorithm copies (TBS + outer) but leave the SPKI + * key OID -> outer == TBS (that check passes), yet the signature + * algorithm now disagrees with the public-key algorithm, which RFC 9802 + * requires verification to reject (SigOidMatchesKeyOid, before the - now + * also invalid - signature is even checked). + * + * Either way verification must fail. */ +static int rfc9802_gen_xmss_oid_tamper(void* key, int keyType, int sigType, + WC_RNG* rng, byte oidLast, byte oidSwap) { EXPECT_DECLS; -#if defined(WOLFSSL_HAVE_XMSS) -#if !defined(NO_FILESYSTEM) && !defined(NO_CERTS) - static const char* const xmssFiles[] = { - "./certs/xmss/bc_xmss_sha2_10_256_root.der", - "./certs/xmss/bc_xmss_sha2_16_256_root.der", - }; - static const char* const xmssmtFiles[] = { - "./certs/xmss/bc_xmssmt_sha2_20_2_256_root.der", - "./certs/xmss/bc_xmssmt_sha2_20_4_256_root.der", - "./certs/xmss/bc_xmssmt_sha2_40_8_256_root.der", - }; - size_t i; - for (i = 0; i < sizeof(xmssFiles) / sizeof(xmssFiles[0]); i++) { - ExpectIntEQ(rfc9802_verify_one_cert(xmssFiles[i], - XMSSk, CTC_XMSS), TEST_SUCCESS); + byte* der = NULL; + int derSz = 0; + word32 off[8]; + int n = 0; + WOLFSSL_CERT_MANAGER* cm = NULL; + + ExpectNotNull(der = (byte*)XMALLOC(16384, NULL, DYNAMIC_TYPE_TMP_BUFFER)); + + if (EXPECT_SUCCESS() && der != NULL) { + Cert cert; + ExpectIntEQ(wc_InitCert(&cert), 0); + rfc9802_gen_set_names(&cert); + cert.sigType = sigType; + cert.isCA = 1; + cert.selfSigned = 1; + cert.daysValid = 365; + ExpectIntGT(wc_MakeCert_ex(&cert, der, 16384, keyType, key, rng), 0); + ExpectIntGT(derSz = wc_SignCert_ex(cert.bodySz, sigType, der, 16384, + keyType, key, rng), 0); } - for (i = 0; i < sizeof(xmssmtFiles) / sizeof(xmssmtFiles[0]); i++) { - ExpectIntEQ(rfc9802_verify_one_cert(xmssmtFiles[i], - XMSSMTk, CTC_XMSSMT), TEST_SUCCESS); + + if (EXPECT_SUCCESS() && derSz > 0) { + n = rfc9802_collect_hbs_oid_offsets(der, (word32)derSz, oidLast, off, 8); + /* TBS-signature, SPKI-key, outer-signature - in that order. */ + ExpectIntEQ(n, 3); } - ExpectIntEQ(rfc9802_xmss_sig_oid_mismatch(), TEST_SUCCESS); - ExpectIntEQ(rfc9802_xmss_chain_verify(), TEST_SUCCESS); -#endif /* !NO_FILESYSTEM && !NO_CERTS */ - /* Pure wolfCrypt-level negative tests don't need filesystem or cert - * support, so they run for any XMSS-enabled build. */ - ExpectIntEQ(rfc9802_xmss_import_negative(), TEST_SUCCESS); + + /* (a) Outer signatureAlgorithm != TBS signatureAlgorithm. */ + if (EXPECT_SUCCESS() && n == 3) { + der[off[2]] = oidSwap; + ExpectNotNull(cm = wolfSSL_CertManagerNew()); + (void)wolfSSL_CertManagerLoadCABuffer(cm, der, (long)derSz, + WOLFSSL_FILETYPE_ASN1); + ExpectIntNE(wolfSSL_CertManagerVerifyBuffer(cm, der, (long)derSz, + WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + if (cm != NULL) { + wolfSSL_CertManagerFree(cm); + cm = NULL; + } + der[off[2]] = oidLast; /* restore */ + } + + /* (b) signatureAlgorithm (both copies) disagrees with the SPKI key OID. */ + if (EXPECT_SUCCESS() && n == 3) { + der[off[0]] = oidSwap; + der[off[2]] = oidSwap; + ExpectNotNull(cm = wolfSSL_CertManagerNew()); + (void)wolfSSL_CertManagerLoadCABuffer(cm, der, (long)derSz, + WOLFSSL_FILETYPE_ASN1); + ExpectIntNE(wolfSSL_CertManagerVerifyBuffer(cm, der, (long)derSz, + WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + if (cm != NULL) { + wolfSSL_CertManagerFree(cm); + cm = NULL; + } + } + + XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return EXPECT_RESULT(); +} +#endif /* XMSS gen support */ + +int test_rfc9802_xmss_x509_gen(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_ASN_TEMPLATE) && defined(WOLFSSL_HAVE_XMSS) && \ + !defined(WOLFSSL_XMSS_VERIFY_ONLY) && \ + defined(WOLFSSL_CERT_GEN) && !defined(NO_FILESYSTEM) && !defined(NO_CERTS) + XmssKey key; + WC_RNG rng; + + ExpectIntEQ(wc_InitRng(&rng), 0); + + /* Single-tree XMSS. */ + remove(XMSS_GEN_TEST_PRIV_KEY_FILE); + ExpectIntEQ(rfc9802_gen_xmss_init(&key, "XMSS-SHA2_10_256"), 0); + ExpectIntEQ(wc_XmssKey_MakeKey(&key, &rng), 0); + ExpectIntEQ((int)key.is_xmssmt, 0); + ExpectIntEQ(rfc9802_gen_roundtrip(&key, XMSS_TYPE, CTC_XMSS, &rng, 16384), + TEST_SUCCESS); + + /* Negative: the XMSSMT_TYPE selector must not be accepted for a + * single-tree XMSS key, and signing a single-tree key as XMSS^MT must be + * rejected. Both checks fire before signing, so no signature is used. */ + if (EXPECT_SUCCESS()) { + Cert cert; + byte* tmp = NULL; + ExpectNotNull(tmp = (byte*)XMALLOC(16384, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + ExpectIntEQ(wc_InitCert(&cert), 0); + rfc9802_gen_set_names(&cert); + cert.sigType = CTC_XMSS; + cert.isCA = 1; + cert.selfSigned = 1; + cert.daysValid = 365; + /* Wrong selector for the key's tree variant. */ + if (tmp != NULL) { + ExpectIntEQ(wc_MakeCert_ex(&cert, tmp, 16384, XMSSMT_TYPE, &key, + &rng), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* Correct selector, but signed with the XMSS^MT OID. */ + ExpectIntGT(wc_MakeCert_ex(&cert, tmp, 16384, XMSS_TYPE, &key, + &rng), 0); + ExpectIntEQ(wc_SignCert_ex(cert.bodySz, CTC_XMSSMT, tmp, 16384, + XMSS_TYPE, &key, &rng), WC_NO_ERR_TRACE(ALGO_ID_E)); + } + XFREE(tmp, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + +#if defined(HAVE_ECC) && defined(HAVE_ECC_KEY_EXPORT) && !defined(WC_NO_RNG) + /* Real CA use case: the XMSS CA signs an ECC leaf; the leaf must chain. */ + ExpectIntEQ(rfc9802_gen_chain(&key, XMSS_TYPE, CTC_XMSS, &rng, 16384), + TEST_SUCCESS); +#endif + /* X.509-level signatureAlgorithm/SPKI OID consistency, reusing this key. */ + ExpectIntEQ(rfc9802_gen_xmss_oid_tamper(&key, XMSS_TYPE, CTC_XMSS, &rng, + /* XMSS */ 0x22, /* swap */ 0x23), TEST_SUCCESS); + + wc_XmssKey_Free(&key); + remove(XMSS_GEN_TEST_PRIV_KEY_FILE); + + /* Multi-tree XMSS^MT: exercises the XMSSMT_TYPE selector, the + * XMSSMTk public-key OID branch and the CTC_XMSSMT signature OID. */ + remove(XMSS_GEN_TEST_PRIV_KEY_FILE); + ExpectIntEQ(rfc9802_gen_xmss_init(&key, "XMSSMT-SHA2_20/2_256"), 0); + ExpectIntEQ(wc_XmssKey_MakeKey(&key, &rng), 0); + ExpectIntEQ((int)key.is_xmssmt, 1); + ExpectIntEQ(rfc9802_gen_roundtrip(&key, XMSSMT_TYPE, CTC_XMSSMT, &rng, + 16384), TEST_SUCCESS); +#if defined(HAVE_ECC) && defined(HAVE_ECC_KEY_EXPORT) && !defined(WC_NO_RNG) + ExpectIntEQ(rfc9802_gen_chain(&key, XMSSMT_TYPE, CTC_XMSSMT, &rng, 16384), + TEST_SUCCESS); +#endif + ExpectIntEQ(rfc9802_gen_xmss_oid_tamper(&key, XMSSMT_TYPE, CTC_XMSSMT, &rng, + /* XMSS^MT */ 0x23, /* swap */ 0x22), TEST_SUCCESS); + wc_XmssKey_Free(&key); + remove(XMSS_GEN_TEST_PRIV_KEY_FILE); + + /* A second XMSS^MT parameter set (different embedded param-set OID and a + * larger signature) to keep the encoder/auto-derive decoder exercised + * across sizes now that the committed multi-size fixtures are gone. */ + remove(XMSS_GEN_TEST_PRIV_KEY_FILE); + ExpectIntEQ(rfc9802_gen_xmss_init(&key, "XMSSMT-SHA2_20/4_256"), 0); + ExpectIntEQ(wc_XmssKey_MakeKey(&key, &rng), 0); + ExpectIntEQ((int)key.is_xmssmt, 1); + ExpectIntEQ(rfc9802_gen_roundtrip(&key, XMSSMT_TYPE, CTC_XMSSMT, &rng, + 16384), TEST_SUCCESS); + wc_XmssKey_Free(&key); + remove(XMSS_GEN_TEST_PRIV_KEY_FILE); + + wc_FreeRng(&rng); #endif return EXPECT_RESULT(); } diff --git a/tests/api/test_lms_xmss.h b/tests/api/test_lms_xmss.h index b2ff579987e..78c66e53e93 100644 --- a/tests/api/test_lms_xmss.h +++ b/tests/api/test_lms_xmss.h @@ -28,12 +28,16 @@ int test_wc_LmsKey_sign_verify(void); int test_wc_LmsKey_reload_cache(void); int test_rfc9802_lms_x509_verify(void); int test_rfc9802_xmss_x509_verify(void); +int test_rfc9802_lms_x509_gen(void); +int test_rfc9802_xmss_x509_gen(void); /* LMS, and RFC 9802 (HSS/LMS and XMSS/XMSS^MT in X.509). */ #define TEST_LMS_XMSS_DECLS \ TEST_DECL_GROUP("lms", test_wc_LmsKey_sign_verify), \ TEST_DECL_GROUP("lms", test_wc_LmsKey_reload_cache), \ TEST_DECL_GROUP("lms", test_rfc9802_lms_x509_verify), \ - TEST_DECL_GROUP("xmss", test_rfc9802_xmss_x509_verify) + TEST_DECL_GROUP("xmss", test_rfc9802_xmss_x509_verify), \ + TEST_DECL_GROUP("lms", test_rfc9802_lms_x509_gen), \ + TEST_DECL_GROUP("xmss", test_rfc9802_xmss_x509_gen) #endif /* WOLFCRYPT_TEST_LMS_XMSS_H */ diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index f4ef7125ef1..cc14bad3492 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -4418,9 +4418,9 @@ static int EncodeName(EncodedName* name, const char* nameStr, byte nameTag, byte #endif #ifdef WOLFSSL_CERT_GEN static int SetValidity(byte* output, int daysValid); -static int MakeAnyCert(Cert* cert, byte* derBuffer, word32 derSz, RsaKey* rsaKey, ecc_key* eccKey, WC_RNG* rng, DsaKey* dsaKey, ed25519_key* ed25519Key, ed448_key* ed448Key, falcon_key* falconKey, wc_MlDsaKey* mldsaKey, SlhDsaKey* slhDsaKey); +static int MakeAnyCert(Cert* cert, byte* derBuffer, word32 derSz, RsaKey* rsaKey, ecc_key* eccKey, WC_RNG* rng, DsaKey* dsaKey, ed25519_key* ed25519Key, ed448_key* ed448Key, falcon_key* falconKey, wc_MlDsaKey* mldsaKey, SlhDsaKey* slhDsaKey, LmsKey* lmsKey, XmssKey* xmssKey); #ifdef WOLFSSL_CERT_REQ -static int MakeCertReq(Cert* cert, byte* derBuffer, word32 derSz, RsaKey* rsaKey, DsaKey* dsaKey, ecc_key* eccKey, ed25519_key* ed25519Key, ed448_key* ed448Key, falcon_key* falconKey, wc_MlDsaKey* mldsaKey, SlhDsaKey* slhDsaKey); +static int MakeCertReq(Cert* cert, byte* derBuffer, word32 derSz, RsaKey* rsaKey, DsaKey* dsaKey, ecc_key* eccKey, ed25519_key* ed25519Key, ed448_key* ed448Key, falcon_key* falconKey, wc_MlDsaKey* mldsaKey, SlhDsaKey* slhDsaKey, LmsKey* lmsKey, XmssKey* xmssKey); #endif #endif #endif @@ -13065,6 +13065,82 @@ int wc_Ed448PublicKeyToDer(const ed448_key* key, byte* output, word32 inLen, return ret; } #endif /* HAVE_ED448 && HAVE_ED448_KEY_EXPORT */ + +#if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY) +/* Encode the public part of an LMS/HSS key in DER. + * + * Per RFC 9802, the SubjectPublicKeyInfo for HSS/LMS uses the + * id-alg-hss-lms-hashsig OID and carries the raw HSS public key in the + * BIT STRING with no additional wrapping. + * + * Pass NULL for output to get the size of the encoding. + * + * @param [in] key LMS key object. + * @param [out] output Buffer to put encoded data in. + * @param [in] inLen Size of buffer in bytes. + * @param [in] withAlg Whether to use SubjectPublicKeyInfo format. + * @return Size of encoded data in bytes on success. + * @return BAD_FUNC_ARG when key is NULL. + */ +int wc_LmsKey_PublicKeyToDer(LmsKey* key, byte* output, word32 inLen, + int withAlg) +{ + int ret; + byte pubKey[HSS_MAX_PUBLIC_KEY_LEN]; + word32 pubKeyLen = (word32)sizeof(pubKey); + + if (key == NULL) { + return BAD_FUNC_ARG; + } + + ret = wc_LmsKey_ExportPubRaw(key, pubKey, &pubKeyLen); + if (ret == 0) { + ret = SetAsymKeyDerPublic(pubKey, pubKeyLen, output, inLen, + HSS_LMSk, withAlg); + } + return ret; +} +#endif /* WOLFSSL_HAVE_LMS && !WOLFSSL_LMS_VERIFY_ONLY */ + +#if defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY) +/* Encode the public part of an XMSS/XMSS^MT key in DER. + * + * Per RFC 9802, the SubjectPublicKeyInfo for XMSS/XMSS^MT uses the + * id-alg-xmss-hashsig / id-alg-xmssmt-hashsig OID and carries the raw + * public key in the BIT STRING with no additional wrapping. + * + * Pass NULL for output to get the size of the encoding. + * + * @param [in] key XMSS key object. + * @param [out] output Buffer to put encoded data in. + * @param [in] inLen Size of buffer in bytes. + * @param [in] withAlg Whether to use SubjectPublicKeyInfo format. + * @return Size of encoded data in bytes on success. + * @return BAD_FUNC_ARG when key is NULL. + */ +int wc_XmssKey_PublicKeyToDer(XmssKey* key, byte* output, word32 inLen, + int withAlg) +{ + int ret; + byte pubKey[2 * WC_XMSS_MAX_N + XMSS_OID_LEN]; + word32 pubKeyLen = (word32)sizeof(pubKey); + int keyType; + + if (key == NULL) { + return BAD_FUNC_ARG; + } + + keyType = key->is_xmssmt ? XMSSMTk : XMSSk; + + ret = wc_XmssKey_ExportPubRaw(key, pubKey, &pubKeyLen); + if (ret == 0) { + ret = SetAsymKeyDerPublic(pubKey, pubKeyLen, output, inLen, + keyType, withAlg); + } + return ret; +} +#endif /* WOLFSSL_HAVE_XMSS && !WOLFSSL_XMSS_VERIFY_ONLY */ + #if !defined(NO_RSA) && !defined(NO_CERTS) #ifdef WOLFSSL_ASN_TEMPLATE /* ASN.1 template for header before RSA key in certificate. */ @@ -27170,7 +27246,8 @@ static int EncodePublicKey(int keyType, byte* output, int outLen, RsaKey* rsaKey, ecc_key* eccKey, ed25519_key* ed25519Key, ed448_key* ed448Key, DsaKey* dsaKey, falcon_key* falconKey, - wc_MlDsaKey* mldsaKey, SlhDsaKey* slhDsaKey) + wc_MlDsaKey* mldsaKey, SlhDsaKey* slhDsaKey, + LmsKey* lmsKey, XmssKey* xmssKey) { int ret = 0; @@ -27183,6 +27260,8 @@ static int EncodePublicKey(int keyType, byte* output, int outLen, (void)falconKey; (void)mldsaKey; (void)slhDsaKey; + (void)lmsKey; + (void)xmssKey; switch (keyType) { #ifndef NO_RSA @@ -27266,6 +27345,23 @@ static int EncodePublicKey(int keyType, byte* output, int outLen, } break; #endif /* WOLFSSL_HAVE_SLHDSA */ + #if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY) + case LMS_KEY: + ret = wc_LmsKey_PublicKeyToDer(lmsKey, output, (word32)outLen, 1); + if (ret <= 0) { + ret = PUBLIC_KEY_E; + } + break; + #endif /* WOLFSSL_HAVE_LMS && !WOLFSSL_LMS_VERIFY_ONLY */ + #if defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY) + case XMSS_KEY: + case XMSSMT_KEY: + ret = wc_XmssKey_PublicKeyToDer(xmssKey, output, (word32)outLen, 1); + if (ret <= 0) { + ret = PUBLIC_KEY_E; + } + break; + #endif /* WOLFSSL_HAVE_XMSS && !WOLFSSL_XMSS_VERIFY_ONLY */ default: ret = PUBLIC_KEY_E; break; @@ -28068,8 +28164,8 @@ static int InternalSignCb(const byte* in, word32 inLen, static int MakeSignature(CertSignCtx* certSignCtx, const byte* buf, word32 sz, byte* sig, word32 sigSz, RsaKey* rsaKey, ecc_key* eccKey, ed25519_key* ed25519Key, ed448_key* ed448Key, falcon_key* falconKey, - wc_MlDsaKey* mldsaKey, SlhDsaKey* slhDsaKey, WC_RNG* rng, - word32 sigAlgoType, void* heap) + wc_MlDsaKey* mldsaKey, SlhDsaKey* slhDsaKey, LmsKey* lmsKey, + XmssKey* xmssKey, WC_RNG* rng, word32 sigAlgoType, void* heap) { int ret = 0; @@ -28082,6 +28178,8 @@ static int MakeSignature(CertSignCtx* certSignCtx, const byte* buf, word32 sz, (void)falconKey; (void)mldsaKey; (void)slhDsaKey; + (void)lmsKey; + (void)xmssKey; (void)rng; (void)heap; @@ -28175,6 +28273,26 @@ static int MakeSignature(CertSignCtx* certSignCtx, const byte* buf, word32 sz, } #endif /* WOLFSSL_HAVE_SLHDSA && !WOLFSSL_SLHDSA_VERIFY_ONLY */ +#if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY) + if (lmsKey) { + word32 outSz = sigSz; + /* RFC 9802: the TBS is signed directly with no pre-hash. */ + ret = wc_LmsKey_Sign(lmsKey, sig, &outSz, buf, (int)sz); + if (ret == 0) + ret = (int)outSz; + } +#endif /* WOLFSSL_HAVE_LMS && !WOLFSSL_LMS_VERIFY_ONLY */ + +#if defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY) + if (xmssKey) { + word32 outSz = sigSz; + /* RFC 9802: the TBS is signed directly with no pre-hash. */ + ret = wc_XmssKey_Sign(xmssKey, sig, &outSz, buf, (int)sz); + if (ret == 0) + ret = (int)outSz; + } +#endif /* WOLFSSL_HAVE_XMSS && !WOLFSSL_XMSS_VERIFY_ONLY */ + if (ret == -1) ret = ALGO_ID_E; @@ -28322,7 +28440,8 @@ static int MakeAnyCert(Cert* cert, byte* derBuffer, word32 derSz, RsaKey* rsaKey, ecc_key* eccKey, WC_RNG* rng, DsaKey* dsaKey, ed25519_key* ed25519Key, ed448_key* ed448Key, falcon_key* falconKey, - wc_MlDsaKey* mldsaKey, SlhDsaKey* slhDsaKey) + wc_MlDsaKey* mldsaKey, SlhDsaKey* slhDsaKey, + LmsKey* lmsKey, XmssKey* xmssKey) { /* TODO: issRaw and sbjRaw should be NUL terminated. */ DECL_ASNSETDATA(dataASN, x509CertASN_Length); @@ -28339,6 +28458,8 @@ static int MakeAnyCert(Cert* cert, byte* derBuffer, word32 derSz, (void)falconKey; (void)mldsaKey; (void)slhDsaKey; + (void)lmsKey; + (void)xmssKey; CALLOC_ASNSETDATA(dataASN, x509CertASN_Length, ret, cert->heap); @@ -28406,6 +28527,16 @@ static int MakeAnyCert(Cert* cert, byte* derBuffer, word32 derSz, } } #endif /* WOLFSSL_HAVE_SLHDSA */ +#if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY) + else if (lmsKey != NULL) { + cert->keyType = LMS_KEY; + } +#endif /* WOLFSSL_HAVE_LMS && !WOLFSSL_LMS_VERIFY_ONLY */ +#if defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY) + else if (xmssKey != NULL) { + cert->keyType = xmssKey->is_xmssmt ? XMSSMT_KEY : XMSS_KEY; + } +#endif /* WOLFSSL_HAVE_XMSS && !WOLFSSL_XMSS_VERIFY_ONLY */ else { ret = BAD_FUNC_ARG; } @@ -28454,7 +28585,7 @@ static int MakeAnyCert(Cert* cert, byte* derBuffer, word32 derSz, /* Calculate public key encoding size. */ ret = EncodePublicKey(cert->keyType, NULL, 0, rsaKey, eccKey, ed25519Key, ed448Key, dsaKey, falconKey, - mldsaKey, slhDsaKey); + mldsaKey, slhDsaKey, lmsKey, xmssKey); publicKeySz = (word32)ret; } if (ret >= 0) { @@ -28641,7 +28772,7 @@ static int MakeAnyCert(Cert* cert, byte* derBuffer, word32 derSz, (int)dataASN[X509CERTASN_IDX_TBS_SPUBKEYINFO_SEQ] .data.buffer.length, rsaKey, eccKey, ed25519Key, ed448Key, dsaKey, - falconKey, mldsaKey, slhDsaKey); + falconKey, mldsaKey, slhDsaKey, lmsKey, xmssKey); } if ((ret >= 0) && (!dataASN[X509CERTASN_IDX_TBS_EXT_SEQ].noOut)) { /* Encode extensions into buffer. */ @@ -28686,6 +28817,8 @@ int wc_MakeCert_ex(Cert* cert, byte* derBuffer, word32 derSz, int keyType, falcon_key* falconKey = NULL; wc_MlDsaKey* mldsaKey = NULL; SlhDsaKey* slhDsaKey = NULL; + LmsKey* lmsKey = NULL; + XmssKey* xmssKey = NULL; if (keyType == RSA_TYPE) rsaKey = (RsaKey*)key; @@ -28719,10 +28852,28 @@ int wc_MakeCert_ex(Cert* cert, byte* derBuffer, word32 derSz, int keyType, else if (IsSlhDsaKeyType(keyType)) slhDsaKey = (SlhDsaKey*)key; #endif +#if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY) + else if (keyType == LMS_TYPE) + lmsKey = (LmsKey*)key; +#endif +#if defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY) + /* The selector must match the key's actual tree variant so XMSS_TYPE and + * XMSSMT_TYPE are not silently interchangeable. */ + else if (keyType == XMSS_TYPE) { + xmssKey = (XmssKey*)key; + if (xmssKey != NULL && xmssKey->is_xmssmt) + return BAD_FUNC_ARG; + } + else if (keyType == XMSSMT_TYPE) { + xmssKey = (XmssKey*)key; + if (xmssKey != NULL && !xmssKey->is_xmssmt) + return BAD_FUNC_ARG; + } +#endif return MakeAnyCert(cert, derBuffer, derSz, rsaKey, eccKey, rng, dsaKey, ed25519Key, ed448Key, falconKey, mldsaKey, - slhDsaKey); + slhDsaKey, lmsKey, xmssKey); } /* Make an x509 Certificate v3 RSA or ECC from cert input, write to buffer */ @@ -28731,7 +28882,7 @@ int wc_MakeCert(Cert* cert, byte* derBuffer, word32 derSz, RsaKey* rsaKey, ecc_key* eccKey, WC_RNG* rng) { return MakeAnyCert(cert, derBuffer, derSz, rsaKey, eccKey, rng, NULL, NULL, - NULL, NULL, NULL, NULL); + NULL, NULL, NULL, NULL, NULL, NULL); } @@ -28799,7 +28950,7 @@ static int MakeCertReq(Cert* cert, byte* derBuffer, word32 derSz, RsaKey* rsaKey, DsaKey* dsaKey, ecc_key* eccKey, ed25519_key* ed25519Key, ed448_key* ed448Key, falcon_key* falconKey, wc_MlDsaKey* mldsaKey, - SlhDsaKey* slhDsaKey) + SlhDsaKey* slhDsaKey, LmsKey* lmsKey, XmssKey* xmssKey) { DECL_ASNSETDATA(dataASN, certReqBodyASN_Length); word32 publicKeySz = 0; @@ -28815,6 +28966,8 @@ static int MakeCertReq(Cert* cert, byte* derBuffer, word32 derSz, (void)falconKey; (void)mldsaKey; (void)slhDsaKey; + (void)lmsKey; + (void)xmssKey; CALLOC_ASNSETDATA(dataASN, certReqBodyASN_Length, ret, cert->heap); @@ -28882,6 +29035,16 @@ static int MakeCertReq(Cert* cert, byte* derBuffer, word32 derSz, } } #endif /* WOLFSSL_HAVE_SLHDSA */ +#if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY) + else if (lmsKey != NULL) { + cert->keyType = LMS_KEY; + } +#endif /* WOLFSSL_HAVE_LMS && !WOLFSSL_LMS_VERIFY_ONLY */ +#if defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY) + else if (xmssKey != NULL) { + cert->keyType = xmssKey->is_xmssmt ? XMSSMT_KEY : XMSS_KEY; + } +#endif /* WOLFSSL_HAVE_XMSS && !WOLFSSL_XMSS_VERIFY_ONLY */ else { ret = BAD_FUNC_ARG; } @@ -28904,7 +29067,7 @@ static int MakeCertReq(Cert* cert, byte* derBuffer, word32 derSz, /* Determine encode public key size. */ ret = EncodePublicKey(cert->keyType, NULL, 0, rsaKey, eccKey, ed25519Key, ed448Key, dsaKey, falconKey, - mldsaKey, slhDsaKey); + mldsaKey, slhDsaKey, lmsKey, xmssKey); publicKeySz = (word32)ret; } if (ret >= 0) { @@ -29024,7 +29187,7 @@ static int MakeCertReq(Cert* cert, byte* derBuffer, word32 derSz, dataASN[CERTREQBODYASN_IDX_SPUBKEYINFO_SEQ].data.buffer.data, (int)dataASN[CERTREQBODYASN_IDX_SPUBKEYINFO_SEQ].data.buffer.length, rsaKey, eccKey, ed25519Key, ed448Key, dsaKey, falconKey, - mldsaKey, slhDsaKey); + mldsaKey, slhDsaKey, lmsKey, xmssKey); } if ((ret >= 0 && derBuffer != NULL) && (!dataASN[CERTREQBODYASN_IDX_EXT_BODY].noOut)) { @@ -29058,6 +29221,8 @@ int wc_MakeCertReq_ex(Cert* cert, byte* derBuffer, word32 derSz, int keyType, falcon_key* falconKey = NULL; wc_MlDsaKey* mldsaKey = NULL; SlhDsaKey* slhDsaKey = NULL; + LmsKey* lmsKey = NULL; + XmssKey* xmssKey = NULL; if (keyType == RSA_TYPE) rsaKey = (RsaKey*)key; @@ -29091,10 +29256,28 @@ int wc_MakeCertReq_ex(Cert* cert, byte* derBuffer, word32 derSz, int keyType, else if (IsSlhDsaKeyType(keyType)) slhDsaKey = (SlhDsaKey*)key; #endif +#if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY) + else if (keyType == LMS_TYPE) + lmsKey = (LmsKey*)key; +#endif +#if defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY) + /* The selector must match the key's actual tree variant so XMSS_TYPE and + * XMSSMT_TYPE are not silently interchangeable. */ + else if (keyType == XMSS_TYPE) { + xmssKey = (XmssKey*)key; + if (xmssKey != NULL && xmssKey->is_xmssmt) + return BAD_FUNC_ARG; + } + else if (keyType == XMSSMT_TYPE) { + xmssKey = (XmssKey*)key; + if (xmssKey != NULL && !xmssKey->is_xmssmt) + return BAD_FUNC_ARG; + } +#endif return MakeCertReq(cert, derBuffer, derSz, rsaKey, dsaKey, eccKey, ed25519Key, ed448Key, falconKey, mldsaKey, - slhDsaKey); + slhDsaKey, lmsKey, xmssKey); } WOLFSSL_ABI @@ -29102,7 +29285,7 @@ int wc_MakeCertReq(Cert* cert, byte* derBuffer, word32 derSz, RsaKey* rsaKey, ecc_key* eccKey) { return MakeCertReq(cert, derBuffer, derSz, rsaKey, NULL, eccKey, NULL, - NULL, NULL, NULL, NULL); + NULL, NULL, NULL, NULL, NULL, NULL); } #endif /* WOLFSSL_CERT_REQ */ @@ -29249,13 +29432,20 @@ static int SignCert(int requestSz, int sType, byte* buf, word32 buffSz, RsaKey* rsaKey, ecc_key* eccKey, ed25519_key* ed25519Key, ed448_key* ed448Key, falcon_key* falconKey, wc_MlDsaKey* mldsaKey, SlhDsaKey* slhDsaKey, - WC_RNG* rng) + LmsKey* lmsKey, XmssKey* xmssKey, WC_RNG* rng) { int sigSz = 0; void* heap = NULL; + /* The signature buffer must hold the largest signature any supported key + * type can produce. LMS/XMSS signatures are parameter-dependent and can + * exceed MAX_ENCODED_SIG_SZ, so size them from the key at runtime. */ + word32 maxSigSz = MAX_ENCODED_SIG_SZ; CertSignCtx certSignCtx_lcl; CertSignCtx* certSignCtx = &certSignCtx_lcl; + (void)lmsKey; + (void)xmssKey; + XMEMSET(certSignCtx, 0, sizeof(*certSignCtx)); if (requestSz < 0) @@ -29282,19 +29472,64 @@ static int SignCert(int requestSz, int sType, byte* buf, word32 buffSz, return NOT_COMPILED_IN; #endif /* HAVE_ECC */ } +#if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY) + else if (lmsKey) { + word32 lmsSigSz = 0; + /* The signature algorithm OID is written from sType. Reject a + * mismatch so we never emit a cert whose signatureAlgorithm + * contradicts its HSS/LMS public key. */ + if (sType != CTC_HSS_LMS) { + WOLFSSL_MSG("LMS key requires CTC_HSS_LMS signature type"); + return ALGO_ID_E; + } + heap = lmsKey->heap; + if (wc_LmsKey_GetSigLen(lmsKey, &lmsSigSz) != 0) + return BAD_FUNC_ARG; + if (lmsSigSz > maxSigSz) + maxSigSz = lmsSigSz; + } +#endif +#if defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY) + else if (xmssKey) { + word32 xmssSigSz = 0; + /* sType must match the tree variant (XMSS vs XMSS^MT) so the + * signatureAlgorithm OID agrees with the XMSS public key OID that + * MakeAnyCert derived from key->is_xmssmt. */ + if (xmssKey->is_xmssmt ? (sType != CTC_XMSSMT) + : (sType != CTC_XMSS)) { + WOLFSSL_MSG("XMSS signature type does not match key variant"); + return ALGO_ID_E; + } + heap = xmssKey->heap; + if (wc_XmssKey_GetSigLen(xmssKey, &xmssSigSz) != 0) + return BAD_FUNC_ARG; + if (xmssSigSz > maxSigSz) + maxSigSz = xmssSigSz; + } +#endif #ifndef WOLFSSL_NO_MALLOC if (certSignCtx->sig == NULL) { - certSignCtx->sig = (byte*)XMALLOC(MAX_ENCODED_SIG_SZ, heap, + certSignCtx->sig = (byte*)XMALLOC(maxSigSz, heap, DYNAMIC_TYPE_TMP_BUFFER); if (certSignCtx->sig == NULL) return MEMORY_E; } +#else + /* Without dynamic memory the signature buffer is a fixed + * MAX_ENCODED_SIG_SZ array in CertSignCtx. LMS/XMSS signatures are + * parameter-dependent and can be larger, so reject rather than overflow + * the fixed buffer. */ + if (maxSigSz > MAX_ENCODED_SIG_SZ) { + WOLFSSL_MSG("LMS/XMSS signature larger than fixed CertSignCtx buffer"); + return BUFFER_E; + } #endif sigSz = MakeSignature(certSignCtx, buf, (word32)requestSz, certSignCtx->sig, - MAX_ENCODED_SIG_SZ, rsaKey, eccKey, ed25519Key, ed448Key, - falconKey, mldsaKey, slhDsaKey, rng, (word32)sType, heap); + maxSigSz, rsaKey, eccKey, ed25519Key, ed448Key, + falconKey, mldsaKey, slhDsaKey, lmsKey, xmssKey, rng, (word32)sType, + heap); #ifdef WOLFSSL_ASYNC_CRYPT if (sigSz == WC_NO_ERR_TRACE(WC_PENDING_E)) { /* Not free'ing certSignCtx->sig here because it could still be in use @@ -29440,7 +29675,7 @@ int wc_MakeSigWithBitStr(byte *sig, int sigSz, int sType, byte* buf, ret = MakeSignature(certSignCtx, buf, (word32)bufSz, certSignCtx->sig, MAX_ENCODED_SIG_SZ, rsaKey, eccKey, ed25519Key, ed448Key, - falconKey, mldsaKey, slhDsaKey, rng, (word32)sType, heap); + falconKey, mldsaKey, slhDsaKey, NULL, NULL, rng, (word32)sType, heap); #ifdef WOLFSSL_ASYNC_CRYPT if (ret == WC_NO_ERR_TRACE(WC_PENDING_E)) { /* Not free'ing certSignCtx->sig here because it could still be in use @@ -29500,6 +29735,8 @@ int wc_SignCert_ex(int requestSz, int sType, byte* buf, word32 buffSz, falcon_key* falconKey = NULL; wc_MlDsaKey* mldsaKey = NULL; SlhDsaKey* slhDsaKey = NULL; + LmsKey* lmsKey = NULL; + XmssKey* xmssKey = NULL; if (keyType == RSA_TYPE) rsaKey = (RsaKey*)key; @@ -29531,16 +29768,35 @@ int wc_SignCert_ex(int requestSz, int sType, byte* buf, word32 buffSz, else if (IsSlhDsaKeyType(keyType)) slhDsaKey = (SlhDsaKey*)key; #endif +#if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY) + else if (keyType == LMS_TYPE) + lmsKey = (LmsKey*)key; +#endif +#if defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY) + /* The selector must match the key's actual tree variant so XMSS_TYPE and + * XMSSMT_TYPE are not silently interchangeable. */ + else if (keyType == XMSS_TYPE) { + xmssKey = (XmssKey*)key; + if (xmssKey != NULL && xmssKey->is_xmssmt) + return BAD_FUNC_ARG; + } + else if (keyType == XMSSMT_TYPE) { + xmssKey = (XmssKey*)key; + if (xmssKey != NULL && !xmssKey->is_xmssmt) + return BAD_FUNC_ARG; + } +#endif return SignCert(requestSz, sType, buf, buffSz, rsaKey, eccKey, ed25519Key, - ed448Key, falconKey, mldsaKey, slhDsaKey, rng); + ed448Key, falconKey, mldsaKey, slhDsaKey, lmsKey, xmssKey, + rng); } int wc_SignCert(int requestSz, int sType, byte* buf, word32 buffSz, RsaKey* rsaKey, ecc_key* eccKey, WC_RNG* rng) { return SignCert(requestSz, sType, buf, buffSz, rsaKey, eccKey, NULL, NULL, - NULL, NULL, NULL, rng); + NULL, NULL, NULL, NULL, NULL, rng); } /* Sign certificate/CSR using a callback function @@ -33935,7 +34191,7 @@ WC_MAYBE_UNUSED static int EncodeBasicOcspResponse(OcspResponse* resp, XMEMSET(&certSignCtx, 0, sizeof(CertSignCtx)); ret = MakeSignature(&certSignCtx, respData, respDataSz, sigData, sigSz, rsaKey, eccKey, NULL, NULL, NULL, NULL, - NULL, rng, resp->sigOID, resp->heap); + NULL, NULL, NULL, rng, resp->sigOID, resp->heap); if (ret > 0) { sigSz = (word32)ret; ret = 0; @@ -36153,7 +36409,7 @@ int wc_SignCRL_ex(const byte* tbsBuf, int tbsSz, int sType, /* Create signature */ sigSz = MakeSignature(certSignCtx, buf, (word32)tbsSz, certSignCtx->sig, MAX_ENCODED_SIG_SZ, rsaKey, eccKey, NULL, NULL, NULL, - NULL, NULL, rng, (word32)sType, heap); + NULL, NULL, NULL, NULL, rng, (word32)sType, heap); if (sigSz < 0) { #ifndef WOLFSSL_NO_MALLOC XFREE(certSignCtx->sig, heap, DYNAMIC_TYPE_TMP_BUFFER); diff --git a/wolfcrypt/src/asn_orig.c b/wolfcrypt/src/asn_orig.c index 7db2a4eed1f..24a3e534469 100644 --- a/wolfcrypt/src/asn_orig.c +++ b/wolfcrypt/src/asn_orig.c @@ -6669,11 +6669,23 @@ static int MakeAnyCert(Cert* cert, byte* derBuffer, word32 derSz, RsaKey* rsaKey, ecc_key* eccKey, WC_RNG* rng, DsaKey* dsaKey, ed25519_key* ed25519Key, ed448_key* ed448Key, falcon_key* falconKey, - wc_MlDsaKey* mldsaKey, SlhDsaKey* slhDsaKey) + wc_MlDsaKey* mldsaKey, SlhDsaKey* slhDsaKey, + LmsKey* lmsKey, XmssKey* xmssKey) { int ret; WC_DECLARE_VAR(der, DerCert, 1, 0); + /* RFC 9802 LMS/XMSS support (both verification and generation) lives only + * in the ASN.1 template encoder; this original/non-template path has no + * LMS/XMSS code at all. Rather than duplicate the encoding here for a + * legacy path that could not verify such certs anyway, generation is + * template-only and rejected here with a clear diagnostic. */ + if ((lmsKey != NULL) || (xmssKey != NULL)) { + WOLFSSL_MSG("LMS/XMSS certificate generation requires " + "WOLFSSL_ASN_TEMPLATE"); + return ALGO_ID_E; + } + if (derBuffer == NULL) return BAD_FUNC_ARG; @@ -7302,11 +7314,19 @@ static int MakeCertReq(Cert* cert, byte* derBuffer, word32 derSz, RsaKey* rsaKey, DsaKey* dsaKey, ecc_key* eccKey, ed25519_key* ed25519Key, ed448_key* ed448Key, falcon_key* falconKey, wc_MlDsaKey* mldsaKey, - SlhDsaKey* slhDsaKey) + SlhDsaKey* slhDsaKey, LmsKey* lmsKey, XmssKey* xmssKey) { int ret; WC_DECLARE_VAR(der, DerCert, 1, 0); + /* LMS/XMSS certificate request generation is only supported with + * WOLFSSL_ASN_TEMPLATE. */ + if ((lmsKey != NULL) || (xmssKey != NULL)) { + WOLFSSL_MSG("LMS/XMSS certificate request generation requires " + "WOLFSSL_ASN_TEMPLATE"); + return ALGO_ID_E; + } + if (eccKey) cert->keyType = ECC_KEY; else if (rsaKey) diff --git a/wolfssl/wolfcrypt/asn.h b/wolfssl/wolfcrypt/asn.h index 5db17afab1b..2f104bf410e 100644 --- a/wolfssl/wolfcrypt/asn.h +++ b/wolfssl/wolfcrypt/asn.h @@ -2773,7 +2773,10 @@ enum cert_enums { SLH_DSA_SHAKE_192S_KEY = 32, SLH_DSA_SHAKE_192F_KEY = 33, SLH_DSA_SHAKE_256S_KEY = 34, - SLH_DSA_SHAKE_256F_KEY = 35 + SLH_DSA_SHAKE_256F_KEY = 35, + LMS_KEY = 36, + XMSS_KEY = 37, + XMSSMT_KEY = 38 }; #ifndef WOLFSSL_NO_DILITHIUM_LEGACY_NAMES diff --git a/wolfssl/wolfcrypt/asn_public.h b/wolfssl/wolfcrypt/asn_public.h index 4fd5f7ba27d..1c6bfc38539 100644 --- a/wolfssl/wolfcrypt/asn_public.h +++ b/wolfssl/wolfcrypt/asn_public.h @@ -99,6 +99,14 @@ This library defines the interface APIs for X509 certificates. typedef struct SlhDsaKey SlhDsaKey; #define WC_SLHDSAKEY_TYPE_DEFINED #endif +#ifndef WC_LMSKEY_TYPE_DEFINED + typedef struct LmsKey LmsKey; + #define WC_LMSKEY_TYPE_DEFINED +#endif +#ifndef WC_XMSSKEY_TYPE_DEFINED + typedef struct XmssKey XmssKey; + #define WC_XMSSKEY_TYPE_DEFINED +#endif enum EncPkcs8Types { ENC_PKCS8_VER_PKCS12 = 1, @@ -172,7 +180,10 @@ enum CertType { ECC_PARAM_TYPE, CHAIN_CERT_TYPE, PKCS7_TYPE, - TRUSTED_CERT_TYPE + TRUSTED_CERT_TYPE, + LMS_TYPE, + XMSS_TYPE, + XMSSMT_TYPE }; #ifndef WOLFSSL_NO_DILITHIUM_LEGACY_NAMES diff --git a/wolfssl/wolfcrypt/settings.h b/wolfssl/wolfcrypt/settings.h index 0fb49e93510..76e7206bdda 100644 --- a/wolfssl/wolfcrypt/settings.h +++ b/wolfssl/wolfcrypt/settings.h @@ -3367,7 +3367,9 @@ extern void uITRON4_free(void *p) ; (defined(HAVE_ED448) && defined(HAVE_ED448_KEY_EXPORT)) || \ (defined(HAVE_CURVE448) && defined(HAVE_CURVE448_KEY_EXPORT)) || \ defined(HAVE_FALCON) || defined(HAVE_DILITHIUM) || \ - defined(WOLFSSL_HAVE_SLHDSA) || defined(HAVE_LIBOQS)) + defined(WOLFSSL_HAVE_SLHDSA) || defined(HAVE_LIBOQS) || \ + (defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY)) || \ + (defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY))) #define WC_ENABLE_ASYM_KEY_EXPORT #endif @@ -3377,7 +3379,9 @@ extern void uITRON4_free(void *p) ; (defined(HAVE_ED448) && defined(HAVE_ED448_KEY_IMPORT)) || \ (defined(HAVE_CURVE448) && defined(HAVE_CURVE448_KEY_IMPORT)) || \ defined(HAVE_FALCON) || defined(HAVE_DILITHIUM) || \ - defined(WOLFSSL_HAVE_SLHDSA) || defined(HAVE_LIBOQS)) + defined(WOLFSSL_HAVE_SLHDSA) || defined(HAVE_LIBOQS) || \ + (defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY)) || \ + (defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY))) #define WC_ENABLE_ASYM_KEY_IMPORT #endif diff --git a/wolfssl/wolfcrypt/wc_lms.h b/wolfssl/wolfcrypt/wc_lms.h index a8d898c6c6a..e770a04c656 100644 --- a/wolfssl/wolfcrypt/wc_lms.h +++ b/wolfssl/wolfcrypt/wc_lms.h @@ -749,7 +749,7 @@ typedef struct HssPrivKey { #define LMS_MAX_LABEL_LEN 32 #endif -typedef struct LmsKey { +struct LmsKey { /* Public key. */ ALIGN16 byte pub[HSS_PUBLIC_KEY_LEN(LMS_MAX_NODE_LEN)]; #ifndef WOLFSSL_LMS_VERIFY_ONLY @@ -788,7 +788,12 @@ typedef struct LmsKey { char label[LMS_MAX_LABEL_LEN]; int labelLen; #endif -} LmsKey; +}; + +#ifndef WC_LMSKEY_TYPE_DEFINED + typedef struct LmsKey LmsKey; + #define WC_LMSKEY_TYPE_DEFINED +#endif #ifdef __cplusplus extern "C" { @@ -822,6 +827,8 @@ WOLFSSL_API int wc_LmsKey_GetPrivLen(const LmsKey* key, word32* len); WOLFSSL_API int wc_LmsKey_Sign(LmsKey* key, byte* sig, word32* sigSz, const byte* msg, int msgSz); WOLFSSL_API int wc_LmsKey_SigsLeft(LmsKey* key); +WOLFSSL_API int wc_LmsKey_PublicKeyToDer(LmsKey* key, byte* output, + word32 inLen, int withAlg); #endif /* ifndef WOLFSSL_LMS_VERIFY_ONLY */ WOLFSSL_API void wc_LmsKey_Free(LmsKey* key); WOLFSSL_API int wc_LmsKey_GetSigLen(const LmsKey* key, word32* len); diff --git a/wolfssl/wolfcrypt/wc_xmss.h b/wolfssl/wolfcrypt/wc_xmss.h index 277d85524ab..1196f7aa94e 100644 --- a/wolfssl/wolfcrypt/wc_xmss.h +++ b/wolfssl/wolfcrypt/wc_xmss.h @@ -346,7 +346,7 @@ typedef struct XmssParams { #define XMSS_MAX_LABEL_LEN 32 #endif -typedef struct XmssKey { +struct XmssKey { /* Public key. */ unsigned char pk[2 * WC_XMSS_MAX_N]; /* OID that identifies parameters. */ @@ -385,7 +385,12 @@ typedef struct XmssKey { char label[XMSS_MAX_LABEL_LEN]; int labelLen; #endif -} XmssKey; +}; + +#ifndef WC_XMSSKEY_TYPE_DEFINED + typedef struct XmssKey XmssKey; + #define WC_XMSSKEY_TYPE_DEFINED +#endif typedef struct XmssState { const XmssParams* params; @@ -446,6 +451,8 @@ WOLFSSL_API int wc_XmssKey_GetPrivLen(const XmssKey* key, word32* len); WOLFSSL_API int wc_XmssKey_Sign(XmssKey* key, byte* sig, word32* sigSz, const byte* msg, int msgSz); WOLFSSL_API int wc_XmssKey_SigsLeft(XmssKey* key); +WOLFSSL_API int wc_XmssKey_PublicKeyToDer(XmssKey* key, byte* output, + word32 inLen, int withAlg); #endif /* ifndef WOLFSSL_XMSS_VERIFY_ONLY */ WOLFSSL_API void wc_XmssKey_Free(XmssKey* key); WOLFSSL_API int wc_XmssKey_GetSigLen(const XmssKey* key, word32* len);