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;