From efa9d6c6e77b5da2b913d66a3b8d8b79bec4bf43 Mon Sep 17 00:00:00 2001 From: Alex Weibel Date: Tue, 17 Sep 2024 15:54:16 -0700 Subject: [PATCH] Add MLKEM768 Hybrid Groups to libssl (#1849) Add support for X25519MLKEM768 and SecP256r1MLKEM768 from https://datatracker.ietf.org/doc/html/draft-kwiatkowski-tls-ecdhe-mlkem --- crypto/obj/obj_dat.h | 24 ++- crypto/obj/obj_mac.num | 2 + crypto/obj/objects.txt | 3 + include/openssl/nid.h | 8 + include/openssl/ssl.h | 8 + ssl/ssl_key_share.cc | 42 ++++- ssl/ssl_test.cc | 395 ++++++++++++++++++++++++++++++++++++++++- 7 files changed, 477 insertions(+), 5 deletions(-) diff --git a/crypto/obj/obj_dat.h b/crypto/obj/obj_dat.h index f29f031bf2..f11703868d 100644 --- a/crypto/obj/obj_dat.h +++ b/crypto/obj/obj_dat.h @@ -56,7 +56,7 @@ /* This file is generated by crypto/obj/objects.go. */ -#define NUM_NID 991 +#define NUM_NID 993 static const uint8_t kObjectData[] = { /* NID_rsadsi */ @@ -7269,6 +7269,18 @@ static const uint8_t kObjectData[] = { 0x04, 0x04, 0x03, + /* NID_X25519MLKEM768 */ + 0x2b, + 0xce, + 0x0f, + 0x63, + 0x36, + /* NID_SecP256r1MLKEM768 */ + 0x2b, + 0xce, + 0x0f, + 0x63, + 0x37, }; static const ASN1_OBJECT kObjects[NUM_NID] = { @@ -8945,6 +8957,10 @@ static const ASN1_OBJECT kObjects[NUM_NID] = { {"MLKEM512", "MLKEM512", NID_MLKEM512, 9, &kObjectData[6288], 0}, {"MLKEM768", "MLKEM768", NID_MLKEM768, 9, &kObjectData[6297], 0}, {"MLKEM1024", "MLKEM1024", NID_MLKEM1024, 9, &kObjectData[6306], 0}, + {"X25519MLKEM768", "X25519MLKEM768", NID_X25519MLKEM768, 5, + &kObjectData[6315], 0}, + {"SecP256r1MLKEM768", "SecP256r1MLKEM768", NID_SecP256r1MLKEM768, 5, + &kObjectData[6320], 0}, }; static const uint16_t kNIDsInShortNameOrder[] = { @@ -9162,9 +9178,11 @@ static const uint16_t kNIDsInShortNameOrder[] = { 16 /* ST */, 143 /* SXNetID */, 981 /* SecP256r1Kyber768Draft00 */, + 992 /* SecP256r1MLKEM768 */, 458 /* UID */, 948 /* X25519 */, 982 /* X25519Kyber768Draft00 */, + 991 /* X25519MLKEM768 */, 961 /* X448 */, 11 /* X500 */, 378 /* X500algorithms */, @@ -10041,6 +10059,7 @@ static const uint16_t kNIDsInLongNameOrder[] = { 167 /* S/MIME Capabilities */, 387 /* SNMPv2 */, 981 /* SecP256r1Kyber768Draft00 */, + 992 /* SecP256r1MLKEM768 */, 512 /* Secure Electronic Transactions */, 386 /* Security */, 394 /* Selected Attribute Types */, @@ -10052,6 +10071,7 @@ static const uint16_t kNIDsInLongNameOrder[] = { 375 /* Trust Root */, 948 /* X25519 */, 982 /* X25519Kyber768Draft00 */, + 991 /* X25519MLKEM768 */, 961 /* X448 */, 12 /* X509 */, 402 /* X509v3 AC Targeting */, @@ -11199,6 +11219,8 @@ static const uint16_t kNIDsInOIDOrder[] = { 734 /* 1.3.132.0.39 (OBJ_sect571r1) */, 982 /* 1.3.9999.99.51 (OBJ_X25519Kyber768Draft00) */, 981 /* 1.3.9999.99.52 (OBJ_SecP256r1Kyber768Draft00) */, + 991 /* 1.3.9999.99.54 (OBJ_X25519MLKEM768) */, + 992 /* 1.3.9999.99.55 (OBJ_SecP256r1MLKEM768) */, 624 /* 2.23.42.3.0.0 (OBJ_set_rootKeyThumb) */, 625 /* 2.23.42.3.0.1 (OBJ_set_addPolicy) */, 626 /* 2.23.42.3.2.1 (OBJ_setAttr_Token_EMV) */, diff --git a/crypto/obj/obj_mac.num b/crypto/obj/obj_mac.num index a7ae20684b..21f71036d7 100644 --- a/crypto/obj/obj_mac.num +++ b/crypto/obj/obj_mac.num @@ -978,3 +978,5 @@ MLKEM1024IPD 987 MLKEM512 988 MLKEM768 989 MLKEM1024 990 +X25519MLKEM768 991 +SecP256r1MLKEM768 992 diff --git a/crypto/obj/objects.txt b/crypto/obj/objects.txt index b11f231a16..67877c73e9 100644 --- a/crypto/obj/objects.txt +++ b/crypto/obj/objects.txt @@ -134,8 +134,11 @@ secg-ellipticCurve 39 : sect571r1 : ffdhe8192 # PQ Group OIDs from OQS +# https://github.com/open-quantum-safe/oqs-provider/blob/main/ALGORITHMS.md#oids 1 3 9999 99 51 : X25519Kyber768Draft00 1 3 9999 99 52 : SecP256r1Kyber768Draft00 +1 3 9999 99 54 : X25519MLKEM768 +1 3 9999 99 55 : SecP256r1MLKEM768 # WAP/TLS curve OIDs (http://www.wapforum.org/) !Alias wap-wsg-idm-ecid wap-wsg 4 diff --git a/include/openssl/nid.h b/include/openssl/nid.h index a88301fdf2..52cd599341 100644 --- a/include/openssl/nid.h +++ b/include/openssl/nid.h @@ -4355,6 +4355,14 @@ extern "C" { #define NID_MLKEM1024 990 #define OBJ_MLKEM1024 2L, 16L, 840L, 1L, 101L, 3L, 4L, 4L, 3L +#define SN_X25519MLKEM768 "X25519MLKEM768" +#define NID_X25519MLKEM768 991 +#define OBJ_X25519MLKEM768 1L, 3L, 9999L, 99L, 54L + +#define SN_SecP256r1MLKEM768 "SecP256r1MLKEM768" +#define NID_SecP256r1MLKEM768 992 +#define OBJ_SecP256r1MLKEM768 1L, 3L, 9999L, 99L, 55L + #if defined(__cplusplus) } /* extern C */ #endif diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h index b8c72aae62..daf02bf5e4 100644 --- a/include/openssl/ssl.h +++ b/include/openssl/ssl.h @@ -2675,6 +2675,10 @@ OPENSSL_EXPORT int SSL_set1_groups_list(SSL *ssl, const char *groups); // https://datatracker.ietf.org/doc/html/draft-tls-westerbaan-xyber768d00 #define SSL_GROUP_X25519_KYBER768_DRAFT00 0x6399 +// https://datatracker.ietf.org/doc/html/draft-kwiatkowski-tls-ecdhe-mlkem.html +#define SSL_GROUP_SECP256R1_MLKEM768 0x11EB +#define SSL_GROUP_X25519_MLKEM768 0x11EC + // PQ and hybrid group IDs are not yet standardized. Current IDs are driven by // community consensus and are defined at // https://github.com/open-quantum-safe/oqs-provider/blob/main/oqs-template/oqs-kem-info.md @@ -2682,6 +2686,10 @@ OPENSSL_EXPORT int SSL_set1_groups_list(SSL *ssl, const char *groups); #define SSL_GROUP_KYBER768_R3 0x023C #define SSL_GROUP_KYBER1024_R3 0x023D +// https://datatracker.ietf.org/doc/html/draft-connolly-tls-mlkem-key-agreement.html +#define SSL_GROUP_MLKEM768 0x0768 +#define SSL_GROUP_MLKEM1024 0x1024 + // SSL_get_group_id returns the ID of the group used by |ssl|'s most recently // completed handshake, or 0 if not applicable. OPENSSL_EXPORT uint16_t SSL_get_group_id(const SSL *ssl); diff --git a/ssl/ssl_key_share.cc b/ssl/ssl_key_share.cc index caefce8eab..497fedb99e 100644 --- a/ssl/ssl_key_share.cc +++ b/ssl/ssl_key_share.cc @@ -34,6 +34,7 @@ #include "internal.h" #include "../crypto/internal.h" #include "../crypto/fipsmodule/ec/internal.h" +#include "../crypto/fipsmodule/ml_kem/ml_kem.h" #include "../crypto/kyber/kem_kyber.h" BSSL_NAMESPACE_BEGIN @@ -638,6 +639,9 @@ class HybridKeyShare : public SSLKeyShare { case SSL_GROUP_KYBER768_R3: *out = KYBER768_R3_PUBLIC_KEY_BYTES; return true; + case SSL_GROUP_MLKEM768: + *out = MLKEM768_PUBLIC_KEY_BYTES; + return true; case SSL_GROUP_X25519: *out = 32; return true; @@ -655,6 +659,9 @@ class HybridKeyShare : public SSLKeyShare { case SSL_GROUP_KYBER768_R3: *out = KYBER768_R3_CIPHERTEXT_BYTES; return true; + case SSL_GROUP_MLKEM768: + *out = MLKEM768_CIPHERTEXT_BYTES; + return true; case SSL_GROUP_X25519: *out = 32; return true; @@ -677,14 +684,20 @@ CONSTEXPR_ARRAY NamedGroup kNamedGroups[] = { {NID_X25519, SSL_GROUP_X25519, "X25519", "x25519"}, {NID_SecP256r1Kyber768Draft00, SSL_GROUP_SECP256R1_KYBER768_DRAFT00, "SecP256r1Kyber768Draft00", ""}, {NID_X25519Kyber768Draft00, SSL_GROUP_X25519_KYBER768_DRAFT00, "X25519Kyber768Draft00", ""}, + {NID_SecP256r1MLKEM768, SSL_GROUP_SECP256R1_MLKEM768, "SecP256r1MLKEM768", ""}, + {NID_X25519MLKEM768, SSL_GROUP_X25519_MLKEM768, "X25519MLKEM768", ""}, }; CONSTEXPR_ARRAY uint16_t kPQGroups[] = { SSL_GROUP_KYBER512_R3, SSL_GROUP_KYBER768_R3, SSL_GROUP_KYBER1024_R3, + SSL_GROUP_MLKEM768, + SSL_GROUP_MLKEM1024, SSL_GROUP_SECP256R1_KYBER768_DRAFT00, - SSL_GROUP_X25519_KYBER768_DRAFT00 + SSL_GROUP_X25519_KYBER768_DRAFT00, + SSL_GROUP_SECP256R1_MLKEM768, + SSL_GROUP_X25519_MLKEM768 }; CONSTEXPR_ARRAY HybridGroup kHybridGroups[] = { @@ -695,13 +708,30 @@ CONSTEXPR_ARRAY HybridGroup kHybridGroups[] = { SSL_GROUP_KYBER768_R3, // component_group_ids[1] }, }, - { SSL_GROUP_X25519_KYBER768_DRAFT00, // group_id { SSL_GROUP_X25519, // component_group_ids[0] SSL_GROUP_KYBER768_R3, // component_group_ids[1] }, + }, + + { + SSL_GROUP_SECP256R1_MLKEM768, // group_id + { + SSL_GROUP_SECP256R1, // component_group_ids[0] + SSL_GROUP_MLKEM768, // component_group_ids[1] + }, + }, + + { + SSL_GROUP_X25519_MLKEM768, // group_id + { + // Note: MLKEM768 is sent first due to FIPS requirements. + // For more details, see https://datatracker.ietf.org/doc/html/draft-kwiatkowski-tls-ecdhe-mlkem.html#section-3 + SSL_GROUP_MLKEM768, // component_group_ids[0] + SSL_GROUP_X25519, // component_group_ids[1] + }, } }; @@ -739,6 +769,14 @@ UniquePtr SSLKeyShare::Create(uint16_t group_id) { return MakeUnique(SSL_GROUP_SECP256R1_KYBER768_DRAFT00); case SSL_GROUP_X25519_KYBER768_DRAFT00: return MakeUnique(SSL_GROUP_X25519_KYBER768_DRAFT00); + case SSL_GROUP_MLKEM768: + // MLKEM768, as a standalone group, is not a NamedGroup; however, we + // need to create MLKEM768 key shares as part of hybrid groups. + return MakeUnique(NID_MLKEM768, SSL_GROUP_MLKEM768); + case SSL_GROUP_SECP256R1_MLKEM768: + return MakeUnique(SSL_GROUP_SECP256R1_MLKEM768); + case SSL_GROUP_X25519_MLKEM768: + return MakeUnique(SSL_GROUP_X25519_MLKEM768); default: return nullptr; } diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc index 6d2150d521..9e1fa5cfa6 100644 --- a/ssl/ssl_test.cc +++ b/ssl/ssl_test.cc @@ -48,6 +48,7 @@ #include "internal.h" #include "../crypto/kyber/kem_kyber.h" #include "../crypto/fipsmodule/ec/internal.h" +#include "../crypto/fipsmodule/ml_kem/ml_kem.h" #if defined(OPENSSL_WINDOWS) // Windows defines struct timeval in winsock2.h. @@ -635,6 +636,39 @@ static const CurveTest kCurveTests[] = { SSL_GROUP_SECP256R1, }, }, + { + "SecP256r1MLKEM768:prime256v1:secp384r1:secp521r1:x25519", + { + SSL_GROUP_SECP256R1_MLKEM768, + SSL_GROUP_SECP256R1, + SSL_GROUP_SECP384R1, + SSL_GROUP_SECP521R1, + SSL_GROUP_X25519, + }, + }, + { + "X25519MLKEM768:prime256v1:secp384r1", + { + SSL_GROUP_X25519_MLKEM768, + SSL_GROUP_SECP256R1, + SSL_GROUP_SECP384R1, + }, + }, + { + "X25519:X25519MLKEM768", + { + SSL_GROUP_X25519, + SSL_GROUP_X25519_MLKEM768, + }, + }, + { + "X25519:SecP256r1MLKEM768:prime256v1", + { + SSL_GROUP_X25519, + SSL_GROUP_SECP256R1_MLKEM768, + SSL_GROUP_SECP256R1, + }, + }, }; @@ -653,6 +687,13 @@ static const GroupTest kKemGroupTests[] = { KYBER768_R3_CIPHERTEXT_BYTES, KYBER_R3_SHARED_SECRET_LEN, }, + { + NID_MLKEM768, + SSL_GROUP_MLKEM768, + MLKEM768_PUBLIC_KEY_BYTES, + MLKEM768_CIPHERTEXT_BYTES, + MLKEM768_SHARED_SECRET_LEN, + }, }; static const HybridGroupTest kHybridGroupTests[] = { @@ -686,6 +727,40 @@ static const HybridGroupTest kHybridGroupTests[] = { KYBER768_R3_CIPHERTEXT_BYTES, // accept_share_sizes[1] }, }, + { + NID_SecP256r1MLKEM768, + SSL_GROUP_SECP256R1_MLKEM768, + P256_KEYSHARE_SIZE + MLKEM768_PUBLIC_KEY_BYTES, + P256_KEYSHARE_SIZE + MLKEM768_CIPHERTEXT_BYTES, + P256_SECRET_SIZE + MLKEM768_SHARED_SECRET_LEN, + { + P256_KEYSHARE_SIZE, // offer_share_sizes[0] + MLKEM768_PUBLIC_KEY_BYTES, // offer_share_sizes[1] + }, + { + P256_KEYSHARE_SIZE, // accept_share_sizes[0] + MLKEM768_CIPHERTEXT_BYTES, // accept_share_sizes[1] + }, + }, + { + NID_X25519MLKEM768, + SSL_GROUP_X25519_MLKEM768, + X25519_KEYSHARE_SIZE + MLKEM768_PUBLIC_KEY_BYTES, + X25519_KEYSHARE_SIZE + MLKEM768_CIPHERTEXT_BYTES, + X25519_SECRET_SIZE + MLKEM768_SHARED_SECRET_LEN, + { + // MLKEM768 is sent first for X25519MLKEM768 for FIPS compliance + // See: https://datatracker.ietf.org/doc/html/draft-kwiatkowski-tls-ecdhe-mlkem.html#section-3 + MLKEM768_PUBLIC_KEY_BYTES, // offer_share_sizes[0] + X25519_KEYSHARE_SIZE, // offer_share_sizes[1] + }, + { + // MLKEM768 is sent first for X25519MLKEM768 for FIPS compliance + // See: https://datatracker.ietf.org/doc/html/draft-kwiatkowski-tls-ecdhe-mlkem.html#section-3 + MLKEM768_CIPHERTEXT_BYTES, // accept_share_sizes[0] + X25519_KEYSHARE_SIZE, // accept_share_sizes[1] + }, + }, }; static const char *kBadCurvesLists[] = { @@ -699,6 +774,8 @@ static const char *kBadCurvesLists[] = { ":X25519:P-256", "kyber768_r3", "x25519_kyber768:prime256v1", + "mlkem768", + "x25519_mlkem768:prime256v1", }; static const HybridHandshakeTest kHybridHandshakeTests[] = { @@ -1014,6 +1091,318 @@ static const HybridHandshakeTest kHybridHandshakeTests[] = { SSL_GROUP_SECP256R1, false, }, + // The corresponding hybrid group should be negotiated when client + // and server support only that group + { + "X25519MLKEM768", + TLS1_3_VERSION, + "X25519MLKEM768", + TLS1_3_VERSION, + SSL_GROUP_X25519_MLKEM768, + false, + }, + + { + "SecP256r1MLKEM768", + TLS1_3_VERSION, + "SecP256r1MLKEM768", + TLS1_3_VERSION, + SSL_GROUP_SECP256R1_MLKEM768, + false, + }, + + // The client's preferred hybrid group should be negotiated when also + // supported by the server, even if the server "prefers"/supports other groups. + { + "X25519MLKEM768:x25519", + TLS1_3_VERSION, + "x25519:prime256v1:X25519MLKEM768", + TLS1_3_VERSION, + SSL_GROUP_X25519_MLKEM768, + false, + }, + + { + "X25519MLKEM768:x25519", + TLS1_3_VERSION, + "X25519MLKEM768:x25519", + TLS1_3_VERSION, + SSL_GROUP_X25519_MLKEM768, + false, + }, + + { + "SecP256r1MLKEM768", + TLS1_3_VERSION, + "X25519MLKEM768:secp384r1:x25519:SecP256r1MLKEM768", + TLS1_3_VERSION, + SSL_GROUP_SECP256R1_MLKEM768, + false, + }, + + // The client lists PQ/hybrid groups as both first and second preferences. + // The key share logic is implemented such that the client will always + // attempt to send one hybrid key share and one classical key share. + // Therefore, the client will send key shares [SecP256r1MLKEM768, x25519], + // skipping X25519MLKEM768, and the server will choose to negotiate + // x25519 since it is the only mutually supported group. + { + "SecP256r1MLKEM768:X25519MLKEM768:x25519", + TLS1_3_VERSION, + "secp384r1:x25519", + TLS1_3_VERSION, + SSL_GROUP_X25519, + false, + }, + + // The client will send key shares [x25519, SecP256r1MLKEM768]. + // The server will negotiate SecP256r1MLKEM768 since it is the only + // mutually supported group. + { + "x25519:secp384r1:SecP256r1MLKEM768", + TLS1_3_VERSION, + "SecP256r1MLKEM768:prime256v1", + TLS1_3_VERSION, + SSL_GROUP_SECP256R1_MLKEM768, + false, + }, + + // The client will send key shares [x25519, SecP256r1MLKEM768]. The + // server will negotiate x25519 since the client listed it as its first + // preference, even though it supports SecP256r1MLKEM768. + { + "x25519:prime256v1:SecP256r1MLKEM768", + TLS1_3_VERSION, + "prime256v1:x25519:SecP256r1MLKEM768", + TLS1_3_VERSION, + SSL_GROUP_X25519, + false, + }, + + // The client will send key shares [SecP256r1MLKEM768, x25519]. + // The server will negotiate SecP256r1MLKEM768 since the client listed + // it as its first preference. + { + "SecP256r1MLKEM768:x25519:prime256v1", + TLS1_3_VERSION, + "prime256v1:x25519:SecP256r1MLKEM768", + TLS1_3_VERSION, + SSL_GROUP_SECP256R1_MLKEM768, + false, + }, + + // In the supported_groups extension, the client will indicate its + // preferences, in order, as [SecP256r1MLKEM768, X25519MLKEM768, + // x25519, prime256v1]. From those groups, it will send key shares + // [SecP256r1MLKEM768, x25519]. The server supports, and receives a + // key share for, x25519. However, when selecting a mutually supported group + // to negotiate, the server recognizes that the client prefers + // X25519MLKEM768 over x25519. Since the server also supports + // X25519MLKEM768, but did not receive a key share for it, it will + // select it and send an HRR. This ensures that the client's highest + // preference group will be negotiated, even at the expense of an additional + // round-trip. + // + // In our SSL implementation, this situation is unique to the case where the + // client supports both ECC and hybrid/PQ. When sending key shares, the + // client will send at most two key shares in one of the following ways: + + // (a) one ECC key share - if the client supports only ECC; + // (b) one PQ key share - if the client supports only PQ; + // (c) one ECC and one PQ key share - if the client supports ECC and PQ. + // + // One of the above cases will be true irrespective of how many groups + // the client supports. If, say, the client supports four ECC groups + // and zero PQ groups, it will still only send a single ECC share. In cases + // (a) and (b), either the server supports that group and chooses to + // negotiate it, or it doesn't support it and sends an HRR. Case (c) is the + // only case where the server might receive a key share for a mutually + // supported group, but chooses to respect the client's preference order + // defined in the supported_groups extension at the expense of an additional + // round-trip. + { + "SecP256r1MLKEM768:X25519MLKEM768:x25519:prime256v1", + TLS1_3_VERSION, + "X25519MLKEM768:prime256v1:x25519", + TLS1_3_VERSION, + SSL_GROUP_X25519_MLKEM768, + true, + }, + + // Like the previous case, but the client's prioritization of ECC and PQ + // is inverted. + { + "x25519:prime256v1:SecP256r1MLKEM768:X25519MLKEM768", + TLS1_3_VERSION, + "X25519MLKEM768:prime256v1", + TLS1_3_VERSION, + SSL_GROUP_SECP256R1, + true, + }, + + // The client will send key shares [SecP256r1MLKEM768, x25519]. The + // server will negotiate X25519MLKEM768 after an HRR. + { + "SecP256r1MLKEM768:X25519MLKEM768:x25519:prime256v1", + TLS1_3_VERSION, + "X25519MLKEM768:prime256v1", + TLS1_3_VERSION, + SSL_GROUP_X25519_MLKEM768, + true, + }, + + // EC should be negotiated when client prefers EC, or server does not + // support hybrid + { + "X25519MLKEM768:x25519", + TLS1_3_VERSION, + "x25519", + TLS1_3_VERSION, + SSL_GROUP_X25519, + false, + }, + { + "x25519:SecP256r1MLKEM768", + TLS1_3_VERSION, + "x25519", + TLS1_3_VERSION, + SSL_GROUP_X25519, + false, + }, + { + "prime256v1:X25519MLKEM768", + TLS1_3_VERSION, + "X25519MLKEM768:prime256v1", + TLS1_3_VERSION, + SSL_GROUP_SECP256R1, + false, + }, + { + "prime256v1:x25519:SecP256r1MLKEM768", + TLS1_3_VERSION, + "x25519:prime256v1:SecP256r1MLKEM768", + TLS1_3_VERSION, + SSL_GROUP_SECP256R1, + false, + }, + + // EC should be negotiated, after a HelloRetryRequest, if the server + // supports only curves for which it did not initially receive a key share + { + "X25519MLKEM768:x25519:SecP256r1MLKEM768:prime256v1", + TLS1_3_VERSION, + "prime256v1", + TLS1_3_VERSION, + SSL_GROUP_SECP256R1, + true, + }, + { + "X25519MLKEM768:SecP256r1MLKEM768:prime256v1:x25519", + TLS1_3_VERSION, + "secp224r1:secp384r1:secp521r1:x25519", + TLS1_3_VERSION, + SSL_GROUP_X25519, + true, + }, + + // Hybrid should be negotiated, after a HelloRetryRequest, if the server + // supports only curves for which it did not initially receive a key share + { + "x25519:prime256v1:SecP256r1MLKEM768:X25519MLKEM768", + TLS1_3_VERSION, + "secp224r1:X25519MLKEM768:secp521r1", + TLS1_3_VERSION, + SSL_GROUP_X25519_MLKEM768, + true, + }, + { + "X25519MLKEM768:x25519:prime256v1:SecP256r1MLKEM768", + TLS1_3_VERSION, + "SecP256r1MLKEM768", + TLS1_3_VERSION, + SSL_GROUP_SECP256R1_MLKEM768, + true, + }, + + // If there is no overlap between client and server groups, + // the handshake should fail + { + "SecP256r1MLKEM768:X25519MLKEM768:secp384r1", + TLS1_3_VERSION, + "prime256v1:x25519", + TLS1_3_VERSION, + 0, + false, + }, + { + "secp384r1:SecP256r1MLKEM768:X25519MLKEM768", + TLS1_3_VERSION, + "prime256v1:x25519", + TLS1_3_VERSION, + 0, + false, + }, + { + "secp384r1:SecP256r1MLKEM768", + TLS1_3_VERSION, + "prime256v1:x25519:X25519MLKEM768", + TLS1_3_VERSION, + 0, + false, + }, + { + "SecP256r1MLKEM768", + TLS1_3_VERSION, + "X25519MLKEM768", + TLS1_3_VERSION, + 0, + false, + }, + + // If the client supports hybrid TLS 1.3, but the server + // only supports TLS 1.2, then TLS 1.2 EC should be negotiated. + { + "SecP256r1MLKEM768:prime256v1", + TLS1_3_VERSION, + "prime256v1:x25519", + TLS1_2_VERSION, + SSL_GROUP_SECP256R1, + false, + }, + + // Same as above, but server also has SecP256r1MLKEM768 in it's + // supported list, but can't use it since TLS 1.3 is the minimum version that + // supports PQ. + { + "SecP256r1MLKEM768:prime256v1", + TLS1_3_VERSION, + "SecP256r1MLKEM768:prime256v1:x25519", + TLS1_2_VERSION, + SSL_GROUP_SECP256R1, + false, + }, + + // If the client configures the curve list to include a hybrid + // curve, then initiates a 1.2 handshake, it will not advertise + // hybrid groups because hybrid is not supported for 1.2. So + // a 1.2 EC handshake will be negotiated (even if the server + // supports 1.3 with corresponding hybrid group). + { + "SecP256r1MLKEM768:x25519", + TLS1_2_VERSION, + "SecP256r1MLKEM768:x25519", + TLS1_3_VERSION, + SSL_GROUP_X25519, + false, + }, + { + "SecP256r1MLKEM768:prime256v1", + TLS1_2_VERSION, + "prime256v1:x25519", + TLS1_2_VERSION, + SSL_GROUP_SECP256R1, + false, + }, }; const HybridGroup* GetHybridGroup(uint16_t group_id){ @@ -12123,7 +12512,7 @@ TEST_P(BadHybridKeyShareAcceptTest, BadHybridKeyShareAccept) { ASSERT_TRUE(buffer); OPENSSL_memcpy(buffer, client_out_public_key_data, client_out_public_key_len); - for (size_t j = client_public_key_index; j < t.offer_share_sizes[i]; j++) { + for (size_t j = client_public_key_index; j < client_public_key_index + t.offer_share_sizes[i]; j++) { buffer[j] = 7; // 7 is arbitrary } Span client_public_key = @@ -12143,6 +12532,7 @@ TEST_P(BadHybridKeyShareAcceptTest, BadHybridKeyShareAccept) { // server ultimately arrived at different shared secrets. EXPECT_TRUE( hybrid_group->component_group_ids[i] == SSL_GROUP_KYBER768_R3 || + hybrid_group->component_group_ids[i] == SSL_GROUP_MLKEM768 || hybrid_group->component_group_ids[i] == SSL_GROUP_X25519 ); @@ -12390,7 +12780,7 @@ TEST_P(BadHybridKeyShareFinishTest, BadHybridKeyShareFinish) { uint8_t *buffer = (uint8_t *)OPENSSL_malloc(server_out_public_key_len); ASSERT_TRUE(buffer); OPENSSL_memcpy(buffer, server_out_public_key_data, server_out_public_key_len); - for (size_t j = server_public_key_index; j < t.accept_share_sizes[i]; j++) { + for (size_t j = server_public_key_index; j < server_public_key_index + t.accept_share_sizes[i]; j++) { buffer[j] = 7; // 7 is arbitrary } Span server_public_key = @@ -12410,6 +12800,7 @@ TEST_P(BadHybridKeyShareFinishTest, BadHybridKeyShareFinish) { // server ultimately arrived at different shared secrets. EXPECT_TRUE( hybrid_group->component_group_ids[i] == SSL_GROUP_KYBER768_R3 || + hybrid_group->component_group_ids[i] == SSL_GROUP_MLKEM768 || hybrid_group->component_group_ids[i] == SSL_GROUP_X25519 );