From 1f9c74f7e6ac2ab514e4584393eda2117a3bfcb9 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Jul 2026 20:42:44 +0000 Subject: [PATCH 1/3] Add SLH-DSA entity certificate support for the TLS 1.3 handshake signature Implement use of SLH-DSA (FIPS 205) entity certificates for TLS 1.3 client/server authentication, i.e. producing and consuming the CertificateVerify handshake signature with SLH-DSA. Follows draft-reddy-tls-slhdsa: the twelve SignatureScheme code points 0x0911-0x091C (matching oqs-provider and the FIPS 205 OIDs already present in asn.c), pure SLH-DSA with an empty context. TLS 1.3 only, mirroring the existing ML-DSA integration. Because SLH-DSA signatures range from ~7.8KB to ~50KB, a single CertificateVerify can exceed the 16KB TLS record limit. SendTls13CertificateVerify now fragments the message across multiple encrypted records when needed (mirroring SendTls13Certificate); the single-record fast path is unchanged for all other algorithms. Incoming reassembly is already handled generically by DoTls13HandShakeMsg. WC_MAX_CERT_VERIFY_SZ and MAX_X509_SIZE gain SLH-DSA branches so the large signatures fit the send buffer and the handshake-message reassembly cap. Details: - internal.h: slhdsa_*_sa_algo enum values, wire code points, SIG_SLHDSA mask, peerSlhDsaKey field, haveSlhDsaSig option flags. - tls13.c: EncodeSigAlg/DecodeTls13SigAlg cases (ML-DSA and SLH-DSA share the 0x09 major byte with disjoint minors), sign via wc_SlhDsaKey_Sign, verify via wc_SlhDsaKey_Verify, decodeSlhDsaKey, fragmented CertificateVerify emit. - internal.c: shared sa_algo<->param<->minor mapping helpers, DecodeSigAlg, AddSuiteHashSigAlgo/InitSuitesHashSigAlgo (advertising only compiled-in parameter sets), ProcessPeerCerts peer-key decode, DecodePrivateKey_ex, Alloc/Free/Reuse key plumbing, haveSig masks. - ssl_load.c: ProcessBufferTryDecodeSlhDsa and cert/key-OID have-flag and keyType detection. - wc_slhdsa.h: WC_SLHDSA_DEFAULT_PARAM placeholder macro (the SHAKE family may be disabled when only SHA2 is built, so a fixed SHAKE placeholder cannot be assumed). Testing: adds tests/test-tls13-slhdsa-entity.conf (registered in suites.c) using a self-signed SLH-DSA-SHAKE-128f entity certificate whose ~17KB signature exercises fragmented CertificateVerify send and reassembly in both directions, plus certs/slhdsa/gen-slhdsa-entity-cert.c to regenerate the fixtures. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01SeLwE2QcsVHDFR5iXW6Hcj --- .../slhdsa/entity-slhdsa-shake-128f-priv.der | Bin 0 -> 84 bytes .../slhdsa/entity-slhdsa-shake-128f-priv.pem | 4 + certs/slhdsa/entity-slhdsa-shake-128f.der | Bin 0 -> 17664 bytes certs/slhdsa/entity-slhdsa-shake-128f.pem | 370 +++++++++++++++++ certs/slhdsa/gen-slhdsa-entity-cert.c | 144 +++++++ certs/slhdsa/include.am | 7 +- src/internal.c | 372 +++++++++++++++++- src/ssl_load.c | 201 +++++++++- src/tls13.c | 349 ++++++++++++++-- tests/suites.c | 15 + tests/test-tls13-slhdsa-entity.conf | 40 ++ wolfssl/internal.h | 50 ++- wolfssl/wolfcrypt/asn.h | 7 +- wolfssl/wolfcrypt/wc_slhdsa.h | 33 ++ 14 files changed, 1552 insertions(+), 40 deletions(-) create mode 100644 certs/slhdsa/entity-slhdsa-shake-128f-priv.der create mode 100644 certs/slhdsa/entity-slhdsa-shake-128f-priv.pem create mode 100644 certs/slhdsa/entity-slhdsa-shake-128f.der create mode 100644 certs/slhdsa/entity-slhdsa-shake-128f.pem create mode 100644 certs/slhdsa/gen-slhdsa-entity-cert.c create mode 100644 tests/test-tls13-slhdsa-entity.conf diff --git a/certs/slhdsa/entity-slhdsa-shake-128f-priv.der b/certs/slhdsa/entity-slhdsa-shake-128f-priv.der new file mode 100644 index 0000000000000000000000000000000000000000..33bec54677d69736ccd857b8a5ab460aae98e0a9 GIT binary patch literal 84 zcmV-a0IUBnQUU=0Fbf6=V1`HmWdj5Q8w5a6C1X)luTmi@H-md5{9z}+a#8L5y3d+} qUz`0wq{4O@csJEjSe)7+Rn}!IHUivbo)0GtvnOdgnyPKasMZ95t{?{h literal 0 HcmV?d00001 diff --git a/certs/slhdsa/entity-slhdsa-shake-128f-priv.pem b/certs/slhdsa/entity-slhdsa-shake-128f-priv.pem new file mode 100644 index 0000000000..911fdcf56e --- /dev/null +++ b/certs/slhdsa/entity-slhdsa-shake-128f-priv.pem @@ -0,0 +1,4 @@ +-----BEGIN PRIVATE KEY----- +MFICAQAwCwYJYIZIAWUDBAMbBEBRJWNRVa9SISo3g3sk/GEnwHJR7f26z5qDX5v9 +QaTCdhp4N9VSWJzaIVXWZSs2Atxlng8nDbMnaTuaqm3GqNYE +-----END PRIVATE KEY----- diff --git a/certs/slhdsa/entity-slhdsa-shake-128f.der b/certs/slhdsa/entity-slhdsa-shake-128f.der new file mode 100644 index 0000000000000000000000000000000000000000..908e5a041fec3d9dab2ea5c2af77ca0a63b7a813 GIT binary patch literal 17664 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 +#include +#include +#include +#include +#include +#include +#include + +#define GEN_BUF_SZ 70000 + +static int save(const char* path, const byte* buf, int sz) +{ + FILE* f = fopen(path, "wb"); + if (f == NULL) { + printf("Unable to open %s for writing\n", path); + return -1; + } + fwrite(buf, 1, (size_t)sz, f); + fclose(f); + return 0; +} + +int main(void) +{ + WC_RNG rng; + SlhDsaKey key; + Cert cert; + byte* der = NULL; + byte* keyder = NULL; + byte* pem = NULL; + int ret, certSz, keySz, pemSz; + + wolfCrypt_Init(); + + if (wc_InitRng(&rng) != 0) { + printf("RNG init failed\n"); + return 1; + } + if ((ret = wc_SlhDsaKey_Init(&key, SLHDSA_SHAKE128F, NULL, + INVALID_DEVID)) != 0) { + printf("wc_SlhDsaKey_Init failed: %d\n", ret); + return 1; + } + if ((ret = wc_SlhDsaKey_MakeKey(&key, &rng)) != 0) { + printf("wc_SlhDsaKey_MakeKey failed: %d\n", ret); + return 1; + } + + der = (byte*)malloc(GEN_BUF_SZ); + keyder = (byte*)malloc(GEN_BUF_SZ); + pem = (byte*)malloc(GEN_BUF_SZ); + if (der == NULL || keyder == NULL || pem == NULL) { + printf("Out of memory\n"); + return 1; + } + + /* PKCS#8 private key (DER + PEM). */ + keySz = wc_SlhDsaKey_PrivateKeyToDer(&key, keyder, GEN_BUF_SZ); + if (keySz < 0) { + printf("wc_SlhDsaKey_PrivateKeyToDer failed: %d\n", keySz); + return 1; + } + save("certs/slhdsa/entity-slhdsa-shake-128f-priv.der", keyder, keySz); + + /* Self-signed certificate (DER + PEM). */ + wc_InitCert(&cert); + strncpy(cert.subject.country, "US", CTC_NAME_SIZE); + strncpy(cert.subject.state, "Montana", CTC_NAME_SIZE); + strncpy(cert.subject.locality, "Bozeman", CTC_NAME_SIZE); + strncpy(cert.subject.org, "wolfSSL_SLH-DSA", CTC_NAME_SIZE); + strncpy(cert.subject.unit, "Entity-SLHDSA-shake-128f", CTC_NAME_SIZE); + strncpy(cert.subject.commonName, "www.wolfssl.com", CTC_NAME_SIZE); + strncpy(cert.subject.email, "facts@wolfssl.com", CTC_NAME_SIZE); + cert.daysValid = 1000; + cert.selfSigned = 1; + cert.isCA = 1; /* self-signed, also usable as its own trust anchor */ + cert.sigType = CTC_SLH_DSA_SHAKE_128F; + + if ((ret = wc_SetSubjectKeyIdFromPublicKey_ex(&cert, + SLH_DSA_SHAKE_128F_TYPE, &key)) < 0) { + printf("wc_SetSubjectKeyIdFromPublicKey_ex failed: %d\n", ret); + return 1; + } + if ((ret = wc_SetAuthKeyIdFromPublicKey_ex(&cert, + SLH_DSA_SHAKE_128F_TYPE, &key)) < 0) { + printf("wc_SetAuthKeyIdFromPublicKey_ex failed: %d\n", ret); + return 1; + } + + ret = wc_MakeCert_ex(&cert, der, GEN_BUF_SZ, SLH_DSA_SHAKE_128F_TYPE, + &key, &rng); + if (ret < 0) { + printf("wc_MakeCert_ex failed: %d\n", ret); + return 1; + } + ret = wc_SignCert_ex(cert.bodySz, cert.sigType, der, GEN_BUF_SZ, + SLH_DSA_SHAKE_128F_TYPE, &key, &rng); + if (ret < 0) { + printf("wc_SignCert_ex failed: %d\n", ret); + return 1; + } + certSz = ret; + save("certs/slhdsa/entity-slhdsa-shake-128f.der", der, certSz); + + pemSz = wc_DerToPem(der, certSz, pem, GEN_BUF_SZ, CERT_TYPE); + if (pemSz > 0) + save("certs/slhdsa/entity-slhdsa-shake-128f.pem", pem, pemSz); + pemSz = wc_DerToPem(keyder, keySz, pem, GEN_BUF_SZ, PKCS8_PRIVATEKEY_TYPE); + if (pemSz > 0) + save("certs/slhdsa/entity-slhdsa-shake-128f-priv.pem", pem, pemSz); + + printf("Generated SLH-DSA-SHAKE-128f entity cert (certSz=%d keySz=%d)\n", + certSz, keySz); + + free(der); + free(keyder); + free(pem); + wc_SlhDsaKey_Free(&key); + wc_FreeRng(&rng); + wolfCrypt_Cleanup(); + return 0; +} diff --git a/certs/slhdsa/include.am b/certs/slhdsa/include.am index 2d9a1bdd37..cafc997805 100644 --- a/certs/slhdsa/include.am +++ b/certs/slhdsa/include.am @@ -33,4 +33,9 @@ EXTRA_DIST += \ certs/slhdsa/server-mldsa44-sha2.pem \ certs/slhdsa/server-mldsa44-sha2.der \ certs/slhdsa/client-mldsa44-sha2.pem \ - certs/slhdsa/client-mldsa44-sha2.der + certs/slhdsa/client-mldsa44-sha2.der \ + certs/slhdsa/gen-slhdsa-entity-cert.c \ + certs/slhdsa/entity-slhdsa-shake-128f-priv.pem \ + certs/slhdsa/entity-slhdsa-shake-128f-priv.der \ + certs/slhdsa/entity-slhdsa-shake-128f.pem \ + certs/slhdsa/entity-slhdsa-shake-128f.der diff --git a/src/internal.c b/src/internal.c index 8c73463766..3a4ee6e5fa 100644 --- a/src/internal.c +++ b/src/internal.c @@ -2356,6 +2356,11 @@ int InitSSL_Side(WOLFSSL* ssl, word16 side) ssl->options.haveMlDsaSig = 1; /* always on client side */ } #endif /* WOLFSSL_HAVE_MLDSA */ +#ifdef WOLFSSL_HAVE_SLHDSA + if (ssl->options.side == WOLFSSL_CLIENT_END) { + ssl->options.haveSlhDsaSig = 1; /* always on client side */ + } +#endif /* WOLFSSL_HAVE_SLHDSA */ #if defined(HAVE_EXTENDED_MASTER) && !defined(NO_WOLFSSL_CLIENT) if (ssl->options.side == WOLFSSL_CLIENT_END) { @@ -2774,6 +2779,11 @@ int InitSSL_Ctx(WOLFSSL_CTX* ctx, WOLFSSL_METHOD* method, void* heap) ctx->haveMlDsaSig = 1; /* always on client side */ /* server can turn on by loading key */ #endif /* WOLFSSL_HAVE_MLDSA */ +#ifdef WOLFSSL_HAVE_SLHDSA + if (method->side == WOLFSSL_CLIENT_END) + ctx->haveSlhDsaSig = 1; /* always on client side */ + /* server can turn on by loading key */ +#endif /* WOLFSSL_HAVE_SLHDSA */ #ifdef HAVE_ECC if (method->side == WOLFSSL_CLIENT_END) { ctx->haveECDSAsig = 1; /* always on client side */ @@ -3475,6 +3485,70 @@ static WC_INLINE void AddSuiteHashSigAlgo(byte* hashSigAlgo, byte macAlgo, } else #endif /* WOLFSSL_HAVE_MLDSA */ + #ifdef WOLFSSL_HAVE_SLHDSA + #ifdef WOLFSSL_SLHDSA_SHA2 + if (sigAlgo == slhdsa_sha2_128s_sa_algo) { + ADD_HASH_SIG_ALGO(hashSigAlgo, inOutIdx, + SLHDSA_SA_MAJOR, SLHDSA_SHA2_128S_SA_MINOR); + } + else + if (sigAlgo == slhdsa_sha2_128f_sa_algo) { + ADD_HASH_SIG_ALGO(hashSigAlgo, inOutIdx, + SLHDSA_SA_MAJOR, SLHDSA_SHA2_128F_SA_MINOR); + } + else + if (sigAlgo == slhdsa_sha2_192s_sa_algo) { + ADD_HASH_SIG_ALGO(hashSigAlgo, inOutIdx, + SLHDSA_SA_MAJOR, SLHDSA_SHA2_192S_SA_MINOR); + } + else + if (sigAlgo == slhdsa_sha2_192f_sa_algo) { + ADD_HASH_SIG_ALGO(hashSigAlgo, inOutIdx, + SLHDSA_SA_MAJOR, SLHDSA_SHA2_192F_SA_MINOR); + } + else + if (sigAlgo == slhdsa_sha2_256s_sa_algo) { + ADD_HASH_SIG_ALGO(hashSigAlgo, inOutIdx, + SLHDSA_SA_MAJOR, SLHDSA_SHA2_256S_SA_MINOR); + } + else + if (sigAlgo == slhdsa_sha2_256f_sa_algo) { + ADD_HASH_SIG_ALGO(hashSigAlgo, inOutIdx, + SLHDSA_SA_MAJOR, SLHDSA_SHA2_256F_SA_MINOR); + } + else + #endif /* WOLFSSL_SLHDSA_SHA2 */ + if (sigAlgo == slhdsa_shake_128s_sa_algo) { + ADD_HASH_SIG_ALGO(hashSigAlgo, inOutIdx, + SLHDSA_SA_MAJOR, SLHDSA_SHAKE_128S_SA_MINOR); + } + else + if (sigAlgo == slhdsa_shake_128f_sa_algo) { + ADD_HASH_SIG_ALGO(hashSigAlgo, inOutIdx, + SLHDSA_SA_MAJOR, SLHDSA_SHAKE_128F_SA_MINOR); + } + else + if (sigAlgo == slhdsa_shake_192s_sa_algo) { + ADD_HASH_SIG_ALGO(hashSigAlgo, inOutIdx, + SLHDSA_SA_MAJOR, SLHDSA_SHAKE_192S_SA_MINOR); + } + else + if (sigAlgo == slhdsa_shake_192f_sa_algo) { + ADD_HASH_SIG_ALGO(hashSigAlgo, inOutIdx, + SLHDSA_SA_MAJOR, SLHDSA_SHAKE_192F_SA_MINOR); + } + else + if (sigAlgo == slhdsa_shake_256s_sa_algo) { + ADD_HASH_SIG_ALGO(hashSigAlgo, inOutIdx, + SLHDSA_SA_MAJOR, SLHDSA_SHAKE_256S_SA_MINOR); + } + else + if (sigAlgo == slhdsa_shake_256f_sa_algo) { + ADD_HASH_SIG_ALGO(hashSigAlgo, inOutIdx, + SLHDSA_SA_MAJOR, SLHDSA_SHAKE_256F_SA_MINOR); + } + else + #endif /* WOLFSSL_HAVE_SLHDSA */ #ifdef WC_RSA_PSS if (sigAlgo == rsa_pss_sa_algo) { /* RSA PSS is sig then mac */ @@ -3589,6 +3663,60 @@ void InitSuitesHashSigAlgo(byte* hashSigAlgo, int haveSig, int tls1_2, keySz, &idx); } #endif /* WOLFSSL_HAVE_MLDSA */ +#ifdef WOLFSSL_HAVE_SLHDSA + /* Only advertise the parameter sets that are actually compiled in, so we + * never offer a scheme we cannot sign or verify. */ + if (haveSig & SIG_SLHDSA) { + #if defined(WOLFSSL_SLHDSA_SHA2) && defined(WOLFSSL_SLHDSA_PARAM_SHA2_128S) + AddSuiteHashSigAlgo(hashSigAlgo, no_mac, slhdsa_sha2_128s_sa_algo, + keySz, &idx); + #endif + #if defined(WOLFSSL_SLHDSA_SHA2) && defined(WOLFSSL_SLHDSA_PARAM_SHA2_128F) + AddSuiteHashSigAlgo(hashSigAlgo, no_mac, slhdsa_sha2_128f_sa_algo, + keySz, &idx); + #endif + #if defined(WOLFSSL_SLHDSA_SHA2) && defined(WOLFSSL_SLHDSA_PARAM_SHA2_192S) + AddSuiteHashSigAlgo(hashSigAlgo, no_mac, slhdsa_sha2_192s_sa_algo, + keySz, &idx); + #endif + #if defined(WOLFSSL_SLHDSA_SHA2) && defined(WOLFSSL_SLHDSA_PARAM_SHA2_192F) + AddSuiteHashSigAlgo(hashSigAlgo, no_mac, slhdsa_sha2_192f_sa_algo, + keySz, &idx); + #endif + #if defined(WOLFSSL_SLHDSA_SHA2) && defined(WOLFSSL_SLHDSA_PARAM_SHA2_256S) + AddSuiteHashSigAlgo(hashSigAlgo, no_mac, slhdsa_sha2_256s_sa_algo, + keySz, &idx); + #endif + #if defined(WOLFSSL_SLHDSA_SHA2) && defined(WOLFSSL_SLHDSA_PARAM_SHA2_256F) + AddSuiteHashSigAlgo(hashSigAlgo, no_mac, slhdsa_sha2_256f_sa_algo, + keySz, &idx); + #endif + #ifdef WOLFSSL_SLHDSA_PARAM_128S + AddSuiteHashSigAlgo(hashSigAlgo, no_mac, slhdsa_shake_128s_sa_algo, + keySz, &idx); + #endif + #ifdef WOLFSSL_SLHDSA_PARAM_128F + AddSuiteHashSigAlgo(hashSigAlgo, no_mac, slhdsa_shake_128f_sa_algo, + keySz, &idx); + #endif + #ifdef WOLFSSL_SLHDSA_PARAM_192S + AddSuiteHashSigAlgo(hashSigAlgo, no_mac, slhdsa_shake_192s_sa_algo, + keySz, &idx); + #endif + #ifdef WOLFSSL_SLHDSA_PARAM_192F + AddSuiteHashSigAlgo(hashSigAlgo, no_mac, slhdsa_shake_192f_sa_algo, + keySz, &idx); + #endif + #ifdef WOLFSSL_SLHDSA_PARAM_256S + AddSuiteHashSigAlgo(hashSigAlgo, no_mac, slhdsa_shake_256s_sa_algo, + keySz, &idx); + #endif + #ifdef WOLFSSL_SLHDSA_PARAM_256F + AddSuiteHashSigAlgo(hashSigAlgo, no_mac, slhdsa_shake_256f_sa_algo, + keySz, &idx); + #endif + } +#endif /* WOLFSSL_HAVE_SLHDSA */ if (haveSig & SIG_RSA) { #ifdef WC_RSA_PSS if (tls1_2) { @@ -4750,6 +4878,84 @@ void InitSuites(Suites* suites, ProtocolVersion pv, int keySz, word16 haveRSA, * hashalgo The hash algorithm. * hsType The signature type. */ +#ifdef WOLFSSL_HAVE_SLHDSA +/* Map a TLS SLH-DSA signature-scheme minor byte (draft-reddy-tls-slhdsa) to + * the internal sa_algo. Returns invalid_sa_algo if unrecognized or the + * parameter family is not compiled in. */ +byte SlhDsaSigMinorToType(byte minor) +{ + switch (minor) { + #ifdef WOLFSSL_SLHDSA_SHA2 + case SLHDSA_SHA2_128S_SA_MINOR: return slhdsa_sha2_128s_sa_algo; + case SLHDSA_SHA2_128F_SA_MINOR: return slhdsa_sha2_128f_sa_algo; + case SLHDSA_SHA2_192S_SA_MINOR: return slhdsa_sha2_192s_sa_algo; + case SLHDSA_SHA2_192F_SA_MINOR: return slhdsa_sha2_192f_sa_algo; + case SLHDSA_SHA2_256S_SA_MINOR: return slhdsa_sha2_256s_sa_algo; + case SLHDSA_SHA2_256F_SA_MINOR: return slhdsa_sha2_256f_sa_algo; + #endif + case SLHDSA_SHAKE_128S_SA_MINOR: return slhdsa_shake_128s_sa_algo; + case SLHDSA_SHAKE_128F_SA_MINOR: return slhdsa_shake_128f_sa_algo; + case SLHDSA_SHAKE_192S_SA_MINOR: return slhdsa_shake_192s_sa_algo; + case SLHDSA_SHAKE_192F_SA_MINOR: return slhdsa_shake_192f_sa_algo; + case SLHDSA_SHAKE_256S_SA_MINOR: return slhdsa_shake_256s_sa_algo; + case SLHDSA_SHAKE_256F_SA_MINOR: return slhdsa_shake_256f_sa_algo; + default: return (byte)invalid_sa_algo; + } +} + +/* Map an internal SLH-DSA sa_algo to its enum SlhDsaParam value. Returns -1 + * if hsType is not an SLH-DSA scheme. */ +int SlhDsaTypeToParam(byte hsType) +{ + switch (hsType) { + #ifdef WOLFSSL_SLHDSA_SHA2 + case slhdsa_sha2_128s_sa_algo: return SLHDSA_SHA2_128S; + case slhdsa_sha2_128f_sa_algo: return SLHDSA_SHA2_128F; + case slhdsa_sha2_192s_sa_algo: return SLHDSA_SHA2_192S; + case slhdsa_sha2_192f_sa_algo: return SLHDSA_SHA2_192F; + case slhdsa_sha2_256s_sa_algo: return SLHDSA_SHA2_256S; + case slhdsa_sha2_256f_sa_algo: return SLHDSA_SHA2_256F; + #endif + case slhdsa_shake_128s_sa_algo: return SLHDSA_SHAKE128S; + case slhdsa_shake_128f_sa_algo: return SLHDSA_SHAKE128F; + case slhdsa_shake_192s_sa_algo: return SLHDSA_SHAKE192S; + case slhdsa_shake_192f_sa_algo: return SLHDSA_SHAKE192F; + case slhdsa_shake_256s_sa_algo: return SLHDSA_SHAKE256S; + case slhdsa_shake_256f_sa_algo: return SLHDSA_SHAKE256F; + default: return -1; + } +} + +/* Is hsType any of the SLH-DSA signature schemes? */ +int IsSlhDsaSigAlgo(byte hsType) +{ + return SlhDsaTypeToParam(hsType) != -1; +} + +/* Map an enum SlhDsaParam value to its internal SLH-DSA sa_algo. Returns + * invalid_sa_algo if the parameter set is not one of the TLS schemes. */ +byte SlhDsaParamToType(int param) +{ + switch (param) { + #ifdef WOLFSSL_SLHDSA_SHA2 + case SLHDSA_SHA2_128S: return slhdsa_sha2_128s_sa_algo; + case SLHDSA_SHA2_128F: return slhdsa_sha2_128f_sa_algo; + case SLHDSA_SHA2_192S: return slhdsa_sha2_192s_sa_algo; + case SLHDSA_SHA2_192F: return slhdsa_sha2_192f_sa_algo; + case SLHDSA_SHA2_256S: return slhdsa_sha2_256s_sa_algo; + case SLHDSA_SHA2_256F: return slhdsa_sha2_256f_sa_algo; + #endif + case SLHDSA_SHAKE128S: return slhdsa_shake_128s_sa_algo; + case SLHDSA_SHAKE128F: return slhdsa_shake_128f_sa_algo; + case SLHDSA_SHAKE192S: return slhdsa_shake_192s_sa_algo; + case SLHDSA_SHAKE192F: return slhdsa_shake_192f_sa_algo; + case SLHDSA_SHAKE256S: return slhdsa_shake_256s_sa_algo; + case SLHDSA_SHAKE256F: return slhdsa_shake_256f_sa_algo; + default: return (byte)invalid_sa_algo; + } +} +#endif /* WOLFSSL_HAVE_SLHDSA */ + void DecodeSigAlg(const byte* input, byte* hashAlgo, byte* hsType) { *hsType = invalid_sa_algo; @@ -4827,22 +5033,39 @@ void DecodeSigAlg(const byte* input, byte* hashAlgo, byte* hsType) } break; #endif /* HAVE_FALCON */ - #ifdef WOLFSSL_HAVE_MLDSA + #if defined(WOLFSSL_HAVE_MLDSA) || defined(WOLFSSL_HAVE_SLHDSA) + /* ML-DSA and SLH-DSA share the same major byte (0x09); their minor + * bytes are disjoint (ML-DSA 0x04-0x06, SLH-DSA 0x11-0x1C). */ case MLDSA_SA_MAJOR: + #ifdef WOLFSSL_HAVE_MLDSA if (input[1] == MLDSA_44_SA_MINOR) { *hsType = mldsa_44_sa_algo; *hashAlgo = sha256_mac; + break; } else if (input[1] == MLDSA_65_SA_MINOR) { *hsType = mldsa_65_sa_algo; *hashAlgo = sha384_mac; + break; } else if (input[1] == MLDSA_87_SA_MINOR) { *hsType = mldsa_87_sa_algo; *hashAlgo = sha512_mac; + break; + } + #endif /* WOLFSSL_HAVE_MLDSA */ + #ifdef WOLFSSL_HAVE_SLHDSA + { + byte slhType = SlhDsaSigMinorToType(input[1]); + if (slhType != (byte)invalid_sa_algo) { + *hsType = slhType; + /* Hash performed as part of sign/verify operation. */ + *hashAlgo = sha512_mac; + } } + #endif /* WOLFSSL_HAVE_SLHDSA */ break; - #endif /* WOLFSSL_HAVE_MLDSA */ + #endif /* WOLFSSL_HAVE_MLDSA || WOLFSSL_HAVE_SLHDSA */ default: *hashAlgo = input[0]; *hsType = input[1]; @@ -7225,6 +7448,7 @@ int SetSSL_CTX(WOLFSSL* ssl, WOLFSSL_CTX* ctx, int writeDup) ssl->options.haveStaticECC = ctx->haveStaticECC; ssl->options.haveFalconSig = ctx->haveFalconSig; ssl->options.haveMlDsaSig = ctx->haveMlDsaSig; + ssl->options.haveSlhDsaSig = ctx->haveSlhDsaSig; #ifndef NO_PSK ssl->options.havePSK = (word16)(ctx->havePSK); @@ -8414,6 +8638,11 @@ void FreeKey(WOLFSSL* ssl, int type, void** pKey) wc_MlDsaKey_Free((wc_MlDsaKey*)*pKey); break; #endif /* WOLFSSL_HAVE_MLDSA */ + #if defined(WOLFSSL_HAVE_SLHDSA) + case DYNAMIC_TYPE_SLHDSA: + wc_SlhDsaKey_Free((SlhDsaKey*)*pKey); + break; + #endif /* WOLFSSL_HAVE_SLHDSA */ #ifndef NO_DH case DYNAMIC_TYPE_DH: #if defined(WC_DH_NONBLOCK) && defined(WOLFSSL_ASYNC_CRYPT_SW) && \ @@ -8522,6 +8751,11 @@ int AllocKey(WOLFSSL* ssl, int type, void** pKey) sz = sizeof(wc_MlDsaKey); break; #endif /* WOLFSSL_HAVE_MLDSA */ + #if defined(WOLFSSL_HAVE_SLHDSA) + case DYNAMIC_TYPE_SLHDSA: + sz = sizeof(SlhDsaKey); + break; + #endif /* WOLFSSL_HAVE_SLHDSA */ #ifndef NO_DH case DYNAMIC_TYPE_DH: sz = sizeof(DhKey); @@ -8640,6 +8874,14 @@ int AllocKey(WOLFSSL* ssl, int type, void** pKey) ret = 0; break; #endif /* WOLFSSL_HAVE_MLDSA */ + #if defined(WOLFSSL_HAVE_SLHDSA) + case DYNAMIC_TYPE_SLHDSA: + /* SLH-DSA requires the parameter set at init; use an always-present + * placeholder here and re-init with the real one once known. */ + ret = wc_SlhDsaKey_Init((SlhDsaKey*)*pKey, WC_SLHDSA_DEFAULT_PARAM, + ssl->heap, ssl->devId); + break; + #endif /* WOLFSSL_HAVE_SLHDSA */ #ifdef HAVE_CURVE448 case DYNAMIC_TYPE_CURVE448: wc_curve448_init((curve448_key*)*pKey); @@ -8686,7 +8928,8 @@ int AllocKey(WOLFSSL* ssl, int type, void** pKey) #if (!defined(NO_CERTS) || !defined(WOLFSSL_NO_TLS12)) && \ (!defined(NO_RSA) || defined(HAVE_ECC) || defined(HAVE_ED25519) || \ defined(HAVE_CURVE25519) || defined(HAVE_ED448) || \ - defined(HAVE_CURVE448) || defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA)) + defined(HAVE_CURVE448) || defined(HAVE_FALCON) || \ + defined(WOLFSSL_HAVE_MLDSA) || defined(WOLFSSL_HAVE_SLHDSA)) static int ReuseKey(WOLFSSL* ssl, int type, void* pKey) { int ret = 0; @@ -8744,6 +8987,14 @@ static int ReuseKey(WOLFSSL* ssl, int type, void* pKey) ret = wc_MlDsaKey_Init((wc_MlDsaKey*)pKey, NULL, INVALID_DEVID); break; #endif /* WOLFSSL_HAVE_MLDSA */ + #if defined(WOLFSSL_HAVE_SLHDSA) + case DYNAMIC_TYPE_SLHDSA: + wc_SlhDsaKey_Free((SlhDsaKey*)pKey); + /* Re-init with placeholder param; caller re-inits with real one. */ + ret = wc_SlhDsaKey_Init((SlhDsaKey*)pKey, WC_SLHDSA_DEFAULT_PARAM, NULL, + INVALID_DEVID); + break; + #endif /* WOLFSSL_HAVE_SLHDSA */ #ifndef NO_DH case DYNAMIC_TYPE_DH: wc_FreeDhKey((DhKey*)pKey); @@ -9096,6 +9347,10 @@ void wolfSSL_ResourceFree(WOLFSSL* ssl) FreeKey(ssl, DYNAMIC_TYPE_MLDSA, (void**)&ssl->peerMlDsaKey); ssl->peerMlDsaKeyPresent = 0; #endif +#if defined(WOLFSSL_HAVE_SLHDSA) + FreeKey(ssl, DYNAMIC_TYPE_SLHDSA, (void**)&ssl->peerSlhDsaKey); + ssl->peerSlhDsaKeyPresent = 0; +#endif #if defined(HAVE_FALCON) FreeKey(ssl, DYNAMIC_TYPE_FALCON, (void**)&ssl->peerFalconKey); ssl->peerFalconKeyPresent = 0; @@ -9382,6 +9637,10 @@ void FreeHandshakeResources(WOLFSSL* ssl) FreeKey(ssl, DYNAMIC_TYPE_MLDSA, (void**)&ssl->peerMlDsaKey); ssl->peerMlDsaKeyPresent = 0; #endif /* WOLFSSL_HAVE_MLDSA */ +#if defined(WOLFSSL_HAVE_SLHDSA) + FreeKey(ssl, DYNAMIC_TYPE_SLHDSA, (void**)&ssl->peerSlhDsaKey); + ssl->peerSlhDsaKeyPresent = 0; +#endif /* WOLFSSL_HAVE_SLHDSA */ } #ifdef HAVE_ECC @@ -17716,6 +17975,59 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, break; } #endif /* WOLFSSL_HAVE_MLDSA */ + #if defined(WOLFSSL_HAVE_SLHDSA) + case SLH_DSA_SHA2_128Sk: + case SLH_DSA_SHA2_128Fk: + case SLH_DSA_SHA2_192Sk: + case SLH_DSA_SHA2_192Fk: + case SLH_DSA_SHA2_256Sk: + case SLH_DSA_SHA2_256Fk: + case SLH_DSA_SHAKE_128Sk: + case SLH_DSA_SHAKE_128Fk: + case SLH_DSA_SHAKE_192Sk: + case SLH_DSA_SHAKE_192Fk: + case SLH_DSA_SHAKE_256Sk: + case SLH_DSA_SHAKE_256Fk: + { + int keyRet = 0; + int slhParam = + wc_SlhDsaOidToParam(args->dCert->keyOID); + if (slhParam < 0) { + ret = PEER_KEY_ERROR; + break; + } + + if (ssl->peerSlhDsaKey == NULL) { + /* alloc/init on demand */ + keyRet = AllocKey(ssl, DYNAMIC_TYPE_SLHDSA, + (void**)&ssl->peerSlhDsaKey); + } else if (ssl->peerSlhDsaKeyPresent) { + keyRet = ReuseKey(ssl, DYNAMIC_TYPE_SLHDSA, + ssl->peerSlhDsaKey); + ssl->peerSlhDsaKeyPresent = 0; + } + + if (keyRet == 0) { + /* AllocKey/ReuseKey used a placeholder parameter + * set; re-init with the certificate's. */ + wc_SlhDsaKey_Free(ssl->peerSlhDsaKey); + keyRet = wc_SlhDsaKey_Init(ssl->peerSlhDsaKey, + slhParam, ssl->heap, ssl->devId); + } + + if (keyRet != 0 || + wc_SlhDsaKey_ImportPublic(ssl->peerSlhDsaKey, + args->dCert->publicKey, + args->dCert->pubKeySize) + != 0) { + ret = PEER_KEY_ERROR; + } + else { + ssl->peerSlhDsaKeyPresent = 1; + } + break; + } + #endif /* WOLFSSL_HAVE_SLHDSA */ default: break; } @@ -29958,6 +30270,9 @@ static int ParseCipherList(Suites* suites, #ifdef WOLFSSL_HAVE_MLDSA haveSig |= SIG_MLDSA; #endif /* WOLFSSL_HAVE_MLDSA */ + #ifdef WOLFSSL_HAVE_SLHDSA + haveSig |= SIG_SLHDSA; + #endif /* WOLFSSL_HAVE_SLHDSA */ } else #ifdef BUILD_TLS_SM4_GCM_SM3 @@ -30156,6 +30471,7 @@ int SetCipherListFromBytes(WOLFSSL_CTX* ctx, Suites* suites, const byte* list, int haveECDSAsig = 0; int haveFalconSig = 0; int haveMlDsaSig = 0; + int haveSlhDsaSig = 0; int haveAnon = 0; int tls1_3 = 0; @@ -30230,6 +30546,9 @@ int SetCipherListFromBytes(WOLFSSL_CTX* ctx, Suites* suites, const byte* list, #ifdef WOLFSSL_HAVE_MLDSA haveMlDsaSig = 1; #endif /* WOLFSSL_HAVE_MLDSA */ + #ifdef WOLFSSL_HAVE_SLHDSA + haveSlhDsaSig = 1; + #endif /* WOLFSSL_HAVE_SLHDSA */ } else #endif /* WOLFSSL_TLS13 */ @@ -30268,6 +30587,7 @@ int SetCipherListFromBytes(WOLFSSL_CTX* ctx, Suites* suites, const byte* list, haveSig |= haveRSAsig ? SIG_RSA : 0; haveSig |= haveFalconSig ? SIG_FALCON : 0; haveSig |= haveMlDsaSig ? SIG_MLDSA : 0; + haveSig |= haveSlhDsaSig ? SIG_SLHDSA : 0; haveSig |= haveAnon ? SIG_ANON : 0; InitSuitesHashSigAlgo(suites->hashSigAlgo, haveSig, 1, tls1_3, keySz, &suites->hashSigAlgoSz); @@ -31689,6 +32009,52 @@ static int DecodePrivateKey_ex(WOLFSSL *ssl, byte keyType, const DerBuffer* key, } } #endif /* WOLFSSL_HAVE_MLDSA */ +#if defined(WOLFSSL_HAVE_SLHDSA) && !defined(WOLFSSL_SLHDSA_VERIFY_ONLY) + #if !defined(NO_RSA) || defined(HAVE_ECC) + FreeKey(ssl, (int)*hsType, hsKey); + #endif + + if (IsSlhDsaSigAlgo(keyType)) { + int slhParam = SlhDsaTypeToParam(keyType); + + *hsType = DYNAMIC_TYPE_SLHDSA; + ret = AllocKey(ssl, (int)*hsType, hsKey); + if (ret != 0) { + goto exit_dpk; + } + + /* AllocKey initialised the key with a placeholder parameter set; + * re-init with the parameter set matching the loaded key. */ + wc_SlhDsaKey_Free((SlhDsaKey*)*hsKey); + ret = wc_SlhDsaKey_Init((SlhDsaKey*)*hsKey, slhParam, ssl->heap, + ssl->devId); + if (ret != 0) { + goto exit_dpk; + } + + WOLFSSL_MSG("Trying SLH-DSA private key"); + + /* Set start of data to beginning of buffer. */ + idx = 0; + PRIVATE_KEY_UNLOCK(); + ret = wc_SlhDsaKey_PrivateKeyDecode(key->buffer, &idx, + (SlhDsaKey*)*hsKey, key->length); + PRIVATE_KEY_LOCK(); + if (ret == 0) { + int slhSigSz; + WOLFSSL_MSG("Using SLH-DSA private key"); + + /* Return the maximum signature length. */ + slhSigSz = wc_SlhDsaKey_SigSize((SlhDsaKey*)*hsKey); + if (slhSigSz <= 0) { + ERROR_OUT(ALGO_ID_E, exit_dpk); + } + *sigLen = (word32)slhSigSz; + + goto exit_dpk; + } + } +#endif /* WOLFSSL_HAVE_SLHDSA */ (void)idx; (void)keySzDecoded; diff --git a/src/ssl_load.c b/src/ssl_load.c index b63a3b8430..6928e8ac05 100644 --- a/src/ssl_load.c +++ b/src/ssl_load.c @@ -1045,6 +1045,82 @@ static int ProcessBufferTryDecodeMlDsa(WOLFSSL_CTX* ctx, WOLFSSL* ssl, } #endif /* WOLFSSL_HAVE_MLDSA */ +#if defined(WOLFSSL_HAVE_SLHDSA) && !defined(WOLFSSL_SLHDSA_VERIFY_ONLY) +/* Try to decode the DER encoding as an SLH-DSA private key. + * + * @param [in] ctx SSL context object. + * @param [in] ssl SSL object. + * @param [in] der DER encoding. + * @param [in, out] keyFormat On in, expected format. 0 means unknown. + * @param [in] heap Dynamic memory allocation hint. + * @param [out] keyType Type of key (an slhdsa_*_sa_algo). + * @param [out] keySize Size of the private key. + * @return 0 on success or when not an SLH-DSA key (with keyFormat 0). + */ +static int ProcessBufferTryDecodeSlhDsa(WOLFSSL_CTX* ctx, WOLFSSL* ssl, + DerBuffer* der, int* keyFormat, void* heap, byte* keyType, int* keySize) +{ + int ret; + word32 idx; + SlhDsaKey* key; + int keyTypeTemp = 0; + int keySizeTemp = 0; + + (void)ctx; + (void)ssl; + + /* Allocate an SLH-DSA key to parse into. */ + key = (SlhDsaKey*)XMALLOC(sizeof(SlhDsaKey), heap, DYNAMIC_TYPE_SLHDSA); + if (key == NULL) { + return MEMORY_E; + } + + /* Initialise with an always-present placeholder parameter set; + * wc_SlhDsaKey_PrivateKeyDecode selects the real one from the key's + * algorithm OID. */ + ret = wc_SlhDsaKey_Init(key, WC_SLHDSA_DEFAULT_PARAM, heap, INVALID_DEVID); + if (ret == 0) { + idx = 0; + PRIVATE_KEY_UNLOCK(); + ret = wc_SlhDsaKey_PrivateKeyDecode(der->buffer, &idx, key, + der->length); + PRIVATE_KEY_LOCK(); + if (ret == 0) { + int param = (int)key->params->param; + + keyTypeTemp = SlhDsaParamToType(param); + if (keyTypeTemp == (byte)invalid_sa_algo) { + ret = ALGO_ID_E; + } + else { + keySizeTemp = wc_SlhDsaKey_PrivateSize(key); + if (keySizeTemp <= 0) { + ret = ALGO_ID_E; + } + } + + if (ret == 0) { + *keyFormat = wc_SlhDsaParamToOid((enum SlhDsaParam)param); + *keyType = (byte)keyTypeTemp; + *keySize = keySizeTemp; + } + } + else if (*keyFormat == 0) { + WOLFSSL_MSG("Not an SLH-DSA key"); + /* Unknown format wasn't SLH-DSA, so keep trying other formats. */ + ret = 0; + } + + /* Free dynamically allocated data in key. */ + wc_SlhDsaKey_Free(key); + } + + /* Dispose of allocated key. */ + XFREE(key, heap, DYNAMIC_TYPE_SLHDSA); + return ret; +} +#endif /* WOLFSSL_HAVE_SLHDSA */ + /* Try to decode DER data is a known private key. * * Checks size meets minimum for key type. @@ -1188,6 +1264,27 @@ static int ProcessBufferTryDecode(WOLFSSL_CTX* ctx, WOLFSSL* ssl, matchAnyKey = 1; } #endif /* WOLFSSL_HAVE_MLDSA */ +#if defined(WOLFSSL_HAVE_SLHDSA) && !defined(WOLFSSL_SLHDSA_VERIFY_ONLY) + /* Try SLH-DSA if key format is an SLH-DSA key OID or yet unknown. */ + if ((ret == 0) && + ((*keyFormat == 0) || + (*keyFormat == SLH_DSA_SHA2_128Sk) || + (*keyFormat == SLH_DSA_SHA2_128Fk) || + (*keyFormat == SLH_DSA_SHA2_192Sk) || + (*keyFormat == SLH_DSA_SHA2_192Fk) || + (*keyFormat == SLH_DSA_SHA2_256Sk) || + (*keyFormat == SLH_DSA_SHA2_256Fk) || + (*keyFormat == SLH_DSA_SHAKE_128Sk) || + (*keyFormat == SLH_DSA_SHAKE_128Fk) || + (*keyFormat == SLH_DSA_SHAKE_192Sk) || + (*keyFormat == SLH_DSA_SHAKE_192Fk) || + (*keyFormat == SLH_DSA_SHAKE_256Sk) || + (*keyFormat == SLH_DSA_SHAKE_256Fk))) { + ret = ProcessBufferTryDecodeSlhDsa(ctx, ssl, der, keyFormat, heap, + keyType, keySz); + matchAnyKey = 1; + } +#endif /* WOLFSSL_HAVE_SLHDSA */ /* Check we know the format. */ if ((ret == 0) && @@ -1513,6 +1610,27 @@ static void wolfssl_set_have_from_key_oid(WOLFSSL_CTX* ctx, WOLFSSL* ssl, } break; #endif /* WOLFSSL_HAVE_MLDSA */ + #ifdef WOLFSSL_HAVE_SLHDSA + case SLH_DSA_SHA2_128Sk: + case SLH_DSA_SHA2_128Fk: + case SLH_DSA_SHA2_192Sk: + case SLH_DSA_SHA2_192Fk: + case SLH_DSA_SHA2_256Sk: + case SLH_DSA_SHA2_256Fk: + case SLH_DSA_SHAKE_128Sk: + case SLH_DSA_SHAKE_128Fk: + case SLH_DSA_SHAKE_192Sk: + case SLH_DSA_SHAKE_192Fk: + case SLH_DSA_SHAKE_256Sk: + case SLH_DSA_SHAKE_256Fk: + if (ssl != NULL) { + ssl->options.haveSlhDsaSig = 1; + } + else { + ctx->haveSlhDsaSig = 1; + } + break; + #endif /* WOLFSSL_HAVE_SLHDSA */ default: WOLFSSL_MSG("Cert key not supported"); break; @@ -1535,6 +1653,9 @@ static void ProcessBufferCertSetHave(WOLFSSL_CTX* ctx, WOLFSSL* ssl, ssl->options.haveECDSAsig = 0; ssl->options.haveFalconSig = 0; ssl->options.haveMlDsaSig = 0; + #ifdef WOLFSSL_HAVE_SLHDSA + ssl->options.haveSlhDsaSig = 0; + #endif } /* Set which signature we have based on the type in the cert. */ @@ -1589,6 +1710,28 @@ static void ProcessBufferCertSetHave(WOLFSSL_CTX* ctx, WOLFSSL* ssl, ctx->haveMlDsaSig = 1; } break; + #endif + #ifdef WOLFSSL_HAVE_SLHDSA + case CTC_SLH_DSA_SHA2_128S: + case CTC_SLH_DSA_SHA2_128F: + case CTC_SLH_DSA_SHA2_192S: + case CTC_SLH_DSA_SHA2_192F: + case CTC_SLH_DSA_SHA2_256S: + case CTC_SLH_DSA_SHA2_256F: + case CTC_SLH_DSA_SHAKE_128S: + case CTC_SLH_DSA_SHAKE_128F: + case CTC_SLH_DSA_SHAKE_192S: + case CTC_SLH_DSA_SHAKE_192F: + case CTC_SLH_DSA_SHAKE_256S: + case CTC_SLH_DSA_SHAKE_256F: + WOLFSSL_MSG("SLH-DSA cert signature"); + if (ssl) { + ssl->options.haveSlhDsaSig = 1; + } + else if (ctx) { + ctx->haveSlhDsaSig = 1; + } + break; #endif default: WOLFSSL_MSG("Cert signature not supported"); @@ -1596,9 +1739,11 @@ static void ProcessBufferCertSetHave(WOLFSSL_CTX* ctx, WOLFSSL* ssl, } #if defined(HAVE_ECC) || defined(HAVE_ED25519) || defined(HAVE_ED448) || \ - defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) || !defined(NO_RSA) + defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) || \ + defined(WOLFSSL_HAVE_SLHDSA) || !defined(NO_RSA) #if defined(HAVE_ECC) || defined(HAVE_ED25519) || defined(HAVE_ED448) || \ - defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) + defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) || \ + defined(WOLFSSL_HAVE_SLHDSA) /* Set the private key curve OID. */ if (ssl != NULL) { ssl->pkCurveOID = cert->pkCurveOID; @@ -1826,6 +1971,33 @@ static int ProcessBufferCertPublicKey(WOLFSSL_CTX* ctx, WOLFSSL* ssl, } break; #endif /* WOLFSSL_HAVE_MLDSA */ + #if defined(WOLFSSL_HAVE_SLHDSA) + case SLH_DSA_SHA2_128Sk: + case SLH_DSA_SHA2_128Fk: + case SLH_DSA_SHA2_192Sk: + case SLH_DSA_SHA2_192Fk: + case SLH_DSA_SHA2_256Sk: + case SLH_DSA_SHA2_256Fk: + case SLH_DSA_SHAKE_128Sk: + case SLH_DSA_SHAKE_128Fk: + case SLH_DSA_SHAKE_192Sk: + case SLH_DSA_SHAKE_192Fk: + case SLH_DSA_SHAKE_256Sk: + case SLH_DSA_SHAKE_256Fk: + { + int slhParam = wc_SlhDsaOidToParam((int)cert->keyOID); + if (slhParam < 0) { + ret = ALGO_ID_E; + break; + } + keyType = SlhDsaParamToType(slhParam); + /* SLH-DSA has fixed, small public keys and no minimum-size + * option; store the public key size for reference. */ + keySz = wc_SlhDsaKey_PublicSizeFromParam( + (enum SlhDsaParam)slhParam); + break; + } + #endif /* WOLFSSL_HAVE_SLHDSA */ default: WOLFSSL_MSG("No key size check done on public key in certificate"); @@ -2036,6 +2208,31 @@ static int ProcessBufferCertAltPublicKey(WOLFSSL_CTX* ctx, WOLFSSL* ssl, } break; #endif /* WOLFSSL_HAVE_MLDSA */ + #if defined(WOLFSSL_HAVE_SLHDSA) + case SLH_DSA_SHA2_128Sk: + case SLH_DSA_SHA2_128Fk: + case SLH_DSA_SHA2_192Sk: + case SLH_DSA_SHA2_192Fk: + case SLH_DSA_SHA2_256Sk: + case SLH_DSA_SHA2_256Fk: + case SLH_DSA_SHAKE_128Sk: + case SLH_DSA_SHAKE_128Fk: + case SLH_DSA_SHAKE_192Sk: + case SLH_DSA_SHAKE_192Fk: + case SLH_DSA_SHAKE_256Sk: + case SLH_DSA_SHAKE_256Fk: + { + int slhParam = wc_SlhDsaOidToParam((int)cert->keyOID); + if (slhParam < 0) { + ret = ALGO_ID_E; + break; + } + keyType = SlhDsaParamToType(slhParam); + keySz = wc_SlhDsaKey_PublicSizeFromParam( + (enum SlhDsaParam)slhParam); + break; + } + #endif /* WOLFSSL_HAVE_SLHDSA */ default: /* In this case, there was an OID that we didn't recognize. diff --git a/src/tls13.c b/src/tls13.c index a20b6ec9e2..8f3c5df7c9 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -182,7 +182,8 @@ static const byte #ifndef NO_CERTS #if !defined(NO_RSA) || defined(HAVE_ECC) || defined(HAVE_ED25519) || \ - defined(HAVE_ED448) || defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) + defined(HAVE_ED448) || defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) || \ + defined(WOLFSSL_HAVE_SLHDSA) static WC_INLINE int GetMsgHash(WOLFSSL* ssl, byte* hash); @@ -8685,7 +8686,8 @@ static int SendTls13CertificateRequest(WOLFSSL* ssl, byte* reqCtx, #ifndef NO_CERTS #if (!defined(NO_WOLFSSL_SERVER) || !defined(WOLFSSL_NO_CLIENT_AUTH)) && \ (!defined(NO_RSA) || defined(HAVE_ECC) || defined(HAVE_ED25519) || \ - defined(HAVE_ED448) || defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA)) + defined(HAVE_ED448) || defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) || \ + defined(WOLFSSL_HAVE_SLHDSA)) /* Encode the signature algorithm into buffer. * * hashalgo The hash algorithm. @@ -8784,6 +8786,58 @@ static WC_INLINE void EncodeSigAlg(const WOLFSSL * ssl, byte hashAlgo, output[1] = MLDSA_87_SA_MINOR; break; #endif +#ifdef WOLFSSL_HAVE_SLHDSA + #ifdef WOLFSSL_SLHDSA_SHA2 + case slhdsa_sha2_128s_sa_algo: + output[0] = SLHDSA_SA_MAJOR; + output[1] = SLHDSA_SHA2_128S_SA_MINOR; + break; + case slhdsa_sha2_128f_sa_algo: + output[0] = SLHDSA_SA_MAJOR; + output[1] = SLHDSA_SHA2_128F_SA_MINOR; + break; + case slhdsa_sha2_192s_sa_algo: + output[0] = SLHDSA_SA_MAJOR; + output[1] = SLHDSA_SHA2_192S_SA_MINOR; + break; + case slhdsa_sha2_192f_sa_algo: + output[0] = SLHDSA_SA_MAJOR; + output[1] = SLHDSA_SHA2_192F_SA_MINOR; + break; + case slhdsa_sha2_256s_sa_algo: + output[0] = SLHDSA_SA_MAJOR; + output[1] = SLHDSA_SHA2_256S_SA_MINOR; + break; + case slhdsa_sha2_256f_sa_algo: + output[0] = SLHDSA_SA_MAJOR; + output[1] = SLHDSA_SHA2_256F_SA_MINOR; + break; + #endif /* WOLFSSL_SLHDSA_SHA2 */ + case slhdsa_shake_128s_sa_algo: + output[0] = SLHDSA_SA_MAJOR; + output[1] = SLHDSA_SHAKE_128S_SA_MINOR; + break; + case slhdsa_shake_128f_sa_algo: + output[0] = SLHDSA_SA_MAJOR; + output[1] = SLHDSA_SHAKE_128F_SA_MINOR; + break; + case slhdsa_shake_192s_sa_algo: + output[0] = SLHDSA_SA_MAJOR; + output[1] = SLHDSA_SHAKE_192S_SA_MINOR; + break; + case slhdsa_shake_192f_sa_algo: + output[0] = SLHDSA_SA_MAJOR; + output[1] = SLHDSA_SHAKE_192F_SA_MINOR; + break; + case slhdsa_shake_256s_sa_algo: + output[0] = SLHDSA_SA_MAJOR; + output[1] = SLHDSA_SHAKE_256S_SA_MINOR; + break; + case slhdsa_shake_256f_sa_algo: + output[0] = SLHDSA_SA_MAJOR; + output[1] = SLHDSA_SHAKE_256F_SA_MINOR; + break; +#endif /* WOLFSSL_HAVE_SLHDSA */ default: break; } @@ -8791,7 +8845,8 @@ static WC_INLINE void EncodeSigAlg(const WOLFSSL * ssl, byte hashAlgo, #endif #if !defined(NO_RSA) || defined(HAVE_ECC) || defined(HAVE_ED25519) || \ - defined(HAVE_ED448) || defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) + defined(HAVE_ED448) || defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) || \ + defined(WOLFSSL_HAVE_SLHDSA) #ifdef WOLFSSL_DUAL_ALG_CERTS /* These match up with what the OQS team has defined. */ #define HYBRID_SA_MAJOR 0xFE @@ -8984,27 +9039,40 @@ static WC_INLINE int DecodeTls13SigAlg(byte* input, byte* hashAlgo, ret = INVALID_PARAMETER; break; #endif /* HAVE_FALCON */ -#if defined(WOLFSSL_HAVE_MLDSA) +#if defined(WOLFSSL_HAVE_MLDSA) || defined(WOLFSSL_HAVE_SLHDSA) + /* ML-DSA and SLH-DSA share the same major byte (0x09); their minor + * bytes are disjoint (ML-DSA 0x04-0x06, SLH-DSA 0x11-0x1C). */ case MLDSA_SA_MAJOR: + ret = INVALID_PARAMETER; + #if defined(WOLFSSL_HAVE_MLDSA) if (input[1] == MLDSA_44_SA_MINOR) { *hsType = mldsa_44_sa_algo; /* Hash performed as part of sign/verify operation. */ *hashAlgo = sha512_mac; + ret = 0; } else if (input[1] == MLDSA_65_SA_MINOR) { *hsType = mldsa_65_sa_algo; - /* Hash performed as part of sign/verify operation. */ *hashAlgo = sha512_mac; + ret = 0; } else if (input[1] == MLDSA_87_SA_MINOR) { *hsType = mldsa_87_sa_algo; - /* Hash performed as part of sign/verify operation. */ *hashAlgo = sha512_mac; + ret = 0; } - else - { - ret = INVALID_PARAMETER; + #endif /* WOLFSSL_HAVE_MLDSA */ + #if defined(WOLFSSL_HAVE_SLHDSA) + if (ret != 0) { + byte slhType = SlhDsaSigMinorToType(input[1]); + if (slhType != invalid_sa_algo) { + *hsType = slhType; + /* Hash performed as part of sign/verify operation. */ + *hashAlgo = sha512_mac; + ret = 0; + } } + #endif /* WOLFSSL_HAVE_SLHDSA */ break; -#endif /* WOLFSSL_HAVE_MLDSA */ +#endif /* WOLFSSL_HAVE_MLDSA || WOLFSSL_HAVE_SLHDSA */ default: *hashAlgo = input[0]; *hsType = input[1]; @@ -10208,6 +10276,11 @@ static int SendTls13CertificateVerify(WOLFSSL* ssl) args->sigAlgo = ssl->buffers.keyType; } #endif /* WOLFSSL_HAVE_MLDSA */ + #if defined(WOLFSSL_HAVE_SLHDSA) + else if (ssl->hsType == DYNAMIC_TYPE_SLHDSA) { + args->sigAlgo = ssl->buffers.keyType; + } + #endif /* WOLFSSL_HAVE_SLHDSA */ else { ERROR_OUT(ALGO_ID_E, exit_scv); } @@ -10374,6 +10447,15 @@ static int SendTls13CertificateVerify(WOLFSSL* ssl) args->sigLen = MLDSA_MAX_SIG_SIZE; } #endif /* WOLFSSL_HAVE_MLDSA */ + #if defined(WOLFSSL_HAVE_SLHDSA) + if (ssl->hsType == DYNAMIC_TYPE_SLHDSA) { + int slhSigSz = wc_SlhDsaKey_SigSize((SlhDsaKey*)ssl->hsKey); + if (slhSigSz <= 0) { + ERROR_OUT(ALGO_ID_E, exit_scv); + } + args->sigLen = (word16)slhSigSz; + } + #endif /* WOLFSSL_HAVE_SLHDSA */ #ifdef WOLFSSL_DUAL_ALG_CERTS if (ssl->sigSpec != NULL && @@ -10497,6 +10579,15 @@ static int SendTls13CertificateVerify(WOLFSSL* ssl) args->length = (word16)args->sigLen; } #endif /* WOLFSSL_HAVE_MLDSA */ + #if defined(WOLFSSL_HAVE_SLHDSA) && !defined(WOLFSSL_SLHDSA_VERIFY_ONLY) + if (ssl->hsType == DYNAMIC_TYPE_SLHDSA) { + /* Pure SLH-DSA with empty context, per draft-reddy-tls-slhdsa. */ + ret = wc_SlhDsaKey_Sign((SlhDsaKey*)ssl->hsKey, NULL, 0, + args->sigData, args->sigDataSz, + sigOut, &args->sigLen, ssl->rng); + args->length = (word16)args->sigLen; + } + #endif /* WOLFSSL_HAVE_SLHDSA */ #if !defined(NO_RSA) && !defined(WOLFSSL_RSA_PUBLIC_ONLY) && \ !defined(WOLFSSL_RSA_VERIFY_ONLY) if (ssl->hsType == DYNAMIC_TYPE_RSA) { @@ -10741,39 +10832,143 @@ static int SendTls13CertificateVerify(WOLFSSL* ssl) } #endif /* WOLFSSL_DTLS13 */ - /* This message is always encrypted. */ - ret = BuildTls13Message(ssl, args->output, - WC_MAX_CERT_VERIFY_SZ + MAX_MSG_EXTRA, - args->output + RECORD_HEADER_SZ, - args->sendSz - RECORD_HEADER_SZ, handshake, - 1, 0, 0); + { + /* Body of the handshake message: [sigAlg(2) | sigLen(2) | sig]. + * This currently sits at args->verify in the output buffer. */ + word32 msgSz = (word32)args->length + HASH_SIG_SIZE + VERIFY_HEADER; + word32 maxFrag = (word32)wolfssl_local_GetMaxPlaintextSize(ssl); + + if (HANDSHAKE_HEADER_SZ + msgSz <= maxFrag) { + /* Fits in a single record: the common path used by RSA, ECC, + * EdDSA and ML-DSA is left byte-for-byte unchanged. */ + + /* This message is always encrypted. */ + ret = BuildTls13Message(ssl, args->output, + WC_MAX_CERT_VERIFY_SZ + MAX_MSG_EXTRA, + args->output + RECORD_HEADER_SZ, + args->sendSz - RECORD_HEADER_SZ, + handshake, 1, 0, 0); - if (ret < 0) { - goto exit_scv; + if (ret < 0) { + goto exit_scv; + } + else { + args->sendSz = ret; + ret = 0; + } + + #if defined(WOLFSSL_CALLBACKS) || defined(OPENSSL_EXTRA) + if (ssl->hsInfoOn) + AddPacketName(ssl, "CertificateVerify"); + if (ssl->toInfoOn) { + ret = AddPacketInfo(ssl, "CertificateVerify", handshake, + args->output, args->sendSz, WRITE_PROTO, 0, + ssl->heap); + if (ret != 0) + goto exit_scv; + } + #endif + + ssl->buffers.outputBuffer.length += (word32)args->sendSz; } else { - args->sendSz = ret; - ret = 0; - } + /* Signature does not fit in a single TLS record (e.g. SLH-DSA, + * whose signatures range up to ~50KB). Fragment the handshake + * message across multiple encrypted records. Only the first + * fragment carries the 4-byte handshake header; the transcript + * hash is maintained correctly because BuildTls13Message hashes + * each fragment's plaintext in order. */ + byte* frag; + word32 offset = 0; + + if (maxFrag <= HANDSHAKE_HEADER_SZ) { + ERROR_OUT(BUFFER_E, exit_scv); + } - #if defined(WOLFSSL_CALLBACKS) || defined(OPENSSL_EXTRA) - if (ssl->hsInfoOn) - AddPacketName(ssl, "CertificateVerify"); - if (ssl->toInfoOn) { - ret = AddPacketInfo(ssl, "CertificateVerify", handshake, - args->output, args->sendSz, WRITE_PROTO, 0, - ssl->heap); + /* Copy the assembled body out of the output buffer before we + * begin overwriting it with per-record data. */ + frag = (byte*)XMALLOC(msgSz, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + if (frag == NULL) { + ERROR_OUT(MEMORY_E, exit_scv); + } + XMEMCPY(frag, args->verify, msgSz); + + while (offset < msgSz && ret == 0) { + byte* output; + word32 fragSz; + word32 i = RECORD_HEADER_SZ; + int recSz; + int thisSendSz; + + if (offset == 0) { + fragSz = maxFrag - HANDSHAKE_HEADER_SZ; + if (fragSz > msgSz) + fragSz = msgSz; + thisSendSz = RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ + + (int)fragSz + MAX_MSG_EXTRA; + } + else { + fragSz = msgSz - offset; + if (fragSz > maxFrag) + fragSz = maxFrag; + thisSendSz = RECORD_HEADER_SZ + (int)fragSz + + MAX_MSG_EXTRA; + } + + if ((ret = CheckAvailableSize(ssl, thisSendSz)) != 0) { + XFREE(frag, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + goto exit_scv; + } + output = GetOutputBuffer(ssl); + + if (offset == 0) { + AddTls13FragHeaders(output, fragSz, 0, msgSz, + certificate_verify, ssl); + i += HANDSHAKE_HEADER_SZ; + } + else { + AddTls13RecordHeader(output, fragSz, handshake, ssl); + } + XMEMCPY(output + i, frag + offset, fragSz); + i += fragSz; + + /* This message is always encrypted. */ + recSz = BuildTls13Message(ssl, output, thisSendSz, + output + RECORD_HEADER_SZ, + (int)(i - RECORD_HEADER_SZ), handshake, 1, 0, 0); + if (recSz < 0) { + ret = recSz; + XFREE(frag, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + goto exit_scv; + } + + #if defined(WOLFSSL_CALLBACKS) || defined(OPENSSL_EXTRA) + if (ssl->hsInfoOn) + AddPacketName(ssl, "CertificateVerify"); + if (ssl->toInfoOn) { + ret = AddPacketInfo(ssl, "CertificateVerify", handshake, + output, recSz, WRITE_PROTO, 0, ssl->heap); + if (ret != 0) { + XFREE(frag, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + goto exit_scv; + } + } + #endif + + ssl->buffers.outputBuffer.length += (word32)recSz; + offset += fragSz; + } + XFREE(frag, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); if (ret != 0) goto exit_scv; } - #endif - ssl->buffers.outputBuffer.length += (word32)args->sendSz; ssl->options.buildingMsg = 0; if (!ssl->options.groupMessages) ret = SendBuffered(ssl); break; } + } default: ret = INPUT_CASE_ERROR; } /* switch(ssl->options.asyncState) */ @@ -10992,6 +11187,41 @@ static int decodeMlDsaKey(WOLFSSL* ssl, int level) } #endif /* WOLFSSL_HAVE_MLDSA */ +#ifdef WOLFSSL_HAVE_SLHDSA +/* ssl->peerCert->sapkiDer is the alternative public key. Hopefully it is an + * SLH-DSA public key. Convert it into a usable public key. */ +static int decodeSlhDsaKey(WOLFSSL* ssl, int param) +{ + int keyRet; + word32 tmpIdx = 0; + + if (ssl->peerSlhDsaKeyPresent) + return INVALID_PARAMETER; + + keyRet = AllocKey(ssl, DYNAMIC_TYPE_SLHDSA, (void**)&ssl->peerSlhDsaKey); + if (keyRet != 0) + return PEER_KEY_ERROR; + + ssl->peerSlhDsaKeyPresent = 1; + + /* AllocKey initialised the key with a placeholder parameter set; re-init + * with the parameter set negotiated in the CertificateVerify. */ + wc_SlhDsaKey_Free(ssl->peerSlhDsaKey); + keyRet = wc_SlhDsaKey_Init(ssl->peerSlhDsaKey, param, ssl->heap, + ssl->devId); + if (keyRet != 0) + return PEER_KEY_ERROR; + + keyRet = wc_SlhDsaKey_PublicKeyDecode(ssl->peerCert.sapkiDer, &tmpIdx, + ssl->peerSlhDsaKey, + ssl->peerCert.sapkiLen); + if (keyRet != 0) + return PEER_KEY_ERROR; + + return 0; +} +#endif /* WOLFSSL_HAVE_SLHDSA */ + #ifdef HAVE_FALCON /* ssl->peerCert->sapkiDer is the alternative public key. Hopefully it is a * falcon public key. Convert it into a usable public key. */ @@ -11207,6 +11437,22 @@ static int DoTls13CertificateVerify(WOLFSSL* ssl, byte* input, ret = decodeMlDsaKey(ssl, WC_ML_DSA_87); break; #endif + #ifdef WOLFSSL_HAVE_SLHDSA + case slhdsa_sha2_128s_sa_algo: + case slhdsa_sha2_128f_sa_algo: + case slhdsa_sha2_192s_sa_algo: + case slhdsa_sha2_192f_sa_algo: + case slhdsa_sha2_256s_sa_algo: + case slhdsa_sha2_256f_sa_algo: + case slhdsa_shake_128s_sa_algo: + case slhdsa_shake_128f_sa_algo: + case slhdsa_shake_192s_sa_algo: + case slhdsa_shake_192f_sa_algo: + case slhdsa_shake_256s_sa_algo: + case slhdsa_shake_256f_sa_algo: + ret = decodeSlhDsaKey(ssl, SlhDsaTypeToParam(sa)); + break; + #endif #ifdef HAVE_FALCON case falcon_level1_sa_algo: ret = decodeFalconKey(ssl, 1); @@ -11250,6 +11496,14 @@ static int DoTls13CertificateVerify(WOLFSSL* ssl, byte* input, ssl->peerMlDsaKeyPresent = 0; } #endif + #ifdef WOLFSSL_HAVE_SLHDSA + else if (ssl->peerSlhDsaKeyPresent && + !IsSlhDsaSigAlgo(sa)) { + FreeKey(ssl, DYNAMIC_TYPE_SLHDSA, + (void**)&ssl->peerSlhDsaKey); + ssl->peerSlhDsaKeyPresent = 0; + } + #endif #ifdef HAVE_FALCON else if (ssl->peerFalconKeyPresent && sa != falcon_level1_sa_algo && @@ -11326,6 +11580,13 @@ static int DoTls13CertificateVerify(WOLFSSL* ssl, byte* input, ssl->peerMlDsaKeyPresent; } #endif + #ifdef WOLFSSL_HAVE_SLHDSA + if (IsSlhDsaSigAlgo(ssl->options.peerSigAlgo)) { + WOLFSSL_MSG("Peer sent SLH-DSA sig"); + validSigAlgo = (ssl->peerSlhDsaKey != NULL) && + ssl->peerSlhDsaKeyPresent; + } + #endif #ifndef NO_RSA if (ssl->options.peerSigAlgo == rsa_sa_algo) { WOLFSSL_MSG("Peer sent PKCS#1.5 algo - not valid TLS 1.3"); @@ -11633,6 +11894,31 @@ static int DoTls13CertificateVerify(WOLFSSL* ssl, byte* input, } } #endif /* WOLFSSL_HAVE_MLDSA */ + #if defined(WOLFSSL_HAVE_SLHDSA) + if (IsSlhDsaSigAlgo(ssl->options.peerSigAlgo) && + (ssl->peerSlhDsaKeyPresent)) { + WOLFSSL_MSG("Doing SLH-DSA peer cert verify"); + /* Pure SLH-DSA with empty context, per draft-reddy-tls-slhdsa. + * wc_SlhDsaKey_Verify returns 0 for a valid signature. */ + ret = wc_SlhDsaKey_Verify(ssl->peerSlhDsaKey, NULL, 0, + args->sigData, args->sigDataSz, + sig, args->sigSz); + + if (ret == 0) { + /* CLIENT/SERVER: data verified with public key from + * certificate. */ + ssl->options.peerAuthGood = 1; + + FreeKey(ssl, DYNAMIC_TYPE_SLHDSA, + (void**)&ssl->peerSlhDsaKey); + ssl->peerSlhDsaKeyPresent = 0; + } + else { + WOLFSSL_MSG("SLH-DSA signature verification failed"); + ret = SIG_VERIFY_E; + } + } + #endif /* WOLFSSL_HAVE_SLHDSA */ /* Check for error */ if (ret != 0) { @@ -14677,7 +14963,8 @@ int wolfSSL_connect_TLSv13(WOLFSSL* ssl) case FIRST_REPLY_THIRD: #if (!defined(NO_CERTS) && (!defined(NO_RSA) || defined(HAVE_ECC) || \ defined(HAVE_ED25519) || defined(HAVE_ED448) || \ - defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA))) && \ + defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) || \ + defined(WOLFSSL_HAVE_SLHDSA))) && \ (!defined(NO_WOLFSSL_SERVER) || !defined(WOLFSSL_NO_CLIENT_AUTH)) if (!ssl->options.resuming && ssl->options.sendVerify) { ssl->error = SendTls13CertificateVerify(ssl); diff --git a/tests/suites.c b/tests/suites.c index d6dd4fa44b..608e094e29 100644 --- a/tests/suites.c +++ b/tests/suites.c @@ -1349,6 +1349,21 @@ int SuiteTest(int argc, char** argv) goto exit; } #endif +#if defined(WOLFSSL_HAVE_SLHDSA) && defined(WOLFSSL_SLHDSA_PARAM_128F) && \ + defined(WOLFSSL_TLS13) + /* SLH-DSA entity (leaf) certificate used for the handshake signature in + * CertificateVerify. SLH-DSA-SHAKE-128f's ~17KB signature also exercises + * fragmented CertificateVerify send + reassembly. */ + XSTRLCPY(argv0[1], "tests/test-tls13-slhdsa-entity.conf", + sizeof(argv0[1])); + printf("starting TLSv13 SLH-DSA entity-cert CertificateVerify tests\n"); + test_harness(&args); + if (args.return_code != 0) { + printf("error from script %d\n", args.return_code); + args.return_code = EXIT_FAILURE; + goto exit; + } +#endif #if defined(HAVE_ECC) && defined(WOLFSSL_SHA512) && \ (defined(HAVE_ECC521) || defined(HAVE_ALL_CURVES)) /* add P-521 certificate cipher suite tests */ diff --git a/tests/test-tls13-slhdsa-entity.conf b/tests/test-tls13-slhdsa-entity.conf new file mode 100644 index 0000000000..0ecd2c1626 --- /dev/null +++ b/tests/test-tls13-slhdsa-entity.conf @@ -0,0 +1,40 @@ +# SLH-DSA entity (leaf) certificate used for the TLS 1.3 handshake signature +# in the CertificateVerify message (draft-reddy-tls-slhdsa). TLS 1.3 only. +# +# Uses a self-signed SLH-DSA-SHAKE-128f certificate. Its ~17KB signature makes +# the CertificateVerify handshake message exceed a single TLS record, so this +# also exercises fragmented CertificateVerify send + reassembly. + +# Server-auth scenario. + +# server TLSv1.3 TLS13-AES128-GCM-SHA256 +-v 4 +-l TLS13-AES128-GCM-SHA256 +-c ./certs/slhdsa/entity-slhdsa-shake-128f.pem +-k ./certs/slhdsa/entity-slhdsa-shake-128f-priv.pem +-d + +# client TLSv1.3 TLS13-AES128-GCM-SHA256 +-v 4 +-l TLS13-AES128-GCM-SHA256 +-A ./certs/slhdsa/entity-slhdsa-shake-128f.pem +-C + +# Mutual-auth scenario (SLH-DSA CertificateVerify in both directions). + +# server TLSv1.3 TLS13-AES128-GCM-SHA256 +-v 4 +-l TLS13-AES128-GCM-SHA256 +-c ./certs/slhdsa/entity-slhdsa-shake-128f.pem +-k ./certs/slhdsa/entity-slhdsa-shake-128f-priv.pem +-A ./certs/slhdsa/entity-slhdsa-shake-128f.pem +-V +# Remove -V when CRL for SLH-DSA certificates available. + +# client TLSv1.3 TLS13-AES128-GCM-SHA256 +-v 4 +-l TLS13-AES128-GCM-SHA256 +-c ./certs/slhdsa/entity-slhdsa-shake-128f.pem +-k ./certs/slhdsa/entity-slhdsa-shake-128f-priv.pem +-A ./certs/slhdsa/entity-slhdsa-shake-128f.pem +-C diff --git a/wolfssl/internal.h b/wolfssl/internal.h index ebafb498af..58e83cd5e3 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -129,6 +129,9 @@ #ifdef WOLFSSL_HAVE_MLDSA #include #endif +#ifdef WOLFSSL_HAVE_SLHDSA + #include +#endif #ifdef HAVE_HKDF #include #endif @@ -1801,6 +1804,23 @@ enum Misc { MLDSA_87_SA_MAJOR = 0x09, MLDSA_87_SA_MINOR = 0x06, + /* These values for SLH-DSA correspond to the code points assigned in + * draft-reddy-tls-slhdsa (0x0911-0x091C) and match what oqs-provider uses. + * The major byte (0x09) is shared with ML-DSA. */ + SLHDSA_SA_MAJOR = 0x09, + SLHDSA_SHA2_128S_SA_MINOR = 0x11, + SLHDSA_SHA2_128F_SA_MINOR = 0x12, + SLHDSA_SHA2_192S_SA_MINOR = 0x13, + SLHDSA_SHA2_192F_SA_MINOR = 0x14, + SLHDSA_SHA2_256S_SA_MINOR = 0x15, + SLHDSA_SHA2_256F_SA_MINOR = 0x16, + SLHDSA_SHAKE_128S_SA_MINOR = 0x17, + SLHDSA_SHAKE_128F_SA_MINOR = 0x18, + SLHDSA_SHAKE_192S_SA_MINOR = 0x19, + SLHDSA_SHAKE_192F_SA_MINOR = 0x1A, + SLHDSA_SHAKE_256S_SA_MINOR = 0x1B, + SLHDSA_SHAKE_256F_SA_MINOR = 0x1C, + MIN_RSA_SHA512_PSS_BITS = 512 * 2 + 8 * 8, /* Min key size */ MIN_RSA_SHA384_PSS_BITS = 384 * 2 + 8 * 8, /* Min key size */ @@ -1981,7 +2001,8 @@ WOLFSSL_LOCAL int NamedGroupIsPqcHybrid(int group); #endif #ifndef MAX_X509_SIZE - #if defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) + #if defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) || \ + defined(WOLFSSL_HAVE_SLHDSA) #define MAX_X509_SIZE (8*1024) /* max static x509 buffer size; ML-DSA is big */ #elif defined(WOLFSSL_HAPROXY) #define MAX_X509_SIZE 3072 /* max static x509 buffer size */ @@ -4012,6 +4033,7 @@ struct WOLFSSL_CTX { byte haveECDSAsig:1; /* server cert signed w/ ECDSA */ byte haveFalconSig:1; /* server cert signed w/ Falcon */ byte haveMlDsaSig:1; /* server cert signed w/ ML-DSA */ + byte haveSlhDsaSig:1; /* server cert signed w/ SLH-DSA */ byte haveStaticECC:1; /* static server ECC private key */ byte partialWrite:1; /* only one msg per write call */ byte autoRetry:1; /* retry read/write on a WANT_{READ|WRITE} */ @@ -4475,9 +4497,10 @@ enum KeyExchangeAlgorithm { #define SIG_FALCON 0x08 #define SIG_MLDSA 0x10 #define SIG_ANON 0x20 +#define SIG_SLHDSA 0x40 /* SIG_ANON is omitted by default */ #define SIG_ALL (SIG_ECDSA | SIG_RSA | SIG_SM2 | SIG_FALCON | \ - SIG_MLDSA) + SIG_MLDSA | SIG_SLHDSA) /* Supported Authentication Schemes */ enum SignatureAlgorithm { @@ -4497,6 +4520,18 @@ enum SignatureAlgorithm { sm2_sa_algo = 17, any_sa_algo = 18, ecc_brainpool_sa_algo = 19, + slhdsa_sha2_128s_sa_algo = 20, + slhdsa_sha2_128f_sa_algo = 21, + slhdsa_sha2_192s_sa_algo = 22, + slhdsa_sha2_192f_sa_algo = 23, + slhdsa_sha2_256s_sa_algo = 24, + slhdsa_sha2_256f_sa_algo = 25, + slhdsa_shake_128s_sa_algo = 26, + slhdsa_shake_128f_sa_algo = 27, + slhdsa_shake_192s_sa_algo = 28, + slhdsa_shake_192f_sa_algo = 29, + slhdsa_shake_256s_sa_algo = 30, + slhdsa_shake_256f_sa_algo = 31, invalid_sa_algo = 255 }; @@ -5167,6 +5202,7 @@ struct Options { word16 haveStaticECC:1; /* static server ECC private key */ word16 haveFalconSig:1; /* server Falcon signed cert */ word16 haveMlDsaSig:1; /* server ML-DSA signed cert */ + word16 haveSlhDsaSig:1; /* server SLH-DSA signed cert */ word16 havePeerCert:1; /* do we have peer's cert */ word16 havePeerVerify:1; /* and peer's cert verify */ word16 usingPSK_cipher:1; /* are using psk as cipher */ @@ -6313,6 +6349,10 @@ struct WOLFSSL { wc_MlDsaKey* peerMlDsaKey; byte peerMlDsaKeyPresent; #endif +#ifdef WOLFSSL_HAVE_SLHDSA + SlhDsaKey* peerSlhDsaKey; + byte peerSlhDsaKeyPresent; +#endif #ifdef HAVE_LIBZ z_stream c_stream; /* compression stream */ z_stream d_stream; /* decompression stream */ @@ -7195,6 +7235,12 @@ WOLFSSL_LOCAL int FindSuite(const Suites* suites, byte first, byte second); WOLFSSL_LOCAL void DecodeSigAlg(const byte* input, byte* hashAlgo, byte* hsType); +#ifdef WOLFSSL_HAVE_SLHDSA +WOLFSSL_LOCAL byte SlhDsaSigMinorToType(byte minor); +WOLFSSL_LOCAL int SlhDsaTypeToParam(byte hsType); +WOLFSSL_LOCAL int IsSlhDsaSigAlgo(byte hsType); +WOLFSSL_LOCAL byte SlhDsaParamToType(int param); +#endif WOLFSSL_LOCAL enum wc_HashType HashAlgoToType(int hashAlgo); #ifndef NO_CERTS diff --git a/wolfssl/wolfcrypt/asn.h b/wolfssl/wolfcrypt/asn.h index 8b12a306b2..a85a498c91 100644 --- a/wolfssl/wolfcrypt/asn.h +++ b/wolfssl/wolfcrypt/asn.h @@ -883,7 +883,12 @@ extern const WOLFSSL_ObjectInfo wolfssl_object_info[]; #endif #endif -#if defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) +#if defined(WOLFSSL_HAVE_SLHDSA) + /* SLH-DSA signatures are large (up to ~50KB for the 'f' parameter sets). + * This buffer must hold the full handshake signature before it is + * fragmented across TLS records in SendTls13CertificateVerify. */ + #define WC_MAX_CERT_VERIFY_SZ (WC_SLHDSA_MAX_SIG_LEN + 1024) +#elif defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) #define WC_MAX_CERT_VERIFY_SZ 6000 /* For ML-DSA */ #elif defined(WOLFSSL_CERT_EXT) #define WC_MAX_CERT_VERIFY_SZ 2048 /* For larger extensions */ diff --git a/wolfssl/wolfcrypt/wc_slhdsa.h b/wolfssl/wolfcrypt/wc_slhdsa.h index ecf29dfea5..3243e6ee43 100644 --- a/wolfssl/wolfcrypt/wc_slhdsa.h +++ b/wolfssl/wolfcrypt/wc_slhdsa.h @@ -567,6 +567,39 @@ enum SlhDsaParam { #endif }; +/* A parameter set that is guaranteed to be compiled in. Use as a placeholder + * for wc_SlhDsaKey_Init when the real parameter set is only known later (e.g. + * determined from a decoded key OID or a negotiated TLS signature scheme). + * The SHAKE family is disabled when only the SHA2 family is enabled, so the + * placeholder cannot be a fixed SHAKE value. */ +#if defined(WOLFSSL_SLHDSA_PARAM_128S) + #define WC_SLHDSA_DEFAULT_PARAM SLHDSA_SHAKE128S +#elif defined(WOLFSSL_SLHDSA_PARAM_128F) + #define WC_SLHDSA_DEFAULT_PARAM SLHDSA_SHAKE128F +#elif defined(WOLFSSL_SLHDSA_PARAM_192S) + #define WC_SLHDSA_DEFAULT_PARAM SLHDSA_SHAKE192S +#elif defined(WOLFSSL_SLHDSA_PARAM_192F) + #define WC_SLHDSA_DEFAULT_PARAM SLHDSA_SHAKE192F +#elif defined(WOLFSSL_SLHDSA_PARAM_256S) + #define WC_SLHDSA_DEFAULT_PARAM SLHDSA_SHAKE256S +#elif defined(WOLFSSL_SLHDSA_PARAM_256F) + #define WC_SLHDSA_DEFAULT_PARAM SLHDSA_SHAKE256F +#elif defined(WOLFSSL_SLHDSA_SHA2) && defined(WOLFSSL_SLHDSA_PARAM_SHA2_128S) + #define WC_SLHDSA_DEFAULT_PARAM SLHDSA_SHA2_128S +#elif defined(WOLFSSL_SLHDSA_SHA2) && defined(WOLFSSL_SLHDSA_PARAM_SHA2_128F) + #define WC_SLHDSA_DEFAULT_PARAM SLHDSA_SHA2_128F +#elif defined(WOLFSSL_SLHDSA_SHA2) && defined(WOLFSSL_SLHDSA_PARAM_SHA2_192S) + #define WC_SLHDSA_DEFAULT_PARAM SLHDSA_SHA2_192S +#elif defined(WOLFSSL_SLHDSA_SHA2) && defined(WOLFSSL_SLHDSA_PARAM_SHA2_192F) + #define WC_SLHDSA_DEFAULT_PARAM SLHDSA_SHA2_192F +#elif defined(WOLFSSL_SLHDSA_SHA2) && defined(WOLFSSL_SLHDSA_PARAM_SHA2_256S) + #define WC_SLHDSA_DEFAULT_PARAM SLHDSA_SHA2_256S +#elif defined(WOLFSSL_SLHDSA_SHA2) && defined(WOLFSSL_SLHDSA_PARAM_SHA2_256F) + #define WC_SLHDSA_DEFAULT_PARAM SLHDSA_SHA2_256F +#else + #error "WOLFSSL_HAVE_SLHDSA requires at least one parameter set" +#endif + /* Helper macro to detect SHA2 parameter sets. */ #ifdef WOLFSSL_SLHDSA_SHA2 #define SLHDSA_IS_SHA2(p) ((p) >= SLHDSA_SHA2_128S) From 320b7b79ee43531ef2c943cfcedd6318152e03c4 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Jul 2026 21:21:31 +0000 Subject: [PATCH 2/3] Fix SLH-DSA buffer-sizing maxima for SHA2-only builds The SLHDSA_MAX_N/A/H_M/INDICES_SZ/MD buffer-sizing maxima in wc_slhdsa.c and the WC_SLHDSA_MAX_PRIV_LEN/PUB_LEN/SEED public maxima in wc_slhdsa.h were conditioned only on the SHAKE-family disable guards (WOLFSSL_SLHDSA_PARAM_NO_256/192/SMALL/FAST). In a SHA2-only build (--enable-slhdsa=sha2) the SHAKE family is disabled, so those guards are all defined and the maxima collapse to their 128-bit-level values, while the SHA2 parameter tables remain fully enabled with larger n/a/h_m. Signing then wrote the FORS/XMSS 'nodes' stack buffers (sized (SLHDSA_MAX_A+1)*SLHDSA_MAX_N and (SLHDSA_MAX_H_M+2)*SLHDSA_MAX_N) using the true parameters and overran them, causing a stack-smashing abort; wc_SlhDsaKey_ExportPrivate similarly failed with BAD_LENGTH_E on the 192/256 SHA2 sets. Derive combined SLHDSA_ALL_NO_* guards (in wc_slhdsa.h) that treat a size/speed class as absent only when it is absent in BOTH the SHAKE and SHA2 families, and size every maximum off those. SHAKE-only and all-parameter builds are unaffected: with the SHA2 family off, WOLFSSL_SLHDSA_NO_SHA2 makes each combined guard equal to its SHAKE guard; with all sets enabled none of the guards are defined, so the maxima resolve to the same largest values as before. Verified under --enable-slhdsa=sha2: benchmark signs all SHA2 parameter sets, testwolfcrypt SLH-DSA (incl. keygen KATs and sign/verify) passes, and a TLS 1.3 handshake using a SHA2 SLH-DSA certificate completes. SHAKE-only and combined builds still pass with no behavior change. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01SeLwE2QcsVHDFR5iXW6Hcj --- wolfcrypt/src/wc_slhdsa.c | 40 ++++++++++++--------- wolfssl/wolfcrypt/wc_slhdsa.h | 66 +++++++++++++++++++++++++++++++++-- 2 files changed, 88 insertions(+), 18 deletions(-) diff --git a/wolfcrypt/src/wc_slhdsa.c b/wolfcrypt/src/wc_slhdsa.c index 4d24d5ff96..381cc87ee2 100644 --- a/wolfcrypt/src/wc_slhdsa.c +++ b/wolfcrypt/src/wc_slhdsa.c @@ -70,20 +70,28 @@ static cpuid_flags_t cpuid_flags = WC_CPUID_INITIALIZER; #define SLHDSA_WM1 (SLHDSA_W - 1) -#ifndef WOLFSSL_SLHDSA_PARAM_NO_256 +/* The buffer-sizing maxima below (SLHDSA_MAX_N/A/H_M/INDICES_SZ/MD) size + * stack/heap buffers that are later written using the true n/a/h_m taken from + * key->params at runtime, so they must stay large enough for every compiled-in + * parameter set across BOTH hash families. They key off the combined + * SLHDSA_ALL_NO_* guards (derived in wc_slhdsa.h), NOT the per-family SHAKE + * 'NO' guards, otherwise a SHA2-only build (SHAKE disabled) would collapse the + * maxima below the SHA2 parameters' true needs and overflow the FORS/XMSS + * 'nodes' buffers. */ +#ifndef SLHDSA_ALL_NO_256 /* Maximum size of hash output. */ #define SLHDSA_MAX_N 32 - #ifndef WOLFSSL_SLHDSA_PARAM_NO_FAST + #ifndef SLHDSA_ALL_NO_FAST /* Maximum number of indices for FORS signatures. */ #define SLHDSA_MAX_INDICES_SZ 35 #else /* Maximum number of indices for FORS signatures. */ #define SLHDSA_MAX_INDICES_SZ 22 #endif -#elif !defined(WOLFSSL_SLHDSA_PARAM_NO_192) +#elif !defined(SLHDSA_ALL_NO_192) /* Maximum size of hash output. */ #define SLHDSA_MAX_N 24 - #ifndef WOLFSSL_SLHDSA_PARAM_NO_FAST + #ifndef SLHDSA_ALL_NO_FAST /* Maximum number of indices for FORS signatures. */ #define SLHDSA_MAX_INDICES_SZ 33 #else @@ -93,7 +101,7 @@ static cpuid_flags_t cpuid_flags = WC_CPUID_INITIALIZER; #else /* Maximum size of hash output. */ #define SLHDSA_MAX_N 16 - #ifndef WOLFSSL_SLHDSA_PARAM_NO_FAST + #ifndef SLHDSA_ALL_NO_FAST /* Maximum number of indices for FORS signatures. */ #define SLHDSA_MAX_INDICES_SZ 33 #else @@ -102,11 +110,11 @@ static cpuid_flags_t cpuid_flags = WC_CPUID_INITIALIZER; #endif #endif -#ifndef WOLFSSL_SLHDSA_PARAM_NO_SMALL - #if !defined(WOLFSSL_SLHDSA_PARAM_NO_256) +#ifndef SLHDSA_ALL_NO_SMALL + #if !defined(SLHDSA_ALL_NO_256) /* Maximum number of trees for FORS. */ #define SLHDSA_MAX_A 14 - #elif !defined(WOLFSSL_SLHDSA_PARAM_NO_192) + #elif !defined(SLHDSA_ALL_NO_192) /* Maximum number of trees for FORS. */ #define SLHDSA_MAX_A 14 #else @@ -114,10 +122,10 @@ static cpuid_flags_t cpuid_flags = WC_CPUID_INITIALIZER; #define SLHDSA_MAX_A 12 #endif #else - #if !defined(WOLFSSL_SLHDSA_PARAM_NO_256) + #if !defined(SLHDSA_ALL_NO_256) /* Maximum number of trees for FORS. */ #define SLHDSA_MAX_A 9 - #elif !defined(WOLFSSL_SLHDSA_PARAM_NO_192) + #elif !defined(SLHDSA_ALL_NO_192) /* Maximum number of trees for FORS. */ #define SLHDSA_MAX_A 8 #else @@ -126,7 +134,7 @@ static cpuid_flags_t cpuid_flags = WC_CPUID_INITIALIZER; #endif #endif -#ifndef WOLFSSL_SLHDSA_PARAM_NO_SMALL +#ifndef SLHDSA_ALL_NO_SMALL /* Maximum height of Merkle tree. */ #define SLHDSA_MAX_H_M 9 #else @@ -147,19 +155,19 @@ wc_static_assert(SLHDSA_MAX_MSG_SZ <= 255); * declarations and the ForceZero() sizes so they cannot drift. */ #define SLHDSA_SHAKE_X4_STATE_W (25 * 4) -#ifndef WOLFSSL_SLHDSA_PARAM_NO_256F +#ifndef SLHDSA_ALL_NO_256F /* Maximum number of bytes to produce from digest of message. */ #define SLHDSA_MAX_MD 49 -#elif !defined(WOLFSSL_SLHDSA_PARAM_NO_256S) +#elif !defined(SLHDSA_ALL_NO_256S) /* Maximum number of bytes to produce from digest of message. */ #define SLHDSA_MAX_MD 47 -#elif !defined(WOLFSSL_SLHDSA_PARAM_NO_192F) +#elif !defined(SLHDSA_ALL_NO_192F) /* Maximum number of bytes to produce from digest of message. */ #define SLHDSA_MAX_MD 42 -#elif !defined(WOLFSSL_SLHDSA_PARAM_NO_192S) +#elif !defined(SLHDSA_ALL_NO_192S) /* Maximum number of bytes to produce from digest of message. */ #define SLHDSA_MAX_MD 39 -#elif !defined(WOLFSSL_SLHDSA_PARAM_NO_128F) +#elif !defined(SLHDSA_ALL_NO_128F) /* Maximum number of bytes to produce from digest of message. */ #define SLHDSA_MAX_MD 34 #else diff --git a/wolfssl/wolfcrypt/wc_slhdsa.h b/wolfssl/wolfcrypt/wc_slhdsa.h index 3243e6ee43..0acc6bfdcc 100644 --- a/wolfssl/wolfcrypt/wc_slhdsa.h +++ b/wolfssl/wolfcrypt/wc_slhdsa.h @@ -468,18 +468,80 @@ #endif /* WOLFSSL_SLHDSA_SHA2 */ +/* ======== Combined family-absence guards ======== */ + +/* A size/speed class is only truly absent when neither its SHAKE variant nor + * its SHA2 variant is compiled in. The per-family 'NO' guards above describe + * one family each; the SHA2 'NO' macros exist only when WOLFSSL_SLHDSA_SHA2 is + * defined, otherwise WOLFSSL_SLHDSA_NO_SHA2 stands for "all SHA2 absent". + * These combined guards let the maximum-size defines below (and the internal + * buffer maxima in wc_slhdsa.c) stay large enough for every compiled-in + * parameter set across both hash families. Without them a SHA2-only build + * (SHAKE disabled) would size buffers for the 128-bit level only and overflow + * on the 192/256-bit SHA2 parameter sets. */ +#if defined(WOLFSSL_SLHDSA_PARAM_NO_256) && \ + (defined(WOLFSSL_SLHDSA_NO_SHA2) || \ + defined(WOLFSSL_SLHDSA_PARAM_NO_SHA2_256)) + #define SLHDSA_ALL_NO_256 +#endif +#if defined(WOLFSSL_SLHDSA_PARAM_NO_192) && \ + (defined(WOLFSSL_SLHDSA_NO_SHA2) || \ + defined(WOLFSSL_SLHDSA_PARAM_NO_SHA2_192)) + #define SLHDSA_ALL_NO_192 +#endif +#if defined(WOLFSSL_SLHDSA_PARAM_NO_SMALL) && \ + (defined(WOLFSSL_SLHDSA_NO_SHA2) || \ + defined(WOLFSSL_SLHDSA_PARAM_NO_SHA2_SMALL)) + #define SLHDSA_ALL_NO_SMALL +#endif +#if defined(WOLFSSL_SLHDSA_PARAM_NO_FAST) && \ + (defined(WOLFSSL_SLHDSA_NO_SHA2) || \ + defined(WOLFSSL_SLHDSA_PARAM_NO_SHA2_FAST)) + #define SLHDSA_ALL_NO_FAST +#endif +#if defined(WOLFSSL_SLHDSA_PARAM_NO_128) && \ + (defined(WOLFSSL_SLHDSA_NO_SHA2) || \ + defined(WOLFSSL_SLHDSA_PARAM_NO_SHA2_128)) + #define SLHDSA_ALL_NO_128 +#endif +#if defined(WOLFSSL_SLHDSA_PARAM_NO_256F) && \ + (defined(WOLFSSL_SLHDSA_NO_SHA2) || \ + defined(WOLFSSL_SLHDSA_PARAM_NO_SHA2_256F)) + #define SLHDSA_ALL_NO_256F +#endif +#if defined(WOLFSSL_SLHDSA_PARAM_NO_256S) && \ + (defined(WOLFSSL_SLHDSA_NO_SHA2) || \ + defined(WOLFSSL_SLHDSA_PARAM_NO_SHA2_256S)) + #define SLHDSA_ALL_NO_256S +#endif +#if defined(WOLFSSL_SLHDSA_PARAM_NO_192F) && \ + (defined(WOLFSSL_SLHDSA_NO_SHA2) || \ + defined(WOLFSSL_SLHDSA_PARAM_NO_SHA2_192F)) + #define SLHDSA_ALL_NO_192F +#endif +#if defined(WOLFSSL_SLHDSA_PARAM_NO_192S) && \ + (defined(WOLFSSL_SLHDSA_NO_SHA2) || \ + defined(WOLFSSL_SLHDSA_PARAM_NO_SHA2_192S)) + #define SLHDSA_ALL_NO_192S +#endif +#if defined(WOLFSSL_SLHDSA_PARAM_NO_128F) && \ + (defined(WOLFSSL_SLHDSA_NO_SHA2) || \ + defined(WOLFSSL_SLHDSA_PARAM_NO_SHA2_128F)) + #define SLHDSA_ALL_NO_128F +#endif + /* ======== Maximum size defines ======== */ /* Determine maximum private and public key lengths based on maximum 256-bit * output length. SHA2 variants have identical sizes to SHAKE counterparts. */ -#ifndef WOLFSSL_SLHDSA_PARAM_NO_256 +#ifndef SLHDSA_ALL_NO_256 /* Maximum private key length. */ #define WC_SLHDSA_MAX_PRIV_LEN WC_SLHDSA_SHAKE256F_PRIV_LEN /* Maximum public key length. */ #define WC_SLHDSA_MAX_PUB_LEN WC_SLHDSA_SHAKE256F_PUB_LEN /* Maximum seed length. */ #define WC_SLHDSA_MAX_SEED WC_SLHDSA_SHAKE256_SEED_LEN -#elif !defined(WOLFSSL_SLHDSA_PARAM_NO_192) +#elif !defined(SLHDSA_ALL_NO_192) /* Maximum private key length. */ #define WC_SLHDSA_MAX_PRIV_LEN WC_SLHDSA_SHAKE192F_PRIV_LEN /* Maximum public key length. */ From 17726ec50227490ff3365d2ea5387b2b3b88f84a Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Jul 2026 21:58:06 +0000 Subject: [PATCH 3/3] Add SLH-DSA wolfSSL<->OpenSSL+oqs-provider TLS 1.3 interop test script Adds scripts/slhdsa-oqs-interop.sh, a manual interoperability harness that exercises the SLH-DSA CertificateVerify handshake signature between wolfSSL and OpenSSL 3.4 + oqs-provider (which supplies the draft-reddy-tls-slhdsa TLS signature schemes at code points 0x0911-0x091C that wolfSSL matches). It runs three scenarios: A) wolfSSL server signs -> OpenSSL+oqs client verifies (SHAKE-128f, SHA2-128s) B) OpenSSL+oqs server signs -> wolfSSL client verifies (SHAKE-128f) SHAKE-128f's ~17KB signature makes the CertificateVerify span multiple TLS records, so the harness also cross-checks fragmented CertificateVerify send and reassembly between the two stacks. The script header documents the from-source build of liboqs (main, OQS_USE_OPENSSL=OFF), OpenSSL 3.4, and oqs-provider (main); OpenSSL 3.4 is used because provider TLS signature algorithms work there and it has no native SLH-DSA to shadow oqs-provider's (OpenSSL >= 3.5 does not expose SLH-DSA as a TLS sig alg and causes oqs-provider to drop it). Verified locally: all three scenarios pass in both signing directions. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01SeLwE2QcsVHDFR5iXW6Hcj --- scripts/include.am | 1 + scripts/slhdsa-oqs-interop.sh | 138 ++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100755 scripts/slhdsa-oqs-interop.sh diff --git a/scripts/include.am b/scripts/include.am index 38dc071466..540d06d616 100644 --- a/scripts/include.am +++ b/scripts/include.am @@ -98,6 +98,7 @@ if !BUILD_IPV6 dist_noinst_SCRIPTS+= scripts/external.test dist_noinst_SCRIPTS+= scripts/google.test dist_noinst_SCRIPTS+= scripts/openssl.test +EXTRA_DIST+= scripts/slhdsa-oqs-interop.sh if BUILD_OCSP dist_noinst_SCRIPTS+= scripts/ocsp.test diff --git a/scripts/slhdsa-oqs-interop.sh b/scripts/slhdsa-oqs-interop.sh new file mode 100755 index 0000000000..8808debc1d --- /dev/null +++ b/scripts/slhdsa-oqs-interop.sh @@ -0,0 +1,138 @@ +#!/usr/bin/env bash +# +# SLH-DSA TLS 1.3 interoperability test: wolfSSL <-> OpenSSL + oqs-provider. +# +# Exercises the SLH-DSA handshake signature (CertificateVerify) in both +# directions against an independent implementation, per draft-reddy-tls-slhdsa +# (TLS SignatureScheme code points 0x0911-0x091C). The larger 'f' parameter +# sets produce >16KB signatures, so this also exercises fragmented +# CertificateVerify send + reassembly across the two stacks. +# +# OpenSSL < 3.5 has no native SLH-DSA and OpenSSL >= 3.5 does not expose +# SLH-DSA as a TLS signature scheme, while oqs-provider drops SLH-DSA when the +# underlying OpenSSL provides it natively. OpenSSL 3.4 (provider TLS-sigalg +# support, no native SLH-DSA) is therefore the reference peer here, with +# liboqs + oqs-provider supplying the SLH-DSA algorithms and the 0x0911 code +# points that wolfSSL matches. +# +# Prerequisites (built from source, see build steps below): +# - liboqs (main) built with OQS_USE_OPENSSL=OFF +# - oqs-provider (main) built against OpenSSL 3.4 +# - OpenSSL 3.4 (shared) +# - wolfSSL configured with: --enable-tls13 --enable-slhdsa [CFLAGS=-DWOLFSSL_SLHDSA_SHA2] +# +# Build steps (run once; OQS_ROOT defaults to $HOME/oqs-interop): +# git clone https://github.com/open-quantum-safe/liboqs +# cmake -S liboqs -B liboqs/build -GNinja -DCMAKE_INSTALL_PREFIX=$OQS_ROOT/local \ +# -DBUILD_SHARED_LIBS=ON -DOQS_BUILD_ONLY_LIB=ON -DOQS_USE_OPENSSL=OFF +# ninja -C liboqs/build install +# git clone -b openssl-3.4 https://github.com/openssl/openssl $OQS_ROOT/openssl34 +# (cd $OQS_ROOT/openssl34 && ./Configure --prefix=$OQS_ROOT/ossl34 shared no-docs \ +# -Wl,-rpath,$OQS_ROOT/ossl34/lib64 && make -j"$(nproc)" && make install_sw) +# git clone https://github.com/open-quantum-safe/oqs-provider +# cmake -S oqs-provider -B oqs-provider/build -GNinja \ +# -DOPENSSL_ROOT_DIR=$OQS_ROOT/ossl34 \ +# -Dliboqs_DIR=$OQS_ROOT/local/lib/cmake/liboqs +# ninja -C oqs-provider/build oqsprovider +# +# Usage: run from the wolfSSL source root after `./configure ... && make`. +# OQS_ROOT=/path/to/oqs-interop ./scripts/slhdsa-oqs-interop.sh + +set -u + +OQS_ROOT="${OQS_ROOT:-$HOME/oqs-interop}" +OSSL_DIR="${OSSL_DIR:-$OQS_ROOT/ossl34}" +OQS_MODULES="${OQS_MODULES:-$OQS_ROOT/oqs-provider/build/lib}" +OQS_LIBS="${OQS_LIBS:-$OQS_ROOT/local/lib}" +OSSL="$OSSL_DIR/bin/openssl" +PORT="${PORT:-11801}" +WOLF_SRV=./examples/server/server +WOLF_CLI=./examples/client/client +CERTDIR=certs/slhdsa +TMP="$(mktemp -d)" +trap 'rm -rf "$TMP"; pkill -f "s_server -tls1_3 -accept $PORT" 2>/dev/null' EXIT + +# Environment for driving the OpenSSL side with oqs-provider. +osslenv() { + env LD_LIBRARY_PATH="$OSSL_DIR/lib64:$OQS_LIBS" \ + OPENSSL_MODULES="$OQS_MODULES" \ + OPENSSL_CONF="${OPENSSL_CONF:-/usr/lib/ssl/openssl.cnf}" "$@" +} +# Environment for driving the wolfSSL examples. +wolfenv() { env LD_LIBRARY_PATH=./src/.libs "$@"; } + +fail=0 +pass() { echo "PASS: $1"; } +fatal() { echo "FAIL: $1"; fail=1; } + +[ -x "$OSSL" ] || { echo "OpenSSL 3.4 not found at $OSSL (see build steps)"; exit 1; } +[ -f "$OQS_MODULES/oqsprovider.so" ] || { echo "oqsprovider.so not found (see build steps)"; exit 1; } + +echo "wolfSSL: $(cat wolfssl/version.h 2>/dev/null | grep LIBWOLFSSL_VERSION_STRING | awk '{print $3}' | tr -d '\"')" +echo "OpenSSL: $(osslenv "$OSSL" version)" +echo "oqs SLH-DSA sig algs: $(osslenv "$OSSL" list -signature-algorithms -provider oqsprovider 2>/dev/null | grep -c slhdsa)" +echo + +# ---- Direction A: wolfSSL server signs, OpenSSL+oqs client verifies ---- +# param=SLH-DSA scheme name (openssl), cert/key = wolfSSL-generated SLH-DSA leaf +dirA() { + local name="$1" cert="$2" key="$3" sigalg="$4" + wolfenv timeout 30 "$WOLF_SRV" -v 4 -c "$cert" -k "$key" -d -p "$PORT" \ + > "$TMP/a_srv.log" 2>&1 & + local sp=$!; sleep 1 + echo | osslenv timeout 25 "$OSSL" s_client -tls1_3 -connect 127.0.0.1:"$PORT" \ + -CAfile "$cert" -provider oqsprovider -provider default \ + -sigalgs "$sigalg" -verify_return_error > "$TMP/a_cli.log" 2>&1 + wait $sp 2>/dev/null + if grep -q "Peer signature type: $sigalg" "$TMP/a_cli.log" \ + && grep -q "Verify return code: 0 (ok)" "$TMP/a_cli.log"; then + pass "A [$name] wolfSSL signs -> OpenSSL+oqs verifies" + else + fatal "A [$name] wolfSSL signs -> OpenSSL+oqs verifies" + tail -3 "$TMP/a_cli.log"; tail -2 "$TMP/a_srv.log" + fi +} + +# ---- Direction B: OpenSSL+oqs server signs, wolfSSL client verifies ---- +dirB() { + local name="$1" sigalg="$2" + # OpenSSL generates its own SLH-DSA leaf so it can load the private key. + osslenv "$OSSL" req -x509 -new -newkey "$sigalg" \ + -provider oqsprovider -provider default -nodes \ + -keyout "$TMP/o_key.pem" -out "$TMP/o_cert.pem" \ + -subj "/CN=openssl-$sigalg" -days 365 > "$TMP/b_gen.log" 2>&1 + osslenv timeout 30 "$OSSL" s_server -tls1_3 -accept "$PORT" \ + -provider oqsprovider -provider default \ + -cert "$TMP/o_cert.pem" -key "$TMP/o_key.pem" -naccept 1 -www \ + > "$TMP/b_srv.log" 2>&1 & + local sp=$!; sleep 1 + wolfenv timeout 25 "$WOLF_CLI" -v 4 -A "$TMP/o_cert.pem" -h 127.0.0.1 -p "$PORT" -g \ + > "$TMP/b_cli.log" 2>&1 + wait $sp 2>/dev/null + if grep -q "Doing SLH-DSA peer cert verify" "$TMP/b_cli.log" \ + && grep -q "DoTls13CertificateVerify, return 0" "$TMP/b_cli.log"; then + pass "B [$name] OpenSSL+oqs signs -> wolfSSL verifies" + else + fatal "B [$name] OpenSSL+oqs signs -> wolfSSL verifies" + tail -4 "$TMP/b_cli.log" + fi +} + +# SHAKE-128f: ~17KB signature -> fragmented CertificateVerify (both directions). +if [ -f "$CERTDIR/entity-slhdsa-shake-128f.pem" ]; then + dirA "SHAKE-128f (fragmented)" \ + "$CERTDIR/entity-slhdsa-shake-128f.pem" \ + "$CERTDIR/entity-slhdsa-shake-128f-priv.pem" slhdsashake128f +fi +dirB "SHAKE-128f (fragmented)" slhdsashake128f + +# SHA2-128s: single-record signature; exercises the SHA2 parameter family. +if [ -f "$CERTDIR/root-slhdsa-sha2-128s.pem" ]; then + dirA "SHA2-128s" \ + "$CERTDIR/root-slhdsa-sha2-128s.pem" \ + "$CERTDIR/root-slhdsa-sha2-128s-priv.pem" slhdsasha2128s +fi + +echo +[ $fail -eq 0 ] && echo "All SLH-DSA interop scenarios passed." || echo "Some scenarios FAILED." +exit $fail