diff --git a/.github/configs/pq-all.json b/.github/configs/pq-all.json index 08f1533e261..0a147cdbd13 100644 --- a/.github/configs/pq-all.json +++ b/.github/configs/pq-all.json @@ -209,5 +209,15 @@ {"name": "pkcs7-mldsa-only", "minutes": 0.3, "comment": "PKCS#7 SignedData with ML-DSA as the only signature algorithm (no RSA, no ECC); guards the ML-DSA-only PKCS7 build path", "configure": ["--enable-cryptonly", "--enable-mldsa", - "--enable-pkcs7", "--disable-rsa", "--disable-ecc"]} + "--enable-pkcs7", "--disable-rsa", "--disable-ecc"]}, +{"name": "pqc-only", "minutes": 2, + "comment": "PQC-only TLS 1.3: ML-KEM key exchange + ML-DSA auth with RSA, ECC and DH disabled (CNSA 2.0); session tickets exercise ML-KEM-only resumption", + "configure": ["--enable-mlkem", "--enable-mldsa", + "--enable-tls-mlkem-standalone", "--enable-session-ticket", + "--disable-rsa", "--disable-ecc", "--disable-dh"]}, +{"name": "slhdsa-pqc-only", "minutes": 2, + "comment": "PQC-only TLS 1.3: ML-KEM key exchange + SLH-DSA (FIPS 205) auth with RSA, ECC and DH disabled; exercises the SLH-DSA CertificateVerify path", + "configure": ["--enable-mlkem", "--enable-slhdsa", + "--enable-tls-mlkem-standalone", "--enable-session-ticket", + "--disable-rsa", "--disable-ecc", "--disable-dh"]} ] diff --git a/configure.ac b/configure.ac index c446c725cdb..568108b87c0 100644 --- a/configure.ac +++ b/configure.ac @@ -8251,7 +8251,8 @@ then # base. If none of those is available, hybrids contribute no usable groups # for TLS 1.3 key exchange. In that case, auto-enable standalone ML-KEM so # TLS 1.3 has a functional KEM. - if test "$ENABLED_MLKEM" = "yes" && test "$ENABLED_ML_KEM" != "no" && \ + if test "$ENABLED_TLS13" = "yes" && test "$ENABLED_MLKEM" = "yes" && \ + test "$ENABLED_ML_KEM" != "no" && \ test "x$ENABLED_ECC" = "xno" && test "x$ENABLED_CURVE25519" = "xno" && \ test "x$ENABLED_CURVE448" = "xno" then diff --git a/examples/client/client.c b/examples/client/client.c index 338c7c9234d..a9f8250aa0c 100644 --- a/examples/client/client.c +++ b/examples/client/client.c @@ -2414,6 +2414,14 @@ THREAD_RETURN WOLFSSL_THREAD client_test(void* args) verifyCert = caEd448CertFile; ourCert = cliEd448CertFile; ourKey = cliEd448KeyFile; + #elif defined(TEST_HAVE_MLDSA_CERTS) + verifyCert = caMldsaCertFile; + ourCert = cliMldsaCertFile; + ourKey = cliMldsaKeyFile; + #elif defined(TEST_HAVE_SLHDSA_CERTS) + verifyCert = caSlhdsaCertFile; + ourCert = cliSlhdsaCertFile; + ourKey = cliSlhdsaKeyFile; #else verifyCert = NULL; ourCert = NULL; @@ -3486,8 +3494,7 @@ THREAD_RETURN WOLFSSL_THREAD client_test(void* args) } #endif -#if defined(NO_RSA) && !defined(HAVE_ECC) && !defined(HAVE_ED25519) && \ - !defined(HAVE_ED448) +#if defined(TEST_NO_CLASSIC_AUTH) && !defined(TEST_HAVE_PQC_CERT_AUTH) if (!usePsk) { usePsk = 1; } diff --git a/examples/echoclient/echoclient.c b/examples/echoclient/echoclient.c index b35fd3c2c43..9e4267484bb 100644 --- a/examples/echoclient/echoclient.c +++ b/examples/echoclient/echoclient.c @@ -121,8 +121,8 @@ void echoclient_test(void* args) #ifdef WOLFSSL_LEANPSK doPSK = 1; #endif -#if defined(NO_CERTS) || (defined(NO_RSA) && !defined(HAVE_ECC) && \ - !defined(HAVE_ED25519) && !defined(HAVE_ED448)) +#if defined(NO_CERTS) || \ + (defined(TEST_NO_CLASSIC_AUTH) && !defined(TEST_HAVE_PQC_CERT_AUTH)) doPSK = 1; #endif (void)doPSK; @@ -160,6 +160,12 @@ void echoclient_test(void* args) #elif defined(HAVE_ED448) if (SSL_CTX_load_verify_locations(ctx, caEd448CertFile, 0) != WOLFSSL_SUCCESS) err_sys("can't load ca file, Please run from wolfSSL home dir"); + #elif defined(NO_RSA) && defined(TEST_HAVE_MLDSA_CERTS) + if (SSL_CTX_load_verify_locations(ctx, caMldsaCertFile, 0) != WOLFSSL_SUCCESS) + err_sys("can't load ca file, Please run from wolfSSL home dir"); + #elif defined(NO_RSA) && defined(TEST_HAVE_SLHDSA_CERTS) + if (SSL_CTX_load_verify_locations(ctx, caSlhdsaCertFile, 0) != WOLFSSL_SUCCESS) + err_sys("can't load ca file, Please run from wolfSSL home dir"); #endif #elif !defined(NO_CERTS) if (!doPSK) diff --git a/examples/echoserver/echoserver.c b/examples/echoserver/echoserver.c index c6e368033e1..65985b77350 100644 --- a/examples/echoserver/echoserver.c +++ b/examples/echoserver/echoserver.c @@ -124,8 +124,7 @@ THREAD_RETURN WOLFSSL_THREAD echoserver_test(void* args) ((func_args*)args)->return_code = -1; /* error state */ #if defined(NO_CERTS) || defined(WOLFSSL_LEANPSK) || \ - (defined(NO_RSA) && !defined(HAVE_ECC) && !defined(HAVE_ED25519) && \ - !defined(HAVE_ED448)) + (defined(TEST_NO_CLASSIC_AUTH) && !defined(TEST_HAVE_PQC_CERT_AUTH)) doPSK = 1; #else doPSK = 0; @@ -212,6 +211,30 @@ THREAD_RETURN WOLFSSL_THREAD echoserver_test(void* args) CERT_FILETYPE) != WOLFSSL_SUCCESS) err_sys("can't load server key file, " "Please run from wolfSSL home dir"); + #elif defined(NO_RSA) && defined(TEST_HAVE_MLDSA_CERTS) && \ + !defined(NO_CERTS) && !defined(WOLFSSL_SNIFFER) + /* ML-DSA (post-quantum-only) */ + if (wolfSSL_CTX_use_certificate_chain_file(ctx, mldsaCertFile) + != WOLFSSL_SUCCESS) + err_sys("can't load server cert file, " + "Please run from wolfSSL home dir"); + + if (wolfSSL_CTX_use_PrivateKey_file(ctx, mldsaKeyFile, CERT_FILETYPE) + != WOLFSSL_SUCCESS) + err_sys("can't load server key file, " + "Please run from wolfSSL home dir"); + #elif defined(NO_RSA) && defined(TEST_HAVE_SLHDSA_CERTS) && \ + !defined(NO_CERTS) && !defined(WOLFSSL_SNIFFER) + /* SLH-DSA (post-quantum-only); entity certs are PEM only */ + if (wolfSSL_CTX_use_certificate_chain_file(ctx, slhdsaCertFile) + != WOLFSSL_SUCCESS) + err_sys("can't load server cert file, " + "Please run from wolfSSL home dir"); + + if (wolfSSL_CTX_use_PrivateKey_file(ctx, slhdsaKeyFile, + CERT_FILETYPE) != WOLFSSL_SUCCESS) + err_sys("can't load server key file, " + "Please run from wolfSSL home dir"); #elif defined(NO_CERTS) /* do nothing, just don't load cert files */ #else diff --git a/examples/server/server.c b/examples/server/server.c index 8ea1fcf6b4b..028744891ea 100644 --- a/examples/server/server.c +++ b/examples/server/server.c @@ -1895,6 +1895,14 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) verifyCert = cliEd448CertFile; ourCert = ed448CertFile; ourKey = ed448KeyFile; + #elif defined(TEST_HAVE_MLDSA_CERTS) + verifyCert = caMldsaCertFile; + ourCert = mldsaCertFile; + ourKey = mldsaKeyFile; + #elif defined(TEST_HAVE_SLHDSA_CERTS) + verifyCert = caSlhdsaCertFile; + ourCert = slhdsaCertFile; + ourKey = slhdsaKeyFile; #else verifyCert = NULL; ourCert = NULL; @@ -2948,8 +2956,7 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) } #endif -#if defined(NO_RSA) && !defined(HAVE_ECC) && !defined(HAVE_ED25519) && \ - !defined(HAVE_ED448) +#if defined(TEST_NO_CLASSIC_AUTH) && !defined(TEST_HAVE_PQC_CERT_AUTH) if (!usePsk) { usePsk = 1; } diff --git a/scripts/ocsp-stapling_tls13multi.test b/scripts/ocsp-stapling_tls13multi.test index 2b05a85498b..b4ed1a47e97 100755 --- a/scripts/ocsp-stapling_tls13multi.test +++ b/scripts/ocsp-stapling_tls13multi.test @@ -60,6 +60,18 @@ else dtls13=yes fi +# The fragmented-send case needs a client that can request a small maximum +# fragment length and a server that can simulate WANT_WRITE. Both options are +# silently ignored otherwise, which would turn the case into a no-op that still +# reports PASSED. +if ! ./examples/client/client -? 2>&1 | grep -q 'Maximum Fragment Length'; then + fragmented_send=no +elif ./examples/server/server -6 -? 2>&1 | grep -q 'Ignoring -6'; then + fragmented_send=no +else + fragmented_send=yes +fi + if [[ ("$tls13" == "no") && ("$dtls13" == "no") ]]; then echo 'skipping ocsp-stapling_tls13multi.test because TLS1.3 is not available.' 1>&2 exit 77 @@ -492,6 +504,27 @@ if [ "$tls13" == "yes" ]; then exit 1 fi printf '%s\n\n' "Test successfully REVOKED!" + + printf '%s\n\n' "------------- TEST CASE 8 FRAGMENTED SEND --------------------" + if [ "$fragmented_send" == "no" ]; then + printf '%s\n\n' "Test SKIPPED: needs HAVE_MAX_FRAGMENT and async I/O." + else + # A small maximum fragment length (-F 1) splits the stapled Certificate + # message over many records, and the server (-6) blocks on every one of + # them, so the send resumes from a WANT_WRITE inside the chain. + remove_single_rF "$ready_file5" + ./examples/server/server -c certs/ocsp/server3-cert.pem \ + -k certs/ocsp/server3-key.pem -R "$ready_file5" \ + -p "$port5" -v 4 -6 & + server_pid5=$! + wait_for_readyFile "$ready_file5" "$server_pid5" "$port5" + ./examples/client/client -C -A certs/ocsp/root-ca-cert.pem -W 1 -v 4 \ + -F 1 -p "$port5" + RESULT=$? + [ "$RESULT" -ne 0 ] && printf '\n\n%s\n' "Client connection 8 failed" \ + && exit 1 + printf '%s\n\n' "Test PASSED!" + fi fi if [ "$dtls13" == "yes" ]; then diff --git a/src/internal.c b/src/internal.c index 953b71ff179..9b4fe918a70 100644 --- a/src/internal.c +++ b/src/internal.c @@ -9243,7 +9243,8 @@ int AllocKey(WOLFSSL* ssl, int type, void** pKey) (!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(WOLFSSL_HAVE_SLHDSA)) + (defined(WOLFSSL_HAVE_MLDSA) && !defined(WOLFSSL_MLDSA_NO_VERIFY)) || \ + defined(WOLFSSL_HAVE_SLHDSA)) static int ReuseKey(WOLFSSL* ssl, int type, void* pKey) { int ret = 0; @@ -9738,6 +9739,17 @@ void wolfSSL_ResourceFree(WOLFSSL* ssl) XFREE(ssl->buffers.tls13CookieSecret.buffer, ssl->heap, DYNAMIC_TYPE_COOKIE_PWD); #endif +#if !defined(NO_CERTS) && defined(WOLFSSL_TLS13) && \ + defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(NO_WOLFSSL_SERVER) + { + /* Release the certificate status extensions of a Certificate message + * that was never sent in full. */ + int extIdx; + + for (extIdx = 0; extIdx < MAX_CERT_EXTENSIONS; extIdx++) + FreeDer(&ssl->buffers.certExts[extIdx]); + } +#endif #ifdef WOLFSSL_TLS13_STREAM_CERT_VERIFY /* Release any in-progress streamed CertificateVerify body (e.g. a * connection torn down mid-send). */ @@ -15403,9 +15415,12 @@ int CopyDecodedToX509(WOLFSSL_X509* x509, DecodedCert* dCert) ret = MEMORY_E; } #endif -#if defined(HAVE_ECC) || defined(HAVE_ED25519) || defined(HAVE_ED448) +#if defined(HAVE_ECC) || defined(HAVE_ED25519) || defined(HAVE_ED448) || \ + defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) || \ + defined(WOLFSSL_HAVE_SLHDSA) x509->pkCurveOID = dCert->pkCurveOID; -#endif /* HAVE_ECC || HAVE_CURVE25519 || HAVE_CURVE448 */ +#endif /* HAVE_ECC || HAVE_ED25519 || HAVE_ED448 || HAVE_FALCON || + * WOLFSSL_HAVE_MLDSA || WOLFSSL_HAVE_SLHDSA */ #ifdef WOLFSSL_DUAL_ALG_CERTS copyRet = CopyDecodedDualAlg(x509, dCert); @@ -17172,6 +17187,10 @@ static int ProcessPeerCertDecodeKey(WOLFSSL* ssl, ProcPeerCertArgs* args, { int ret = *pRet; + /* Every case below sits under an algorithm guard, so a build with no + * peer-verifiable key type leaves this unused. */ + (void)ssl; + switch (args->dCert->keyOID) { #ifndef NO_RSA #ifdef WC_RSA_PSS @@ -32938,6 +32957,9 @@ static int DecodePrivateKey_ex(WOLFSSL *ssl, byte keyType, const DerBuffer* key, int devSlhParam = -1; #endif + /* Every reader below sits under an algorithm guard. */ + (void)ssl; + /* make sure private key exists */ if (key == NULL || key->buffer == NULL) { /* allow no private key if using external */ diff --git a/src/ssl.c b/src/ssl.c index e69a335c05d..c0c7c7f07eb 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -49,7 +49,10 @@ #if !defined(WOLFSSL_ALLOW_NO_SUITES) && !defined(WOLFCRYPT_ONLY) #if defined(NO_DH) && !defined(HAVE_ECC) && !defined(WOLFSSL_STATIC_RSA) \ && !defined(WOLFSSL_STATIC_DH) && !defined(WOLFSSL_STATIC_PSK) \ - && !defined(HAVE_CURVE25519) && !defined(HAVE_CURVE448) + && !defined(HAVE_CURVE25519) && !defined(HAVE_CURVE448) \ + && (!defined(WOLFSSL_TLS13) \ + || !defined(WOLFSSL_HAVE_MLKEM_CLIENT_SUPPORT) \ + || defined(WOLFSSL_TLS_NO_MLKEM_STANDALONE)) #error "No cipher suites defined because DH disabled, ECC disabled, " \ "and no static suites defined. Please see top of README" #endif diff --git a/src/ssl_load.c b/src/ssl_load.c index 2e97ce48df8..9fb323f01f9 100644 --- a/src/ssl_load.c +++ b/src/ssl_load.c @@ -1164,6 +1164,9 @@ static int ProcessBufferTryDecode(WOLFSSL_CTX* ctx, WOLFSSL* ssl, (void)heap; (void)devId; (void)type; + /* Only the per-algorithm decoders below read these back. */ + (void)keyType; + (void)keySz; /* Validate parameters. */ if ((der == NULL) || (keyFormat == NULL)) { diff --git a/src/tls.c b/src/tls.c index c27f08968de..be1533996bb 100644 --- a/src/tls.c +++ b/src/tls.c @@ -4672,17 +4672,6 @@ int TLSX_UseCertificateStatusRequestV2(TLSX** extensions, byte status_type, #endif /* HAVE_CERTIFICATE_STATUS_REQUEST_V2 */ -/* ML-KEM client support requires generating a key pair (encapsulation key) and - * decapsulating the server's ciphertext. */ -#if defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_MAKE_KEY) && \ - !defined(WOLFSSL_MLKEM_NO_DECAPSULATE) - #define WOLFSSL_HAVE_MLKEM_CLIENT_SUPPORT -#endif -/* ML-KEM server support requires encapsulating to the client's key. */ -#if defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE) - #define WOLFSSL_HAVE_MLKEM_SERVER_SUPPORT -#endif - #if defined(HAVE_SUPPORTED_CURVES) || \ (defined(WOLFSSL_TLS13) && defined(HAVE_SUPPORTED_CURVES)) @@ -16752,7 +16741,9 @@ int TLSX_PopulateExtensions(WOLFSSL* ssl, byte isServer) modes = 1 << PSK_KE; } #if !defined(NO_DH) || defined(HAVE_ECC) || \ - defined(HAVE_CURVE25519) || defined(HAVE_CURVE448) + defined(HAVE_CURVE25519) || defined(HAVE_CURVE448) || \ + (defined(WOLFSSL_HAVE_MLKEM_CLIENT_SUPPORT) && \ + !defined(WOLFSSL_TLS_NO_MLKEM_STANDALONE)) if (!ssl->options.noPskDheKe) { modes |= 1 << PSK_DHE_KE; } diff --git a/src/tls13.c b/src/tls13.c index 83ac9bd39a5..492b2b4132f 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -8816,6 +8816,7 @@ static WC_INLINE void EncodeSigAlg(const WOLFSSL * ssl, byte hashAlgo, byte hsType, byte* output) { (void)ssl; + (void)hashAlgo; switch (hsType) { #ifdef HAVE_ECC case ecc_dsa_sa_algo: @@ -8848,7 +8849,6 @@ static WC_INLINE void EncodeSigAlg(const WOLFSSL * ssl, byte hashAlgo, case ed25519_sa_algo: output[0] = ED25519_SA_MAJOR; output[1] = ED25519_SA_MINOR; - (void)hashAlgo; break; #endif #ifdef HAVE_ED448 @@ -8856,7 +8856,6 @@ static WC_INLINE void EncodeSigAlg(const WOLFSSL * ssl, byte hashAlgo, case ed448_sa_algo: output[0] = ED448_SA_MAJOR; output[1] = ED448_SA_MINOR; - (void)hashAlgo; break; #endif #ifndef NO_RSA @@ -9664,33 +9663,46 @@ static int WriteCSRToBuffer(WOLFSSL* ssl, DerBuffer** certExts, for (extIdx = 0; extIdx < (word16)(extSz_num); extIdx++) { tmpSz = TLSX_CSR_GetSize_ex(csr, 0, (int)extIdx); - if (tmpSz > (OPAQUE8_LEN + OPAQUE24_LEN) && - certExts[extIdx] == NULL) { - /* csr extension is not zero */ - if (tmpSz > WOLFSSL_MAX_16BIT) - return BUFFER_E; - extSz[extIdx] = (word16)tmpSz; - - ret = AllocDer(&certExts[extIdx], extSz[extIdx] + ex_offset, - CERT_TYPE, ssl->heap); - if (ret < 0) - return ret; - der = certExts[extIdx]; - - /* write extension type */ - c16toa(ext->type, der->buffer - + OPAQUE16_LEN); - /* writes extension data length. */ - c16toa(extSz[extIdx], der->buffer - + HELLO_EXT_TYPE_SZ + OPAQUE16_LEN); - /* write extension data */ - extSz[extIdx] = (word16)TLSX_CSR_Write_ex(csr, - der->buffer + ex_offset, 0, extIdx); - /* add extension offset */ - extSz[extIdx] += (word16)ex_offset; - /* extension length */ - c16toa(extSz[extIdx] - OPAQUE16_LEN, - der->buffer); + if (ssl->fragOffset != 0 && certExts[extIdx] != NULL) { + /* A fragmented send is being resumed and this buffer was + * written by the earlier call. extSz starts over on every + * call, so recover this entry's size from the length written + * into the buffer. */ + ato16(certExts[extIdx]->buffer, &extSz[extIdx]); + extSz[extIdx] += OPAQUE16_LEN; + } + else { + /* Not a resume, so anything still allocated here is left over + * from a completed message and must not be reused. */ + FreeDer(&certExts[extIdx]); + + if (tmpSz > (OPAQUE8_LEN + OPAQUE24_LEN)) { + /* csr extension is not zero */ + if (tmpSz > WOLFSSL_MAX_16BIT) + return BUFFER_E; + extSz[extIdx] = (word16)tmpSz; + + ret = AllocDer(&certExts[extIdx], extSz[extIdx] + ex_offset, + CERT_TYPE, ssl->heap); + if (ret < 0) + return ret; + der = certExts[extIdx]; + + /* write extension type */ + c16toa(ext->type, der->buffer + + OPAQUE16_LEN); + /* writes extension data length. */ + c16toa(extSz[extIdx], der->buffer + + HELLO_EXT_TYPE_SZ + OPAQUE16_LEN); + /* write extension data */ + extSz[extIdx] = (word16)TLSX_CSR_Write_ex(csr, + der->buffer + ex_offset, 0, extIdx); + /* add extension offset */ + extSz[extIdx] += (word16)ex_offset; + /* extension length */ + c16toa(extSz[extIdx] - OPAQUE16_LEN, + der->buffer); + } } totalSz += extSz[extIdx]; } @@ -9877,7 +9889,8 @@ static int SendTls13Certificate(WOLFSSL* ssl) word32 totalextSz = 0; word32 len = 0; word32 idx = 0; - word32 offset = OPAQUE16_LEN; + word32 offset = 0; + word32 entrySz = 0; byte* p = NULL; byte certReqCtxLen = 0; sword32 length; @@ -9957,9 +9970,14 @@ static int SendTls13Certificate(WOLFSSL* ssl) && ssl->options.handShakeDone) #endif ) { - ret = SetupOcspResp(ssl); - if (ret != 0) - return ret; + /* Build the responses once. A resumed send reuses them: looking + * them up again appends another set of requests to the extension + * until it overflows with MAX_CERT_EXTENSIONS_ERR. */ + if (ssl->fragOffset == 0) { + ret = SetupOcspResp(ssl); + if (ret != 0) + return ret; + } if ((1 + ssl->buffers.certChainCnt) > MAX_CERT_EXTENSIONS) ret = MAX_CERT_EXTENSIONS_ERR; @@ -10007,6 +10025,50 @@ static int SendTls13Certificate(WOLFSSL* ssl) extIdx = 0; + /* Only ssl->fragOffset survives a WANT_WRITE, so a resume inside the chain + * has to rebuild the walk cursor from it. */ + if (certChainSz > 0 && ssl->fragOffset >= certSz + extSz[0]) { + word32 chainPos = ssl->fragOffset - (certSz + extSz[0]); + + #if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(NO_WOLFSSL_SERVER) + /* The leaf is behind us and its buffer was rebuilt above. */ + FreeDer(&ssl->buffers.certExts[0]); + #endif + + while (chainPos > 0) { + word32 prevIdx = idx; + + len = NextCert(ssl->buffers.certChain->buffer, + ssl->buffers.certChain->length, &idx); + if (len == 0) + break; + #if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && \ + !defined(NO_WOLFSSL_SERVER) + if (extIdx + 1 < MAX_CERT_EXTENSIONS) + extIdx++; + #endif + entrySz = len + extSz[extIdx]; + + if (chainPos < entrySz) { + /* Resume part way through this entry. */ + p = ssl->buffers.certChain->buffer + prevIdx; + offset = chainPos; + chainPos = 0; + } + else { + /* Entry already sent in full; stay primed for the next one. */ + #if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && \ + !defined(NO_WOLFSSL_SERVER) + /* Its buffer was rebuilt above and nothing writes it again. */ + FreeDer(&ssl->buffers.certExts[extIdx]); + #endif + chainPos -= entrySz; + offset = 0; + entrySz = 0; + } + } + } + while (length > 0 && ret == 0) { byte* output = NULL; word32 fragSz = 0; @@ -10107,7 +10169,7 @@ static int SendTls13Certificate(WOLFSSL* ssl) while (fragSz > 0) { word32 l; - if (offset == len + OPAQUE16_LEN) { + if (offset == entrySz) { /* Find next CA certificate to write out. */ offset = 0; /* Point to the start of current cert in chain buffer. */ @@ -10121,6 +10183,8 @@ static int SendTls13Certificate(WOLFSSL* ssl) if (extIdx + 1 < MAX_CERT_EXTENSIONS) extIdx++; #endif + /* Certificate and its extensions make up the entry. */ + entrySz = len + extSz[extIdx]; } /* Write out certificate and extension. */ l = AddCertExt(ssl, p, len, extSz[extIdx], offset, fragSz, @@ -10133,10 +10197,8 @@ static int SendTls13Certificate(WOLFSSL* ssl) if (extIdx != 0 && extIdx < MAX_CERT_EXTENSIONS && ssl->buffers.certExts[extIdx] != NULL && - offset == len + extSz[extIdx]) { + offset == entrySz) { FreeDer(&ssl->buffers.certExts[extIdx]); - /* for next chain cert */ - len += extSz[extIdx] - OPAQUE16_LEN; } } } @@ -10855,6 +10917,8 @@ static int SendTls13CertificateVerify(WOLFSSL* ssl) sigOut += OPAQUE16_LEN; } #endif + /* Only the per-algorithm signing branches below consume this. */ + (void)sigOut; #ifdef HAVE_ECC if (ssl->hsType == DYNAMIC_TYPE_ECC) { #if defined(WOLFSSL_SM2) && defined(WOLFSSL_SM3) @@ -12661,7 +12725,8 @@ static int DoTls13CertificateVerify(WOLFSSL* ssl, byte* input, return ret; } -#endif /* !NO_RSA || HAVE_ECC */ +#endif /* !NO_RSA || HAVE_ECC || HAVE_ED25519 || HAVE_ED448 || + * HAVE_FALCON || WOLFSSL_HAVE_MLDSA || WOLFSSL_HAVE_SLHDSA */ #endif /* !NO_CERTS */ /* Parse and handle a TLS v1.3 Finished message. diff --git a/tests/api.c b/tests/api.c index d396634ac99..1a9bde7c97a 100644 --- a/tests/api.c +++ b/tests/api.c @@ -10920,7 +10920,10 @@ static int test_wolfSSL_PKCS8(void) #if !defined(NO_FILESYSTEM) && !defined(NO_ASN) && defined(HAVE_PKCS8) && \ !defined(WOLFCRYPT_ONLY) && !defined(NO_TLS) && \ (!defined(WOLFSSL_NO_TLS12) || defined(WOLFSSL_TLS13)) -#if !defined(NO_WOLFSSL_CLIENT) || !defined(NO_WOLFSSL_SERVER) +/* Without RSA, ECC or PEM decoding every key load below compiles out, leaving + * nothing to test. */ +#if (!defined(NO_WOLFSSL_CLIENT) || !defined(NO_WOLFSSL_SERVER)) && \ + (!defined(NO_RSA) || defined(HAVE_ECC) || defined(WOLFSSL_PEM_TO_DER)) byte buff[FOURK_BUF]; byte der[FOURK_BUF]; #ifndef NO_RSA @@ -11136,7 +11139,8 @@ static int test_wolfSSL_PKCS8(void) #endif /* HAVE_ECC */ wolfSSL_CTX_free(ctx); -#endif /* !NO_WOLFSSL_CLIENT || !NO_WOLFSSL_SERVER */ +#endif /* (!NO_WOLFSSL_CLIENT || !NO_WOLFSSL_SERVER) && + * (!NO_RSA || HAVE_ECC || WOLFSSL_PEM_TO_DER) */ #endif /* !NO_FILESYSTEM && !NO_ASN && HAVE_PKCS8 */ return EXPECT_RESULT(); } diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index c5fe3189c36..7ad9339c69f 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -84,6 +84,19 @@ int test_tls13_apis(void) #elif defined(HAVE_ED448) const char* ourCert = ed448CertFile; const char* ourKey = ed448KeyFile; +/* The CERT_FILES gates, not the signing ones: only a certificate on the context + * is needed here, so the key load below is allowed to fail. */ +#elif defined(TEST_HAVE_MLDSA_CERT_FILES) + const char* ourCert = mldsaCertFile; + const char* ourKey = mldsaKeyFile; +#elif defined(TEST_HAVE_SLHDSA_CERT_FILES) + const char* ourCert = slhdsaCertFile; + const char* ourKey = slhdsaKeyFile; +#else + /* No certificate to load. The loads below ignore the return value and a + * NULL filename fails cleanly rather than being dereferenced. */ + const char* ourCert = NULL; + const char* ourKey = NULL; #endif #endif #endif diff --git a/tests/test-tls13-slhdsa-entity-128s.conf b/tests/test-tls13-slhdsa-entity-128s.conf index fe4cd5e9f75..16f11a14dbf 100644 --- a/tests/test-tls13-slhdsa-entity-128s.conf +++ b/tests/test-tls13-slhdsa-entity-128s.conf @@ -39,3 +39,42 @@ -k ./certs/slhdsa/client-slhdsa-shake-128s-priv.pem -A ./certs/slhdsa/root-slhdsa-shake-128s.pem -C + +# Server auth with simulated WANT_WRITE (-6): leaf plus root push the +# Certificate message past one record, so the send resumes mid chain. + +# server TLSv1.3 TLS13-AES128-GCM-SHA256 +-v 4 +-l TLS13-AES128-GCM-SHA256 +-c ./certs/slhdsa/server-slhdsa-shake-128s.pem +-k ./certs/slhdsa/server-slhdsa-shake-128s-priv.pem +-d +-6 + +# client TLSv1.3 TLS13-AES128-GCM-SHA256 +-v 4 +-l TLS13-AES128-GCM-SHA256 +-A ./certs/slhdsa/root-slhdsa-shake-128s.pem +-C +-6 + +# Mutual auth with simulated WANT_WRITE, so the client's own Certificate +# send resumes mid chain too. + +# server TLSv1.3 TLS13-AES128-GCM-SHA256 +-v 4 +-l TLS13-AES128-GCM-SHA256 +-c ./certs/slhdsa/server-slhdsa-shake-128s.pem +-k ./certs/slhdsa/server-slhdsa-shake-128s-priv.pem +-A ./certs/slhdsa/root-slhdsa-shake-128s.pem +-V +-6 + +# client TLSv1.3 TLS13-AES128-GCM-SHA256 +-v 4 +-l TLS13-AES128-GCM-SHA256 +-c ./certs/slhdsa/client-slhdsa-shake-128s.pem +-k ./certs/slhdsa/client-slhdsa-shake-128s-priv.pem +-A ./certs/slhdsa/root-slhdsa-shake-128s.pem +-C +-6 diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index eb0ca1830e0..01abcb934ce 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -29292,11 +29292,23 @@ static int SetValidity(byte* before, byte* after, int daysValid) #if defined(WOLFSSL_CERT_GEN) || defined(WOLFSSL_CERT_REQ) +/* MakeSignatureCb backs both MakeSignature()'s local RSA/ECC signing and the + * wc_SignCert_cb() offload path, so either one needs it. */ +#if (!defined(NO_RSA) && !defined(WOLFSSL_RSA_PUBLIC_ONLY) && \ + !defined(WOLFSSL_RSA_VERIFY_ONLY)) || \ + (defined(HAVE_ECC) && defined(HAVE_ECC_SIGN)) || \ + defined(WOLFSSL_CERT_SIGN_CB) /* Forward declaration for internal use */ static int MakeSignatureCb(CertSignCtx* certSignCtx, const byte* buf, word32 sz, byte* sig, word32 sigSz, int sigAlgoType, int keyType, wc_SignCertCb signCb, void* signCtx, WC_RNG* rng, void* heap); +#endif /* (!NO_RSA && !WOLFSSL_RSA_PUBLIC_ONLY && !WOLFSSL_RSA_VERIFY_ONLY) || + * (HAVE_ECC && HAVE_ECC_SIGN) || WOLFSSL_CERT_SIGN_CB */ +/* InternalSignCb is only used by MakeSignature()'s local signing path. */ +#if (!defined(NO_RSA) && !defined(WOLFSSL_RSA_PUBLIC_ONLY) && \ + !defined(WOLFSSL_RSA_VERIFY_ONLY)) || \ + (defined(HAVE_ECC) && defined(HAVE_ECC_SIGN)) /* Internal context for default signing operations (when no callback provided) */ typedef struct { void* key; @@ -29383,6 +29395,8 @@ static int InternalSignCb(const byte* in, word32 inLen, return ret; } +#endif /* (!NO_RSA && !WOLFSSL_RSA_PUBLIC_ONLY && !WOLFSSL_RSA_VERIFY_ONLY) || + * (HAVE_ECC && HAVE_ECC_SIGN) */ #endif /* WOLFSSL_CERT_GEN || WOLFSSL_CERT_REQ */ @@ -29662,10 +29676,13 @@ static int MakeSignature(CertSignCtx* certSignCtx, const byte* buf, word32 sz, { int ret = 0; + (void)certSignCtx; (void)buf; (void)sz; (void)sig; (void)sigSz; + (void)rsaKey; + (void)eccKey; (void)ed25519Key; (void)ed448Key; (void)falconKey; @@ -29674,6 +29691,7 @@ static int MakeSignature(CertSignCtx* certSignCtx, const byte* buf, word32 sz, (void)lmsKey; (void)xmssKey; (void)rng; + (void)sigAlgoType; (void)heap; /* For RSA and ECC, use the callback path to eliminate duplication */ @@ -29789,7 +29807,12 @@ static int MakeSignature(CertSignCtx* certSignCtx, const byte* buf, word32 sz, if (ret == -1) ret = ALGO_ID_E; +#if (!defined(NO_RSA) && !defined(WOLFSSL_RSA_PUBLIC_ONLY) && \ + !defined(WOLFSSL_RSA_VERIFY_ONLY)) || \ + (defined(HAVE_ECC) && defined(HAVE_ECC_SIGN)) exit_ms: +#endif /* (!NO_RSA && !WOLFSSL_RSA_PUBLIC_ONLY && !WOLFSSL_RSA_VERIFY_ONLY) || + * (HAVE_ECC && HAVE_ECC_SIGN) */ if (ret < 0) { WOLFSSL_ERROR_VERBOSE(ret); } @@ -30807,6 +30830,10 @@ int wc_MakeCertReq(Cert* cert, byte* derBuffer, word32 derSz, #if defined(WOLFSSL_CERT_GEN) || defined(WOLFSSL_CERT_REQ) +#if (!defined(NO_RSA) && !defined(WOLFSSL_RSA_PUBLIC_ONLY) && \ + !defined(WOLFSSL_RSA_VERIFY_ONLY)) || \ + (defined(HAVE_ECC) && defined(HAVE_ECC_SIGN)) || \ + defined(WOLFSSL_CERT_SIGN_CB) /* Internal function to create signature using callback * This allows external signing implementations (e.g., TPM, HSM) without * requiring the crypto callback infrastructure. @@ -30939,6 +30966,8 @@ static int MakeSignatureCb(CertSignCtx* certSignCtx, const byte* buf, return ret; } +#endif /* (!NO_RSA && !WOLFSSL_RSA_PUBLIC_ONLY && !WOLFSSL_RSA_VERIFY_ONLY) || + * (HAVE_ECC && HAVE_ECC_SIGN) || WOLFSSL_CERT_SIGN_CB */ #endif /* WOLFSSL_CERT_GEN || WOLFSSL_CERT_REQ */ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 49e7cafe811..2966d39c1e7 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -221,6 +221,17 @@ extern "C" { #endif +/* ML-KEM client support requires generating a key pair (encapsulation key) and + * decapsulating the server's ciphertext. */ +#if defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_MAKE_KEY) && \ + !defined(WOLFSSL_MLKEM_NO_DECAPSULATE) + #define WOLFSSL_HAVE_MLKEM_CLIENT_SUPPORT +#endif +/* ML-KEM server support requires encapsulating to the client's key. */ +#if defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE) + #define WOLFSSL_HAVE_MLKEM_SERVER_SUPPORT +#endif + /* Define or comment out the cipher suites you'd like to be compiled in make sure to use at least one BUILD_SSL_xxx or BUILD_TLS_xxx is defined @@ -854,7 +865,10 @@ #if !defined(WOLFCRYPT_ONLY) && defined(NO_PSK) && \ (defined(NO_DH) || !defined(HAVE_ANON)) && \ defined(NO_RSA) && !defined(HAVE_ECC) && \ - !defined(HAVE_ED25519) && !defined(HAVE_ED448) + !defined(HAVE_ED25519) && !defined(HAVE_ED448) && \ + (!defined(WOLFSSL_TLS13) || \ + (!defined(HAVE_FALCON) && !defined(WOLFSSL_HAVE_MLDSA) && \ + !defined(WOLFSSL_HAVE_SLHDSA))) #error "No cipher suites available with this build" #endif diff --git a/wolfssl/test.h b/wolfssl/test.h index abe701cf1a4..c33928fc9d2 100644 --- a/wolfssl/test.h +++ b/wolfssl/test.h @@ -52,6 +52,13 @@ #include #include #include +/* The credential gates below read the derived sub-config macros of these. */ +#ifdef WOLFSSL_HAVE_MLDSA + #include +#endif +#ifdef WOLFSSL_HAVE_SLHDSA + #include +#endif #ifdef ATOMIC_USER #include @@ -461,19 +468,78 @@ err_sys_with_errno_func(const char* msg, const char *file, int line) } while(0) -#ifndef WOLFSSL_NO_TLS12 -#define SERVER_DEFAULT_VERSION 3 +/* No classic public key authentication compiled in. Shared with the PSK + * fallbacks in the examples so the conditions cannot drift apart. */ +#if defined(NO_RSA) && !defined(HAVE_ECC) && !defined(HAVE_ED25519) && \ + !defined(HAVE_ED448) + #define TEST_NO_CLASSIC_AUTH +#endif + +/* The ML-DSA credential paths below name real files. Loading the certificate + * needs verification support only. */ +#if defined(WOLFSSL_HAVE_MLDSA) && \ + (!defined(WOLFSSL_NO_ML_DSA_44) || !defined(WOLFSSL_NO_ML_DSA_65) || \ + !defined(WOLFSSL_NO_ML_DSA_87)) + #define TEST_HAVE_MLDSA_CERT_FILES +#endif + +/* The same for SLH-DSA. Only the 128s parameter sets have certificates wired + * up, and the entity certificates ship as PEM only. */ +#if defined(WOLFSSL_HAVE_SLHDSA) && defined(WOLFSSL_PEM_TO_DER) && \ + (defined(WOLFSSL_SLHDSA_PARAM_128S) || \ + defined(WOLFSSL_SLHDSA_PARAM_SHA2_128S)) + #define TEST_HAVE_SLHDSA_CERT_FILES +#endif + +/* The same credentials, plus the ability to sign and to verify: a verify-only + * build can neither load the key nor produce a CertificateVerify, and a + * sign-only build cannot check the peer's chain. */ +#if defined(TEST_HAVE_MLDSA_CERT_FILES) && \ + defined(WOLFSSL_MLDSA_PRIVATE_KEY) && !defined(WOLFSSL_MLDSA_NO_SIGN) && \ + !defined(WOLFSSL_MLDSA_NO_VERIFY) + #define TEST_HAVE_MLDSA_CERTS +#endif +#if defined(TEST_HAVE_SLHDSA_CERT_FILES) && \ + !defined(WOLFSSL_SLHDSA_VERIFY_ONLY) + #define TEST_HAVE_SLHDSA_CERTS +#endif + +/* The examples can authenticate with a post-quantum certificate. Both + * algorithms are TLS 1.3 only, so the version is folded in. Falcon is absent + * on purpose: it has no credentials in the ladders. */ +#if defined(WOLFSSL_TLS13) && \ + (defined(TEST_HAVE_MLDSA_CERTS) || defined(TEST_HAVE_SLHDSA_CERTS)) + #define TEST_HAVE_PQC_CERT_AUTH +#endif + +/* No key exchange that TLS 1.2 and earlier can negotiate. ML-KEM is the only + * one left and it is TLS 1.3 only, so those versions have no cipher suite at + * all. Mirrors the check guarding the "No cipher suites defined" #error in + * src/ssl.c. */ +#if defined(WOLFSSL_TLS13) && defined(NO_DH) && !defined(HAVE_ECC) && \ + !defined(HAVE_CURVE25519) && !defined(HAVE_CURVE448) && \ + !defined(WOLFSSL_STATIC_RSA) && !defined(WOLFSSL_STATIC_DH) && \ + !defined(WOLFSSL_STATIC_PSK) + #define TEST_NO_CLASSIC_KEX +#endif + +/* A post-quantum-only build can only work over TLS 1.3, so default to it: + * post-quantum certificate authentication and standalone ML-KEM are both + * TLS 1.3 only. A build left with nothing but PSK over a classic key exchange + * keeps the TLS 1.2 default. One macro so the client and server defaults + * cannot drift apart. */ +#if defined(WOLFSSL_NO_TLS12) || defined(TEST_NO_CLASSIC_KEX) || \ + (defined(TEST_NO_CLASSIC_AUTH) && defined(TEST_HAVE_PQC_CERT_AUTH)) + #define TEST_DEFAULT_TLS_VERSION 4 #else -#define SERVER_DEFAULT_VERSION 4 + #define TEST_DEFAULT_TLS_VERSION 3 #endif + +#define SERVER_DEFAULT_VERSION TEST_DEFAULT_TLS_VERSION #define SERVER_DTLS_DEFAULT_VERSION (-2) #define SERVER_INVALID_VERSION (-99) #define SERVER_DOWNGRADE_VERSION (-98) -#ifndef WOLFSSL_NO_TLS12 -#define CLIENT_DEFAULT_VERSION 3 -#else -#define CLIENT_DEFAULT_VERSION 4 -#endif +#define CLIENT_DEFAULT_VERSION TEST_DEFAULT_TLS_VERSION #define CLIENT_DTLS_DEFAULT_VERSION (-2) #define CLIENT_INVALID_VERSION (-99) #define CLIENT_DOWNGRADE_VERSION (-98) @@ -682,6 +748,97 @@ err_sys_with_errno_func(const char* msg, const char *file, int line) #endif #endif +/* ML-DSA (FIPS 204) certificate material. The mldsa-cert files are + * self-signed, so the same file is both peer certificate and trust anchor. + * MLDSA_TEST_LEVEL and MLDSA_TEST_DIR compose the path, which depends on the + * built parameter level and on the target base directory. */ +#if defined(TEST_HAVE_MLDSA_CERT_FILES) + +#if !defined(WOLFSSL_NO_ML_DSA_65) + #define MLDSA_TEST_LEVEL "mldsa65" +#elif !defined(WOLFSSL_NO_ML_DSA_44) + #define MLDSA_TEST_LEVEL "mldsa44" +#else + #define MLDSA_TEST_LEVEL "mldsa87" +#endif + +#if defined(WOLFSSL_NO_CURRDIR) || defined(WOLFSSL_MDK_SHELL) + #define MLDSA_TEST_DIR "certs/mldsa/" +#elif defined(NETOS) && defined(HAVE_FIPS) + #define MLDSA_TEST_DIR FS_VOLUME1_DIR "certs/mldsa/" +#else + #define MLDSA_TEST_DIR "./certs/mldsa/" +#endif + +#ifdef WOLFSSL_PEM_TO_DER + #ifndef mldsaCertFile + #define mldsaCertFile MLDSA_TEST_DIR MLDSA_TEST_LEVEL "-cert.pem" + #endif + #ifndef mldsaKeyFile + #define mldsaKeyFile MLDSA_TEST_DIR MLDSA_TEST_LEVEL "-key.pem" + #endif +#else + #ifndef mldsaCertFile + #define mldsaCertFile MLDSA_TEST_DIR MLDSA_TEST_LEVEL "-cert.der" + #endif + #ifndef mldsaKeyFile + /* Not _priv-only.der: only -key.der matches the certificate. */ + #define mldsaKeyFile MLDSA_TEST_DIR MLDSA_TEST_LEVEL "-key.der" + #endif +#endif +#ifndef cliMldsaCertFile + #define cliMldsaCertFile mldsaCertFile +#endif +#ifndef cliMldsaKeyFile + #define cliMldsaKeyFile mldsaKeyFile +#endif +#ifndef caMldsaCertFile + #define caMldsaCertFile mldsaCertFile +#endif +#endif /* TEST_HAVE_MLDSA_CERT_FILES */ + +/* SLH-DSA (FIPS 205) certificate material. Unlike the ML-DSA files above, the + * leaves are signed by a shared 128s root, so the trust anchor is a distinct + * file. SLHDSA_TEST_FAM picks the SHAKE family, falling back to SHA2 only when + * SHAKE-128s is not compiled in. */ +#if defined(TEST_HAVE_SLHDSA_CERT_FILES) + +#if defined(WOLFSSL_SLHDSA_PARAM_128S) + #define SLHDSA_TEST_FAM "shake" +#else + #define SLHDSA_TEST_FAM "sha2" +#endif + +#if defined(WOLFSSL_NO_CURRDIR) || defined(WOLFSSL_MDK_SHELL) + #define SLHDSA_TEST_DIR "certs/slhdsa/" +#elif defined(NETOS) && defined(HAVE_FIPS) + #define SLHDSA_TEST_DIR FS_VOLUME1_DIR "certs/slhdsa/" +#else + #define SLHDSA_TEST_DIR "./certs/slhdsa/" +#endif + +#ifndef slhdsaCertFile + #define slhdsaCertFile SLHDSA_TEST_DIR "server-slhdsa-" SLHDSA_TEST_FAM \ + "-128s.pem" +#endif +#ifndef slhdsaKeyFile + #define slhdsaKeyFile SLHDSA_TEST_DIR "server-slhdsa-" SLHDSA_TEST_FAM \ + "-128s-priv.pem" +#endif +#ifndef cliSlhdsaCertFile + #define cliSlhdsaCertFile SLHDSA_TEST_DIR "client-slhdsa-" SLHDSA_TEST_FAM \ + "-128s.pem" +#endif +#ifndef cliSlhdsaKeyFile + #define cliSlhdsaKeyFile SLHDSA_TEST_DIR "client-slhdsa-" SLHDSA_TEST_FAM \ + "-128s-priv.pem" +#endif +#ifndef caSlhdsaCertFile + #define caSlhdsaCertFile SLHDSA_TEST_DIR "root-slhdsa-" SLHDSA_TEST_FAM \ + "-128s.pem" +#endif +#endif /* TEST_HAVE_SLHDSA_CERT_FILES */ + #ifdef WOLFSSL_PEM_TO_DER #define CERT_FILETYPE WOLFSSL_FILETYPE_PEM #else