Skip to content

Commit da038c6

Browse files
authored
Merge pull request #10299 from Frauschi/pqc_key_share_fix
Fix PQC key exchange with multiple KEM key shares
2 parents 15b1045 + 3524ece commit da038c6

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
@@ -35430,16 +35430,14 @@ static int DoSessionTicket(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
3543035430
int NamedGroupIsPqc(int group)
3543135431
{
3543235432
switch (group) {
35433-
#ifndef WOLFSSL_NO_ML_KEM
35433+
/* FIPS 204 ML-KEM */
3543435434
case WOLFSSL_ML_KEM_512:
3543535435
case WOLFSSL_ML_KEM_768:
3543635436
case WOLFSSL_ML_KEM_1024:
35437-
#endif
35438-
#ifdef WOLFSSL_MLKEM_KYBER
35437+
/* Kyber Round 3 */
3543935438
case WOLFSSL_KYBER_LEVEL1:
3544035439
case WOLFSSL_KYBER_LEVEL3:
3544135440
case WOLFSSL_KYBER_LEVEL5:
35442-
#endif
3544335441
return 1;
3544435442
default:
3544535443
return 0;
@@ -35449,17 +35447,12 @@ static int DoSessionTicket(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
3544935447
/* Returns 1 when the given group is a PQC hybrid group, 0 otherwise. */
3545035448
int NamedGroupIsPqcHybrid(int group)
3545135449
{
35452-
#if defined(WOLFSSL_PQC_HYBRIDS) || defined(WOLFSSL_EXTRA_PQC_HYBRIDS) || \
35453-
defined(WOLFSSL_MLKEM_KYBER)
35454-
3545535450
switch (group) {
35456-
#ifndef WOLFSSL_NO_ML_KEM
35457-
#ifdef WOLFSSL_PQC_HYBRIDS
35451+
/* Standardized hybrids */
3545835452
case WOLFSSL_SECP256R1MLKEM768:
3545935453
case WOLFSSL_X25519MLKEM768:
3546035454
case WOLFSSL_SECP384R1MLKEM1024:
35461-
#endif /* WOLFSSL_PQC_HYBRIDS */
35462-
#ifdef WOLFSSL_EXTRA_PQC_HYBRIDS
35455+
/* Additional experimental hybrids */
3546335456
case WOLFSSL_SECP256R1MLKEM512:
3546435457
case WOLFSSL_SECP384R1MLKEM768:
3546535458
case WOLFSSL_SECP521R1MLKEM1024:
@@ -35470,25 +35463,18 @@ static int DoSessionTicket(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
3547035463
case WOLFSSL_P384_ML_KEM_768_OLD:
3547135464
case WOLFSSL_P521_ML_KEM_1024_OLD:
3547235465
#endif
35473-
#endif /* WOLFSSL_EXTRA_PQC_HYBRIDS */
35474-
#endif
35475-
#ifdef WOLFSSL_MLKEM_KYBER
35466+
/* Kyber round 3 hybrids */
3547635467
case WOLFSSL_P256_KYBER_LEVEL3:
3547735468
case WOLFSSL_X25519_KYBER_LEVEL3:
3547835469
case WOLFSSL_P256_KYBER_LEVEL1:
3547935470
case WOLFSSL_P384_KYBER_LEVEL3:
3548035471
case WOLFSSL_P521_KYBER_LEVEL5:
3548135472
case WOLFSSL_X25519_KYBER_LEVEL1:
3548235473
case WOLFSSL_X448_KYBER_LEVEL3:
35483-
#endif
3548435474
return 1;
3548535475
default:
3548635476
return 0;
3548735477
}
35488-
#else
35489-
(void)group;
35490-
return 0;
35491-
#endif
3549235478
}
3549335479
#endif /* WOLFSSL_HAVE_MLKEM */
3549435480

src/tls.c

Lines changed: 22 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -9082,16 +9082,18 @@ static void TLSX_KeyShare_FreeAll(KeyShareEntry* list, void* heap)
90829082
wc_curve448_free((curve448_key*)current->key);
90839083
#endif
90849084
}
9085-
#ifdef WOLFSSL_HAVE_MLKEM
90869085
else if (WOLFSSL_NAMED_GROUP_IS_PQC(current->group)) {
9086+
#ifdef WOLFSSL_HAVE_MLKEM
90879087
wc_KyberKey_Free((KyberKey*)current->key);
90889088
#ifndef WOLFSSL_TLSX_PQC_MLKEM_STORE_OBJ
90899089
if (current->privKey != NULL) {
90909090
ForceZero(current->privKey, current->privKeyLen);
90919091
}
90929092
#endif
9093+
#endif
90939094
}
90949095
else if (WOLFSSL_NAMED_GROUP_IS_PQC_HYBRID(current->group)) {
9096+
#ifdef WOLFSSL_HAVE_MLKEM
90959097
int ecc_group = 0;
90969098
findEccPqc(&ecc_group, NULL, NULL, current->group);
90979099

@@ -9129,8 +9131,8 @@ static void TLSX_KeyShare_FreeAll(KeyShareEntry* list, void* heap)
91299131
wc_ecc_free((ecc_key*)current->key);
91309132
#endif
91319133
}
9132-
}
91339134
#endif
9135+
}
91349136
else {
91359137
#ifdef HAVE_ECC
91369138
#if defined(WC_ECC_NONBLOCK) && defined(WOLFSSL_ASYNC_CRYPT_SW) && \
@@ -10304,42 +10306,16 @@ static int TLSX_KeyShareEntry_Parse(const WOLFSSL* ssl, const byte* input,
1030410306
*seenGroupsCnt = i + 1;
1030510307
}
1030610308

10307-
#if defined(WOLFSSL_HAVE_MLKEM)
10308-
if ((WOLFSSL_NAMED_GROUP_IS_PQC(group)
10309-
#if !defined(WOLFSSL_ASYNC_CRYPT)
10310-
|| WOLFSSL_NAMED_GROUP_IS_PQC_HYBRID(group)
10311-
#endif
10312-
) && ssl->options.side == WOLFSSL_SERVER_END) {
10313-
/* When handling a key share containing a KEM public key on the server
10314-
* end, we have to perform the encapsulation immediately in order to
10315-
* send the resulting ciphertext back to the client in the ServerHello
10316-
* message. As the public key is not stored and we do not modify it, we
10317-
* don't have to create a copy of it.
10318-
* In case of a hybrid key exchange, the ECDH part is also performed
10319-
* immediately (to not split the generation of the master secret).
10320-
* Hence, we also don't have to store this public key either.
10321-
*
10322-
* When WOLFSSL_ASYNC_CRYPT is enabled, this handling is not possible
10323-
* for the hybrid case, as the ECC part is performed asynchronously,
10324-
* requiring the key share data to be stored.
10325-
*/
10326-
ke = (byte *)&input[offset];
10327-
} else
10328-
#endif
10329-
{
10330-
/* Store a copy in the key share object. */
10331-
ke = (byte*)XMALLOC(keLen, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY);
10332-
if (ke == NULL)
10333-
return MEMORY_E;
10334-
XMEMCPY(ke, &input[offset], keLen);
10335-
}
10309+
/* Store a copy in the key share object. */
10310+
ke = (byte*)XMALLOC(keLen, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY);
10311+
if (ke == NULL)
10312+
return MEMORY_E;
10313+
XMEMCPY(ke, &input[offset], keLen);
1033610314

1033710315
/* Populate a key share object in the extension. */
1033810316
ret = TLSX_KeyShare_Use(ssl, group, keLen, ke, kse, extensions);
1033910317
if (ret != 0) {
10340-
if (ke != &input[offset]) {
10341-
XFREE(ke, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY);
10342-
}
10318+
XFREE(ke, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY);
1034310319
return ret;
1034410320
}
1034510321

@@ -11066,46 +11042,8 @@ int TLSX_KeyShare_Use(const WOLFSSL* ssl, word16 group, word16 len, byte* data,
1106611042
return ret;
1106711043
}
1106811044

11069-
11070-
#if defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE)
11071-
if (ssl->options.side == WOLFSSL_SERVER_END &&
11072-
WOLFSSL_NAMED_GROUP_IS_PQC(group)) {
11073-
if (TLSX_IsGroupSupported(group)) {
11074-
ret = TLSX_KeyShare_HandlePqcKeyServer((WOLFSSL*)ssl,
11075-
keyShareEntry,
11076-
data, len,
11077-
ssl->arrays->preMasterSecret,
11078-
&ssl->arrays->preMasterSz);
11079-
if (ret != 0)
11080-
return ret;
11081-
}
11082-
else {
11083-
XFREE(keyShareEntry->ke, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY);
11084-
keyShareEntry->ke = NULL;
11085-
keyShareEntry->keLen = 0;
11086-
}
11087-
}
11088-
else
11089-
#if !defined(WOLFSSL_ASYNC_CRYPT)
11090-
if (ssl->options.side == WOLFSSL_SERVER_END &&
11091-
WOLFSSL_NAMED_GROUP_IS_PQC_HYBRID(group)) {
11092-
if (TLSX_IsGroupSupported(group)) {
11093-
ret = TLSX_KeyShare_HandlePqcHybridKeyServer((WOLFSSL*)ssl,
11094-
keyShareEntry,
11095-
data, len);
11096-
if (ret != 0)
11097-
return ret;
11098-
}
11099-
else {
11100-
XFREE(keyShareEntry->ke, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY);
11101-
keyShareEntry->ke = NULL;
11102-
keyShareEntry->keLen = 0;
11103-
}
11104-
}
11105-
else
11106-
#endif
11107-
#endif
1110811045
if (data != NULL) {
11046+
/* Store the peer data in the key share object. */
1110911047
XFREE(keyShareEntry->ke, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY);
1111011048
keyShareEntry->ke = data;
1111111049
keyShareEntry->keLen = len;
@@ -11293,7 +11231,10 @@ static int TLSX_KeyShare_GroupRank(const WOLFSSL* ssl, int group)
1129311231
byte numGroups;
1129411232

1129511233
if (ssl->numGroups == 0) {
11296-
return 0;
11234+
/* If the user didn't specify a group list with a preferred order,
11235+
* use the internal preferred group list. */
11236+
groups = preferredGroup;
11237+
numGroups = PREFERRED_GROUP_SZ;
1129711238
}
1129811239
else {
1129911240
groups = ssl->group;
@@ -11590,9 +11531,7 @@ int TLSX_KeyShare_Choose(const WOLFSSL *ssl, TLSX* extensions,
1159011531

1159111532
/* Use server's preference order. */
1159211533
for (clientKSE = list; clientKSE != NULL; clientKSE = clientKSE->next) {
11593-
if ((clientKSE->ke == NULL) &&
11594-
(!WOLFSSL_NAMED_GROUP_IS_PQC(clientKSE->group)) &&
11595-
(!WOLFSSL_NAMED_GROUP_IS_PQC_HYBRID(clientKSE->group)))
11534+
if (clientKSE->ke == NULL)
1159611535
continue;
1159711536

1159811537
#ifdef WOLFSSL_SM2
@@ -11681,26 +11620,17 @@ int TLSX_KeyShare_Setup(WOLFSSL *ssl, KeyShareEntry* clientKSE)
1168111620
return ret;
1168211621

1168311622
if (clientKSE->key == NULL) {
11684-
#ifdef WOLFSSL_HAVE_MLKEM
11685-
if (WOLFSSL_NAMED_GROUP_IS_PQC(clientKSE->group)
11686-
#if !defined(WOLFSSL_ASYNC_CRYPT)
11687-
|| WOLFSSL_NAMED_GROUP_IS_PQC_HYBRID(clientKSE->group)
11688-
#endif
11689-
) {
11690-
/* Going to need the public key (AKA ciphertext). */
11691-
serverKSE->pubKey = clientKSE->pubKey;
11692-
clientKSE->pubKey = NULL;
11693-
serverKSE->pubKeyLen = clientKSE->pubKeyLen;
11694-
clientKSE->pubKeyLen = 0;
11623+
#if defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE)
11624+
if (WOLFSSL_NAMED_GROUP_IS_PQC(clientKSE->group)) {
11625+
ret = TLSX_KeyShare_HandlePqcKeyServer(ssl, serverKSE,
11626+
clientKSE->ke, clientKSE->keLen,
11627+
ssl->arrays->preMasterSecret, &ssl->arrays->preMasterSz);
1169511628
}
11696-
else
11697-
#if defined(WOLFSSL_ASYNC_CRYPT)
11698-
if (WOLFSSL_NAMED_GROUP_IS_PQC_HYBRID(clientKSE->group)) {
11629+
else if (WOLFSSL_NAMED_GROUP_IS_PQC_HYBRID(clientKSE->group)) {
1169911630
ret = TLSX_KeyShare_HandlePqcHybridKeyServer(ssl, serverKSE,
1170011631
clientKSE->ke, clientKSE->keLen);
1170111632
}
1170211633
else
11703-
#endif
1170411634
#endif
1170511635
{
1170611636
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);
@@ -68,6 +69,7 @@ int test_tls13_cert_with_extern_psk_sh_confirms_resumption(void);
6869
TEST_DECL_GROUP("tls13", test_tls13_bad_psk_binder), \
6970
TEST_DECL_GROUP("tls13", test_tls13_rpk_handshake), \
7071
TEST_DECL_GROUP("tls13", test_tls13_pq_groups), \
72+
TEST_DECL_GROUP("tls13", test_tls13_multi_pqc_key_share), \
7173
TEST_DECL_GROUP("tls13", test_tls13_early_data), \
7274
TEST_DECL_GROUP("tls13", test_tls13_same_ch), \
7375
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)