From f6fd661eaea9412160bef0be696d2b6880165a50 Mon Sep 17 00:00:00 2001 From: Bas Westerbaan Date: Thu, 20 Aug 2026 17:25:51 +0200 Subject: [PATCH 1/2] RTG-5132 Bump BoringSSL Most of our patches required work: 1. pq: (a) Upstream removed Kyber. We keep X25519Kyber768 and P256Kyber768 by implementing Kyber768 on top of ML-KEM-768. (b) Improve documentation of what's currently supported. 2. allow-crl-extensions-bad-version: Properly document what the patch actually does. Also improve its tests. 3. relax-cert-validation: Upstream removed enforce_rsa_key_usage; inline the behaviour instead. --- boring-sys/Cargo.toml | 10 +- boring-sys/build/main.rs | 2 +- boring-sys/deps/boringssl | 2 +- .../patches/bad-cert-verification.patch | 39 +- boring-sys/patches/boring-pq.patch | 629 ++++++++++-------- .../patches/relax-cert-validation.patch | 91 ++- boring-sys/patches/rpk.patch | 38 +- boring-sys/patches/underscore-wildcards.patch | 30 +- boring/src/lib.rs | 32 +- 9 files changed, 509 insertions(+), 364 deletions(-) diff --git a/boring-sys/Cargo.toml b/boring-sys/Cargo.toml index 009fff672..d9ba68bcb 100644 --- a/boring-sys/Cargo.toml +++ b/boring-sys/Cargo.toml @@ -63,7 +63,7 @@ rpk = [] # Require mlkem.h mlkem = [] -# Violate RFC 5280 5.1.2.1 and allow certs with CRL extensions in X.509 version 1. +# Violate RFC 5280 5.1.2.1 and allow extensions on X.509 version 1 CRLs. # This feature is not guaranteed to be supported in future minor releases. allow-crl-extensions-bad-version = [] @@ -80,9 +80,11 @@ underscore-wildcards = [] # 19 (see `include/openssl/base.h`) changed `enforce_rsa_key_usage` to # `true`, making a client-side RSA leaf whose keyUsage does not include the # bit required by the negotiated cipher suite a fatal handshake error -# (KEY_USAGE_BIT_INCORRECT). Enabling this feature applies a build-time -# patch that sets the default back to `false`, so RSA keyUsage mismatches -# are non-fatal. Non-RSA keyUsage enforcement is unaffected. +# (KEY_USAGE_BIT_INCORRECT). Upstream later removed the +# `enforce_rsa_key_usage` config field entirely, so enabling this feature +# applies a build-time patch that restores the conditional the flag used to +# guard, hardwired to the relaxed behaviour, making RSA keyUsage mismatches +# non-fatal again. Non-RSA keyUsage enforcement is unaffected. relax-cert-validation = [] [build-dependencies] diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index a6bb4bd47..2c391f33b 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -502,7 +502,7 @@ fn ensure_patches_applied(config: &Config) -> io::Result<()> { if config.features.allow_crl_extensions_bad_version { println!( - "cargo:warning=applying the patch for disabling cert version \ + "cargo:warning=applying the patch for disabling CRL version \ validation for extensions" ); apply_patch(config, "bad-cert-verification.patch")?; diff --git a/boring-sys/deps/boringssl b/boring-sys/deps/boringssl index e2a57cfb4..7c1efd8d6 160000 --- a/boring-sys/deps/boringssl +++ b/boring-sys/deps/boringssl @@ -1 +1 @@ -Subproject commit e2a57cfb4d915b4ba820585aef9fdee7bca13fe5 +Subproject commit 7c1efd8d6ffb36a57feba44e8c73cf674801f3cb diff --git a/boring-sys/patches/bad-cert-verification.patch b/boring-sys/patches/bad-cert-verification.patch index 7eb1f6c8b..bf6d21d3c 100644 --- a/boring-sys/patches/bad-cert-verification.patch +++ b/boring-sys/patches/bad-cert-verification.patch @@ -1,36 +1,36 @@ From fe0b517fa34063990a83268edf7a3cd9ba0b2362 Mon Sep 17 00:00:00 2001 From: Yuchen Wu Date: Mon, 13 Mar 2023 14:28:10 -0700 -Subject: [PATCH] PINGORA-474: disable cert version check for openssl +Subject: [PATCH] PINGORA-474: disable CRL version check for openssl compatibility -Some free customers use badly crafted cert (with verification off). -Openssl allows these cert but boring does't. +Some free customers use badly crafted CRLs (with verification off). +Openssl allows these CRLs but boring does't. --- - crypto/x509/x509_test.cc | 4 ++-- + crypto/x509/x509_test.cc | 4 +--- crypto/x509/x_crl.cc | 2 ++ - 2 files changed, 4 insertions(+), 2 deletions(-) + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc -index aa5bfda5d..15c1c73ee 100644 +index f98aa26cc..c881a4199 100644 --- a/crypto/x509/x509_test.cc +++ b/crypto/x509/x509_test.cc -@@ -3859,8 +3859,8 @@ TEST(X509Test, InvalidVersion) { - EXPECT_FALSE(CertFromPEM(kNegativeVersionPEM)); - EXPECT_FALSE(CertFromPEM(kFutureVersionPEM)); - EXPECT_FALSE(CertFromPEM(kOverflowVersionPEM)); -- EXPECT_FALSE(CertFromPEM(kV1WithExtensionsPEM)); -- EXPECT_FALSE(CertFromPEM(kV2WithExtensionsPEM)); -+ ASSERT_TRUE(CertFromPEM(kV1WithExtensionsPEM)); -+ ASSERT_TRUE(CertFromPEM(kV2WithExtensionsPEM)); - EXPECT_FALSE(CertFromPEM(kV1WithIssuerUniqueIDPEM)); +@@ -4473,9 +4473,7 @@ TEST(X509Test, InvalidVersion) { + EXPECT_TRUE(ErrorsAreAndClear({{ERR_LIB_ASN1, ASN1_R_DECODE_ERROR}})); EXPECT_FALSE(CertFromPEM(kV1WithSubjectUniqueIDPEM)); - EXPECT_FALSE(CRLFromPEM(kV1CRLWithExtensionsPEM)); + EXPECT_TRUE(ErrorsAreAndClear({{ERR_LIB_ASN1, ASN1_R_DECODE_ERROR}})); +- EXPECT_FALSE(CRLFromPEM(kV1CRLWithExtensionsPEM)); +- EXPECT_TRUE( +- ErrorsAreAndClear({{ERR_LIB_X509, X509_R_INVALID_FIELD_FOR_VERSION}})); ++ EXPECT_TRUE(CRLFromPEM(kV1CRLWithExtensionsPEM)); + EXPECT_FALSE(CRLFromPEM(kV1CRLWithEntryExtensionsPEM)); + EXPECT_TRUE(ErrorsAreAndClear({{ERR_LIB_X509, X509_R_INVALID_VERSION}})); + EXPECT_FALSE(CRLFromPEM(kV3CRLPEM)); diff --git a/crypto/x509/x_crl.cc b/crypto/x509/x_crl.cc -index 1d22ed638..4f50bc03f 100644 +index d30c17253..5af682ca9 100644 --- a/crypto/x509/x_crl.cc +++ b/crypto/x509/x_crl.cc -@@ -148,10 +148,12 @@ static int crl_cb(int operation, ASN1_VALUE **pval, const ASN1_ITEM *it, +@@ -157,10 +157,12 @@ static int crl_cb(int operation, ASN1_VALUE **pval, const ASN1_ITEM *it, } // Per RFC 5280, section 5.1.2.1, extensions require v2. @@ -43,6 +43,3 @@ index 1d22ed638..4f50bc03f 100644 // Extensions is a SEQUENCE SIZE (1..MAX), so it cannot be empty. An empty // extensions list is encoded by omitting the OPTIONAL field. --- -2.39.5 - diff --git a/boring-sys/patches/boring-pq.patch b/boring-sys/patches/boring-pq.patch index 7e7d92d1f..4a4c5c36e 100644 --- a/boring-sys/patches/boring-pq.patch +++ b/boring-sys/patches/boring-pq.patch @@ -16,26 +16,32 @@ This patch adds: non post-quantum and a post-quantum keyshare if available. These functions allow one to change the behaviour to only send a single keyshare. + +4. Restores X25519Kyber768Draft00 under 0x6399, removed upstream in + 188ce3c13. + +Upstream deleted its Kyber implementation, so both Kyber hybrids are now +built on ML-KEM-768, applying Kyber's key derivation +SHAKE-256(K || SHA3-256(c)) to the ML-KEM shared secret. See +https://words.filippo.io/mlkem768/#bonus-track-using-a-ml-kem-implementation-as-kyber-v3 --- crypto/obj/obj_dat.h | 6 +- crypto/obj/obj_mac.num | 1 + crypto/obj/objects.txt | 1 + include/openssl/nid.h | 3 + - include/openssl/ssl.h | 15 ++++ - ssl/extensions.cc | 26 ++++--- - ssl/internal.h | 12 ++- - ssl/ssl_key_share.cc | 111 +++++++++++++++++++++++++++- - ssl/ssl_lib.cc | 16 +++- - ssl/ssl_test.cc | 24 +++++- - ssl/test/runner/basic_tests.go | 2 + + include/openssl/ssl.h | 17 ++ + ssl/extensions.cc | 35 ++-- + ssl/internal.h | 12 +- + ssl/ssl_key_share.cc | 257 ++++++++++++++++++++++++++++ + ssl/ssl_lib.cc | 16 +- + ssl/ssl_test.cc | 31 +++- + ssl/test/fuzzer.h | 6 +- ssl/test/runner/cbc_tests.go | 3 + ssl/test/runner/common.go | 2 +- - ssl/test/runner/curve_tests.go | 28 +++---- - ssl/test/runner/ech_tests.go | 24 +++++- - ssl/test/runner/extension_tests.go | 3 +- - ssl/test/runner/key_update_tests.go | 6 +- - tool/client.cc | 9 +++ - 18 files changed, 249 insertions(+), 43 deletions(-) + ssl/test/runner/curve_tests.go | 16 ++ + ssl/test/runner/key_update_tests.go | 2 + + tool/client.cc | 9 + + 16 files changed, 393 insertions(+), 24 deletions(-) diff --git a/crypto/obj/obj_dat.h b/crypto/obj/obj_dat.h index feb8f2d1e..7ca28d9f5 100644 @@ -97,7 +103,7 @@ index 67b9ade43..384b25753 100644 # NIDs for PQ/T hybrid KEMs (no corresponding OIDs). diff --git a/include/openssl/nid.h b/include/openssl/nid.h -index 6b3bb4506..068fc5977 100644 +index 6b3bb4506..113f7653b 100644 --- a/include/openssl/nid.h +++ b/include/openssl/nid.h @@ -5511,6 +5511,9 @@ extern "C" { @@ -111,19 +117,29 @@ index 6b3bb4506..068fc5977 100644 #if defined(__cplusplus) } /* extern C */ diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h -index 055b3e025..09d6508f4 100644 +index 3b48c2a08..0dab3b037 100644 --- a/include/openssl/ssl.h +++ b/include/openssl/ssl.h -@@ -2586,6 +2586,7 @@ OPENSSL_EXPORT size_t SSL_CTX_get_num_tickets(const SSL_CTX *ctx); +@@ -2618,7 +2618,9 @@ OPENSSL_EXPORT size_t SSL_CTX_get_num_tickets(const SSL_CTX *ctx); + #define SSL_GROUP_SECP521R1 25 + #define SSL_GROUP_X25519 29 #define SSL_GROUP_X25519_MLKEM768 0x11ec - #define SSL_GROUP_X25519_KYBER768_DRAFT00 0x6399 ++#define SSL_GROUP_X25519_KYBER768_DRAFT00 0x6399 #define SSL_GROUP_MLKEM1024 0x0202 +#define SSL_GROUP_P256_KYBER768_DRAFT00 0xfe32 - // SSL_CTX_set1_group_ids sets the preferred groups for |ctx| to |group_ids|. - // Each element of |group_ids| should be a unique one of the |SSL_GROUP_*| -@@ -6241,6 +6242,20 @@ OPENSSL_EXPORT int SSL_CTX_set1_curves_list(SSL_CTX *ctx, const char *curves); - // SSL_set1_curves_list calls |SSL_set1_groups_list|. + // SSL_CTX_set1_group_ids sets the preferred groups for `ctx` to `group_ids`. + // Each element of `group_ids` should be a unique one of the `SSL_GROUP_*` +@@ -6274,6 +6276,7 @@ OPENSSL_EXPORT int SSL_CTX_set_tlsext_status_arg(SSL_CTX *ctx, void *arg); + #define SSL_CURVE_SECP384R1 SSL_GROUP_SECP384R1 + #define SSL_CURVE_SECP521R1 SSL_GROUP_SECP521R1 + #define SSL_CURVE_X25519 SSL_GROUP_X25519 ++#define SSL_CURVE_X25519_KYBER768_DRAFT00 SSL_GROUP_X25519_KYBER768_DRAFT00 + + // SSL_get_curve_id calls `SSL_get_group_id`. + OPENSSL_EXPORT uint16_t SSL_get_curve_id(const SSL *ssl); +@@ -6298,6 +6301,20 @@ OPENSSL_EXPORT int SSL_CTX_set1_curves_list(SSL_CTX *ctx, const char *curves); + // SSL_set1_curves_list calls `SSL_set1_groups_list`. OPENSSL_EXPORT int SSL_set1_curves_list(SSL *ssl, const char *curves); +// By default, a client will send both a non post-quantum and a post-quantum @@ -141,21 +157,30 @@ index 055b3e025..09d6508f4 100644 +OPENSSL_EXPORT void SSL_CTX_use_second_keyshare(SSL_CTX *ctx, int enabled); + // TLSEXT_nid_unknown is a constant used in OpenSSL for - // |SSL_get_negotiated_group| to return an unrecognized group. BoringSSL never + // `SSL_get_negotiated_group` to return an unrecognized group. BoringSSL never // returns this value, but we define this constant for compatibility. diff --git a/ssl/extensions.cc b/ssl/extensions.cc -index db1a29139..05c8c1e16 100644 +index 90238d912..56de76e37 100644 --- a/ssl/extensions.cc +++ b/ssl/extensions.cc -@@ -107,6 +107,7 @@ static bool tls1_check_duplicate_extensions(const CBS *cbs) { +@@ -107,7 +107,15 @@ static bool tls1_check_duplicate_extensions(const CBS *cbs) { + } + static bool is_post_quantum_group(uint16_t id) { - switch (id) { - case SSL_GROUP_X25519_KYBER768_DRAFT00: +- return id == SSL_GROUP_X25519_MLKEM768 || id == SSL_GROUP_MLKEM1024; ++ switch (id) { ++ case SSL_GROUP_X25519_KYBER768_DRAFT00: + case SSL_GROUP_P256_KYBER768_DRAFT00: - case SSL_GROUP_X25519_MLKEM768: - case SSL_GROUP_MLKEM1024: - return true; -@@ -2418,18 +2419,21 @@ bool ssl_setup_key_shares(SSL_HANDSHAKE *hs, uint16_t override_group_id) { ++ case SSL_GROUP_X25519_MLKEM768: ++ case SSL_GROUP_MLKEM1024: ++ return true; ++ default: ++ return false; ++ } + } + + bool ssl_parse_client_hello_with_trailing_data(const SSLImpl *ssl, CBS *cbs, +@@ -2464,18 +2472,21 @@ bool ssl_setup_key_shares(SSL_HANDSHAKE *hs, uint16_t override_group_id) { if (!default_key_shares.TryPushBack(supported_group_list[0])) { return false; } @@ -189,19 +214,19 @@ index db1a29139..05c8c1e16 100644 selected_key_shares.emplace(default_key_shares); } diff --git a/ssl/internal.h b/ssl/internal.h -index 2bea8f62d..47b688bf4 100644 +index 96f6d3d3d..c7a63f4b8 100644 --- a/ssl/internal.h +++ b/ssl/internal.h -@@ -913,7 +913,7 @@ struct NamedGroup { +@@ -918,7 +918,7 @@ struct NamedGroup { Span NamedGroups(); // kNumNamedGroups is the number of supported groups. --constexpr size_t kNumNamedGroups = 7u; +-constexpr size_t kNumNamedGroups = 6u; +constexpr size_t kNumNamedGroups = 8u; // DefaultSupportedGroupIds returns the list of IDs for the default groups that // are supported when the caller hasn't explicitly configured supported groups. -@@ -3531,6 +3531,11 @@ struct SSL_CONFIG { +@@ -3580,6 +3580,11 @@ struct SSL_CONFIG { // permute_extensions is whether to permute extensions when sending messages. bool permute_extensions : 1; @@ -213,7 +238,7 @@ index 2bea8f62d..47b688bf4 100644 // aes_hw_override if set indicates we should override checking for aes // hardware support, and use the value in aes_hw_override_value instead. bool aes_hw_override : 1; -@@ -4172,6 +4177,11 @@ struct ssl_ctx_st : public bssl::RefCounted { +@@ -4232,6 +4237,11 @@ class SSLContext : public ssl_ctx_st, public RefCounted { // permute_extensions is whether to permute extensions when sending messages. bool permute_extensions : 1; @@ -226,72 +251,135 @@ index 2bea8f62d..47b688bf4 100644 // protocols from the peer. bool allow_unknown_alpn_protos : 1; diff --git a/ssl/ssl_key_share.cc b/ssl/ssl_key_share.cc -index d155b5527..4fb08906b 100644 +index 84394b247..2f006e982 100644 --- a/ssl/ssl_key_share.cc +++ b/ssl/ssl_key_share.cc -@@ -193,6 +193,109 @@ class X25519KeyShare : public SSLKeyShare { +@@ -32,6 +32,7 @@ + #include + #include + ++#include "../crypto/fipsmodule/keccak/internal.h" + #include "../crypto/internal.h" + #include "internal.h" + +@@ -192,6 +193,253 @@ class X25519KeyShare : public SSLKeyShare { uint8_t private_key_[32]; }; ++// Kyber768 (round-3, "draft00") is implemented on top of ML-KEM-768. See ++// https://words.filippo.io/mlkem768/#bonus-track-using-a-ml-kem-implementation-as-kyber-v3 ++constexpr size_t kKyber768PublicKeyBytes = MLKEM768_PUBLIC_KEY_BYTES; ++constexpr size_t kKyber768CiphertextBytes = MLKEM768_CIPHERTEXT_BYTES; ++constexpr size_t kKyber768SharedSecretBytes = 32; ++ ++// kyber768_kdf applies Kyber's KDF(K || H(c)) to `mlkem_secret`, the shared ++// secret that ML-KEM produced for `ciphertext`. ++void kyber768_kdf(uint8_t out[kKyber768SharedSecretBytes], ++ const uint8_t mlkem_secret[MLKEM_SHARED_SECRET_BYTES], ++ Span ciphertext) { ++ uint8_t buf[MLKEM_SHARED_SECRET_BYTES + 32]; ++ OPENSSL_memcpy(buf, mlkem_secret, MLKEM_SHARED_SECRET_BYTES); ++ BORINGSSL_keccak(buf + MLKEM_SHARED_SECRET_BYTES, 32, ciphertext.data(), ++ ciphertext.size(), boringssl_sha3_256); ++ BORINGSSL_keccak(out, kKyber768SharedSecretBytes, buf, sizeof(buf), ++ boringssl_shake256); ++ OPENSSL_cleanse(buf, sizeof(buf)); ++} ++ ++// Kyber768KEM is a Kyber768 KEM built out of ML-KEM-768. On decapsulation ++// failure it derives from ML-KEM's implicit-rejection secret rather than ++// Kyber's. That output is unpredictable by design and is not an ++// interoperability target. ++class Kyber768KEM { ++ public: ++ void Generate(uint8_t out_public_key[kKyber768PublicKeyBytes]) { ++ MLKEM768_generate_key(out_public_key, /*optional_out_seed=*/nullptr, ++ &private_key_); ++ } ++ ++ bool Encap(uint8_t out_ciphertext[kKyber768CiphertextBytes], ++ uint8_t out_secret[kKyber768SharedSecretBytes], ++ Span peer_key) { ++ MLKEM768_public_key peer_public_key; ++ CBS cbs; ++ CBS_init(&cbs, peer_key.data(), peer_key.size()); ++ if (!MLKEM768_parse_public_key(&peer_public_key, &cbs)) { ++ return false; ++ } ++ ++ uint8_t mlkem_secret[MLKEM_SHARED_SECRET_BYTES]; ++ MLKEM768_encap(out_ciphertext, mlkem_secret, &peer_public_key); ++ kyber768_kdf(out_secret, mlkem_secret, ++ Span(out_ciphertext, kKyber768CiphertextBytes)); ++ OPENSSL_cleanse(mlkem_secret, sizeof(mlkem_secret)); ++ return true; ++ } ++ ++ bool Decap(uint8_t out_secret[kKyber768SharedSecretBytes], ++ Span ciphertext) { ++ uint8_t mlkem_secret[MLKEM_SHARED_SECRET_BYTES]; ++ if (!MLKEM768_decap(mlkem_secret, ciphertext.data(), ciphertext.size(), ++ &private_key_)) { ++ return false; ++ } ++ kyber768_kdf(out_secret, mlkem_secret, ciphertext); ++ OPENSSL_cleanse(mlkem_secret, sizeof(mlkem_secret)); ++ return true; ++ } ++ ++ private: ++ MLKEM768_private_key private_key_; ++}; ++ ++// P256Kyber768Draft00 is a hybrid of P-256 and Kyber768 under a private ++// codepoint. It is not standardised and is retained for compliance reasons. +class P256Kyber768Draft00KeyShare : public SSLKeyShare { + public: -+ P256Kyber768Draft00KeyShare() -+ : ecks_(EC_group_p256(), SSL_GROUP_SECP256R1) {} ++ P256Kyber768Draft00KeyShare() : ecks_(EC_group_p256(), SSL_GROUP_SECP256R1) {} + + uint16_t GroupID() const override { + return SSL_GROUP_P256_KYBER768_DRAFT00; + } + + bool Generate(CBB *out) override { -+ uint8_t kyber_public_key[KYBER_PUBLIC_KEY_BYTES]; -+ KYBER_generate_key(kyber_public_key, &kyber_private_key_); -+ -+ if(!ecks_.Generate(out) || -+ !CBB_add_bytes(out, kyber_public_key, sizeof(kyber_public_key))) { -+ return false; -+ } -+ -+ return true; ++ uint8_t kyber_public_key[kKyber768PublicKeyBytes]; ++ kyber_.Generate(kyber_public_key); ++ return ecks_.Generate(out) && ++ CBB_add_bytes(out, kyber_public_key, sizeof(kyber_public_key)); + } + + bool Encap(CBB *out_ciphertext, Array *out_secret, + uint8_t *out_alert, Span peer_key) override { -+ Array ec_secret; -+ + *out_alert = SSL_AD_INTERNAL_ERROR; + -+ if(peer_key.size() != p256_share_size + KYBER_PUBLIC_KEY_BYTES) { ++ if (peer_key.size() != kP256ShareSize + kKyber768PublicKeyBytes) { + *out_alert = SSL_AD_ILLEGAL_PARAMETER; + OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); + return false; + } + ++ Array ec_secret; + if (!ecks_.Encap(out_ciphertext, &ec_secret, out_alert, -+ peer_key.subspan(0, p256_share_size))) { ++ peer_key.subspan(0, kP256ShareSize))) { + return false; + } + -+ KYBER_public_key peer_kyber_pub; -+ CBS peer_kyber_cbs; -+ CBS_init(&peer_kyber_cbs, peer_key.data() + p256_share_size, -+ KYBER_PUBLIC_KEY_BYTES); ++ Array secret; ++ if (!secret.InitForOverwrite(kP256SecretSize + ++ kKyber768SharedSecretBytes)) { ++ return false; ++ } ++ OPENSSL_memcpy(secret.data(), ec_secret.data(), ec_secret.size()); + -+ if (!KYBER_parse_public_key(&peer_kyber_pub, &peer_kyber_cbs)) { ++ uint8_t kyber_ciphertext[kKyber768CiphertextBytes]; ++ if (!kyber_.Encap(kyber_ciphertext, secret.data() + kP256SecretSize, ++ peer_key.subspan(kP256ShareSize))) { + *out_alert = SSL_AD_ILLEGAL_PARAMETER; + OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); + return false; + } + -+ uint8_t kyber_ciphertext[KYBER_CIPHERTEXT_BYTES]; -+ Array secret; -+ if (!secret.InitForOverwrite(p256_secret_size + KYBER_SHARED_SECRET_BYTES)) { -+ return false; -+ } -+ OPENSSL_memcpy(secret.data(), ec_secret.data(), ec_secret.size()); -+ KYBER_encap(kyber_ciphertext, secret.data() + p256_secret_size, -+ &peer_kyber_pub); -+ -+ if(!CBB_add_bytes(out_ciphertext, kyber_ciphertext, ++ if (!CBB_add_bytes(out_ciphertext, kyber_ciphertext, + sizeof(kyber_ciphertext))) { + return false; + } @@ -304,46 +392,135 @@ index d155b5527..4fb08906b 100644 + Span ciphertext) override { + *out_alert = SSL_AD_INTERNAL_ERROR; + -+ Array ec_secret; -+ -+ if (ciphertext.size() != p256_share_size + KYBER_CIPHERTEXT_BYTES) { ++ if (ciphertext.size() != kP256ShareSize + kKyber768CiphertextBytes) { + *out_alert = SSL_AD_ILLEGAL_PARAMETER; + OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); + return false; + } + ++ Array ec_secret; + if (!ecks_.Decap(&ec_secret, out_alert, -+ ciphertext.subspan(0, p256_share_size))) { ++ ciphertext.subspan(0, kP256ShareSize))) { + return false; + } + + Array secret; -+ if (!secret.InitForOverwrite(p256_secret_size + KYBER_SHARED_SECRET_BYTES)) { ++ if (!secret.InitForOverwrite(kP256SecretSize + ++ kKyber768SharedSecretBytes)) { + return false; + } + OPENSSL_memcpy(secret.data(), ec_secret.data(), ec_secret.size()); -+ KYBER_decap(secret.data() + p256_secret_size, -+ ciphertext.data() + p256_share_size, &kyber_private_key_); ++ ++ if (!kyber_.Decap(secret.data() + kP256SecretSize, ++ ciphertext.subspan(kP256ShareSize))) { ++ *out_alert = SSL_AD_ILLEGAL_PARAMETER; ++ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); ++ return false; ++ } ++ + *out_secret = std::move(secret); + return true; + } + + private: ++ static constexpr size_t kP256ShareSize = 65; ++ static constexpr size_t kP256SecretSize = 32; ++ + ECKeyShare ecks_; -+ KYBER_private_key kyber_private_key_; ++ Kyber768KEM kyber_; ++}; ++ ++// draft-tls-westerbaan-xyber768d00-03 ++class X25519Kyber768KeyShare : public SSLKeyShare { ++ public: ++ X25519Kyber768KeyShare() {} ++ ++ uint16_t GroupID() const override { ++ return SSL_GROUP_X25519_KYBER768_DRAFT00; ++ } ++ ++ bool Generate(CBB *out) override { ++ uint8_t x25519_public_key[X25519_PUBLIC_VALUE_LEN]; ++ X25519_keypair(x25519_public_key, x25519_private_key_); ++ ++ uint8_t kyber_public_key[kKyber768PublicKeyBytes]; ++ kyber_.Generate(kyber_public_key); ++ ++ return CBB_add_bytes(out, x25519_public_key, sizeof(x25519_public_key)) && ++ CBB_add_bytes(out, kyber_public_key, sizeof(kyber_public_key)); ++ } ++ ++ bool Encap(CBB *out_ciphertext, Array *out_secret, ++ uint8_t *out_alert, Span peer_key) override { ++ *out_alert = SSL_AD_INTERNAL_ERROR; ++ ++ Array secret; ++ if (!secret.InitForOverwrite(X25519_SHARED_KEY_LEN + ++ kKyber768SharedSecretBytes)) { ++ return false; ++ } ++ ++ uint8_t x25519_public_key[X25519_PUBLIC_VALUE_LEN]; ++ X25519_keypair(x25519_public_key, x25519_private_key_); ++ ++ uint8_t kyber_ciphertext[kKyber768CiphertextBytes]; ++ if (peer_key.size() != X25519_PUBLIC_VALUE_LEN + kKyber768PublicKeyBytes || ++ !X25519(secret.data(), x25519_private_key_, peer_key.data()) || ++ !kyber_.Encap(kyber_ciphertext, secret.data() + X25519_SHARED_KEY_LEN, ++ peer_key.subspan(X25519_PUBLIC_VALUE_LEN))) { ++ *out_alert = SSL_AD_ILLEGAL_PARAMETER; ++ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); ++ return false; ++ } + -+ static constexpr size_t p256_share_size = 65; -+ static constexpr size_t p256_secret_size = 32; ++ if (!CBB_add_bytes(out_ciphertext, x25519_public_key, ++ sizeof(x25519_public_key)) || ++ !CBB_add_bytes(out_ciphertext, kyber_ciphertext, ++ sizeof(kyber_ciphertext))) { ++ return false; ++ } ++ ++ *out_secret = std::move(secret); ++ return true; ++ } ++ ++ bool Decap(Array *out_secret, uint8_t *out_alert, ++ Span ciphertext) override { ++ *out_alert = SSL_AD_INTERNAL_ERROR; ++ ++ Array secret; ++ if (!secret.InitForOverwrite(X25519_SHARED_KEY_LEN + ++ kKyber768SharedSecretBytes)) { ++ return false; ++ } ++ ++ if (ciphertext.size() != ++ X25519_PUBLIC_VALUE_LEN + kKyber768CiphertextBytes || ++ !X25519(secret.data(), x25519_private_key_, ciphertext.data()) || ++ !kyber_.Decap(secret.data() + X25519_SHARED_KEY_LEN, ++ ciphertext.subspan(X25519_PUBLIC_VALUE_LEN))) { ++ *out_alert = SSL_AD_ILLEGAL_PARAMETER; ++ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); ++ return false; ++ } ++ ++ *out_secret = std::move(secret); ++ return true; ++ } ++ ++ private: ++ uint8_t x25519_private_key_[X25519_PRIVATE_KEY_LEN]; ++ Kyber768KEM kyber_; +}; + - // draft-tls-westerbaan-xyber768d00-03 - class X25519Kyber768KeyShare : public SSLKeyShare { + // RFC 10024 + class X25519MLKEM768KeyShare : public SSLKeyShare { public: -@@ -441,9 +544,11 @@ constexpr NamedGroup kNamedGroups[] = { +@@ -350,8 +598,12 @@ constexpr NamedGroup kNamedGroups[] = { + {NID_secp384r1, SSL_GROUP_SECP384R1, "P-384", "secp384r1"}, {NID_secp521r1, SSL_GROUP_SECP521R1, "P-521", "secp521r1"}, {NID_X25519, SSL_GROUP_X25519, "X25519", "x25519"}, - {NID_X25519Kyber768Draft00, SSL_GROUP_X25519_KYBER768_DRAFT00, -- "X25519Kyber768Draft00", ""}, ++ {NID_X25519Kyber768Draft00, SSL_GROUP_X25519_KYBER768_DRAFT00, + "X25519Kyber768Draft00", "Xyber768D00"}, {NID_X25519MLKEM768, SSL_GROUP_X25519_MLKEM768, "X25519MLKEM768", ""}, {NID_ML_KEM_1024, SSL_GROUP_MLKEM1024, "MLKEM1024", ""}, @@ -352,17 +529,20 @@ index d155b5527..4fb08906b 100644 }; static_assert(std::size(kNamedGroups) == kNumNamedGroups, -@@ -455,6 +560,8 @@ Span NamedGroups() { return kNamedGroups; } - +@@ -364,6 +616,7 @@ Span NamedGroups() { return kNamedGroups; } Span DefaultSupportedGroupIds() { static const uint16_t kDefaultSupportedGroupIds[] = { -+ SSL_GROUP_X25519_MLKEM768, + SSL_GROUP_X25519_MLKEM768, + SSL_GROUP_P256_KYBER768_DRAFT00, SSL_GROUP_X25519, SSL_GROUP_SECP256R1, SSL_GROUP_SECP384R1, -@@ -478,6 +585,8 @@ UniquePtr SSLKeyShare::Create(uint16_t group_id) { +@@ -383,8 +636,12 @@ UniquePtr SSLKeyShare::Create(uint16_t group_id) { + return MakeUnique(); + case SSL_GROUP_X25519_MLKEM768: return MakeUnique(); ++ case SSL_GROUP_X25519_KYBER768_DRAFT00: ++ return MakeUnique(); case SSL_GROUP_MLKEM1024: return MakeUnique(); + case SSL_GROUP_P256_KYBER768_DRAFT00: @@ -371,26 +551,26 @@ index d155b5527..4fb08906b 100644 return nullptr; } diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc -index 89702eaaf..a7505841c 100644 +index a0c746b38..8c8b2bad4 100644 --- a/ssl/ssl_lib.cc +++ b/ssl/ssl_lib.cc -@@ -385,6 +385,7 @@ ssl_ctx_st::ssl_ctx_st(const SSL_METHOD *ssl_method) - channel_id_enabled(false), +@@ -394,6 +394,7 @@ bssl::SSLContext::SSLContext(const SSL_METHOD *ssl_method) grease_enabled(false), + grease_sigalgs_enabled(false), permute_extensions(false), + disable_second_keyshare(false), allow_unknown_alpn_protos(false), false_start_allowed_without_alpn(false), handoff(false), -@@ -517,6 +518,7 @@ SSL *SSL_new(SSL_CTX *ctx) { +@@ -527,6 +528,7 @@ SSL *SSL_new(SSL_CTX *ctx) { ssl->config->retain_only_sha256_of_client_certs = - ctx->retain_only_sha256_of_client_certs; - ssl->config->permute_extensions = ctx->permute_extensions; -+ ssl->config->disable_second_keyshare = ctx->disable_second_keyshare; - ssl->config->aes_hw_override = ctx->aes_hw_override; - ssl->config->aes_hw_override_value = ctx->aes_hw_override_value; - ssl->config->compliance_policy = ctx->compliance_policy; -@@ -582,6 +584,7 @@ SSL_CONFIG::SSL_CONFIG(SSL *ssl_arg) + ctx_impl->retain_only_sha256_of_client_certs; + ssl->config->permute_extensions = ctx_impl->permute_extensions; ++ ssl->config->disable_second_keyshare = ctx_impl->disable_second_keyshare; + ssl->config->aes_hw_override = ctx_impl->aes_hw_override; + ssl->config->aes_hw_override_value = ctx_impl->aes_hw_override_value; + ssl->config->compliance_policy = ctx_impl->compliance_policy; +@@ -593,6 +595,7 @@ SSL_CONFIG::SSL_CONFIG(SSLImpl *ssl_arg) jdk11_workaround(false), quic_use_legacy_codepoint(false), permute_extensions(false), @@ -398,23 +578,23 @@ index 89702eaaf..a7505841c 100644 alps_use_new_codepoint(true), server_padding_enabled(false) { assert(ssl); -@@ -3390,6 +3393,15 @@ int SSL_set1_curves_list(SSL *ssl, const char *curves) { +@@ -3529,6 +3532,15 @@ int SSL_set1_curves_list(SSL *ssl, const char *curves) { return SSL_set1_groups_list(ssl, curves); } +void SSL_use_second_keyshare(SSL *ssl, int enabled) { -+ ssl->config->disable_second_keyshare = !enabled; ++ FromOpaque(ssl)->config->disable_second_keyshare = !enabled; +} + +void SSL_CTX_use_second_keyshare(SSL_CTX *ctx, int enabled) { -+ ctx->disable_second_keyshare = !enabled; ++ FromOpaque(ctx)->disable_second_keyshare = !enabled; +} + + namespace fips202205 { // (References are to SP 800-52r2): -@@ -3401,7 +3413,9 @@ namespace fips202205 { +@@ -3540,7 +3552,9 @@ namespace fips202205 { // Section 3.3.1 // "The server shall be configured to only use cipher suites that are // composed entirely of NIST approved algorithms" @@ -426,10 +606,21 @@ index 89702eaaf..a7505841c 100644 static const uint16_t kSigAlgs[] = { SSL_SIGN_RSA_PKCS1_SHA256, diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc -index a91a6268c..3999ad61b 100644 +index fc185ec33..5e0af17cf 100644 --- a/ssl/ssl_test.cc +++ b/ssl/ssl_test.cc -@@ -522,6 +522,14 @@ static const CurveTest kCurveTests[] = { +@@ -495,6 +495,10 @@ static const CurveTest kCurveTests[] = { + "P-256", + {SSL_GROUP_SECP256R1}, + }, ++ { ++ "P-256:X25519Kyber768Draft00", ++ {SSL_GROUP_SECP256R1, SSL_GROUP_X25519_KYBER768_DRAFT00}, ++ }, + { + "P-256:X25519MLKEM768", + {SSL_GROUP_SECP256R1, SSL_GROUP_X25519_MLKEM768}, +@@ -507,6 +511,14 @@ static const CurveTest kCurveTests[] = { "MLKEM1024:X25519MLKEM768", {SSL_GROUP_MLKEM1024, SSL_GROUP_X25519_MLKEM768}, }, @@ -444,72 +635,72 @@ index a91a6268c..3999ad61b 100644 { "P-256:P-384:P-521:X25519", -@@ -683,7 +691,9 @@ TEST(SSLTest, CurveRules) { +@@ -686,8 +698,10 @@ TEST(SSLTest, CurveRules) { } TEST(SSLTest, DefaultCurves) { -- const uint16_t kDefaults[] = {SSL_GROUP_X25519, SSL_GROUP_SECP256R1, +- const uint16_t kDefaults[] = {SSL_GROUP_X25519_MLKEM768, SSL_GROUP_X25519, +- SSL_GROUP_SECP256R1, SSL_GROUP_SECP384R1}; + const uint16_t kDefaults[] = {SSL_GROUP_X25519_MLKEM768, + SSL_GROUP_P256_KYBER768_DRAFT00, + SSL_GROUP_X25519, SSL_GROUP_SECP256R1, - SSL_GROUP_SECP384R1}; ++ SSL_GROUP_SECP384R1}; // Test the group ID APIs. -@@ -1643,6 +1653,9 @@ static bool GetClientHello(SSL *ssl, std::vector *out) { - static size_t GetClientHelloLen(uint16_t max_version, uint16_t session_version, - size_t ticket_len) { + { +@@ -1670,7 +1684,8 @@ static size_t GetClientHelloLen(uint16_t max_version, uint16_t session_version, bssl::UniquePtr ctx(SSL_CTX_new(TLS_method())); -+ // RTG-3417 bas: we need to disable PQ here so that the small ClientHello -+ // padding tests properly tests things. -+ SSL_CTX_set1_curves_list(ctx.get(), "X25519"); - bssl::UniquePtr session = - CreateSessionWithTicket(session_version, ticket_len); - if (!ctx || !session) { -@@ -2317,7 +2330,10 @@ TEST(SSLTest, SetGroupIdsWithEqualPreference) { - // Test that the SSL group flags are defaulted to zero when zero groups are set + + // Reduce the number of supported groups, as we need ClientHellos smaller +- // than 254 bytes for SSLTest.Padding. ++ // than 254 bytes for SSLTest.Padding. This also keeps the post-quantum ++ // groups out of the ClientHello, which the padding tests require. + uint16_t groups[] = {SSL_GROUP_X25519, SSL_GROUP_SECP256R1, + SSL_GROUP_SECP384R1}; + SSL_CTX_set1_group_ids(ctx.get(), groups, sizeof(groups) / sizeof(*groups)); +@@ -2350,7 +2365,9 @@ TEST(SSLTest, SetGroupIdsWithEqualPreference) { // (i.e. using the default groups). TEST(SSLTest, SetGroupIdsWithFlags_DefaultGroups) { -- const uint16_t kDefaultGroups[] = {SSL_GROUP_X25519, SSL_GROUP_SECP256R1, -+ const uint16_t kDefaultGroups[] = {SSL_GROUP_X25519_MLKEM768, + const uint16_t kDefaultGroups[] = {SSL_GROUP_X25519_MLKEM768, +- SSL_GROUP_X25519, SSL_GROUP_SECP256R1, + SSL_GROUP_P256_KYBER768_DRAFT00, + SSL_GROUP_X25519, + SSL_GROUP_SECP256R1, SSL_GROUP_SECP384R1}; const uint32_t kBogusFlags[] = {SSL_GROUP_FLAG_EQUAL_PREFERENCE_WITH_NEXT, SSL_GROUP_FLAG_EQUAL_PREFERENCE_WITH_NEXT, 0}; -@@ -7112,7 +7128,9 @@ TEST(SSLTest, ApplyHandoffRemovesUnsupportedCurves) { +@@ -7209,8 +7226,10 @@ TEST(SSLTest, ApplyHandoffRemovesUnsupportedCurves) { // The default list of groups is used before applying the handoff. - EXPECT_THAT(server->config->supported_group_list, -- ElementsAreArray({SSL_GROUP_X25519, SSL_GROUP_SECP256R1, + EXPECT_THAT(FromOpaque(server.get())->config->supported_group_list, +- ElementsAreArray({SSL_GROUP_X25519_MLKEM768, SSL_GROUP_X25519, +- SSL_GROUP_SECP256R1, SSL_GROUP_SECP384R1})); + ElementsAreArray({SSL_GROUP_X25519_MLKEM768, + SSL_GROUP_P256_KYBER768_DRAFT00, + SSL_GROUP_X25519, SSL_GROUP_SECP256R1, - SSL_GROUP_SECP384R1})); ++ SSL_GROUP_SECP384R1})); ASSERT_TRUE(SSL_apply_handoff(server.get(), handoff)); - EXPECT_EQ(1u, server->config->supported_group_list.size()); -diff --git a/ssl/test/runner/basic_tests.go b/ssl/test/runner/basic_tests.go -index 54d49637b..5887c715a 100644 ---- a/ssl/test/runner/basic_tests.go -+++ b/ssl/test/runner/basic_tests.go -@@ -132,6 +132,7 @@ read alert 1 0 - `write hs 1 - read hs 3 - write hs 1 -+write hs 1 - read hs 2 - read hs 11 - read hs 12 -@@ -2003,6 +2004,7 @@ read alert 1 0 - write hs 2 - write hs 8 - write hs 11 -+write hs 11 - write hs 15 - write hs 20 - read hs 20 + EXPECT_EQ(1u, FromOpaque(server.get())->config->supported_group_list.size()); + EXPECT_EQ(SSL_GROUP_SECP256R1, +diff --git a/ssl/test/fuzzer.h b/ssl/test/fuzzer.h +index c3a37bf8e..dbc6d896f 100644 +--- a/ssl/test/fuzzer.h ++++ b/ssl/test/fuzzer.h +@@ -407,8 +407,10 @@ class TLSFuzzer { + } + + static const uint16_t kGroups[] = { +- SSL_GROUP_X25519_MLKEM768, SSL_GROUP_MLKEM1024, SSL_GROUP_X25519, +- SSL_GROUP_SECP256R1, SSL_GROUP_SECP384R1, SSL_GROUP_SECP521R1}; ++ SSL_GROUP_X25519_MLKEM768, SSL_GROUP_X25519_KYBER768_DRAFT00, ++ SSL_GROUP_MLKEM1024, SSL_GROUP_X25519, ++ SSL_GROUP_SECP256R1, SSL_GROUP_SECP384R1, ++ SSL_GROUP_SECP521R1}; + if (!SSL_CTX_set1_group_ids(ctx_.get(), kGroups, std::size(kGroups))) { + return false; + } diff --git a/ssl/test/runner/cbc_tests.go b/ssl/test/runner/cbc_tests.go -index 6f49d12af..5e970b2b5 100644 +index 6f49d12af..c17e3f277 100644 --- a/ssl/test/runner/cbc_tests.go +++ b/ssl/test/runner/cbc_tests.go @@ -14,6 +14,8 @@ @@ -525,12 +716,12 @@ index 6f49d12af..5e970b2b5 100644 "-partial-write", // BoringSSL disables 3DES by default. "-cipher", "ALL:3DES", -+ "-curves", strconv.Itoa(int(CurveX25519)), ++ "-curves", strconv.Itoa(int(CurveX25519)), }, }) } diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go -index c241c418e..9ffafa248 100644 +index 53fd5ea99..e21047412 100644 --- a/ssl/test/runner/common.go +++ b/ssl/test/runner/common.go @@ -2186,7 +2186,7 @@ type ProtocolBugs struct { @@ -543,151 +734,54 @@ index c241c418e..9ffafa248 100644 // ExpectKeyShares, if not nil, lists (in order) the curves that a ClientHello diff --git a/ssl/test/runner/curve_tests.go b/ssl/test/runner/curve_tests.go -index 956a73203..d04a1531d 100644 +index 54d02105e..6aa8fb181 100644 --- a/ssl/test/runner/curve_tests.go +++ b/ssl/test/runner/curve_tests.go -@@ -582,17 +582,6 @@ func addCurveTests() { - }) - } - -- // ML-KEM and Kyber should not be offered by default as a client. -- testCases = append(testCases, testCase{ -- name: "PostQuantumNotEnabledByDefaultInClients", -- config: Config{ -- MinVersion: VersionTLS13, -- Bugs: ProtocolBugs{ -- FailIfPostQuantumOffered: true, -- }, -- }, -- }) -- - for _, curve := range testCurves { - if !isMLKEMGroup(curve.id) { - continue -@@ -682,18 +671,19 @@ func addCurveTests() { - }) - } +@@ -698,6 +698,22 @@ func addCurveTests() { + }, + }) -- // As a server, ML-KEMs and Kyber are not yet supported by default. + // If ML-KEM is offered, both X25519 and ML-KEM should have a key-share. - testCases = append(testCases, testCase{ -- testType: serverTest, -- name: "PostQuantumNotEnabledByDefaultForAServer", ++ testCases = append(testCases, testCase{ + name: "NotJustMLKEMKeyShare", - config: Config{ -- MinVersion: VersionTLS13, -- CurvePreferences: []CurveID{CurveX25519MLKEM768, CurveMLKEM1024, CurveX25519Kyber768, CurveX25519}, -- DefaultCurves: []CurveID{CurveX25519MLKEM768, CurveMLKEM1024, CurveX25519Kyber768}, ++ config: Config{ + MinVersion: VersionTLS13, + Bugs: ProtocolBugs{ + ExpectedKeyShares: []CurveID{CurveX25519MLKEM768, CurveX25519}, + }, - }, - flags: []string{ -- "-server-preference", -- "-expect-curve-id", strconv.Itoa(int(CurveX25519)), ++ }, ++ flags: []string{ + "-curves", strconv.Itoa(int(CurveX25519MLKEM768)), + "-curves", strconv.Itoa(int(CurveX25519)), + "-expect-curve-id", strconv.Itoa(int(CurveX25519MLKEM768)), - }, - }) - -diff --git a/ssl/test/runner/ech_tests.go b/ssl/test/runner/ech_tests.go -index 2cd3c10d3..f19d8d20a 100644 ---- a/ssl/test/runner/ech_tests.go -+++ b/ssl/test/runner/ech_tests.go -@@ -451,7 +451,8 @@ func addEncryptedClientHelloTests() { - expectMsgCallback += clientAndServerHello - } - // EncryptedExtensions onwards. -- expectMsgCallback += `write hs 8 -+ if protocol != dtls { -+ expectMsgCallback += `write hs 8 - write hs 11 - write hs 15 - write hs 20 -@@ -462,6 +463,20 @@ write hs 4 - read ack - read ack - ` -+ } else { -+ expectMsgCallback += `write hs 8 -+write hs 11 -+write hs 11 -+write hs 15 -+write hs 20 -+read hs 20 -+write ack -+write hs 4 -+write hs 4 -+read ack -+read ack -+` -+ } - if protocol != dtls { - expectMsgCallback = strings.ReplaceAll(expectMsgCallback, "write ack\n", "") - expectMsgCallback = strings.ReplaceAll(expectMsgCallback, "read ack\n", "") -@@ -2349,8 +2364,11 @@ read ack - - // Test the message callback is correctly reported, with and without - // HelloRetryRequest. -- clientAndServerHello := "write clienthelloinner\nwrite hs 1\nread hs 2\n" -- clientAndServerHelloInitial := clientAndServerHello -+ clientAndServerHelloInitial := "write clienthelloinner\nwrite hs 1\nwrite hs 1\nread hs 2\n" -+ clientAndServerHello := "write clienthelloinner\nwrite hs 1\nread hs 2\n" -+ if protocol != dtls { -+ clientAndServerHelloInitial = clientAndServerHello -+ } - if protocol == tls { - clientAndServerHelloInitial += "write ccs\n" - } -diff --git a/ssl/test/runner/extension_tests.go b/ssl/test/runner/extension_tests.go -index ab9b0b02d..834da257f 100644 ---- a/ssl/test/runner/extension_tests.go -+++ b/ssl/test/runner/extension_tests.go -@@ -16,6 +16,7 @@ package runner - - import ( - "fmt" -+ "strconv" - ) - - func addExtensionTests() { -@@ -2001,7 +2002,7 @@ func addExtensionTests() { - // This hostname just needs to be long enough to push the - // ClientHello into F5's danger zone between 256 and 511 bytes - // long. -- flags: []string{"-host-name", "01234567890123456789012345678901234567890123456789012345678901234567890123456789.com"}, -+ flags: []string{"-host-name", "01234567890123456789012345678901234567890123456789012345678901234567890123456789.com", "-curves", strconv.Itoa(int(CurveX25519))}, - }) - - // Test that illegal extensions in TLS 1.3 are rejected by the client if ++ }, ++ }) ++ + // If two ML-KEMs are configured, only the preferred one's + // key share should be sent. + testCases = append(testCases, testCase{ diff --git a/ssl/test/runner/key_update_tests.go b/ssl/test/runner/key_update_tests.go -index f98528265..2068b1102 100644 +index 39ad24baf..f7335bb9a 100644 --- a/ssl/test/runner/key_update_tests.go +++ b/ssl/test/runner/key_update_tests.go -@@ -14,7 +14,10 @@ - - package runner +@@ -16,6 +16,7 @@ package runner --import "slices" -+import ( -+ "slices" + import ( + "slices" + "strconv" -+) + ) func addKeyUpdateTests() { - // TLS tests. -@@ -295,6 +298,7 @@ func addKeyUpdateTests() { +@@ -296,6 +297,7 @@ func addKeyUpdateTests() { }, }, shimSendsKeyUpdateBeforeRead: true, -+ flags: []string{"-curves", strconv.Itoa(int(CurveX25519))}, ++ flags: []string{"-curves", strconv.Itoa(int(CurveX25519))}, }) // Test that shim responds to KeyUpdate requests. diff --git a/tool/client.cc b/tool/client.cc -index 1653af8da..cab725d40 100644 +index 1f2b19965..6cf555433 100644 --- a/tool/client.cc +++ b/tool/client.cc @@ -156,6 +156,11 @@ static const struct argument kArguments[] = { @@ -713,6 +807,3 @@ index 1653af8da..cab725d40 100644 // Configure accepted roots. if (args_map.count("-root-certs") != 0) { if (!SSL_CTX_load_verify_locations( --- -2.50.1 (Apple Git-155) - diff --git a/boring-sys/patches/relax-cert-validation.patch b/boring-sys/patches/relax-cert-validation.patch index 884e5e57b..f880dd328 100644 --- a/boring-sys/patches/relax-cert-validation.patch +++ b/boring-sys/patches/relax-cert-validation.patch @@ -14,19 +14,80 @@ BoringSSL pin accepted them. There is no Rust API to relax this per connection. Restore the historical default (off) so RSA keyUsage mismatches are non-fatal again; non-RSA keyUsage enforcement is unaffected. + +Upstream removed the enforce_rsa_key_usage field in 39dc2564e, so this +now restores the conditional it used to guard instead of flipping a +default. --- -diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc -index 89702eaaf..4be64f8a5 100644 ---- a/ssl/ssl_lib.cc -+++ b/ssl/ssl_lib.cc -@@ -575,7 +575,7 @@ SSL_CONFIG::SSL_CONFIG(SSL *ssl_arg) - signed_cert_timestamps_enabled(false), - ocsp_stapling_enabled(false), - channel_id_enabled(false), -- enforce_rsa_key_usage(true), -+ enforce_rsa_key_usage(false), - retain_only_sha256_of_client_certs(false), - handoff(false), - shed_handshake_config(false), --- -2.39.5 + ssl/handshake_client.cc | 13 ++++++++++++- + ssl/test/runner/key_usage_tests.go | 15 +++++++++++---- + 2 files changed, 23 insertions(+), 5 deletions(-) + +diff --git a/ssl/handshake_client.cc b/ssl/handshake_client.cc +index a4a53cda6..950ac611d 100644 +--- a/ssl/handshake_client.cc ++++ b/ssl/handshake_client.cc +@@ -1402,11 +1402,22 @@ static enum ssl_hs_wait_t do_send_client_key_exchange(SSL_HANDSHAKE *hs) { + + // Check the key usage matches the cipher suite. Key usage is only checked + // for X.509 certs. (RPKs have no keyUsage to enforce.) ++ // ++ // BoringSSL enforces this for RSA leaves too, but many real-world origins ++ // serve RSA certificates whose keyUsage does not assert the bit that the ++ // negotiated cipher suite requires, and OpenSSL accepts them. Restore the ++ // historical, non-fatal behaviour for RSA only. Enforcement for non-RSA ++ // keys is unaffected; in particular it is still needed to distinguish ECDH ++ // certificates, which we do not support, from ECDSA certificates. ++ // See https://crbug.com/795089. + ssl_key_usage_t intended_use = (alg_k & SSL_kRSA) + ? key_usage_encipherment + : key_usage_digital_signature; + if (!ssl_cert_check_key_usage(&leaf_cbs, intended_use)) { +- return ssl_hs_error; ++ if (EVP_PKEY_id(hs->peer_pubkey.get()) != EVP_PKEY_RSA) { ++ return ssl_hs_error; ++ } ++ ERR_clear_error(); + } + } + +diff --git a/ssl/test/runner/key_usage_tests.go b/ssl/test/runner/key_usage_tests.go +index c9555066f..8b7d0bffc 100644 +--- a/ssl/test/runner/key_usage_tests.go ++++ b/ssl/test/runner/key_usage_tests.go +@@ -82,6 +82,15 @@ func addRSAKeyUsageTests() { + } + + for _, ver := range tlsVersions { ++ // The relax-cert-validation patch restores the historical behaviour of ++ // not enforcing RSA keyUsage on the client. That check only ever lived ++ // on the TLS 1.2-and-below code path, so TLS 1.3 still rejects these. ++ rsaClientEnforced := ver.version >= VersionTLS13 ++ rsaClientError := "" ++ if rsaClientEnforced { ++ rsaClientError = ":KEY_USAGE_BIT_INCORRECT:" ++ } ++ + testCases = append(testCases, testCase{ + testType: clientTest, + name: "RSAKeyUsage-Client-WantSignature-GotEncipherment-" + ver.name, +@@ -91,8 +100,8 @@ func addRSAKeyUsageTests() { + Credential: &encCert, + CipherSuites: dsSuites, + }, +- shouldFail: true, +- expectedError: ":KEY_USAGE_BIT_INCORRECT:", ++ shouldFail: rsaClientEnforced, ++ expectedError: rsaClientError, + }) + + testCases = append(testCases, testCase{ +@@ -128,8 +137,6 @@ func addRSAKeyUsageTests() { + Credential: &dsCert, + CipherSuites: encSuites, + }, +- shouldFail: true, +- expectedError: ":KEY_USAGE_BIT_INCORRECT:", + }) + } + diff --git a/boring-sys/patches/rpk.patch b/boring-sys/patches/rpk.patch index 5a97bbe20..e73c3a0dd 100644 --- a/boring-sys/patches/rpk.patch +++ b/boring-sys/patches/rpk.patch @@ -16,14 +16,14 @@ The new methods match the Rust bindings present in boring <= 5: --- include/openssl/ssl.h | 55 +++++++++++++++++++++++++++++++++++++++++++ ssl/ssl_credential.cc | 48 +++++++++++++++++++++++++++++++++++++ - ssl/ssl_lib.cc | 28 ++++++++++++++++++++++ - 3 files changed, 131 insertions(+) + ssl/ssl_lib.cc | 31 ++++++++++++++++++++++++ + 3 files changed, 134 insertions(+) diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h -index 055b3e025..9bfb82215 100644 +index 0dab3b037..4747aae96 100644 --- a/include/openssl/ssl.h +++ b/include/openssl/ssl.h -@@ -4027,6 +4027,61 @@ OPENSSL_EXPORT int SSL_get_peer_cert_type(const SSL *ssl); +@@ -4090,6 +4090,61 @@ OPENSSL_EXPORT int SSL_get_peer_cert_type(const SSL *ssl); OPENSSL_EXPORT EVP_PKEY *SSL_get0_peer_rpk(const SSL *ssl); @@ -86,10 +86,10 @@ index 055b3e025..9bfb82215 100644 // // Password Authenticated Key Exchange protocols allow client and server to diff --git a/ssl/ssl_credential.cc b/ssl/ssl_credential.cc -index b1090a212..5e2b2e17a 100644 +index d7317da80..448f78a90 100644 --- a/ssl/ssl_credential.cc +++ b/ssl/ssl_credential.cc -@@ -403,6 +403,54 @@ SSL_CREDENTIAL *SSL_CREDENTIAL_new_raw_public_key_custom( +@@ -440,6 +440,54 @@ SSL_CREDENTIAL *SSL_CREDENTIAL_new_raw_public_key_custom( return cred.release(); } @@ -145,41 +145,41 @@ index b1090a212..5e2b2e17a 100644 FromOpaque(cred)->UpRefInternal(); } diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc -index 89702eaaf..06c6eb1e6 100644 +index 8c8b2bad4..ade682944 100644 --- a/ssl/ssl_lib.cc +++ b/ssl/ssl_lib.cc -@@ -3804,3 +3804,31 @@ void SSL_set_server_padding_enabled(SSL *ssl, int enabled) { +@@ -3961,3 +3961,34 @@ void SSL_set_server_padding_enabled(SSL *ssl, int enabled) { int SSL_server_sent_requested_padding(const SSL *ssl) { - return ssl->s3->server_sent_requested_padding; + return FromOpaque(ssl)->s3->server_sent_requested_padding; } + +// The following functions are compatibility shims for the Rust bindings in +// boring-sys. They are not part of upstream BoringSSL. + +const EVP_PKEY *SSL_get0_peer_pubkey(const SSL *ssl) { -+ if (ssl->s3->hs == nullptr) { ++ const auto *ssl_impl = FromOpaque(ssl); ++ if (ssl_impl->s3->hs == nullptr) { + return nullptr; + } -+ return ssl->s3->hs->peer_pubkey.get(); ++ return ssl_impl->s3->hs->peer_pubkey.get(); +} + +void SSL_CTX_get0_accepted_peer_cert_types(const SSL_CTX *ctx, + const uint8_t **types, + size_t *types_len) { -+ *types = ctx->accepted_peer_cert_types.data(); -+ *types_len = ctx->accepted_peer_cert_types.size(); ++ const auto *ctx_impl = FromOpaque(ctx); ++ *types = ctx_impl->accepted_peer_cert_types.data(); ++ *types_len = ctx_impl->accepted_peer_cert_types.size(); +} + +void SSL_get0_accepted_peer_cert_types(const SSL *ssl, const uint8_t **types, + size_t *types_len) { -+ if (ssl->config == nullptr) { ++ const auto *ssl_impl = FromOpaque(ssl); ++ if (ssl_impl->config == nullptr) { + *types = nullptr; + *types_len = 0; + return; + } -+ *types = ssl->config->accepted_peer_cert_types.data(); -+ *types_len = ssl->config->accepted_peer_cert_types.size(); ++ *types = ssl_impl->config->accepted_peer_cert_types.data(); ++ *types_len = ssl_impl->config->accepted_peer_cert_types.size(); +} --- -2.50.1 (Apple Git-155) - diff --git a/boring-sys/patches/underscore-wildcards.patch b/boring-sys/patches/underscore-wildcards.patch index 20c97d3f5..5e02e5437 100644 --- a/boring-sys/patches/underscore-wildcards.patch +++ b/boring-sys/patches/underscore-wildcards.patch @@ -4,16 +4,16 @@ Date: Wed, 27 May 2026 13:59:54 -0700 Subject: [PATCH] Introduce X509_CHECK_FLAG_UNDERSCORE_WILDCARDS --- - crypto/x509/v3_utl.cc | 4 +++- - crypto/x509/x509_test.cc | 27 +++++++++++++++++++++++++++ - include/openssl/x509.h | 3 +++ - 3 files changed, 33 insertions(+), 1 deletion(-) + crypto/x509/v3_utl.cc | 4 +++- + crypto/x509/x509_test.cc | 27 +++++++++++++++++++++++++++ + include/openssl/x509.h | 4 ++++ + 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/crypto/x509/v3_utl.cc b/crypto/x509/v3_utl.cc -index 6605c5c0d..10fb91a63 100644 +index 765dfa634..ab45d3b13 100644 --- a/crypto/x509/v3_utl.cc +++ b/crypto/x509/v3_utl.cc -@@ -709,7 +709,9 @@ static int wildcard_match(const unsigned char *prefix, size_t prefix_len, +@@ -697,7 +697,9 @@ static int wildcard_match(const unsigned char *prefix, size_t prefix_len, // Check that the part matched by the wildcard contains only // permitted characters and only matches a single label. for (p = wildcard_start; p != wildcard_end; ++p) { @@ -25,10 +25,10 @@ index 6605c5c0d..10fb91a63 100644 } } diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc -index cc79b0901..e32dc575e 100644 +index f151a8dd8..278fc5481 100644 --- a/crypto/x509/x509_test.cc +++ b/crypto/x509/x509_test.cc -@@ -5778,6 +5778,33 @@ TEST(X509Test, Names) { +@@ -5871,6 +5871,33 @@ TEST(X509Test, Names) { /*invalid_emails=*/{}, /*flags=*/0, }, @@ -63,19 +63,17 @@ index cc79b0901..e32dc575e 100644 size_t i = 0; diff --git a/include/openssl/x509.h b/include/openssl/x509.h -index e72d9ca9b..6dba691c3 100644 +index d08e888b2..d4dc47643 100644 --- a/include/openssl/x509.h +++ b/include/openssl/x509.h -@@ -3372,6 +3372,9 @@ OPENSSL_EXPORT int X509_VERIFY_PARAM_add1_host(X509_VERIFY_PARAM *param, +@@ -3372,6 +3372,10 @@ OPENSSL_EXPORT int X509_VERIFY_PARAM_add1_host(X509_VERIFY_PARAM *param, // enabled when subjectAltNames is missing. #define X509_CHECK_FLAG_NEVER_CHECK_SUBJECT 0x20 -+// X509_CHECK_FLAG_UNDERSCORE_WILDCARDS allows underscores in DNS wildcard matches. ++// X509_CHECK_FLAG_UNDERSCORE_WILDCARDS allows underscores in DNS wildcard ++// matches. +#define X509_CHECK_FLAG_UNDERSCORE_WILDCARDS 0x40 + - // X509_VERIFY_PARAM_set_hostflags sets the name-checking flags on |param| to - // |flags|. |flags| should be a combination of |X509_CHECK_FLAG_*| constants. + // X509_VERIFY_PARAM_set_hostflags sets the name-checking flags on `param` to + // `flags`. `flags` should be a combination of `X509_CHECK_FLAG_*` constants. OPENSSL_EXPORT void X509_VERIFY_PARAM_set_hostflags(X509_VERIFY_PARAM *param, --- -2.50.1 (Apple Git-155) - diff --git a/boring/src/lib.rs b/boring/src/lib.rs index dc47c1f67..967a3c63e 100644 --- a/boring/src/lib.rs +++ b/boring/src/lib.rs @@ -13,7 +13,8 @@ //! //! ## BoringSSL version //! -//! By default, the crate aims to statically link with the latest BoringSSL master branch. +//! By default, the crate statically links against a pinned BoringSSL revision, vendored as a +//! submodule at `boring-sys/deps/boringssl`. The current pin is upstream tag `0.20260813.0`. //! *Note*: any BoringSSL revision bumps will be released as a major version update of all crates. //! //! # Compilation and linking options @@ -75,26 +76,21 @@ //! The crate can be compiled with [RawPublicKey](https://datatracker.ietf.org/doc/html/rfc7250) //! support by turning on `rpk` compilation feature. //! -//! ## Experimental post-quantum cryptography +//! ## Post-quantum cryptography //! -//! The crate can be compiled with [post-quantum cryptography](https://blog.cloudflare.com/post-quantum-for-all/) -//! support by turning on `post-quantum` compilation feature. +//! Upstream BoringSSL now supports and enables `X25519MLKEM768`, the recommended +//! post-quantum key agreement from RFC 10024, by default. The `pq-experimental` +//! feature is a no-op kept only for backwards compatibility. //! -//! Upstream BoringSSL support the post-quantum hybrid key agreement `X25519Kyber768Draft00`. Most -//! users should stick to that one for now. Enabling this feature, adds a few other post-quantum key -//! agreements: -//! -//! - `X25519MLKEM768` is the successor of `X25519Kyber768Draft00`. We expect servers to switch -//! before the end of 2024. -//! - `X25519Kyber768Draft00Old` is the same as `X25519Kyber768Draft00`, but under its old codepoint. -//! - `X25519Kyber512Draft00`. Similar to `X25519Kyber768Draft00`, but uses level 1 parameter set for -//! Kyber. Not recommended. It's useful to test whether the shorter ClientHello upsets fewer middle -//! boxes. -//! - `P256Kyber768Draft00`. Similar again to `X25519Kyber768Draft00`, but uses P256 as classical -//! part. It uses a non-standard codepoint. Not recommended. +//! We patch BpringSSL to additionally support + +//! - `X25519Kyber768Draft00`, under codepoint `0x6399`. This is the pre-standard predecessor of +//! `X25519MLKEM768`. Not recommended; it's being phased out. +//! - `P256Kyber768Draft00`, under codepoint `0xfe32`, which uses P-256 as the classical part. +//! In the past we needed this for compliance reasons. Not recommended; it's being phased out. //! -//! Presently all these key agreements are deployed by Cloudflare, but we do not guarantee continued -//! support for them. +//! Presently all these key agreements are deployed by Cloudflare. We only support `X25519MLKEM768` +//! long term. #[macro_use] extern crate bitflags; From 553d389cf309fdea5075e8fa2d2f731be67d96cc Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Fri, 28 Aug 2026 18:56:53 +0200 Subject: [PATCH 2/2] RTG-5132 Enable X25519MLKEM768 in FIPS compliance profile Enable X25519MLKEM768 in the fips202205 compliance profile, previously we added P256Kyber768Draft00 to it as well. Update commit message to reflect that X25519MLKEM768 is already enabled by default. Update prefix_symbols.h for the added `SSL_use_second_keyshare` API, otherwise `ninja run_tests` fails. Remove unnecessary fuzzer and test changes (`-curves`, comments). Remove NotJustMLKEMKeyShare case, this was a wrongly resolved merge conflict, NotJustMLKEMKeyShare-X25519MLKEM768 already covers it. --- boring-sys/patches/boring-pq.patch | 229 +++++++++++------------------ 1 file changed, 83 insertions(+), 146 deletions(-) diff --git a/boring-sys/patches/boring-pq.patch b/boring-sys/patches/boring-pq.patch index 4a4c5c36e..5a62bcdec 100644 --- a/boring-sys/patches/boring-pq.patch +++ b/boring-sys/patches/boring-pq.patch @@ -1,47 +1,43 @@ -From fc01a66ac146778c4e6651d6fc6d62c7e12bfb32 Mon Sep 17 00:00:00 2001 -From: Christopher Patton +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Bas Westerbaan Date: Wed, 27 May 2026 09:55:52 -0700 Subject: [PATCH] Add additional post-quantum key agreement This patch adds: -1. Enable X25519MLKEM768 by default. +1. Enable X25519MLKEM768 in the fips202205 compliance profile. Upstream + previously enabled it by default in commit e748facbaa5e. -2. Supports for P256Kyber768Draft00 under 0xfe32, which we temporarily +2. Supports for P256Kyber768Draft00 (0xfe32), which we temporarily need for compliance reasons. (Note that this is not the codepoint allocated for that exchange in the IANA table.) - Enables by default and in FIPS mode. + Enabled by default and in the fips202205 compliance profile. -3. Add SSL(_CTX)_use_second_keyshare. By default BoringSSL will send a - non post-quantum and a post-quantum keyshare if available. These - functions allow one to change the behaviour to only send a single - keyshare. +3. Add SSL(_CTX)_use_second_keyshare. By default BoringSSL clients send + both a PQ and non-PQ keyshare if permitted. These functions allow one + to change the behaviour to only send a single keyshare. For testing + purposes, add option `bssl client -disable-second-keyshare`. -4. Restores X25519Kyber768Draft00 under 0x6399, removed upstream in - 188ce3c13. - -Upstream deleted its Kyber implementation, so both Kyber hybrids are now -built on ML-KEM-768, applying Kyber's key derivation -SHAKE-256(K || SHA3-256(c)) to the ML-KEM shared secret. See -https://words.filippo.io/mlkem768/#bonus-track-using-a-ml-kem-implementation-as-kyber-v3 +4. Restore X25519Kyber768Draft00 (0x6399), removed upstream in 188ce3c1. + Upstream deleted its Kyber implementation, so both Kyber hybrids are + now built on ML-KEM-768, applying Kyber's key derivation + `SHAKE-256(K || SHA3-256(c))` to the ML-KEM shared secret. See + https://words.filippo.io/mlkem768/#bonus-track-using-a-ml-kem-implementation-as-kyber-v3 --- - crypto/obj/obj_dat.h | 6 +- - crypto/obj/obj_mac.num | 1 + - crypto/obj/objects.txt | 1 + - include/openssl/nid.h | 3 + - include/openssl/ssl.h | 17 ++ - ssl/extensions.cc | 35 ++-- - ssl/internal.h | 12 +- - ssl/ssl_key_share.cc | 257 ++++++++++++++++++++++++++++ - ssl/ssl_lib.cc | 16 +- - ssl/ssl_test.cc | 31 +++- - ssl/test/fuzzer.h | 6 +- - ssl/test/runner/cbc_tests.go | 3 + - ssl/test/runner/common.go | 2 +- - ssl/test/runner/curve_tests.go | 16 ++ - ssl/test/runner/key_update_tests.go | 2 + - tool/client.cc | 9 + - 16 files changed, 393 insertions(+), 24 deletions(-) + crypto/obj/obj_dat.h | 6 +- + crypto/obj/obj_mac.num | 1 + + crypto/obj/objects.txt | 1 + + include/openssl/nid.h | 3 + + include/openssl/prefix_symbols.h | 4 + + include/openssl/ssl.h | 17 ++ + ssl/extensions.cc | 35 ++- + ssl/internal.h | 12 +- + ssl/ssl_key_share.cc | 257 +++++++++++++++++++++ + ssl/ssl_lib.cc | 16 +- + ssl/ssl_test.cc | 28 ++- + ssl/test/runner/compliance_policy_tests.go | 2 + + tool/client.cc | 9 + + 13 files changed, 371 insertions(+), 20 deletions(-) diff --git a/crypto/obj/obj_dat.h b/crypto/obj/obj_dat.h index feb8f2d1e..7ca28d9f5 100644 @@ -116,6 +112,42 @@ index 6b3bb4506..113f7653b 100644 #if defined(__cplusplus) } /* extern C */ +diff --git a/include/openssl/prefix_symbols.h b/include/openssl/prefix_symbols.h +index 6e68c58d1..c9b51aafa 100644 +--- a/include/openssl/prefix_symbols.h ++++ b/include/openssl/prefix_symbols.h +@@ -2100,6 +2100,7 @@ + #pragma redefine_extname SSL_CTX_use_certificate_chain_file BORINGSSL_ADD_USER_LABEL_AND_PREFIX(SSL_CTX_use_certificate_chain_file) + #pragma redefine_extname SSL_CTX_use_certificate_file BORINGSSL_ADD_USER_LABEL_AND_PREFIX(SSL_CTX_use_certificate_file) + #pragma redefine_extname SSL_CTX_use_psk_identity_hint BORINGSSL_ADD_USER_LABEL_AND_PREFIX(SSL_CTX_use_psk_identity_hint) ++#pragma redefine_extname SSL_CTX_use_second_keyshare BORINGSSL_ADD_USER_LABEL_AND_PREFIX(SSL_CTX_use_second_keyshare) + #pragma redefine_extname SSL_ECH_KEYS_add BORINGSSL_ADD_USER_LABEL_AND_PREFIX(SSL_ECH_KEYS_add) + #pragma redefine_extname SSL_ECH_KEYS_free BORINGSSL_ADD_USER_LABEL_AND_PREFIX(SSL_ECH_KEYS_free) + #pragma redefine_extname SSL_ECH_KEYS_has_duplicate_config_id BORINGSSL_ADD_USER_LABEL_AND_PREFIX(SSL_ECH_KEYS_has_duplicate_config_id) +@@ -2449,6 +2450,7 @@ + #pragma redefine_extname SSL_use_certificate_ASN1 BORINGSSL_ADD_USER_LABEL_AND_PREFIX(SSL_use_certificate_ASN1) + #pragma redefine_extname SSL_use_certificate_file BORINGSSL_ADD_USER_LABEL_AND_PREFIX(SSL_use_certificate_file) + #pragma redefine_extname SSL_use_psk_identity_hint BORINGSSL_ADD_USER_LABEL_AND_PREFIX(SSL_use_psk_identity_hint) ++#pragma redefine_extname SSL_use_second_keyshare BORINGSSL_ADD_USER_LABEL_AND_PREFIX(SSL_use_second_keyshare) + #pragma redefine_extname SSL_used_hello_retry_request BORINGSSL_ADD_USER_LABEL_AND_PREFIX(SSL_used_hello_retry_request) + #pragma redefine_extname SSL_version BORINGSSL_ADD_USER_LABEL_AND_PREFIX(SSL_version) + #pragma redefine_extname SSL_want BORINGSSL_ADD_USER_LABEL_AND_PREFIX(SSL_want) +@@ -5237,6 +5239,7 @@ + #define SSL_CTX_use_certificate_chain_file BORINGSSL_ADD_PREFIX(SSL_CTX_use_certificate_chain_file) + #define SSL_CTX_use_certificate_file BORINGSSL_ADD_PREFIX(SSL_CTX_use_certificate_file) + #define SSL_CTX_use_psk_identity_hint BORINGSSL_ADD_PREFIX(SSL_CTX_use_psk_identity_hint) ++#define SSL_CTX_use_second_keyshare BORINGSSL_ADD_PREFIX(SSL_CTX_use_second_keyshare) + #define SSL_ECH_KEYS_add BORINGSSL_ADD_PREFIX(SSL_ECH_KEYS_add) + #define SSL_ECH_KEYS_free BORINGSSL_ADD_PREFIX(SSL_ECH_KEYS_free) + #define SSL_ECH_KEYS_has_duplicate_config_id BORINGSSL_ADD_PREFIX(SSL_ECH_KEYS_has_duplicate_config_id) +@@ -5586,6 +5589,7 @@ + #define SSL_use_certificate_ASN1 BORINGSSL_ADD_PREFIX(SSL_use_certificate_ASN1) + #define SSL_use_certificate_file BORINGSSL_ADD_PREFIX(SSL_use_certificate_file) + #define SSL_use_psk_identity_hint BORINGSSL_ADD_PREFIX(SSL_use_psk_identity_hint) ++#define SSL_use_second_keyshare BORINGSSL_ADD_PREFIX(SSL_use_second_keyshare) + #define SSL_used_hello_retry_request BORINGSSL_ADD_PREFIX(SSL_used_hello_retry_request) + #define SSL_version BORINGSSL_ADD_PREFIX(SSL_version) + #define SSL_want BORINGSSL_ADD_PREFIX(SSL_want) diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h index 3b48c2a08..0dab3b037 100644 --- a/include/openssl/ssl.h @@ -551,7 +583,7 @@ index 84394b247..2f006e982 100644 return nullptr; } diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc -index a0c746b38..8c8b2bad4 100644 +index a0c746b38..f4df3a0c6 100644 --- a/ssl/ssl_lib.cc +++ b/ssl/ssl_lib.cc @@ -394,6 +394,7 @@ bssl::SSLContext::SSLContext(const SSL_METHOD *ssl_method) @@ -600,13 +632,13 @@ index a0c746b38..8c8b2bad4 100644 // composed entirely of NIST approved algorithms" -static const uint16_t kGroups[] = {SSL_GROUP_SECP256R1, SSL_GROUP_SECP384R1}; +static const uint16_t kGroups[] = { -+ SSL_GROUP_P256_KYBER768_DRAFT00, ++ SSL_GROUP_X25519_MLKEM768, SSL_GROUP_P256_KYBER768_DRAFT00, + SSL_GROUP_SECP256R1, SSL_GROUP_SECP384R1}; static const uint16_t kSigAlgs[] = { SSL_SIGN_RSA_PKCS1_SHA256, diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc -index fc185ec33..5e0af17cf 100644 +index fc185ec33..a406724cb 100644 --- a/ssl/ssl_test.cc +++ b/ssl/ssl_test.cc @@ -495,6 +495,10 @@ static const CurveTest kCurveTests[] = { @@ -648,17 +680,7 @@ index fc185ec33..5e0af17cf 100644 // Test the group ID APIs. { -@@ -1670,7 +1684,8 @@ static size_t GetClientHelloLen(uint16_t max_version, uint16_t session_version, - bssl::UniquePtr ctx(SSL_CTX_new(TLS_method())); - - // Reduce the number of supported groups, as we need ClientHellos smaller -- // than 254 bytes for SSLTest.Padding. -+ // than 254 bytes for SSLTest.Padding. This also keeps the post-quantum -+ // groups out of the ClientHello, which the padding tests require. - uint16_t groups[] = {SSL_GROUP_X25519, SSL_GROUP_SECP256R1, - SSL_GROUP_SECP384R1}; - SSL_CTX_set1_group_ids(ctx.get(), groups, sizeof(groups) / sizeof(*groups)); -@@ -2350,7 +2365,9 @@ TEST(SSLTest, SetGroupIdsWithEqualPreference) { +@@ -2350,7 +2364,9 @@ TEST(SSLTest, SetGroupIdsWithEqualPreference) { // (i.e. using the default groups). TEST(SSLTest, SetGroupIdsWithFlags_DefaultGroups) { const uint16_t kDefaultGroups[] = {SSL_GROUP_X25519_MLKEM768, @@ -669,7 +691,7 @@ index fc185ec33..5e0af17cf 100644 SSL_GROUP_SECP384R1}; const uint32_t kBogusFlags[] = {SSL_GROUP_FLAG_EQUAL_PREFERENCE_WITH_NEXT, SSL_GROUP_FLAG_EQUAL_PREFERENCE_WITH_NEXT, 0}; -@@ -7209,8 +7226,10 @@ TEST(SSLTest, ApplyHandoffRemovesUnsupportedCurves) { +@@ -7209,8 +7225,10 @@ TEST(SSLTest, ApplyHandoffRemovesUnsupportedCurves) { // The default list of groups is used before applying the handoff. EXPECT_THAT(FromOpaque(server.get())->config->supported_group_list, @@ -682,104 +704,19 @@ index fc185ec33..5e0af17cf 100644 ASSERT_TRUE(SSL_apply_handoff(server.get(), handoff)); EXPECT_EQ(1u, FromOpaque(server.get())->config->supported_group_list.size()); EXPECT_EQ(SSL_GROUP_SECP256R1, -diff --git a/ssl/test/fuzzer.h b/ssl/test/fuzzer.h -index c3a37bf8e..dbc6d896f 100644 ---- a/ssl/test/fuzzer.h -+++ b/ssl/test/fuzzer.h -@@ -407,8 +407,10 @@ class TLSFuzzer { - } - - static const uint16_t kGroups[] = { -- SSL_GROUP_X25519_MLKEM768, SSL_GROUP_MLKEM1024, SSL_GROUP_X25519, -- SSL_GROUP_SECP256R1, SSL_GROUP_SECP384R1, SSL_GROUP_SECP521R1}; -+ SSL_GROUP_X25519_MLKEM768, SSL_GROUP_X25519_KYBER768_DRAFT00, -+ SSL_GROUP_MLKEM1024, SSL_GROUP_X25519, -+ SSL_GROUP_SECP256R1, SSL_GROUP_SECP384R1, -+ SSL_GROUP_SECP521R1}; - if (!SSL_CTX_set1_group_ids(ctx_.get(), kGroups, std::size(kGroups))) { - return false; - } -diff --git a/ssl/test/runner/cbc_tests.go b/ssl/test/runner/cbc_tests.go -index 6f49d12af..c17e3f277 100644 ---- a/ssl/test/runner/cbc_tests.go -+++ b/ssl/test/runner/cbc_tests.go -@@ -14,6 +14,8 @@ - - package runner - -+import "strconv" -+ - func addCBCPaddingTests() { - testCases = append(testCases, testCase{ - name: "MaxCBCPadding", -@@ -104,6 +106,7 @@ func addCBCSplittingTests() { - "-partial-write", - // BoringSSL disables 3DES by default. - "-cipher", "ALL:3DES", -+ "-curves", strconv.Itoa(int(CurveX25519)), - }, - }) - } -diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go -index 53fd5ea99..e21047412 100644 ---- a/ssl/test/runner/common.go -+++ b/ssl/test/runner/common.go -@@ -2186,7 +2186,7 @@ type ProtocolBugs struct { - FailIfHelloRetryRequested bool - - // FailIfPostQuantumOffered will cause a server to reject a ClientHello if -- // post-quantum curves are supported. -+ // post-quantum curves are not supported. - FailIfPostQuantumOffered bool - - // ExpectKeyShares, if not nil, lists (in order) the curves that a ClientHello -diff --git a/ssl/test/runner/curve_tests.go b/ssl/test/runner/curve_tests.go -index 54d02105e..6aa8fb181 100644 ---- a/ssl/test/runner/curve_tests.go -+++ b/ssl/test/runner/curve_tests.go -@@ -698,6 +698,22 @@ func addCurveTests() { - }, - }) - -+ // If ML-KEM is offered, both X25519 and ML-KEM should have a key-share. -+ testCases = append(testCases, testCase{ -+ name: "NotJustMLKEMKeyShare", -+ config: Config{ -+ MinVersion: VersionTLS13, -+ Bugs: ProtocolBugs{ -+ ExpectedKeyShares: []CurveID{CurveX25519MLKEM768, CurveX25519}, -+ }, -+ }, -+ flags: []string{ -+ "-curves", strconv.Itoa(int(CurveX25519MLKEM768)), -+ "-curves", strconv.Itoa(int(CurveX25519)), -+ "-expect-curve-id", strconv.Itoa(int(CurveX25519MLKEM768)), -+ }, -+ }) -+ - // If two ML-KEMs are configured, only the preferred one's - // key share should be sent. - testCases = append(testCases, testCase{ -diff --git a/ssl/test/runner/key_update_tests.go b/ssl/test/runner/key_update_tests.go -index 39ad24baf..f7335bb9a 100644 ---- a/ssl/test/runner/key_update_tests.go -+++ b/ssl/test/runner/key_update_tests.go -@@ -16,6 +16,7 @@ package runner - - import ( - "slices" -+ "strconv" - ) - - func addKeyUpdateTests() { -@@ -296,6 +297,7 @@ func addKeyUpdateTests() { - }, - }, - shimSendsKeyUpdateBeforeRead: true, -+ flags: []string{"-curves", strconv.Itoa(int(CurveX25519))}, - }) - - // Test that shim responds to KeyUpdate requests. +diff --git a/ssl/test/runner/compliance_policy_tests.go b/ssl/test/runner/compliance_policy_tests.go +index 7ccf636aa..837845f93 100644 +--- a/ssl/test/runner/compliance_policy_tests.go ++++ b/ssl/test/runner/compliance_policy_tests.go +@@ -148,6 +148,8 @@ func addCompliancePolicyTests() { + switch curve.id { + case CurveP256, CurveP384: + isFIPSCurve = true ++ case CurveX25519MLKEM768: ++ isFIPSCurve = true + } + + var isWPACurve bool diff --git a/tool/client.cc b/tool/client.cc index 1f2b19965..6cf555433 100644 --- a/tool/client.cc