From 76b1300adb17bc6efe24a3033020cd3ea71fc4fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Fri, 13 Mar 2026 17:48:35 +0100 Subject: [PATCH] ML-KEM fixes * DTLS 1.3 cookie and CH frag handling * static memory handling * Fix memory leak in TLS server PQC handling in case of ECH * Make sure hybrids are actually tested in testsuite --- examples/client/client.c | 16 +++- examples/server/server.c | 4 +- src/internal.c | 35 ++++++++- src/tls.c | 23 ++++-- tests/api.c | 5 +- tests/suites.c | 140 +++++++++++++++++++++++++++++------ wolfssl/ssl.h | 9 +-- wolfssl/wolfcrypt/memory.h | 18 ++++- wolfssl/wolfcrypt/settings.h | 8 +- 9 files changed, 208 insertions(+), 50 deletions(-) diff --git a/examples/client/client.c b/examples/client/client.c index 112d2aee21..c3f8d67d53 100644 --- a/examples/client/client.c +++ b/examples/client/client.c @@ -557,6 +557,18 @@ static void SetKeyShare(WOLFSSL* ssl, int onlyKeyShare, int useX25519, else { err_sys("unable to use post-quantum KEM"); } + + #ifdef WOLFSSL_DTLS13 + if (wolfSSL_dtls(ssl)) { + /* When the KeyShare is too large for an unfragmented + * ClientHello, DTLS sends an empty KeyShare extension to + * use the Hello Retry Request to enable fragmentation. + * In order to enforce our desired PQC algorithm in the + * second ClientHello, we need to set it as the only one + * allowed in the SupportedGroups extension. */ + setGroups = 1; + } + #endif /* WOLFSSL_DTLS13 */ } } #endif @@ -2299,8 +2311,8 @@ THREAD_RETURN WOLFSSL_THREAD client_test(void* args) #ifdef WOLFSSL_STATIC_MEMORY - #if (defined(HAVE_ECC) && !defined(ALT_ECC_SIZE)) \ - || defined(SESSION_CERTS) + #if (defined(HAVE_ECC) && !defined(ALT_ECC_SIZE)) || \ + defined(SESSION_CERTS) || defined(WOLFSSL_HAVE_MLKEM) /* big enough to handle most cases including session certs */ byte memory[320000]; #else diff --git a/examples/server/server.c b/examples/server/server.c index 07e480eb55..e642b69509 100644 --- a/examples/server/server.c +++ b/examples/server/server.c @@ -1763,8 +1763,8 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) /* Note: Actual memory used is much less, this is the entire buffer buckets, * which is partitioned into pools of common sizes. To adjust the buckets * sizes see WOLFMEM_BUCKETS in memory.h */ - #if (defined(HAVE_ECC) && !defined(ALT_ECC_SIZE)) \ - || defined(SESSION_CERTS) + #if (defined(HAVE_ECC) && !defined(ALT_ECC_SIZE)) || \ + defined(SESSION_CERTS) || defined(WOLFSSL_HAVE_MLKEM) /* big enough to handle most cases including session certs */ #if !defined(WOLFSSL_NO_CLIENT_AUTH) && \ ((defined(HAVE_ED25519) && !defined(NO_ED25519_CLIENT_AUTH)) || \ diff --git a/src/internal.c b/src/internal.c index 7897a294bf..652b9895ca 100644 --- a/src/internal.c +++ b/src/internal.c @@ -2251,6 +2251,26 @@ int InitSSL_Side(WOLFSSL* ssl, word16 side) WOLFSSL_MSG("DTLS Cookie Secret error"); return ret; } + #if defined(WOLFSSL_DTLS13) + if (IsAtLeastTLSv1_3(ssl->version)) { + #if defined(WOLFSSL_SEND_HRR_COOKIE) + ret = wolfSSL_send_hrr_cookie(ssl, NULL, 0); + if (ret != WOLFSSL_SUCCESS) { + WOLFSSL_MSG("DTLS1.3 Cookie secret error"); + return ret; + } + #endif /* WOLFSSL_SEND_HRR_COOKIE */ + #if defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_HAVE_MLKEM) + /* Allow fragmentation of the second ClientHello due to the + * large PQC key share. */ + ret = wolfSSL_dtls13_allow_ch_frag(ssl, 1); + if (ret != WOLFSSL_SUCCESS) { + WOLFSSL_MSG("DTLS1.3 CH frag error"); + return ret; + } + #endif /* WOLFSSL_DTLS_CH_FRAG && WOLFSSL_HAVE_MLKEM */ + } + #endif /* WOLFSSL_DTLS13 */ } #endif /* WOLFSSL_DTLS && !NO_WOLFSSL_SERVER */ @@ -8006,15 +8026,26 @@ int InitSSL(WOLFSSL* ssl, WOLFSSL_CTX* ctx, int writeDup) WOLFSSL_MSG("DTLS Cookie Secret error"); return ret; } -#if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) + #if defined(WOLFSSL_DTLS13) if (IsAtLeastTLSv1_3(ssl->version)) { + #if defined(WOLFSSL_SEND_HRR_COOKIE) ret = wolfSSL_send_hrr_cookie(ssl, NULL, 0); if (ret != WOLFSSL_SUCCESS) { WOLFSSL_MSG("DTLS1.3 Cookie secret error"); return ret; } + #endif /* WOLFSSL_SEND_HRR_COOKIE */ + #if defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_HAVE_MLKEM) + /* Allow fragmentation of the second ClientHello due to the + * large PQC key share. */ + ret = wolfSSL_dtls13_allow_ch_frag(ssl, 1); + if (ret != WOLFSSL_SUCCESS) { + WOLFSSL_MSG("DTLS1.3 CH frag error"); + return ret; + } + #endif /* WOLFSSL_DTLS_CH_FRAG && WOLFSSL_HAVE_MLKEM */ } -#endif /* WOLFSSL_DTLS13 && WOLFSSL_SEND_HRR_COOKIE */ + #endif /* WOLFSSL_DTLS13 */ } #endif /* WOLFSSL_DTLS && !NO_WOLFSSL_SERVER */ diff --git a/src/tls.c b/src/tls.c index 011c22eee6..951918579a 100644 --- a/src/tls.c +++ b/src/tls.c @@ -10534,6 +10534,7 @@ static int TLSX_KeyShare_HandlePqcKeyServer(WOLFSSL* ssl, keyShareEntry->ke = NULL; keyShareEntry->keLen = 0; + XFREE(keyShareEntry->pubKey, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY); keyShareEntry->pubKey = ciphertext; keyShareEntry->pubKeyLen = ctSz; ciphertext = NULL; @@ -10770,6 +10771,7 @@ static int TLSX_KeyShare_HandlePqcHybridKeyServer(WOLFSSL* ssl, XMEMCPY(ciphertext + ecc_kse->pubKeyLen, pqc_kse->pubKey, ctSz); } + XFREE(keyShareEntry->pubKey, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY); keyShareEntry->pubKey = ciphertext; keyShareEntry->pubKeyLen = ecc_kse->pubKeyLen + ctSz; ciphertext = NULL; @@ -10859,13 +10861,20 @@ int TLSX_KeyShare_Use(const WOLFSSL* ssl, word16 group, word16 len, byte* data, #if defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE) if (ssl->options.side == WOLFSSL_SERVER_END && WOLFSSL_NAMED_GROUP_IS_PQC(group)) { - ret = TLSX_KeyShare_HandlePqcKeyServer((WOLFSSL*)ssl, - keyShareEntry, - data, len, - ssl->arrays->preMasterSecret, - &ssl->arrays->preMasterSz); - if (ret != 0) - return ret; + if (TLSX_IsGroupSupported(group)) { + ret = TLSX_KeyShare_HandlePqcKeyServer((WOLFSSL*)ssl, + keyShareEntry, + data, len, + ssl->arrays->preMasterSecret, + &ssl->arrays->preMasterSz); + if (ret != 0) + return ret; + } + else { + XFREE(keyShareEntry->ke, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY); + keyShareEntry->ke = NULL; + keyShareEntry->keLen = 0; + } } else if (ssl->options.side == WOLFSSL_SERVER_END && WOLFSSL_NAMED_GROUP_IS_PQC_HYBRID(group)) { diff --git a/tests/api.c b/tests/api.c index f256c06814..2527fd756d 100644 --- a/tests/api.c +++ b/tests/api.c @@ -259,7 +259,8 @@ #endif #if defined(WOLFSSL_STATIC_MEMORY) && !defined(WOLFCRYPT_ONLY) - #if (defined(HAVE_ECC) && !defined(ALT_ECC_SIZE)) || defined(SESSION_CERTS) + #if (defined(HAVE_ECC) && !defined(ALT_ECC_SIZE)) || \ + defined(SESSION_CERTS) || defined(WOLFSSL_HAVE_MLKEM) #ifdef OPENSSL_EXTRA #define TEST_TLS_STATIC_MEMSZ (400000) #else @@ -32014,7 +32015,7 @@ static int test_dtls13_frag_ch_pq(void) { EXPECT_DECLS; #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ - && defined(WOLFSSL_DTLS_CH_FRAG) && defined(HAVE_LIBOQS) + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_HAVE_MLKEM) WOLFSSL_CTX *ctx_c = NULL; WOLFSSL_CTX *ctx_s = NULL; WOLFSSL *ssl_c = NULL; diff --git a/tests/suites.c b/tests/suites.c index b0cbca6ff7..3b461b7769 100644 --- a/tests/suites.c +++ b/tests/suites.c @@ -169,57 +169,151 @@ static int IsValidCipherSuite(const char* line, char *suite, size_t suite_spc) } #if defined(WOLFSSL_HAVE_MLKEM) + +#define MATCH_PQC(b, s, l) ((l) == sizeof(s) - 1 && \ + XSTRNCMP((b), (s), sizeof(s) - 1) == 0) + static int IsKyberLevelAvailable(const char* line) { int available = 0; - const char* find = "--pqc "; - const char* begin = strstr(line, find); - const char* end; + const char* begin = XSTRSTR(line, "--pqc"); + size_t len = 0; if (begin != NULL) { - begin += 6; - end = XSTRSTR(begin, " "); + begin += XSTRLEN("--pqc"); + while (*begin == ' ' || *begin == '\t') { + begin++; + } - #ifndef WOLFSSL_NO_ML_KEM - if ((size_t)end - (size_t)begin == 10) { + if (*begin != '\0') { + const char* end = begin; + while (*end != '\0' && *end != ' ' && *end != '\t') { + end++; + } + len = (size_t)(end - begin); + } + } + + if (begin != NULL && len > 0) { +#ifndef WOLFSSL_NO_ML_KEM #ifndef WOLFSSL_NO_ML_KEM_512 - if (XSTRNCMP(begin, "ML_KEM_512", 10) == 0) { + if (MATCH_PQC(begin, "ML_KEM_512", len)) { available = 1; } #endif #ifndef WOLFSSL_NO_ML_KEM_768 - if (XSTRNCMP(begin, "ML_KEM_768", 10) == 0) { + if (MATCH_PQC(begin, "ML_KEM_768", len)) { available = 1; } #endif - } #ifndef WOLFSSL_NO_ML_KEM_1024 - if ((size_t)end - (size_t)begin == 11) { - if (XSTRNCMP(begin, "ML_KEM_1024", 11) == 0) { + if (MATCH_PQC(begin, "ML_KEM_1024", len)) { available = 1; } - } #endif - #endif - #ifdef WOLFSSL_MLKEM_KYBER - if ((size_t)end - (size_t)begin == 12) { - #ifndef WOLFSSL_NO_KYBER512 - if (XSTRNCMP(begin, "KYBER_LEVEL1", 12) == 0) { + + #if !defined(WOLFSSL_NO_ML_KEM_512) && defined(HAVE_ECC) + if (MATCH_PQC(begin, "SecP256r1MLKEM512", len)) { available = 1; } + #ifdef WOLFSSL_ML_KEM_USE_OLD_IDS + if (MATCH_PQC(begin, "P256_ML_KEM_512_OLD", len)) { + available = 1; + } + #endif + #endif + #if !defined(WOLFSSL_NO_ML_KEM_768) && defined(HAVE_ECC) + if (MATCH_PQC(begin, "SecP384r1MLKEM768", len)) { + available = 1; + } + if (MATCH_PQC(begin, "SecP256r1MLKEM768", len)) { + available = 1; + } + #ifdef WOLFSSL_ML_KEM_USE_OLD_IDS + if (MATCH_PQC(begin, "P384_ML_KEM_768_OLD", len)) { + available = 1; + } + #endif + #endif + #if !defined(WOLFSSL_NO_ML_KEM_768) && defined(HAVE_CURVE25519) + if (MATCH_PQC(begin, "X25519MLKEM768", len)) { + available = 1; + } + #endif + #if !defined(WOLFSSL_NO_ML_KEM_1024) && defined(HAVE_ECC) + if (MATCH_PQC(begin, "SecP521r1MLKEM1024", len)) { + available = 1; + } + if (MATCH_PQC(begin, "SecP384r1MLKEM1024", len)) { + available = 1; + } + #ifdef WOLFSSL_ML_KEM_USE_OLD_IDS + if (MATCH_PQC(begin, "P521_ML_KEM_1024_OLD", len)) { + available = 1; + } + #endif + #endif + #if !defined(WOLFSSL_NO_ML_KEM_512) && defined(HAVE_CURVE25519) + if (MATCH_PQC(begin, "X25519MLKEM512", len)) { + available = 1; + } + #endif + #if !defined(WOLFSSL_NO_ML_KEM_768) && defined(HAVE_CURVE448) + if (MATCH_PQC(begin, "X448MLKEM768", len)) { + available = 1; + } + #endif +#endif /* !WOLFSSL_NO_ML_KEM */ +#ifdef WOLFSSL_MLKEM_KYBER + #ifndef WOLFSSL_NO_KYBER512 + if (MATCH_PQC(begin, "KYBER_LEVEL1", len)) { + available = 1; + } + #ifdef HAVE_ECC + if (MATCH_PQC(begin, "P256_KYBER_LEVEL1", len)) { + available = 1; + } + #endif #endif #ifndef WOLFSSL_NO_KYBER768 - if (XSTRNCMP(begin, "KYBER_LEVEL3", 12) == 0) { + if (MATCH_PQC(begin, "KYBER_LEVEL3", len)) { available = 1; } + #ifdef HAVE_ECC + if (MATCH_PQC(begin, "P384_KYBER_LEVEL3", len)) { + available = 1; + } + if (MATCH_PQC(begin, "P256_KYBER_LEVEL3", len)) { + available = 1; + } + #endif #endif #ifndef WOLFSSL_NO_KYBER1024 - if (XSTRNCMP(begin, "KYBER_LEVEL5", 12) == 0) { + if (MATCH_PQC(begin, "KYBER_LEVEL5", len)) { + available = 1; + } + #ifdef HAVE_ECC + if (MATCH_PQC(begin, "P521_KYBER_LEVEL5", len)) { available = 1; } #endif - } - #endif + #endif + #if !defined(WOLFSSL_NO_KYBER512) && defined(HAVE_CURVE25519) + if (MATCH_PQC(begin, "X25519_KYBER_LEVEL1", len)) { + available = 1; + } + #endif + #if !defined(WOLFSSL_NO_KYBER768) && defined(HAVE_CURVE25519) + if (MATCH_PQC(begin, "X25519_KYBER_LEVEL3", len)) { + available = 1; + } + #endif + #if !defined(WOLFSSL_NO_KYBER768) && defined(HAVE_CURVE448) + if (MATCH_PQC(begin, "X448_KYBER_LEVEL3", len)) { + available = 1; + } + #endif +#endif /* WOLFSSL_MLKEM_KYBER */ } #if defined(WOLFSSL_MLKEM_NO_MAKE_KEY) || \ @@ -910,7 +1004,7 @@ int SuiteTest(int argc, char** argv) char* myArgv[3]; #ifdef WOLFSSL_STATIC_MEMORY - byte memory[200000]; + byte memory[320000]; #endif printf(" Begin Cipher Suite Tests\n"); diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index e5df24edf6..631f763248 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -4750,16 +4750,15 @@ enum { WOLFSSL_P256_KYBER_LEVEL3 = 25498, #endif /* WOLFSSL_MLKEM_KYBER */ #ifndef WOLFSSL_NO_ML_KEM - /* Taken from draft-connolly-tls-mlkem-key-agreement, see: - * https://github.com/dconnolly/draft-connolly-tls-mlkem-key-agreement/ + /* Taken from draft-ietf-tls-mlkem, see: + * https://datatracker.ietf.org/doc/draft-ietf-tls-mlkem/ */ WOLFSSL_ML_KEM_512 = 512, WOLFSSL_ML_KEM_768 = 513, WOLFSSL_ML_KEM_1024 = 514, - /* Taken from draft-kwiatkowski-tls-ecdhe-mlkem. see: - * https://github.com/post-quantum-cryptography/ - * draft-kwiatkowski-tls-ecdhe-mlkem/ + /* Taken from draft-ietf-tls-ecdhe-mlkem. see: + * https://datatracker.ietf.org/doc/draft-ietf-tls-ecdhe-mlkem/ */ WOLFSSL_SECP256R1MLKEM768 = 4587, WOLFSSL_X25519MLKEM768 = 4588, diff --git a/wolfssl/wolfcrypt/memory.h b/wolfssl/wolfcrypt/memory.h index 268a233f0a..1ea303a4e1 100644 --- a/wolfssl/wolfcrypt/memory.h +++ b/wolfssl/wolfcrypt/memory.h @@ -175,15 +175,25 @@ WOLFSSL_API int wolfSSL_GetAllocators(wolfSSL_Malloc_cb* mf, #define WOLFMEM_BUCKETS 64,128,256,512,1024,8192,32768,\ 65536,LARGEST_MEM_BUCKET #endif + #elif defined(WOLFSSL_HAVE_MLKEM) + /* extra storage in structs for multiple attributes and order */ + #define WOLFMEM_BUCKETS 64,128,256,512,1024,2432,4096,8192,\ + LARGEST_MEM_BUCKET #else /* default size of chunks of memory to separate into */ #define WOLFMEM_BUCKETS 64,128,256,512,1024,2432,3456,4544,\ LARGEST_MEM_BUCKET #endif #elif defined(OPENSSL_EXTRA) - /* extra storage in structs for multiple attributes and order */ - #define WOLFMEM_BUCKETS 64,128,256,512,1024,2432,3360,4480,\ - LARGEST_MEM_BUCKET + #ifdef WOLFSSL_HAVE_MLKEM + /* extra storage in structs for multiple attributes and order */ + #define WOLFMEM_BUCKETS 64,128,256,512,1024,2432,4096,8192,\ + LARGEST_MEM_BUCKET + #else + /* extra storage in structs for multiple attributes and order */ + #define WOLFMEM_BUCKETS 64,128,256,512,1024,2432,3360,4480,\ + LARGEST_MEM_BUCKET + #endif #elif defined(WOLFSSL_CERT_EXT) #define WOLFMEM_BUCKETS 64,128,256,512,1024,2432,3456,4544,\ LARGEST_MEM_BUCKET @@ -203,6 +213,8 @@ WOLFSSL_API int wolfSSL_GetAllocators(wolfSSL_Malloc_cb* mf, #else #define WOLFMEM_DIST 30,10,8,15,8,10,8,5,1 #endif + #elif defined(WOLFSSL_HAVE_MLKEM) + #define WOLFMEM_DIST 49,10,6,14,5,6,14,1,1 #elif !defined(WOLFSSL_STATIC_MEMORY_SMALL) #define WOLFMEM_DIST 49,10,6,14,5,6,9,1,1 #else diff --git a/wolfssl/wolfcrypt/settings.h b/wolfssl/wolfcrypt/settings.h index 66ed85d6ee..be31fa63ab 100644 --- a/wolfssl/wolfcrypt/settings.h +++ b/wolfssl/wolfcrypt/settings.h @@ -4549,10 +4549,10 @@ extern void uITRON4_free(void *p) ; #define WOLFSSL_DILITHIUM_VERIFY_NO_MALLOC #endif -#if defined(HAVE_PQC) && defined(WOLFSSL_DTLS13) && \ - !defined(WOLFSSL_DTLS_CH_FRAG) -#warning "Using DTLS 1.3 + pqc without WOLFSSL_DTLS_CH_FRAG will probably" \ - "fail.Use --enable-dtls-frag-ch to enable it." +#if defined(HAVE_PQC) && defined(WOLFSSL_HAVE_MLKEM) && \ + defined(WOLFSSL_DTLS13) && !defined(WOLFSSL_DTLS_CH_FRAG) +#define WOLFSSL_DTLS_CH_FRAG +#warning "WOLFSSL_DTLS_CH_FRAG is enabled to support PQC in DTLS 1.3" #endif #if !defined(WOLFSSL_DTLS13) && defined(WOLFSSL_DTLS_CH_FRAG) #error "WOLFSSL_DTLS_CH_FRAG only works with DTLS 1.3"