Skip to content

Commit 3524ece

Browse files
committed
Fix PQC key exchange with multiple KEM key shares
1 parent 7b53303 commit 3524ece

5 files changed

Lines changed: 126 additions & 117 deletions

File tree

src/internal.c

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -35405,16 +35405,14 @@ static int DoSessionTicket(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
3540535405
int NamedGroupIsPqc(int group)
3540635406
{
3540735407
switch (group) {
35408-
#ifndef WOLFSSL_NO_ML_KEM
35408+
/* FIPS 204 ML-KEM */
3540935409
case WOLFSSL_ML_KEM_512:
3541035410
case WOLFSSL_ML_KEM_768:
3541135411
case WOLFSSL_ML_KEM_1024:
35412-
#endif
35413-
#ifdef WOLFSSL_MLKEM_KYBER
35412+
/* Kyber Round 3 */
3541435413
case WOLFSSL_KYBER_LEVEL1:
3541535414
case WOLFSSL_KYBER_LEVEL3:
3541635415
case WOLFSSL_KYBER_LEVEL5:
35417-
#endif
3541835416
return 1;
3541935417
default:
3542035418
return 0;
@@ -35424,17 +35422,12 @@ static int DoSessionTicket(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
3542435422
/* Returns 1 when the given group is a PQC hybrid group, 0 otherwise. */
3542535423
int NamedGroupIsPqcHybrid(int group)
3542635424
{
35427-
#if defined(WOLFSSL_PQC_HYBRIDS) || defined(WOLFSSL_EXTRA_PQC_HYBRIDS) || \
35428-
defined(WOLFSSL_MLKEM_KYBER)
35429-
3543035425
switch (group) {
35431-
#ifndef WOLFSSL_NO_ML_KEM
35432-
#ifdef WOLFSSL_PQC_HYBRIDS
35426+
/* Standardized hybrids */
3543335427
case WOLFSSL_SECP256R1MLKEM768:
3543435428
case WOLFSSL_X25519MLKEM768:
3543535429
case WOLFSSL_SECP384R1MLKEM1024:
35436-
#endif /* WOLFSSL_PQC_HYBRIDS */
35437-
#ifdef WOLFSSL_EXTRA_PQC_HYBRIDS
35430+
/* Additional experimental hybrids */
3543835431
case WOLFSSL_SECP256R1MLKEM512:
3543935432
case WOLFSSL_SECP384R1MLKEM768:
3544035433
case WOLFSSL_SECP521R1MLKEM1024:
@@ -35445,25 +35438,18 @@ static int DoSessionTicket(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
3544535438
case WOLFSSL_P384_ML_KEM_768_OLD:
3544635439
case WOLFSSL_P521_ML_KEM_1024_OLD:
3544735440
#endif
35448-
#endif /* WOLFSSL_EXTRA_PQC_HYBRIDS */
35449-
#endif
35450-
#ifdef WOLFSSL_MLKEM_KYBER
35441+
/* Kyber round 3 hybrids */
3545135442
case WOLFSSL_P256_KYBER_LEVEL3:
3545235443
case WOLFSSL_X25519_KYBER_LEVEL3:
3545335444
case WOLFSSL_P256_KYBER_LEVEL1:
3545435445
case WOLFSSL_P384_KYBER_LEVEL3:
3545535446
case WOLFSSL_P521_KYBER_LEVEL5:
3545635447
case WOLFSSL_X25519_KYBER_LEVEL1:
3545735448
case WOLFSSL_X448_KYBER_LEVEL3:
35458-
#endif
3545935449
return 1;
3546035450
default:
3546135451
return 0;
3546235452
}
35463-
#else
35464-
(void)group;
35465-
return 0;
35466-
#endif
3546735453
}
3546835454
#endif /* WOLFSSL_HAVE_MLKEM */
3546935455

src/tls.c

Lines changed: 22 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -9071,16 +9071,18 @@ static void TLSX_KeyShare_FreeAll(KeyShareEntry* list, void* heap)
90719071
wc_curve448_free((curve448_key*)current->key);
90729072
#endif
90739073
}
9074-
#ifdef WOLFSSL_HAVE_MLKEM
90759074
else if (WOLFSSL_NAMED_GROUP_IS_PQC(current->group)) {
9075+
#ifdef WOLFSSL_HAVE_MLKEM
90769076
wc_KyberKey_Free((KyberKey*)current->key);
90779077
#ifndef WOLFSSL_TLSX_PQC_MLKEM_STORE_OBJ
90789078
if (current->privKey != NULL) {
90799079
ForceZero(current->privKey, current->privKeyLen);
90809080
}
90819081
#endif
9082+
#endif
90829083
}
90839084
else if (WOLFSSL_NAMED_GROUP_IS_PQC_HYBRID(current->group)) {
9085+
#ifdef WOLFSSL_HAVE_MLKEM
90849086
int ecc_group = 0;
90859087
findEccPqc(&ecc_group, NULL, NULL, current->group);
90869088

@@ -9118,8 +9120,8 @@ static void TLSX_KeyShare_FreeAll(KeyShareEntry* list, void* heap)
91189120
wc_ecc_free((ecc_key*)current->key);
91199121
#endif
91209122
}
9121-
}
91229123
#endif
9124+
}
91239125
else {
91249126
#ifdef HAVE_ECC
91259127
#if defined(WC_ECC_NONBLOCK) && defined(WOLFSSL_ASYNC_CRYPT_SW) && \
@@ -10284,42 +10286,16 @@ static int TLSX_KeyShareEntry_Parse(const WOLFSSL* ssl, const byte* input,
1028410286
*seenGroupsCnt = i + 1;
1028510287
}
1028610288

10287-
#if defined(WOLFSSL_HAVE_MLKEM)
10288-
if ((WOLFSSL_NAMED_GROUP_IS_PQC(group)
10289-
#if !defined(WOLFSSL_ASYNC_CRYPT)
10290-
|| WOLFSSL_NAMED_GROUP_IS_PQC_HYBRID(group)
10291-
#endif
10292-
) && ssl->options.side == WOLFSSL_SERVER_END) {
10293-
/* When handling a key share containing a KEM public key on the server
10294-
* end, we have to perform the encapsulation immediately in order to
10295-
* send the resulting ciphertext back to the client in the ServerHello
10296-
* message. As the public key is not stored and we do not modify it, we
10297-
* don't have to create a copy of it.
10298-
* In case of a hybrid key exchange, the ECDH part is also performed
10299-
* immediately (to not split the generation of the master secret).
10300-
* Hence, we also don't have to store this public key either.
10301-
*
10302-
* When WOLFSSL_ASYNC_CRYPT is enabled, this handling is not possible
10303-
* for the hybrid case, as the ECC part is performed asynchronously,
10304-
* requiring the key share data to be stored.
10305-
*/
10306-
ke = (byte *)&input[offset];
10307-
} else
10308-
#endif
10309-
{
10310-
/* Store a copy in the key share object. */
10311-
ke = (byte*)XMALLOC(keLen, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY);
10312-
if (ke == NULL)
10313-
return MEMORY_E;
10314-
XMEMCPY(ke, &input[offset], keLen);
10315-
}
10289+
/* Store a copy in the key share object. */
10290+
ke = (byte*)XMALLOC(keLen, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY);
10291+
if (ke == NULL)
10292+
return MEMORY_E;
10293+
XMEMCPY(ke, &input[offset], keLen);
1031610294

1031710295
/* Populate a key share object in the extension. */
1031810296
ret = TLSX_KeyShare_Use(ssl, group, keLen, ke, kse, extensions);
1031910297
if (ret != 0) {
10320-
if (ke != &input[offset]) {
10321-
XFREE(ke, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY);
10322-
}
10298+
XFREE(ke, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY);
1032310299
return ret;
1032410300
}
1032510301

@@ -11046,46 +11022,8 @@ int TLSX_KeyShare_Use(const WOLFSSL* ssl, word16 group, word16 len, byte* data,
1104611022
return ret;
1104711023
}
1104811024

11049-
11050-
#if defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE)
11051-
if (ssl->options.side == WOLFSSL_SERVER_END &&
11052-
WOLFSSL_NAMED_GROUP_IS_PQC(group)) {
11053-
if (TLSX_IsGroupSupported(group)) {
11054-
ret = TLSX_KeyShare_HandlePqcKeyServer((WOLFSSL*)ssl,
11055-
keyShareEntry,
11056-
data, len,
11057-
ssl->arrays->preMasterSecret,
11058-
&ssl->arrays->preMasterSz);
11059-
if (ret != 0)
11060-
return ret;
11061-
}
11062-
else {
11063-
XFREE(keyShareEntry->ke, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY);
11064-
keyShareEntry->ke = NULL;
11065-
keyShareEntry->keLen = 0;
11066-
}
11067-
}
11068-
else
11069-
#if !defined(WOLFSSL_ASYNC_CRYPT)
11070-
if (ssl->options.side == WOLFSSL_SERVER_END &&
11071-
WOLFSSL_NAMED_GROUP_IS_PQC_HYBRID(group)) {
11072-
if (TLSX_IsGroupSupported(group)) {
11073-
ret = TLSX_KeyShare_HandlePqcHybridKeyServer((WOLFSSL*)ssl,
11074-
keyShareEntry,
11075-
data, len);
11076-
if (ret != 0)
11077-
return ret;
11078-
}
11079-
else {
11080-
XFREE(keyShareEntry->ke, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY);
11081-
keyShareEntry->ke = NULL;
11082-
keyShareEntry->keLen = 0;
11083-
}
11084-
}
11085-
else
11086-
#endif
11087-
#endif
1108811025
if (data != NULL) {
11026+
/* Store the peer data in the key share object. */
1108911027
XFREE(keyShareEntry->ke, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY);
1109011028
keyShareEntry->ke = data;
1109111029
keyShareEntry->keLen = len;
@@ -11273,7 +11211,10 @@ static int TLSX_KeyShare_GroupRank(const WOLFSSL* ssl, int group)
1127311211
byte numGroups;
1127411212

1127511213
if (ssl->numGroups == 0) {
11276-
return 0;
11214+
/* If the user didn't specify a group list with a preferred order,
11215+
* use the internal preferred group list. */
11216+
groups = preferredGroup;
11217+
numGroups = PREFERRED_GROUP_SZ;
1127711218
}
1127811219
else {
1127911220
groups = ssl->group;
@@ -11570,9 +11511,7 @@ int TLSX_KeyShare_Choose(const WOLFSSL *ssl, TLSX* extensions,
1157011511

1157111512
/* Use server's preference order. */
1157211513
for (clientKSE = list; clientKSE != NULL; clientKSE = clientKSE->next) {
11573-
if ((clientKSE->ke == NULL) &&
11574-
(!WOLFSSL_NAMED_GROUP_IS_PQC(clientKSE->group)) &&
11575-
(!WOLFSSL_NAMED_GROUP_IS_PQC_HYBRID(clientKSE->group)))
11514+
if (clientKSE->ke == NULL)
1157611515
continue;
1157711516

1157811517
#ifdef WOLFSSL_SM2
@@ -11661,26 +11600,17 @@ int TLSX_KeyShare_Setup(WOLFSSL *ssl, KeyShareEntry* clientKSE)
1166111600
return ret;
1166211601

1166311602
if (clientKSE->key == NULL) {
11664-
#ifdef WOLFSSL_HAVE_MLKEM
11665-
if (WOLFSSL_NAMED_GROUP_IS_PQC(clientKSE->group)
11666-
#if !defined(WOLFSSL_ASYNC_CRYPT)
11667-
|| WOLFSSL_NAMED_GROUP_IS_PQC_HYBRID(clientKSE->group)
11668-
#endif
11669-
) {
11670-
/* Going to need the public key (AKA ciphertext). */
11671-
serverKSE->pubKey = clientKSE->pubKey;
11672-
clientKSE->pubKey = NULL;
11673-
serverKSE->pubKeyLen = clientKSE->pubKeyLen;
11674-
clientKSE->pubKeyLen = 0;
11603+
#if defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE)
11604+
if (WOLFSSL_NAMED_GROUP_IS_PQC(clientKSE->group)) {
11605+
ret = TLSX_KeyShare_HandlePqcKeyServer(ssl, serverKSE,
11606+
clientKSE->ke, clientKSE->keLen,
11607+
ssl->arrays->preMasterSecret, &ssl->arrays->preMasterSz);
1167511608
}
11676-
else
11677-
#if defined(WOLFSSL_ASYNC_CRYPT)
11678-
if (WOLFSSL_NAMED_GROUP_IS_PQC_HYBRID(clientKSE->group)) {
11609+
else if (WOLFSSL_NAMED_GROUP_IS_PQC_HYBRID(clientKSE->group)) {
1167911610
ret = TLSX_KeyShare_HandlePqcHybridKeyServer(ssl, serverKSE,
1168011611
clientKSE->ke, clientKSE->keLen);
1168111612
}
1168211613
else
11683-
#endif
1168411614
#endif
1168511615
{
1168611616
ret = TLSX_KeyShare_GenKey(ssl, serverKSE);

tests/api/test_tls13.c

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2667,6 +2667,101 @@ int test_tls13_pq_groups(void)
26672667
return EXPECT_RESULT();
26682668
}
26692669

2670+
/* Regression test handling multiple PQC key shares in the ClientHello.
2671+
*
2672+
* Previously, the server eagerly ran KEM encapsulation on every PQC/hybrid
2673+
* key_share entry while parsing the ClientHello, clobbering
2674+
* ssl->arrays->preMasterSecret with whichever entry was parsed last.
2675+
* When the ClientHello offers both SecP384R1_MLKEM1024 and pure
2676+
* ML_KEM_1024, the resulting handshake either produces keys that the
2677+
* client cannot decrypt (hybrid chosen, pure-ML-KEM secret written) or
2678+
* trips a BUFFER_E inside the second encapsulation. Either ordering
2679+
* causes the handshake to fail.
2680+
*
2681+
* The test runs a memio TLS 1.3 handshake for both orderings and
2682+
* expects the handshake to complete successfully with the hybrid group
2683+
* selected (higher server preference rank). */
2684+
#if defined(WOLFSSL_TLS13) && defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
2685+
defined(WOLFSSL_HAVE_MLKEM) && defined(WOLFSSL_PQC_HYBRIDS) && \
2686+
!defined(WOLFSSL_TLS_NO_MLKEM_STANDALONE) && \
2687+
!defined(WOLFSSL_NO_ML_KEM_1024) && \
2688+
!defined(WOLFSSL_MLKEM_NO_ENCAPSULATE) && \
2689+
!defined(WOLFSSL_MLKEM_NO_DECAPSULATE) && \
2690+
!defined(WOLFSSL_MLKEM_NO_MAKE_KEY) && \
2691+
defined(HAVE_ECC) && (defined(HAVE_ECC384) || defined(HAVE_ALL_CURVES)) && \
2692+
ECC_MIN_KEY_SZ <= 384 && \
2693+
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER)
2694+
#define TEST_TLS13_MULTI_PQC_KEY_SHARE_ENABLED
2695+
2696+
/* Run one TLS 1.3 memio handshake where the client offers both
2697+
* WOLFSSL_SECP384R1MLKEM1024 and WOLFSSL_ML_KEM_1024 in the key_share
2698+
* extension, in the order dictated by `hybridFirst`. */
2699+
static int test_tls13_multi_pqc_key_share_once(int hybridFirst)
2700+
{
2701+
EXPECT_DECLS;
2702+
WOLFSSL_CTX *ctx_c = NULL;
2703+
WOLFSSL_CTX *ctx_s = NULL;
2704+
WOLFSSL *ssl_c = NULL;
2705+
WOLFSSL *ssl_s = NULL;
2706+
struct test_memio_ctx test_ctx;
2707+
2708+
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
2709+
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
2710+
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0);
2711+
2712+
wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL);
2713+
wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_NONE, NULL);
2714+
2715+
/* Force the client to include both PQC key shares in the ClientHello
2716+
* by calling UseKeyShare twice. The order of the UseKeyShare calls
2717+
* determines the order of the entries in the key_share extension. */
2718+
if (hybridFirst) {
2719+
ExpectIntEQ(wolfSSL_UseKeyShare(ssl_c, WOLFSSL_SECP384R1MLKEM1024),
2720+
WOLFSSL_SUCCESS);
2721+
ExpectIntEQ(wolfSSL_UseKeyShare(ssl_c, WOLFSSL_ML_KEM_1024),
2722+
WOLFSSL_SUCCESS);
2723+
}
2724+
else {
2725+
ExpectIntEQ(wolfSSL_UseKeyShare(ssl_c, WOLFSSL_ML_KEM_1024),
2726+
WOLFSSL_SUCCESS);
2727+
ExpectIntEQ(wolfSSL_UseKeyShare(ssl_c, WOLFSSL_SECP384R1MLKEM1024),
2728+
WOLFSSL_SUCCESS);
2729+
}
2730+
2731+
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
2732+
2733+
/* The server ranks SecP384R1_MLKEM1024 higher than ML_KEM_1024, so
2734+
* the hybrid group must be selected regardless of client ordering. */
2735+
ExpectStrEQ(wolfSSL_get_curve_name(ssl_s), "SecP384r1MLKEM1024");
2736+
ExpectStrEQ(wolfSSL_get_curve_name(ssl_c), "SecP384r1MLKEM1024");
2737+
2738+
wolfSSL_free(ssl_c);
2739+
wolfSSL_free(ssl_s);
2740+
wolfSSL_CTX_free(ctx_c);
2741+
wolfSSL_CTX_free(ctx_s);
2742+
return EXPECT_RESULT();
2743+
}
2744+
#endif /* TEST_TLS13_MULTI_PQC_KEY_SHARE_ENABLED */
2745+
2746+
int test_tls13_multi_pqc_key_share(void)
2747+
{
2748+
EXPECT_DECLS;
2749+
#ifdef TEST_TLS13_MULTI_PQC_KEY_SHARE_ENABLED
2750+
/* Hybrid first, then pure ML-KEM: pre-fix the server selected the
2751+
* hybrid but had overwritten preMasterSecret with the pure-KEM
2752+
* result, producing 32-byte KE Secret instead of 80 and causing the
2753+
* client to fail to decrypt the server's first encrypted record. */
2754+
ExpectIntEQ(test_tls13_multi_pqc_key_share_once(1), TEST_SUCCESS);
2755+
2756+
/* Pure ML-KEM first, then hybrid: pre-fix the server tripped
2757+
* BUFFER_E inside the second encapsulation because preMasterSz was
2758+
* left at 32 from the first call, and the hybrid handler then
2759+
* overflowed the preMasterSecret buffer. */
2760+
ExpectIntEQ(test_tls13_multi_pqc_key_share_once(0), TEST_SUCCESS);
2761+
#endif
2762+
return EXPECT_RESULT();
2763+
}
2764+
26702765
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
26712766
defined(WOLFSSL_EARLY_DATA) && defined(HAVE_SESSION_TICKET)
26722767
static int test_tls13_read_until_write_ok(WOLFSSL* ssl, void* buf, int bufLen)

tests/api/test_tls13.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ int test_tls13_cipher_suites(void);
2929
int test_tls13_bad_psk_binder(void);
3030
int test_tls13_rpk_handshake(void);
3131
int test_tls13_pq_groups(void);
32+
int test_tls13_multi_pqc_key_share(void);
3233
int test_tls13_early_data(void);
3334
int test_tls13_same_ch(void);
3435
int test_tls13_hrr_different_cs(void);
@@ -67,6 +68,7 @@ int test_tls13_cert_with_extern_psk_sh_confirms_resumption(void);
6768
TEST_DECL_GROUP("tls13", test_tls13_bad_psk_binder), \
6869
TEST_DECL_GROUP("tls13", test_tls13_rpk_handshake), \
6970
TEST_DECL_GROUP("tls13", test_tls13_pq_groups), \
71+
TEST_DECL_GROUP("tls13", test_tls13_multi_pqc_key_share), \
7072
TEST_DECL_GROUP("tls13", test_tls13_early_data), \
7173
TEST_DECL_GROUP("tls13", test_tls13_same_ch), \
7274
TEST_DECL_GROUP("tls13", test_tls13_hrr_different_cs), \

wolfssl/ssl.h

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4779,9 +4779,8 @@ enum {
47794779
WOLFSSL_FFDHE_8192 = 260,
47804780
WOLFSSL_FFDHE_END = 511,
47814781

4782-
#ifdef WOLFSSL_HAVE_MLKEM
4782+
/* Post Quantum KEM code points */
47834783

4784-
#ifdef WOLFSSL_MLKEM_KYBER
47854784
/* Old code points to keep compatibility with Kyber Round 3.
47864785
* Taken from OQS's openssl provider, see:
47874786
* https://github.com/open-quantum-safe/oqs-provider/blob/main/oqs-template/
@@ -4798,8 +4797,7 @@ enum {
47984797
WOLFSSL_X448_KYBER_LEVEL3 = 12176,
47994798
WOLFSSL_X25519_KYBER_LEVEL3 = 25497,
48004799
WOLFSSL_P256_KYBER_LEVEL3 = 25498,
4801-
#endif /* WOLFSSL_MLKEM_KYBER */
4802-
#ifndef WOLFSSL_NO_ML_KEM
4800+
48034801
/* Taken from draft-ietf-tls-mlkem, see:
48044802
* https://datatracker.ietf.org/doc/draft-ietf-tls-mlkem/
48054803
*/
@@ -4828,8 +4826,6 @@ enum {
48284826
WOLFSSL_SECP521R1MLKEM1024 = 12109,
48294827
WOLFSSL_X25519MLKEM512 = 12214,
48304828
WOLFSSL_X448MLKEM768 = 12215,
4831-
#endif /* WOLFSSL_NO_ML_KEM */
4832-
#endif /* WOLFSSL_HAVE_MLKEM */
48334829
WOLF_ENUM_DUMMY_LAST_ELEMENT(SSL_H)
48344830
};
48354831

0 commit comments

Comments
 (0)