Skip to content

Commit 5705c30

Browse files
committed
Fix PQC key exchange with multiple KEM key shares
1 parent 31278ee commit 5705c30

5 files changed

Lines changed: 127 additions & 117 deletions

File tree

src/internal.c

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -35383,16 +35383,14 @@ static int DoSessionTicket(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
3538335383
int NamedGroupIsPqc(int group)
3538435384
{
3538535385
switch (group) {
35386-
#ifndef WOLFSSL_NO_ML_KEM
35386+
/* FIPS 204 ML-KEM */
3538735387
case WOLFSSL_ML_KEM_512:
3538835388
case WOLFSSL_ML_KEM_768:
3538935389
case WOLFSSL_ML_KEM_1024:
35390-
#endif
35391-
#ifdef WOLFSSL_MLKEM_KYBER
35390+
/* Kyber Round 3 */
3539235391
case WOLFSSL_KYBER_LEVEL1:
3539335392
case WOLFSSL_KYBER_LEVEL3:
3539435393
case WOLFSSL_KYBER_LEVEL5:
35395-
#endif
3539635394
return 1;
3539735395
default:
3539835396
return 0;
@@ -35402,17 +35400,12 @@ static int DoSessionTicket(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
3540235400
/* Returns 1 when the given group is a PQC hybrid group, 0 otherwise. */
3540335401
int NamedGroupIsPqcHybrid(int group)
3540435402
{
35405-
#if defined(WOLFSSL_PQC_HYBRIDS) || defined(WOLFSSL_EXTRA_PQC_HYBRIDS) || \
35406-
defined(WOLFSSL_MLKEM_KYBER)
35407-
3540835403
switch (group) {
35409-
#ifndef WOLFSSL_NO_ML_KEM
35410-
#ifdef WOLFSSL_PQC_HYBRIDS
35404+
/* Standardized hybrids */
3541135405
case WOLFSSL_SECP256R1MLKEM768:
3541235406
case WOLFSSL_X25519MLKEM768:
3541335407
case WOLFSSL_SECP384R1MLKEM1024:
35414-
#endif /* WOLFSSL_PQC_HYBRIDS */
35415-
#ifdef WOLFSSL_EXTRA_PQC_HYBRIDS
35408+
/* Additional experimental hybrids */
3541635409
case WOLFSSL_SECP256R1MLKEM512:
3541735410
case WOLFSSL_SECP384R1MLKEM768:
3541835411
case WOLFSSL_SECP521R1MLKEM1024:
@@ -35423,25 +35416,18 @@ static int DoSessionTicket(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
3542335416
case WOLFSSL_P384_ML_KEM_768_OLD:
3542435417
case WOLFSSL_P521_ML_KEM_1024_OLD:
3542535418
#endif
35426-
#endif /* WOLFSSL_EXTRA_PQC_HYBRIDS */
35427-
#endif
35428-
#ifdef WOLFSSL_MLKEM_KYBER
35419+
/* Kyber round 3 hybrids */
3542935420
case WOLFSSL_P256_KYBER_LEVEL3:
3543035421
case WOLFSSL_X25519_KYBER_LEVEL3:
3543135422
case WOLFSSL_P256_KYBER_LEVEL1:
3543235423
case WOLFSSL_P384_KYBER_LEVEL3:
3543335424
case WOLFSSL_P521_KYBER_LEVEL5:
3543435425
case WOLFSSL_X25519_KYBER_LEVEL1:
3543535426
case WOLFSSL_X448_KYBER_LEVEL3:
35436-
#endif
3543735427
return 1;
3543835428
default:
3543935429
return 0;
3544035430
}
35441-
#else
35442-
(void)group;
35443-
return 0;
35444-
#endif
3544535431
}
3544635432
#endif /* WOLFSSL_HAVE_MLKEM */
3544735433

src/tls.c

Lines changed: 22 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -9164,16 +9164,18 @@ static void TLSX_KeyShare_FreeAll(KeyShareEntry* list, void* heap)
91649164
wc_curve448_free((curve448_key*)current->key);
91659165
#endif
91669166
}
9167-
#ifdef WOLFSSL_HAVE_MLKEM
91689167
else if (WOLFSSL_NAMED_GROUP_IS_PQC(current->group)) {
9168+
#ifdef WOLFSSL_HAVE_MLKEM
91699169
wc_KyberKey_Free((KyberKey*)current->key);
91709170
#ifndef WOLFSSL_TLSX_PQC_MLKEM_STORE_OBJ
91719171
if (current->privKey != NULL) {
91729172
ForceZero(current->privKey, current->privKeyLen);
91739173
}
91749174
#endif
9175+
#endif
91759176
}
91769177
else if (WOLFSSL_NAMED_GROUP_IS_PQC_HYBRID(current->group)) {
9178+
#ifdef WOLFSSL_HAVE_MLKEM
91779179
int ecc_group = 0;
91789180
findEccPqc(&ecc_group, NULL, NULL, current->group);
91799181

@@ -9211,8 +9213,8 @@ static void TLSX_KeyShare_FreeAll(KeyShareEntry* list, void* heap)
92119213
wc_ecc_free((ecc_key*)current->key);
92129214
#endif
92139215
}
9214-
}
92159216
#endif
9217+
}
92169218
else {
92179219
#ifdef HAVE_ECC
92189220
#if defined(WC_ECC_NONBLOCK) && defined(WOLFSSL_ASYNC_CRYPT_SW) && \
@@ -10373,42 +10375,16 @@ static int TLSX_KeyShareEntry_Parse(const WOLFSSL* ssl, const byte* input,
1037310375
*seenGroupsCnt = i + 1;
1037410376
}
1037510377

10376-
#if defined(WOLFSSL_HAVE_MLKEM)
10377-
if ((WOLFSSL_NAMED_GROUP_IS_PQC(group)
10378-
#if !defined(WOLFSSL_ASYNC_CRYPT)
10379-
|| WOLFSSL_NAMED_GROUP_IS_PQC_HYBRID(group)
10380-
#endif
10381-
) && ssl->options.side == WOLFSSL_SERVER_END) {
10382-
/* When handling a key share containing a KEM public key on the server
10383-
* end, we have to perform the encapsulation immediately in order to
10384-
* send the resulting ciphertext back to the client in the ServerHello
10385-
* message. As the public key is not stored and we do not modify it, we
10386-
* don't have to create a copy of it.
10387-
* In case of a hybrid key exchange, the ECDH part is also performed
10388-
* immediately (to not split the generation of the master secret).
10389-
* Hence, we also don't have to store this public key either.
10390-
*
10391-
* When WOLFSSL_ASYNC_CRYPT is enabled, this handling is not possible
10392-
* for the hybrid case, as the ECC part is performed asynchronously,
10393-
* requiring the key share data to be stored.
10394-
*/
10395-
ke = (byte *)&input[offset];
10396-
} else
10397-
#endif
10398-
{
10399-
/* Store a copy in the key share object. */
10400-
ke = (byte*)XMALLOC(keLen, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY);
10401-
if (ke == NULL)
10402-
return MEMORY_E;
10403-
XMEMCPY(ke, &input[offset], keLen);
10404-
}
10378+
/* Store a copy in the key share object. */
10379+
ke = (byte*)XMALLOC(keLen, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY);
10380+
if (ke == NULL)
10381+
return MEMORY_E;
10382+
XMEMCPY(ke, &input[offset], keLen);
1040510383

1040610384
/* Populate a key share object in the extension. */
1040710385
ret = TLSX_KeyShare_Use(ssl, group, keLen, ke, kse, extensions);
1040810386
if (ret != 0) {
10409-
if (ke != &input[offset]) {
10410-
XFREE(ke, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY);
10411-
}
10387+
XFREE(ke, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY);
1041210388
return ret;
1041310389
}
1041410390

@@ -11135,46 +11111,8 @@ int TLSX_KeyShare_Use(const WOLFSSL* ssl, word16 group, word16 len, byte* data,
1113511111
return ret;
1113611112
}
1113711113

11138-
11139-
#if defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE)
11140-
if (ssl->options.side == WOLFSSL_SERVER_END &&
11141-
WOLFSSL_NAMED_GROUP_IS_PQC(group)) {
11142-
if (TLSX_IsGroupSupported(group)) {
11143-
ret = TLSX_KeyShare_HandlePqcKeyServer((WOLFSSL*)ssl,
11144-
keyShareEntry,
11145-
data, len,
11146-
ssl->arrays->preMasterSecret,
11147-
&ssl->arrays->preMasterSz);
11148-
if (ret != 0)
11149-
return ret;
11150-
}
11151-
else {
11152-
XFREE(keyShareEntry->ke, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY);
11153-
keyShareEntry->ke = NULL;
11154-
keyShareEntry->keLen = 0;
11155-
}
11156-
}
11157-
else
11158-
#if !defined(WOLFSSL_ASYNC_CRYPT)
11159-
if (ssl->options.side == WOLFSSL_SERVER_END &&
11160-
WOLFSSL_NAMED_GROUP_IS_PQC_HYBRID(group)) {
11161-
if (TLSX_IsGroupSupported(group)) {
11162-
ret = TLSX_KeyShare_HandlePqcHybridKeyServer((WOLFSSL*)ssl,
11163-
keyShareEntry,
11164-
data, len);
11165-
if (ret != 0)
11166-
return ret;
11167-
}
11168-
else {
11169-
XFREE(keyShareEntry->ke, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY);
11170-
keyShareEntry->ke = NULL;
11171-
keyShareEntry->keLen = 0;
11172-
}
11173-
}
11174-
else
11175-
#endif
11176-
#endif
1117711114
if (data != NULL) {
11115+
/* Store the peer data in the key share object. */
1117811116
XFREE(keyShareEntry->ke, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY);
1117911117
keyShareEntry->ke = data;
1118011118
keyShareEntry->keLen = len;
@@ -11362,7 +11300,10 @@ static int TLSX_KeyShare_GroupRank(const WOLFSSL* ssl, int group)
1136211300
byte numGroups;
1136311301

1136411302
if (ssl->numGroups == 0) {
11365-
return 0;
11303+
/* If the user didn't specify a group list with a preferred order,
11304+
* use the internal preferred group list. */
11305+
groups = preferredGroup;
11306+
numGroups = PREFERRED_GROUP_SZ;
1136611307
}
1136711308
else {
1136811309
groups = ssl->group;
@@ -11664,9 +11605,7 @@ int TLSX_KeyShare_Choose(const WOLFSSL *ssl, TLSX* extensions,
1166411605

1166511606
/* Use server's preference order. */
1166611607
for (clientKSE = list; clientKSE != NULL; clientKSE = clientKSE->next) {
11667-
if ((clientKSE->ke == NULL) &&
11668-
(!WOLFSSL_NAMED_GROUP_IS_PQC(clientKSE->group)) &&
11669-
(!WOLFSSL_NAMED_GROUP_IS_PQC_HYBRID(clientKSE->group)))
11608+
if (clientKSE->ke == NULL)
1167011609
continue;
1167111610

1167211611
#ifdef WOLFSSL_SM2
@@ -11755,26 +11694,17 @@ int TLSX_KeyShare_Setup(WOLFSSL *ssl, KeyShareEntry* clientKSE)
1175511694
return ret;
1175611695

1175711696
if (clientKSE->key == NULL) {
11758-
#ifdef WOLFSSL_HAVE_MLKEM
11759-
if (WOLFSSL_NAMED_GROUP_IS_PQC(clientKSE->group)
11760-
#if !defined(WOLFSSL_ASYNC_CRYPT)
11761-
|| WOLFSSL_NAMED_GROUP_IS_PQC_HYBRID(clientKSE->group)
11762-
#endif
11763-
) {
11764-
/* Going to need the public key (AKA ciphertext). */
11765-
serverKSE->pubKey = clientKSE->pubKey;
11766-
clientKSE->pubKey = NULL;
11767-
serverKSE->pubKeyLen = clientKSE->pubKeyLen;
11768-
clientKSE->pubKeyLen = 0;
11697+
#if defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE)
11698+
if (WOLFSSL_NAMED_GROUP_IS_PQC(clientKSE->group)) {
11699+
ret = TLSX_KeyShare_HandlePqcKeyServer(ssl, serverKSE,
11700+
clientKSE->ke, clientKSE->keLen,
11701+
ssl->arrays->preMasterSecret, &ssl->arrays->preMasterSz);
1176911702
}
11770-
else
11771-
#if defined(WOLFSSL_ASYNC_CRYPT)
11772-
if (WOLFSSL_NAMED_GROUP_IS_PQC_HYBRID(clientKSE->group)) {
11703+
else if (WOLFSSL_NAMED_GROUP_IS_PQC_HYBRID(clientKSE->group)) {
1177311704
ret = TLSX_KeyShare_HandlePqcHybridKeyServer(ssl, serverKSE,
1177411705
clientKSE->ke, clientKSE->keLen);
1177511706
}
1177611707
else
11777-
#endif
1177811708
#endif
1177911709
{
1178011710
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: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4766,9 +4766,8 @@ enum {
47664766
WOLFSSL_FFDHE_8192 = 260,
47674767
WOLFSSL_FFDHE_END = 511,
47684768

4769-
#ifdef HAVE_PQC
4769+
/* Post Quantum KEM code points */
47704770

4771-
#ifdef WOLFSSL_MLKEM_KYBER
47724771
/* Old code points to keep compatibility with Kyber Round 3.
47734772
* Taken from OQS's openssl provider, see:
47744773
* https://github.com/open-quantum-safe/oqs-provider/blob/main/oqs-template/
@@ -4785,8 +4784,7 @@ enum {
47854784
WOLFSSL_X448_KYBER_LEVEL3 = 12176,
47864785
WOLFSSL_X25519_KYBER_LEVEL3 = 25497,
47874786
WOLFSSL_P256_KYBER_LEVEL3 = 25498,
4788-
#endif /* WOLFSSL_MLKEM_KYBER */
4789-
#ifndef WOLFSSL_NO_ML_KEM
4787+
47904788
/* Taken from draft-ietf-tls-mlkem, see:
47914789
* https://datatracker.ietf.org/doc/draft-ietf-tls-mlkem/
47924790
*/
@@ -4815,8 +4813,7 @@ enum {
48154813
WOLFSSL_SECP521R1MLKEM1024 = 12109,
48164814
WOLFSSL_X25519MLKEM512 = 12214,
48174815
WOLFSSL_X448MLKEM768 = 12215,
4818-
#endif /* WOLFSSL_NO_ML_KEM */
4819-
#endif /* HAVE_PQC */
4816+
48204817
WOLF_ENUM_DUMMY_LAST_ELEMENT(SSL_H)
48214818
};
48224819

0 commit comments

Comments
 (0)