diff --git a/certs/include.am b/certs/include.am index b19881d31f..cbca4b5c73 100644 --- a/certs/include.am +++ b/certs/include.am @@ -160,6 +160,8 @@ include certs/falcon/include.am include certs/rsapss/include.am include certs/dilithium/include.am include certs/sphincs/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 new file mode 100644 index 0000000000..824d5664af Binary files /dev/null and b/certs/lms/bc_hss_L2_H5_W8_root.der differ diff --git a/certs/lms/bc_hss_L3_H5_W4_root.der b/certs/lms/bc_hss_L3_H5_W4_root.der new file mode 100644 index 0000000000..43904f7ae7 Binary files /dev/null and b/certs/lms/bc_hss_L3_H5_W4_root.der differ diff --git a/certs/lms/bc_lms_chain_ca.der b/certs/lms/bc_lms_chain_ca.der new file mode 100644 index 0000000000..e3cebcccbb Binary files /dev/null and b/certs/lms/bc_lms_chain_ca.der differ diff --git a/certs/lms/bc_lms_chain_leaf.der b/certs/lms/bc_lms_chain_leaf.der new file mode 100644 index 0000000000..4346b7aa15 Binary files /dev/null and b/certs/lms/bc_lms_chain_leaf.der differ diff --git a/certs/lms/bc_lms_sha256_h10_w8_root.der b/certs/lms/bc_lms_sha256_h10_w8_root.der new file mode 100644 index 0000000000..c9ca9bf226 Binary files /dev/null and b/certs/lms/bc_lms_sha256_h10_w8_root.der differ diff --git a/certs/lms/bc_lms_sha256_h5_w4_root.der b/certs/lms/bc_lms_sha256_h5_w4_root.der new file mode 100644 index 0000000000..ef4941ee8c Binary files /dev/null and b/certs/lms/bc_lms_sha256_h5_w4_root.der differ diff --git a/certs/lms/include.am b/certs/lms/include.am new file mode 100644 index 0000000000..6b7fbf90b3 --- /dev/null +++ b/certs/lms/include.am @@ -0,0 +1,11 @@ +# vim:ft=automake +# All paths should be given relative to the root +# + +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 diff --git a/certs/xmss/bc_xmss_sha2_10_256_root.der b/certs/xmss/bc_xmss_sha2_10_256_root.der new file mode 100644 index 0000000000..12d70a002e Binary files /dev/null and b/certs/xmss/bc_xmss_sha2_10_256_root.der differ diff --git a/certs/xmss/bc_xmss_sha2_16_256_root.der b/certs/xmss/bc_xmss_sha2_16_256_root.der new file mode 100644 index 0000000000..91f3bf5546 Binary files /dev/null and b/certs/xmss/bc_xmss_sha2_16_256_root.der 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 new file mode 100644 index 0000000000..24b47019e1 Binary files /dev/null and b/certs/xmss/bc_xmssmt_sha2_20_2_256_root.der 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 new file mode 100644 index 0000000000..b3037316d2 Binary files /dev/null and b/certs/xmss/bc_xmssmt_sha2_20_4_256_root.der 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 new file mode 100644 index 0000000000..870faa2c18 Binary files /dev/null and b/certs/xmss/bc_xmssmt_sha2_40_8_256_root.der differ diff --git a/certs/xmss/include.am b/certs/xmss/include.am new file mode 100644 index 0000000000..2acd79cc07 --- /dev/null +++ b/certs/xmss/include.am @@ -0,0 +1,10 @@ +# 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 diff --git a/scripts/asn1_oid_sum.pl b/scripts/asn1_oid_sum.pl index fb48857a3b..c108d092f1 100755 --- a/scripts/asn1_oid_sum.pl +++ b/scripts/asn1_oid_sum.pl @@ -308,6 +308,9 @@ sub print_footer { my @sphincs_small_1 = ( 1, 3, 9999, 6, 7, 10 ); my @sphincs_small_3 = ( 1, 3, 9999, 6, 8, 7 ); my @sphincs_small_5 = ( 1, 3, 9999, 6, 9, 7 ); +my @hss_lms = ( 1, 2, 840, 113549, 1, 9, 16, 3, 17 ); +my @xmss = ( 1, 3, 6, 1, 5, 5, 7, 6, 34 ); +my @xmssmt = ( 1, 3, 6, 1, 5, 5, 7, 6, 35 ); my @keys = ( { name => "ANON", oid => \@anon }, @@ -337,6 +340,9 @@ sub print_footer { { name => "SPHINCS_SMALL_LEVEL1", oid => \@sphincs_small_1 }, { name => "SPHINCS_SMALL_LEVEL3", oid => \@sphincs_small_3 }, { name => "SPHINCS_SMALL_LEVEL5", oid => \@sphincs_small_5 }, + { name => "HSS_LMS", oid => \@hss_lms }, + { name => "XMSS", oid => \@xmss }, + { name => "XMSSMT", oid => \@xmssmt }, ); print_sum_enum("Key", "k", \@keys); @@ -1138,6 +1144,12 @@ sub print_footer { same => 1 }, { name => "CTC_SPHINCS_SMALL_LEVEL5", oid => \@sphincs_small_5, same => 1 }, + { name => "CTC_HSS_LMS", oid => \@hss_lms, + same => 1 }, + { name => "CTC_XMSS", oid => \@xmss, + same => 1 }, + { name => "CTC_XMSSMT", oid => \@xmssmt, + same => 1 }, ); print_enum("Ctc_SigType", "", \@sig_types, 32, 48); diff --git a/tests/api.c b/tests/api.c index b3193c5ad0..e6f9568785 100644 --- a/tests/api.c +++ b/tests/api.c @@ -35621,6 +35621,7 @@ int stopOnFail = 0; /*----------------------------------------------------------------------------*/ int test_wc_LmsKey_sign_verify(void); int test_wc_LmsKey_reload_cache(void); +int test_rfc9802_x509_verify(void); #if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY) @@ -35786,6 +35787,465 @@ int test_wc_LmsKey_reload_cache(void) return EXPECT_RESULT(); } +/*----------------------------------------------------------------------------*/ +/* RFC 9802 (HSS/LMS and XMSS/XMSS^MT in X.509) tests */ +/*----------------------------------------------------------------------------*/ + +/* For every committed self-signed test certificate confirm: + * - wc_ParseCert succeeds on the RFC 9802 AlgorithmIdentifier encoding + * (OID-only SEQUENCE, no NULL parameters) + * - keyOID and signatureOID are set to the expected values + * - loading as a trust anchor and verifying the same bytes through + * wolfSSL_CertManagerVerifyBuffer exercises the ConfirmSignature + * path and succeeds on a valid cert + * - flipping a byte in the signature AND flipping a byte in the + * TBSCertificate both cause verification to fail. + * + * Test vectors are in certs/lms/ and certs/xmss/, generated with Bouncy + * Castle 1.81. BC's default XMSS / XMSS^MT X.509 encoding uses pre- + * 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) +/* 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. */ +#define RFC9802_TEST_MAX_CERT_SIZE (1 << 20) + +/* Load a whole file into a freshly-allocated buffer. Caller frees. */ +static int rfc9802_load_file(const char* path, byte** out, int* outLen) +{ + EXPECT_DECLS; + XFILE f = XBADFILE; + long sz = 0; + size_t got = 0; + byte* buf = NULL; + + *out = NULL; + *outLen = 0; + ExpectTrue((f = XFOPEN(path, "rb")) != XBADFILE); + if (f == XBADFILE) + return TEST_FAIL; + if (XFSEEK(f, 0, XSEEK_END) == 0) + sz = XFTELL(f); + (void)XFSEEK(f, 0, XSEEK_SET); + ExpectIntGT(sz, 0); + ExpectIntLT(sz, RFC9802_TEST_MAX_CERT_SIZE); + ExpectNotNull(buf = (byte*)XMALLOC((size_t)sz, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + if (buf != NULL) { + got = XFREAD(buf, 1, (size_t)sz, f); + ExpectIntEQ(got, (size_t)sz); + } + XFCLOSE(f); + *out = buf; + *outLen = (int)sz; + return EXPECT_RESULT(); +} + +static int rfc9802_verify_one_cert(const char* path, word32 expectedKeyOID, + word32 expectedSigOID) +{ + EXPECT_DECLS; + byte* buf = NULL; + byte* tampered = NULL; + int bytes = 0; + DecodedCert cert; + WOLFSSL_CERT_MANAGER* cm = NULL; + word32 certBegin = 0; + word32 sigIndex = 0; + + ExpectIntEQ(rfc9802_load_file(path, &buf, &bytes), TEST_SUCCESS); + if (buf == NULL) + return TEST_FAIL; + + /* Parse + check OIDs, capture certBegin and sigIndex for later tamper. */ + wc_InitDecodedCert(&cert, buf, (word32)bytes, NULL); + ExpectIntEQ(wc_ParseCert(&cert, CERT_TYPE, NO_VERIFY, NULL), 0); + ExpectIntEQ((int)cert.keyOID, (int)expectedKeyOID); + ExpectIntEQ((int)cert.signatureOID, (int)expectedSigOID); + certBegin = cert.certBegin; + sigIndex = cert.sigIndex; + wc_FreeDecodedCert(&cert); + + /* Full verify against a self-installed trust anchor. */ + ExpectNotNull(cm = wolfSSL_CertManagerNew()); + ExpectIntEQ(wolfSSL_CertManagerLoadCABuffer(cm, buf, (long)bytes, + WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CertManagerVerifyBuffer(cm, buf, (long)bytes, + WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + if (cm != NULL) { + wolfSSL_CertManagerFree(cm); + cm = NULL; + } + + ExpectNotNull(tampered = (byte*)XMALLOC((size_t)bytes, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + + /* Negative 1: flip a byte inside the signatureValue BIT STRING. + * Everything after sigIndex is the signatureAlgorithm + the BIT + * STRING payload, so flipping the last byte is always inside the + * signature content. */ + if (tampered != NULL) { + XMEMCPY(tampered, buf, (size_t)bytes); + tampered[bytes - 1] ^= 0x01; + ExpectNotNull(cm = wolfSSL_CertManagerNew()); + ExpectIntEQ(wolfSSL_CertManagerLoadCABuffer(cm, buf, (long)bytes, + WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + ExpectIntNE(wolfSSL_CertManagerVerifyBuffer(cm, tampered, + (long)bytes, WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + if (cm != NULL) { + wolfSSL_CertManagerFree(cm); + cm = NULL; + } + } + + /* Negative 2: flip a byte at the midpoint of the TBSCertificate. The + * TBS is the first element of the outer Certificate SEQUENCE and + * its bytes lie between (certBegin + outerSeqHeader) and sigIndex. + * Picking the midpoint ensures we're inside TBS regardless of the + * fixture's DN / extensions layout. */ + if (tampered != NULL && sigIndex > certBegin + 8u) { + word32 midTbs = certBegin + 8 + ((sigIndex - (certBegin + 8)) / 2); + XMEMCPY(tampered, buf, (size_t)bytes); + tampered[midTbs] ^= 0x01; + ExpectNotNull(cm = wolfSSL_CertManagerNew()); + ExpectIntEQ(wolfSSL_CertManagerLoadCABuffer(cm, buf, (long)bytes, + WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + ExpectIntNE(wolfSSL_CertManagerVerifyBuffer(cm, tampered, + (long)bytes, WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + if (cm != NULL) { + wolfSSL_CertManagerFree(cm); + cm = NULL; + } + } + + /* The fixtures MUST carry a KeyUsage extension with at least one of + * digitalSignature / nonRepudiation / keyCertSign / cRLSign set per + * RFC 9802 sec 3. Re-parse and assert that wolfSSL recorded a non- + * empty set of KeyUsage bits from one of those values. */ + wc_InitDecodedCert(&cert, buf, (word32)bytes, NULL); + ExpectIntEQ(wc_ParseCert(&cert, CERT_TYPE, NO_VERIFY, NULL), 0); + ExpectIntEQ(cert.extKeyUsageSet, 1); + ExpectIntNE(cert.extKeyUsage & (KEYUSE_DIGITAL_SIG | KEYUSE_CONTENT_COMMIT | + KEYUSE_KEY_CERT_SIGN | KEYUSE_CRL_SIGN), 0); + wc_FreeDecodedCert(&cert); + + XFREE(tampered, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(buf, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return EXPECT_RESULT(); +} +#endif + +/* Direct wolfCrypt-level negative tests for the parameter-derivation + * helpers used by the RFC 9802 parse path. These exercise failure modes + * (unknown algorithm bytes, truncated inputs, mismatches) that a real + * cert body wouldn't easily reach. */ +#if defined(WOLFSSL_HAVE_LMS) +static int rfc9802_lms_import_negative(void) +{ + EXPECT_DECLS; + LmsKey key; + /* 60-byte buffer matches HSS_PUBLIC_KEY_LEN(32), just like a valid + * SHA-256/M32/H5 key; the algorithm-type bytes are junk so param + * derivation must fail cleanly. */ + byte junk[60]; + + XMEMSET(junk, 0, sizeof(junk)); + /* levels=1, lmsType=0xFFFFFFFF, lmOtsType=0xFFFFFFFF. */ + junk[3] = 1; + XMEMSET(junk + 4, 0xFF, 4); + XMEMSET(junk + 8, 0xFF, 4); + + /* Unknown algorithm types must be rejected. */ + ExpectIntEQ(wc_LmsKey_Init(&key, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_LmsKey_ImportPubRaw(&key, junk, sizeof(junk)), + WC_NO_ERR_TRACE(NOT_COMPILED_IN)); + wc_LmsKey_Free(&key); + + /* Too-short buffer: only L + lmsType, no lmOtsType. */ + ExpectIntEQ(wc_LmsKey_Init(&key, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_LmsKey_ImportPubRaw(&key, junk, 8), + WC_NO_ERR_TRACE(BUFFER_E)); + wc_LmsKey_Free(&key); + + /* Pre-set params that disagree with the raw key's algorithm bytes: + * configure H=5/W=8 but feed buffer that claims H=10 / W=2. */ + XMEMSET(junk, 0, sizeof(junk)); + junk[3] = 1; /* levels=1 */ + junk[7] = 6; /* lmsType = LMS_SHA256_M32_H10 = 6 */ + junk[11] = 2; /* lmOtsType = LMOTS_SHA256_N32_W2 = 2 */ + ExpectIntEQ(wc_LmsKey_Init(&key, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_LmsKey_SetParameters(&key, 1, 5, 8), 0); + ExpectIntEQ(wc_LmsKey_ImportPubRaw(&key, junk, sizeof(junk)), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + wc_LmsKey_Free(&key); + + /* GetSigLen on a key with no params set must not NULL-deref the + * params pointer; it must return BAD_FUNC_ARG instead. */ + { + word32 sigLen = 0; + ExpectIntEQ(wc_LmsKey_Init(&key, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_LmsKey_GetSigLen(&key, &sigLen), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + wc_LmsKey_Free(&key); + } + + /* Partial-write invariant: a length mismatch after a successful + * auto-derive must leave key->params NULL. Build a buffer whose + * leading u32str(L) || lmsType || lmOtsType identifies a known + * parameter set, but truncate to one byte less than the real pub + * key length so the post-derive length check fails. */ + { + byte truncated[59]; /* HSS_PUBLIC_KEY_LEN(32) is 60 */ + XMEMSET(truncated, 0, sizeof(truncated)); + truncated[3] = 1; /* L = 1 */ + truncated[7] = 5; /* lmsType = LMS_SHA256_M32_H5 */ + truncated[11] = 4; /* lmOtsType = LMOTS_SHA256_N32_W4 */ + ExpectIntEQ(wc_LmsKey_Init(&key, NULL, INVALID_DEVID), 0); + ExpectNull(key.params); + ExpectIntEQ(wc_LmsKey_ImportPubRaw(&key, truncated, + sizeof(truncated)), WC_NO_ERR_TRACE(BUFFER_E)); + ExpectNull(key.params); + wc_LmsKey_Free(&key); + } + + return EXPECT_RESULT(); +} +#endif + +#if defined(WOLFSSL_HAVE_XMSS) +static int rfc9802_xmss_import_negative(void) +{ + EXPECT_DECLS; + XmssKey key; + byte junk[8]; + + XMEMSET(junk, 0, sizeof(junk)); + + /* Too-short buffer. */ + ExpectIntEQ(wc_XmssKey_Init(&key, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_XmssKey_ImportPubRaw_ex(&key, junk, 2, 0), + WC_NO_ERR_TRACE(BUFFER_E)); + wc_XmssKey_Free(&key); + + /* Unknown OID (all-zero) for both XMSS and XMSS^MT. */ + ExpectIntEQ(wc_XmssKey_Init(&key, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_XmssKey_ImportPubRaw_ex(&key, junk, sizeof(junk), 0), + WC_NO_ERR_TRACE(NOT_COMPILED_IN)); + wc_XmssKey_Free(&key); + ExpectIntEQ(wc_XmssKey_Init(&key, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_XmssKey_ImportPubRaw_ex(&key, junk, sizeof(junk), 1), + WC_NO_ERR_TRACE(NOT_COMPILED_IN)); + wc_XmssKey_Free(&key); + + /* NULL key / input. */ + ExpectIntEQ(wc_XmssKey_ImportPubRaw_ex(NULL, junk, sizeof(junk), 0), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_XmssKey_Init(&key, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_XmssKey_ImportPubRaw_ex(&key, NULL, 8, 0), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + wc_XmssKey_Free(&key); + + /* GetSigLen on a key with no params set must not NULL-deref the + * params pointer; it must return BAD_FUNC_ARG instead. */ + { + word32 sigLen = 0; + ExpectIntEQ(wc_XmssKey_Init(&key, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_XmssKey_GetSigLen(&key, &sigLen), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + wc_XmssKey_Free(&key); + } + + /* Once params have been configured (state != INITED), the OID + * prefix in the raw key MUST match key->oid and is_xmssmt MUST + * match key->is_xmssmt. Set XMSS-SHA2_10_256 and feed a valid- + * sized buffer whose 4-byte OID prefix is bogus -> BAD_FUNC_ARG. */ + { + byte mismatch[XMSS_SHA256_PUBLEN]; + ExpectIntEQ(wc_XmssKey_Init(&key, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_XmssKey_SetParamStr(&key, "XMSS-SHA2_10_256"), 0); + XMEMSET(mismatch, 0, sizeof(mismatch)); + mismatch[3] = 0x77; /* nonsense OID */ + ExpectIntEQ(wc_XmssKey_ImportPubRaw_ex(&key, mismatch, + sizeof(mismatch), 0), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* Same buffer with the correct OID, but is_xmssmt hint + * contradicts the configured family -> BAD_FUNC_ARG. */ + mismatch[3] = 0x01; /* WC_XMSS_OID_SHA2_10_256 */ + ExpectIntEQ(wc_XmssKey_ImportPubRaw_ex(&key, mismatch, + sizeof(mismatch), 1), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + wc_XmssKey_Free(&key); + } + + return EXPECT_RESULT(); +} +#endif + +/* X.509-level negative: swap a valid fixture's outer signatureAlgorithm + * OID byte so the cert declares XMSS where the SPKI is XMSS^MT (or + * vice versa). SigOidMatchesKeyOid must reject this before any crypto. + * + * Locates the outer signatureAlgorithm OID precisely from the parsed + * DecodedCert rather than byte-scanning, which would otherwise be + * prone to false matches inside the signatureValue BIT STRING. */ +#if defined(WOLFSSL_HAVE_XMSS) +static int rfc9802_xmss_sig_oid_mismatch(void) +{ + EXPECT_DECLS; + byte* buf = NULL; + int bytes = 0; + DecodedCert cert; + WOLFSSL_CERT_MANAGER* cm = NULL; + word32 sigIndex = 0; + + ExpectIntEQ(rfc9802_load_file( + "./certs/xmss/bc_xmss_sha2_10_256_root.der", &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); + + /* Outer signatureAlgorithm at sigIndex looks like + * 30 LL 06 oidLen oidBytes... + * For RFC 9802 XMSS the whole SEQUENCE is 10 bytes, so LL and + * oidLen are short-form (<128). Walk the two length bytes to find + * the last byte of the OID (0x22 for XMSS, 0x23 for XMSS^MT). */ + if (bytes > 0 && (word32)bytes > sigIndex + 4u && + buf[sigIndex] == 0x30 && buf[sigIndex + 2] == 0x06) { + byte oidLen = buf[sigIndex + 3]; + word32 lastOidByte = sigIndex + 4u + oidLen - 1u; + if (lastOidByte < (word32)bytes && buf[lastOidByte] == 0x22) { + /* Patch XMSS -> XMSS^MT in the outer signatureAlgorithm. */ + buf[lastOidByte] = 0x23; + ExpectNotNull(cm = wolfSSL_CertManagerNew()); + /* After the patch the cert is self-inconsistent: SPKI says + * XMSS, outer signatureAlgorithm says XMSS^MT. Verification + * must fail (either at parse/load or at ConfirmSignature). */ + (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; + } + } + else { + /* Fixture shape is unexpected; fail loudly rather than + * silently skip the negative test. */ + ExpectIntEQ(1, 0); + } + } + else { + ExpectIntEQ(1, 0); + } + + XFREE(buf, NULL, DYNAMIC_TYPE_TMP_BUFFER); + 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) +static int rfc9802_lms_chain_verify(void) +{ + EXPECT_DECLS; + byte* caBuf = NULL; + byte* leafBuf = NULL; + int caLen = 0; + int leafLen = 0; + WOLFSSL_CERT_MANAGER* cm = NULL; + + 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); + + ExpectNotNull(cm = wolfSSL_CertManagerNew()); + /* Only the CA is a trust anchor; the leaf is verified against it. */ + ExpectIntEQ(wolfSSL_CertManagerLoadCABuffer(cm, caBuf, (long)caLen, + WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CertManagerVerifyBuffer(cm, leafBuf, (long)leafLen, + WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + + /* Without loading the CA the leaf must NOT verify. */ + 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; + } + + XFREE(leafBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(caBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return EXPECT_RESULT(); +} +#endif + +int test_rfc9802_x509_verify(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_HAVE_LMS) + { + /* 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). */ + static const char* const lmsFiles[] = { + "./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", + }; + 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); + } + } +#endif +#if defined(WOLFSSL_HAVE_XMSS) + { + 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); + } + for (i = 0; i < sizeof(xmssmtFiles) / sizeof(xmssmtFiles[0]); i++) { + ExpectIntEQ(rfc9802_verify_one_cert(xmssmtFiles[i], + XMSSMTk, CTC_XMSSMT), TEST_SUCCESS); + } + ExpectIntEQ(rfc9802_xmss_import_negative(), TEST_SUCCESS); + ExpectIntEQ(rfc9802_xmss_sig_oid_mismatch(), TEST_SUCCESS); + } +#endif +#if defined(WOLFSSL_HAVE_LMS) + ExpectIntEQ(rfc9802_lms_import_negative(), TEST_SUCCESS); + ExpectIntEQ(rfc9802_lms_chain_verify(), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + + #if defined(WOLFSSL_SNIFFER) && defined(WOLFSSL_SNIFFER_CHAIN_INPUT) static int test_sniffer_chain_input_overflow(void) { @@ -37060,6 +37520,9 @@ TEST_CASE testCases[] = { TEST_DECL_GROUP("lms", test_wc_LmsKey_sign_verify), TEST_DECL_GROUP("lms", test_wc_LmsKey_reload_cache), + /* RFC 9802 (HSS/LMS and XMSS/XMSS^MT in X.509) */ + TEST_DECL_GROUP("lms", test_rfc9802_x509_verify), + /* PEM and DER APIs. */ TEST_DECL(test_wc_PemToDer), TEST_DECL(test_wc_AllocDer), diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index 90b964c217..d79085a3b3 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -4635,6 +4635,17 @@ static int ParseCRL_Extensions(DecodedCRL* dcrl, const byte* buf, word32* inOutI static const byte sigSphincsSmall_Level5Oid[] = {43, 206, 15, 6, 9, 7}; #endif /* HAVE_SPHINCS */ +#ifdef WOLFSSL_HAVE_LMS + /* RFC 9802 id-alg-hss-lms-hashsig: 1.2.840.113549.1.9.16.3.17 */ + static const byte sigHssLmsOid[] = + {42, 134, 72, 134, 247, 13, 1, 9, 16, 3, 17}; +#endif /* WOLFSSL_HAVE_LMS */ +#ifdef WOLFSSL_HAVE_XMSS + /* RFC 9802 id-alg-xmss-hashsig: 1.3.6.1.5.5.7.6.34 */ + static const byte sigXmssOid[] = {43, 6, 1, 5, 5, 7, 6, 34}; + /* RFC 9802 id-alg-xmssmt-hashsig: 1.3.6.1.5.5.7.6.35 */ + static const byte sigXmssMtOid[] = {43, 6, 1, 5, 5, 7, 6, 35}; +#endif /* WOLFSSL_HAVE_XMSS */ /* keyType */ #ifndef NO_DSA @@ -4723,6 +4734,17 @@ static int ParseCRL_Extensions(DecodedCRL* dcrl, const byte* buf, word32* inOutI static const byte keySphincsSmall_Level5Oid[] = {43, 206, 15, 6, 9, 7}; #endif /* HAVE_SPHINCS */ +#ifdef WOLFSSL_HAVE_LMS + /* RFC 9802 id-alg-hss-lms-hashsig: 1.2.840.113549.1.9.16.3.17 */ + static const byte keyHssLmsOid[] = + {42, 134, 72, 134, 247, 13, 1, 9, 16, 3, 17}; +#endif /* WOLFSSL_HAVE_LMS */ +#ifdef WOLFSSL_HAVE_XMSS + /* RFC 9802 id-alg-xmss-hashsig: 1.3.6.1.5.5.7.6.34 */ + static const byte keyXmssOid[] = {43, 6, 1, 5, 5, 7, 6, 34}; + /* RFC 9802 id-alg-xmssmt-hashsig: 1.3.6.1.5.5.7.6.35 */ + static const byte keyXmssMtOid[] = {43, 6, 1, 5, 5, 7, 6, 35}; +#endif /* WOLFSSL_HAVE_XMSS */ /* curveType */ #ifdef HAVE_ECC @@ -5591,6 +5613,22 @@ const byte* OidFromId(word32 id, word32 type, word32* oidSz) *oidSz = sizeof(sigSphincsSmall_Level5Oid); break; #endif /* HAVE_SPHINCS */ + #ifdef WOLFSSL_HAVE_LMS + case CTC_HSS_LMS: + oid = sigHssLmsOid; + *oidSz = sizeof(sigHssLmsOid); + break; + #endif /* WOLFSSL_HAVE_LMS */ + #ifdef WOLFSSL_HAVE_XMSS + case CTC_XMSS: + oid = sigXmssOid; + *oidSz = sizeof(sigXmssOid); + break; + case CTC_XMSSMT: + oid = sigXmssMtOid; + *oidSz = sizeof(sigXmssMtOid); + break; + #endif /* WOLFSSL_HAVE_XMSS */ default: break; } @@ -5716,6 +5754,22 @@ const byte* OidFromId(word32 id, word32 type, word32* oidSz) *oidSz = sizeof(keySphincsSmall_Level5Oid); break; #endif /* HAVE_SPHINCS */ + #ifdef WOLFSSL_HAVE_LMS + case HSS_LMSk: + oid = keyHssLmsOid; + *oidSz = sizeof(keyHssLmsOid); + break; + #endif /* WOLFSSL_HAVE_LMS */ + #ifdef WOLFSSL_HAVE_XMSS + case XMSSk: + oid = keyXmssOid; + *oidSz = sizeof(keyXmssOid); + break; + case XMSSMTk: + oid = keyXmssMtOid; + *oidSz = sizeof(keyXmssMtOid); + break; + #endif /* WOLFSSL_HAVE_XMSS */ default: break; } @@ -12166,7 +12220,8 @@ void wc_FreeDecodedCert(DecodedCert* cert) } #if defined(HAVE_ED25519) || defined(HAVE_ED448) || defined(HAVE_FALCON) || \ - defined(HAVE_DILITHIUM) || defined(HAVE_SPHINCS) + defined(HAVE_DILITHIUM) || defined(HAVE_SPHINCS) || \ + defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS) /* Store the key data under the BIT_STRING in dynamically allocated data. * * @param [in, out] cert Certificate object. @@ -13096,6 +13151,22 @@ static int GetCertKey(DecodedCert* cert, const byte* source, word32* inOutIdx, ret = StoreKey(cert, source, &srcIdx, maxIdx); break; #endif /* HAVE_SPHINCS */ + #ifdef WOLFSSL_HAVE_LMS + case HSS_LMSk: + cert->pkCurveOID = HSS_LMSk; + ret = StoreKey(cert, source, &srcIdx, maxIdx); + break; + #endif /* WOLFSSL_HAVE_LMS */ + #ifdef WOLFSSL_HAVE_XMSS + case XMSSk: + cert->pkCurveOID = XMSSk; + ret = StoreKey(cert, source, &srcIdx, maxIdx); + break; + case XMSSMTk: + cert->pkCurveOID = XMSSMTk; + ret = StoreKey(cert, source, &srcIdx, maxIdx); + break; + #endif /* WOLFSSL_HAVE_XMSS */ #ifndef NO_DSA case DSAk: cert->publicKey = source + pubIdx; @@ -15567,6 +15638,13 @@ static WC_INLINE int IsSigAlgoECC(word32 algoOID) || (algoOID == SPHINCS_SMALL_LEVEL3k) || (algoOID == SPHINCS_SMALL_LEVEL5k) #endif + #ifdef WOLFSSL_HAVE_LMS + || (algoOID == HSS_LMSk) + #endif + #ifdef WOLFSSL_HAVE_XMSS + || (algoOID == XMSSk) + || (algoOID == XMSSMTk) + #endif ); } @@ -15914,6 +15992,25 @@ void FreeSignatureCtx(SignatureCtx* sigCtx) #endif break; #endif /* HAVE_SPHINCS */ + #ifdef WOLFSSL_HAVE_LMS + case HSS_LMSk: + wc_LmsKey_Free(sigCtx->key.lms); + #ifndef WOLFSSL_NO_MALLOC + XFREE(sigCtx->key.lms, sigCtx->heap, DYNAMIC_TYPE_LMS); + sigCtx->key.lms = NULL; + #endif + break; + #endif /* WOLFSSL_HAVE_LMS */ + #ifdef WOLFSSL_HAVE_XMSS + case XMSSk: + case XMSSMTk: + wc_XmssKey_Free(sigCtx->key.xmss); + #ifndef WOLFSSL_NO_MALLOC + XFREE(sigCtx->key.xmss, sigCtx->heap, DYNAMIC_TYPE_XMSS); + sigCtx->key.xmss = NULL; + #endif + break; + #endif /* WOLFSSL_HAVE_XMSS */ default: break; } /* switch (keyOID) */ @@ -16106,6 +16203,17 @@ static int HashForSignature(const byte* buf, word32 bufSz, word32 sigOID, /* Hashes done in signing operation. */ break; #endif + #ifdef WOLFSSL_HAVE_LMS + case CTC_HSS_LMS: + /* RFC 9802 sec 2: no digest is applied before signing. */ + break; + #endif + #ifdef WOLFSSL_HAVE_XMSS + case CTC_XMSS: + case CTC_XMSSMT: + /* RFC 9802 sec 2: no digest is applied before signing. */ + break; + #endif default: ret = HASH_TYPE_E; @@ -16291,6 +16399,16 @@ static int SigOidMatchesKeyOid(word32 sigOID, word32 keyOID) case SPHINCS_SMALL_LEVEL5k: return (sigOID == CTC_SPHINCS_SMALL_LEVEL5); #endif + #ifdef WOLFSSL_HAVE_LMS + case HSS_LMSk: + return (sigOID == CTC_HSS_LMS); + #endif + #ifdef WOLFSSL_HAVE_XMSS + case XMSSk: + return (sigOID == CTC_XMSS); + case XMSSMTk: + return (sigOID == CTC_XMSSMT); + #endif } /* Default to reject unknown key types */ @@ -16917,6 +17035,56 @@ int ConfirmSignature(SignatureCtx* sigCtx, break; } #endif /* HAVE_SPHINCS */ + #ifdef WOLFSSL_HAVE_LMS + case HSS_LMSk: + { + sigCtx->verify = 0; + #ifndef WOLFSSL_NO_MALLOC + sigCtx->key.lms = (LmsKey*)XMALLOC(sizeof(LmsKey), + sigCtx->heap, DYNAMIC_TYPE_LMS); + if (sigCtx->key.lms == NULL) { + ERROR_OUT(MEMORY_E, exit_cs); + } + #endif + if ((ret = wc_LmsKey_Init(sigCtx->key.lms, + sigCtx->heap, sigCtx->devId)) < 0) { + goto exit_cs; + } + /* RFC 9802: SubjectPublicKeyInfo BIT STRING holds the + * raw HSS public key bytes. Auto-derives params. */ + if ((ret = wc_LmsKey_ImportPubRaw(sigCtx->key.lms, + key, keySz)) < 0) { + WOLFSSL_MSG("ASN Key import error HSS/LMS"); + goto exit_cs; + } + break; + } + #endif /* WOLFSSL_HAVE_LMS */ + #ifdef WOLFSSL_HAVE_XMSS + case XMSSk: + case XMSSMTk: + { + int is_xmssmt = (keyOID == XMSSMTk); + sigCtx->verify = 0; + #ifndef WOLFSSL_NO_MALLOC + sigCtx->key.xmss = (XmssKey*)XMALLOC(sizeof(XmssKey), + sigCtx->heap, DYNAMIC_TYPE_XMSS); + if (sigCtx->key.xmss == NULL) { + ERROR_OUT(MEMORY_E, exit_cs); + } + #endif + if ((ret = wc_XmssKey_Init(sigCtx->key.xmss, + sigCtx->heap, sigCtx->devId)) < 0) { + goto exit_cs; + } + if ((ret = wc_XmssKey_ImportPubRaw_ex(sigCtx->key.xmss, + key, keySz, is_xmssmt)) < 0) { + WOLFSSL_MSG("ASN Key import error XMSS/XMSS^MT"); + goto exit_cs; + } + break; + } + #endif /* WOLFSSL_HAVE_XMSS */ default: WOLFSSL_MSG("Verify Key type unknown"); ret = ASN_UNKNOWN_OID_E; @@ -17118,6 +17286,25 @@ int ConfirmSignature(SignatureCtx* sigCtx, break; } #endif /* HAVE_SPHINCS */ + #ifdef WOLFSSL_HAVE_LMS + case HSS_LMSk: + { + ret = wc_LmsKey_Verify(sigCtx->key.lms, sig, sigSz, + buf, (int)bufSz); + sigCtx->verify = (ret == 0); + break; + } + #endif /* WOLFSSL_HAVE_LMS */ + #ifdef WOLFSSL_HAVE_XMSS + case XMSSk: + case XMSSMTk: + { + ret = wc_XmssKey_Verify(sigCtx->key.xmss, sig, sigSz, + buf, (int)bufSz); + sigCtx->verify = (ret == 0); + break; + } + #endif /* WOLFSSL_HAVE_XMSS */ default: break; } /* switch (keyOID) */ @@ -17382,6 +17569,33 @@ int ConfirmSignature(SignatureCtx* sigCtx, break; } #endif /* HAVE_SPHINCS */ + #ifdef WOLFSSL_HAVE_LMS + case HSS_LMSk: + { + if (sigCtx->verify == 1) { + ret = 0; + } + else { + WOLFSSL_MSG("HSS/LMS Verify didn't match"); + ret = ASN_SIG_CONFIRM_E; + } + break; + } + #endif /* WOLFSSL_HAVE_LMS */ + #ifdef WOLFSSL_HAVE_XMSS + case XMSSk: + case XMSSMTk: + { + if (sigCtx->verify == 1) { + ret = 0; + } + else { + WOLFSSL_MSG("XMSS/XMSS^MT Verify didn't match"); + ret = ASN_SIG_CONFIRM_E; + } + break; + } + #endif /* WOLFSSL_HAVE_XMSS */ default: break; } /* switch (keyOID) */ diff --git a/wolfcrypt/src/wc_lms.c b/wolfcrypt/src/wc_lms.c index 99a3e19e81..d2f0ee44dc 100644 --- a/wolfcrypt/src/wc_lms.c +++ b/wolfcrypt/src/wc_lms.c @@ -1418,35 +1418,95 @@ int wc_LmsKey_ExportPubRaw(const LmsKey* key, byte* out, word32* outLen) /* Imports a raw public key buffer from in array to LmsKey key. * - * The LMS parameters must be set first with wc_LmsKey_SetLmsParm or - * wc_LmsKey_SetParameters, and inLen must match the length returned - * by wc_LmsKey_GetPubLen. + * If the LMS parameters have already been configured (via + * wc_LmsKey_SetLmsParm or wc_LmsKey_SetParameters), the levels / + * lms_algorithm_type / lmots_algorithm_type encoded in the raw key are + * checked for consistency and inLen must match wc_LmsKey_GetPubLen. * - * Call wc_LmsKey_GetPubLen beforehand to determine pubLen. + * If the parameters have not yet been set (key->params == NULL), they + * are derived from the raw public key prefix (RFC 8554 sec 3.3 / sec + * 6.1: u32str(L) || lms_algorithm_type || lmots_algorithm_type) and + * matched against the static parameter map. The candidate is held in + * a local until the length check passes, so a length mismatch leaves + * key->params NULL. * * @param [in, out] key LMS key to put public key in. * @param [in] in Buffer holding encoded public key. * @param [in] inLen Length of encoded public key in bytes. * @return 0 on success. - * @return BAD_FUNC_ARG when key or in is NULL. - * @return BUFFER_E when inLen does not match public key length by parameters. + * @return BAD_FUNC_ARG when key or in is NULL, or when the raw key's + * levels / lmsType / lmOtsType disagree with pre-set params. + * @return BUFFER_E when inLen is too small to contain the LMS type + * fields, or doesn't match the public key length determined + * by parameters. + * @return NOT_COMPILED_IN when the derived parameter set isn't built in. */ int wc_LmsKey_ImportPubRaw(LmsKey* key, const byte* in, word32 inLen) { - int ret = 0; + int ret = 0; + const LmsParams* matched = NULL; /* Validate parameters. */ if ((key == NULL) || (in == NULL)) { ret = BAD_FUNC_ARG; } + /* Need at least L || lmsType || lmOtsType to derive or validate. */ + if ((ret == 0) && (inLen < (word32)(LMS_L_LEN + 2 * LMS_TYPE_LEN))) { + ret = BUFFER_E; + } + + if (ret == 0) { + word32 levels = 0; + word32 lmsType = 0; + word32 lmOtsType = 0; + + /* RFC 8554 sec 3.3 / sec 6.1: HSS public key = u32str(L) || pub[0], + * where pub[0] starts with lms_algorithm_type || lmots_algorithm_type. + */ + ato32(in + 0, &levels); + ato32(in + LMS_L_LEN, &lmsType); + ato32(in + LMS_L_LEN + LMS_TYPE_LEN, &lmOtsType); + + if (key->params == NULL) { + /* Auto-derive: find matching entry in the static map. Hold + * the candidate in a local until the length check passes to + * avoid leaving key->params half-set on failure. */ + int i; + ret = WC_NO_ERR_TRACE(NOT_COMPILED_IN); + for (i = 0; i < WC_LMS_MAP_LEN; i++) { + if (((word32)wc_lms_map[i].params.levels == levels) && + ((word32)wc_lms_map[i].params.lmsType == lmsType) && + ((word32)wc_lms_map[i].params.lmOtsType == lmOtsType)) { + matched = &wc_lms_map[i].params; + ret = 0; + break; + } + } + if (ret != 0) { + WOLFSSL_MSG("error: LMS params from pub key not supported"); + } + } + else { + /* Validate against pre-set params. */ + if (((word32)key->params->levels != levels) || + ((word32)key->params->lmsType != lmsType) || + ((word32)key->params->lmOtsType != lmOtsType)) { + WOLFSSL_MSG("error: LMS pub key doesn't match set params"); + ret = BAD_FUNC_ARG; + } + else { + matched = key->params; + } + } + } if ((ret == 0) && - (inLen != (word32)HSS_PUBLIC_KEY_LEN(key->params->hash_len))) { - /* Something inconsistent. Parameters weren't set, or input - * pub key is wrong.*/ - return BUFFER_E; + (inLen != (word32)HSS_PUBLIC_KEY_LEN(matched->hash_len))) { + ret = BUFFER_E; } if (ret == 0) { + /* Commit params (no-op when already set) and copy the key. */ + key->params = matched; XMEMCPY(key->pub, in, inLen); if (key->state != WC_LMS_STATE_OK) @@ -1465,14 +1525,15 @@ int wc_LmsKey_ImportPubRaw(LmsKey* key, const byte* in, word32 inLen) * @param [in] key LMS key. * @param [out] len Length of a signature in bytes. * @return 0 on success. - * @return BAD_FUNC_ARG when key or len is NULL. + * @return BAD_FUNC_ARG when key or len is NULL, or when the LMS + * parameters have not been configured on the key. */ int wc_LmsKey_GetSigLen(const LmsKey* key, word32* len) { int ret = 0; /* Validate parameters. */ - if ((key == NULL) || (len == NULL)) { + if ((key == NULL) || (len == NULL) || (key->params == NULL)) { ret = BAD_FUNC_ARG; } diff --git a/wolfcrypt/src/wc_xmss.c b/wolfcrypt/src/wc_xmss.c index 2a80deb217..596a21940b 100644 --- a/wolfcrypt/src/wc_xmss.c +++ b/wolfcrypt/src/wc_xmss.c @@ -1487,10 +1487,144 @@ int wc_XmssKey_ExportPubRaw(const XmssKey* key, byte* out, word32* outLen) return ret; } +/* Imports a raw public key buffer from in array to XmssKey key, taking + * an is_xmssmt hint to disambiguate the XMSS / XMSS^MT OID namespaces + * when params have not yet been configured on the key. + * + * Accepts a key in INITED, PARMSET, OK or VERIFYONLY state. When state + * is INITED, params are derived from the 4-byte OID prefix at the start + * of the raw key (RFC 8391 Appendix B.1 / C.1) using is_xmssmt to pick + * the XMSS or XMSS^MT table; key->oid, key->is_xmssmt and key->params + * are populated. When params have already been set, the 4-byte OID + * prefix is checked for consistency and is_xmssmt is ignored. + * + * @param [in, out] key XMSS key. + * @param [in] in Array holding public key. + * @param [in] inLen Length of array in bytes. + * @param [in] is_xmssmt 0 to search the XMSS table, non-zero to + * search the XMSS^MT table. Only used when + * the key is in the INITED state. + * + * @return 0 on success. + * @return BAD_FUNC_ARG when a parameter is NULL or the OID prefix + * contradicts pre-set params. + * @return BUFFER_E if array is incorrect size. + * @return BAD_STATE_E when wrong state for operation. + * @return NOT_COMPILED_IN when the derived parameter set isn't built in. + */ +int wc_XmssKey_ImportPubRaw_ex(XmssKey* key, const byte* in, word32 inLen, + int is_xmssmt) +{ + int ret = 0; + word32 pubLen = 0; + word32 oid = 0; + + /* Validate parameters. */ + if ((key == NULL) || (in == NULL)) { + ret = BAD_FUNC_ARG; + } + if ((ret == 0) && (inLen < XMSS_OID_LEN)) { + ret = BUFFER_E; + } + + /* Reject only states where the key is unusable. INITED means params + * not yet configured (we'll derive them); PARMSET / OK / VERIFYONLY + * means params are already set (we verify the OID prefix matches). */ + if ((ret == 0) && + (key->state != WC_XMSS_STATE_INITED) && + (key->state != WC_XMSS_STATE_PARMSET) && + (key->state != WC_XMSS_STATE_OK) && + (key->state != WC_XMSS_STATE_VERIFYONLY)) { + WOLFSSL_MSG("error: XMSS key not ready for import"); + ret = BAD_STATE_E; + } + + if (ret == 0) { + /* OID is encoded big-endian in the first 4 bytes. */ + ato32(in, &oid); + + if (key->state == WC_XMSS_STATE_INITED) { + /* Auto-derive params from OID prefix, using is_xmssmt hint. */ + ret = WC_NO_ERR_TRACE(NOT_COMPILED_IN); + if (is_xmssmt) { + #if WOLFSSL_XMSS_MAX_HEIGHT >= 20 + unsigned int i; + for (i = 0; i < WC_XMSSMT_ALG_LEN; i++) { + if (wc_xmssmt_alg[i].oid == oid) { + key->params = &wc_xmssmt_alg[i].params; + ret = 0; + break; + } + } + #else + /* XMSS^MT compiled out; ret stays at NOT_COMPILED_IN. */ + (void)oid; + #endif + } + else { + #if WOLFSSL_XMSS_MIN_HEIGHT <= 20 + unsigned int i; + for (i = 0; i < WC_XMSS_ALG_LEN; i++) { + if (wc_xmss_alg[i].oid == oid) { + key->params = &wc_xmss_alg[i].params; + ret = 0; + break; + } + } + #else + /* XMSS compiled out; ret stays at NOT_COMPILED_IN. */ + (void)oid; + #endif + } + + if (ret != 0) { + WOLFSSL_MSG("error: XMSS OID from pub key not supported"); + } + else { + key->oid = oid; + key->is_xmssmt = is_xmssmt ? 1 : 0; + key->state = WC_XMSS_STATE_PARMSET; + } + } + else { + /* Params already set; OID prefix and family must match. */ + if (oid != key->oid) { + WOLFSSL_MSG("error: XMSS pub OID doesn't match set params"); + ret = BAD_FUNC_ARG; + } + else if ((is_xmssmt ? 1 : 0) != (int)key->is_xmssmt) { + WOLFSSL_MSG("error: XMSS is_xmssmt hint contradicts set params"); + ret = BAD_FUNC_ARG; + } + } + } + + /* Length check (params guaranteed set on success path). */ + if (ret == 0) { + ret = wc_XmssKey_GetPubLen(key, &pubLen); + } + if ((ret == 0) && (inLen != pubLen)) { + ret = BUFFER_E; + } + + if (ret == 0) { + /* Copy the public key data into key (skipping the OID prefix). */ + XMEMCPY(key->pk, in + XMSS_OID_LEN, pubLen - XMSS_OID_LEN); + + if (key->state != WC_XMSS_STATE_OK) + key->state = WC_XMSS_STATE_VERIFYONLY; + } + + return ret; +} + /* Imports a raw public key buffer from in array to XmssKey key. * * The XMSS parameters must be set first with wc_XmssKey_SetParamStr, * and inLen must match the length returned by wc_XmssKey_GetPubLen. + * If the caller only has the raw public-key bytes and has not yet + * configured the parameter set, use wc_XmssKey_ImportPubRaw_ex which + * derives parameters from the OID prefix at the start of the buffer. * * @param [in, out] key XMSS key. * @param [in] in Array holding public key. @@ -1559,7 +1693,7 @@ int wc_XmssKey_GetSigLen(const XmssKey* key, word32* len) int ret = 0; /* Validate parameters. */ - if ((key == NULL) || (len == NULL)) { + if ((key == NULL) || (len == NULL) || (key->params == NULL)) { ret = BAD_FUNC_ARG; } /* Validate state. */ diff --git a/wolfssl/wolfcrypt/asn.h b/wolfssl/wolfcrypt/asn.h index abdfa953f9..3838d559c1 100644 --- a/wolfssl/wolfcrypt/asn.h +++ b/wolfssl/wolfcrypt/asn.h @@ -76,6 +76,12 @@ that can be serialized and deserialized in a cross-platform way. #ifdef HAVE_DILITHIUM #include #endif +#ifdef WOLFSSL_HAVE_LMS + #include +#endif +#ifdef WOLFSSL_HAVE_XMSS + #include +#endif #ifndef NO_SHA #include #endif @@ -1602,6 +1608,20 @@ struct SignatureCtx { struct sphincs_key* sphincs; #endif #endif + #ifdef WOLFSSL_HAVE_LMS + #ifdef WOLFSSL_NO_MALLOC + LmsKey lms[1]; + #else + LmsKey* lms; + #endif + #endif + #ifdef WOLFSSL_HAVE_XMSS + #ifdef WOLFSSL_NO_MALLOC + XmssKey xmss[1]; + #else + XmssKey* xmss; + #endif + #endif #ifndef WOLFSSL_NO_MALLOC void* ptr; #endif @@ -2695,7 +2715,12 @@ enum cert_enums { SPHINCS_FAST_LEVEL5_KEY = 26, SPHINCS_SMALL_LEVEL1_KEY = 27, SPHINCS_SMALL_LEVEL3_KEY = 28, - SPHINCS_SMALL_LEVEL5_KEY = 29 + SPHINCS_SMALL_LEVEL5_KEY = 29, + /* RFC 9802. Reserved for future cert-gen support; verify-only + * today (wolfcrypt/src/asn.c is driven by DecodedCert.keyOID). */ + HSS_LMS_KEY = 30, + XMSS_KEY = 31, + XMSSMT_KEY = 32 }; #endif /* WOLFSSL_CERT_GEN */ diff --git a/wolfssl/wolfcrypt/oid_sum.h b/wolfssl/wolfcrypt/oid_sum.h index f0d4a2fe30..1a303393f8 100644 --- a/wolfssl/wolfcrypt/oid_sum.h +++ b/wolfssl/wolfcrypt/oid_sum.h @@ -207,7 +207,13 @@ enum Key_Sum { /* 0x2b,0xce,0x0f,0x06,0x08,0x07 */ SPHINCS_SMALL_LEVEL3k = 285, /* 1.3.9999.6.8.7 */ /* 0x2b,0xce,0x0f,0x06,0x09,0x07 */ - SPHINCS_SMALL_LEVEL5k = 286 /* 1.3.9999.6.9.7 */ + SPHINCS_SMALL_LEVEL5k = 286, /* 1.3.9999.6.9.7 */ + /* 0x2a,0x86,0x48,0x86,0xf7,0x0d,0x01,0x09,0x10,0x03,0x11 */ + HSS_LMSk = 688, /* 1.2.840.113549.1.9.16.3.17 */ + /* 0x2b,0x06,0x01,0x05,0x05,0x07,0x06,0x22 */ + XMSSk = 107, /* 1.3.6.1.5.5.7.6.34 */ + /* 0x2b,0x06,0x01,0x05,0x05,0x07,0x06,0x23 */ + XMSSMTk = 108 /* 1.3.6.1.5.5.7.6.35 */ #else /* 0x00 */ ANONk = 0x7fffffff, /* 0.0 */ @@ -260,7 +266,13 @@ enum Key_Sum { /* 0x2b,0xce,0x0f,0x06,0x08,0x07 */ SPHINCS_SMALL_LEVEL3k = 0x06f0c923, /* 1.3.9999.6.8.7 */ /* 0x2b,0xce,0x0f,0x06,0x09,0x07 */ - SPHINCS_SMALL_LEVEL5k = 0x06f0c922 /* 1.3.9999.6.9.7 */ + SPHINCS_SMALL_LEVEL5k = 0x06f0c922, /* 1.3.9999.6.9.7 */ + /* 0x2a,0x86,0x48,0x86,0xf7,0x0d,0x01,0x09,0x10,0x03,0x11 */ + HSS_LMSk = 0x70a78832, /* 1.2.840.113549.1.9.16.3.17 */ + /* 0x2b,0x06,0x01,0x05,0x05,0x07,0x06,0x22 */ + XMSSk = 0x2707012e, /* 1.3.6.1.5.5.7.6.34 */ + /* 0x2b,0x06,0x01,0x05,0x05,0x07,0x06,0x23 */ + XMSSMTk = 0x2607012e /* 1.3.6.1.5.5.7.6.35 */ #endif }; @@ -1591,7 +1603,13 @@ enum Ctc_SigType { /* 0x2b,0xce,0x0f,0x06,0x08,0x07 */ CTC_SPHINCS_SMALL_LEVEL3 = 285, /* 1.3.9999.6.8.7 */ /* 0x2b,0xce,0x0f,0x06,0x09,0x07 */ - CTC_SPHINCS_SMALL_LEVEL5 = 286 /* 1.3.9999.6.9.7 */ + CTC_SPHINCS_SMALL_LEVEL5 = 286, /* 1.3.9999.6.9.7 */ + /* 0x2a,0x86,0x48,0x86,0xf7,0x0d,0x01,0x09,0x10,0x03,0x11 */ + CTC_HSS_LMS = 688, /* 1.2.840.113549.1.9.16.3.17 */ + /* 0x2b,0x06,0x01,0x05,0x05,0x07,0x06,0x22 */ + CTC_XMSS = 107, /* 1.3.6.1.5.5.7.6.34 */ + /* 0x2b,0x06,0x01,0x05,0x05,0x07,0x06,0x23 */ + CTC_XMSSMT = 108 /* 1.3.6.1.5.5.7.6.35 */ #else /* 0x2a,0x86,0x48,0xce,0x38,0x04,0x03 */ CTC_SHAwDSA = 0x314b8212, /* 1.2.840.10040.4.3 */ @@ -1672,7 +1690,13 @@ enum Ctc_SigType { /* 0x2b,0xce,0x0f,0x06,0x08,0x07 */ CTC_SPHINCS_SMALL_LEVEL3 = 0x06f0c923, /* 1.3.9999.6.8.7 */ /* 0x2b,0xce,0x0f,0x06,0x09,0x07 */ - CTC_SPHINCS_SMALL_LEVEL5 = 0x06f0c922 /* 1.3.9999.6.9.7 */ + CTC_SPHINCS_SMALL_LEVEL5 = 0x06f0c922, /* 1.3.9999.6.9.7 */ + /* 0x2a,0x86,0x48,0x86,0xf7,0x0d,0x01,0x09,0x10,0x03,0x11 */ + CTC_HSS_LMS = 0x70a78832, /* 1.2.840.113549.1.9.16.3.17 */ + /* 0x2b,0x06,0x01,0x05,0x05,0x07,0x06,0x22 */ + CTC_XMSS = 0x2707012e, /* 1.3.6.1.5.5.7.6.34 */ + /* 0x2b,0x06,0x01,0x05,0x05,0x07,0x06,0x23 */ + CTC_XMSSMT = 0x2607012e /* 1.3.6.1.5.5.7.6.35 */ #endif }; diff --git a/wolfssl/wolfcrypt/types.h b/wolfssl/wolfcrypt/types.h index b6c8049e48..29962ebfec 100644 --- a/wolfssl/wolfcrypt/types.h +++ b/wolfssl/wolfcrypt/types.h @@ -1381,6 +1381,7 @@ enum { DYNAMIC_TYPE_SHA = 106, DYNAMIC_TYPE_SLHDSA = 107, DYNAMIC_TYPE_OCSP_RESPONSE = 108, + DYNAMIC_TYPE_XMSS = 109, DYNAMIC_TYPE_SNIFFER_SERVER = 1000, DYNAMIC_TYPE_SNIFFER_SESSION = 1001, DYNAMIC_TYPE_SNIFFER_PB = 1002, diff --git a/wolfssl/wolfcrypt/wc_xmss.h b/wolfssl/wolfcrypt/wc_xmss.h index 5ed8823b65..80fe0d6991 100644 --- a/wolfssl/wolfcrypt/wc_xmss.h +++ b/wolfssl/wolfcrypt/wc_xmss.h @@ -422,6 +422,8 @@ WOLFSSL_API int wc_XmssKey_ExportPubRaw(const XmssKey* key, byte* out, word32* outLen); WOLFSSL_API int wc_XmssKey_ImportPubRaw(XmssKey* key, const byte* in, word32 inLen); +WOLFSSL_API int wc_XmssKey_ImportPubRaw_ex(XmssKey* key, const byte* in, + word32 inLen, int is_xmssmt); WOLFSSL_API int wc_XmssKey_Verify(XmssKey* key, const byte* sig, word32 sigSz, const byte* msg, int msgSz);