diff --git a/boring-sys/Cargo.toml b/boring-sys/Cargo.toml index 1904a15f4..c61a695cc 100644 --- a/boring-sys/Cargo.toml +++ b/boring-sys/Cargo.toml @@ -75,6 +75,16 @@ allow-crl-extensions-bad-version = [] # `BORING_BSSL{,_FIPS}_SOURCE_PATH`. underscore-wildcards = [] +# Restores the historical BoringSSL default of not enforcing RSA keyUsage +# during TLS handshakes. BoringSSL starting with `BORINGSSL_API_VERSION` +# 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. +relax-rsa-key-usage = [] + [build-dependencies] bindgen = { workspace = true } cmake = { workspace = true } diff --git a/boring-sys/build/config.rs b/boring-sys/build/config.rs index 1fb5ac2b8..04a2819dc 100644 --- a/boring-sys/build/config.rs +++ b/boring-sys/build/config.rs @@ -22,6 +22,7 @@ pub(crate) struct Features { pub(crate) rpk: bool, pub(crate) underscore_wildcards: bool, pub(crate) allow_crl_extensions_bad_version: bool, + pub(crate) relax_rsa_key_usage: bool, } pub(crate) struct Env { @@ -114,7 +115,9 @@ impl Config { ); } - let features_with_patches_enabled = self.features.rpk || self.features.underscore_wildcards; + let features_with_patches_enabled = self.features.rpk + || self.features.underscore_wildcards + || self.features.relax_rsa_key_usage; let patches_required = features_with_patches_enabled && !self.env.assume_patched; @@ -138,6 +141,7 @@ impl Features { rpk: cfg!(feature = "rpk"), underscore_wildcards: cfg!(feature = "underscore-wildcards"), allow_crl_extensions_bad_version: cfg!(feature = "allow-crl-extensions-bad-version"), + relax_rsa_key_usage: cfg!(feature = "relax-rsa-key-usage"), } } diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index 509f563c0..2b17a058f 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -444,12 +444,14 @@ fn ensure_patches_applied(config: &Config) -> io::Result<()> { ); return Ok(()); } else if config.env.source_path.is_some() - && (config.features.rpk || config.features.underscore_wildcards) + && (config.features.rpk + || config.features.underscore_wildcards + || config.features.relax_rsa_key_usage) { panic!( "BORING_BSSL_ASSUME_PATCHED must be set when setting BORING_BSSL_SOURCE_PATH and using any of the following - features: rpk, underscore-wildcards" + features: rpk, underscore-wildcards, relax-rsa-key-usage" ); } @@ -485,6 +487,11 @@ fn ensure_patches_applied(config: &Config) -> io::Result<()> { apply_patch(config, "underscore-wildcards.patch")?; } + if config.features.relax_rsa_key_usage { + println!("cargo:warning=applying RSA key-usage enforcement relaxation patch"); + apply_patch(config, "relax-rsa-key-usage-enforcement.patch")?; + } + Ok(()) } diff --git a/boring-sys/patches/relax-rsa-key-usage-enforcement.patch b/boring-sys/patches/relax-rsa-key-usage-enforcement.patch new file mode 100644 index 000000000..884e5e57b --- /dev/null +++ b/boring-sys/patches/relax-rsa-key-usage-enforcement.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Kevin Bartlett Guthrie +Date: Wed, 10 Jun 2026 14:00:00 -0400 +Subject: [PATCH] Default enforce_rsa_key_usage to off + +BoringSSL starting with BORINGSSL_API_VERSION 19 (see +include/openssl/base.h) flipped the SSL_CONFIG default for +enforce_rsa_key_usage to true. As a client, this makes an RSA leaf whose +keyUsage does not assert the bit required by the cipher suite a fatal +handshake error (KEY_USAGE_BIT_INCORRECT) instead of the historical +non-fatal behaviour. +Many real upstream origins serve such certs; OpenSSL and the prior +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. +--- +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 diff --git a/boring/Cargo.toml b/boring/Cargo.toml index 0aa34592f..c77bf96ad 100644 --- a/boring/Cargo.toml +++ b/boring/Cargo.toml @@ -52,6 +52,10 @@ rpk = ["credential", "boring-sys/rpk"] # `BORING_BSSL{,_FIPS}_ASSUME_PATCHED`. underscore-wildcards = ["boring-sys/underscore-wildcards"] +# Restores the historical BoringSSL default of not enforcing RSA keyUsage +# during TLS handshakes. +relax-rsa-key-usage = ["boring-sys/relax-rsa-key-usage"] + # **DO NOT USE** This will be removed without warning in future releases. # Alias for 'fips', only for backwards compatibility. fips-precompiled = ["fips"] diff --git a/boring/src/ssl/test/verify.rs b/boring/src/ssl/test/verify.rs index b7981e68c..6c7015ec4 100644 --- a/boring/src/ssl/test/verify.rs +++ b/boring/src/ssl/test/verify.rs @@ -1,6 +1,6 @@ use super::server::Server; use crate::hash::MessageDigest; -use crate::ssl::SslVerifyMode; +use crate::ssl::{SslFiletype, SslOptions, SslVerifyMode, SslVersion}; use crate::x509::store::X509StoreBuilder; use crate::x509::X509; use hex; @@ -148,3 +148,68 @@ fn ssl_callback() { client.connect(); assert!(CALLED_BACK.load(Ordering::SeqCst)); } + +/// Verifies that an RSA leaf certificate whose keyUsage extension lacks the +/// bit required by the negotiated cipher suite is rejected when BoringSSL +/// enforces RSA keyUsage and accepted when enforcement is relaxed. +/// +/// The test certificates live in `boring/test/rsa-key-usage-relaxed*.pem` and +/// were generated by `boring/test/generate-rsa-key-usage-relaxed-certs.sh`. +/// That script creates a leaf cert with `keyUsage = keyEncipherment` only (no +/// `digitalSignature`). Pinning the handshake to TLS 1.2 + +/// `ECDHE-RSA-AES128-GCM-SHA256` makes the required bit `digitalSignature`, so +/// the connection fails with `KEY_USAGE_BIT_INCORRECT` unless the +/// `relax-rsa-key-usage` feature is enabled. +#[test] +fn rsa_key_usage_mismatch() { + const CIPHER: &str = "ECDHE-RSA-AES128-GCM-SHA256"; + + let mut server = Server::builder(); + server + .ctx() + .set_certificate_chain_file("test/rsa-key-usage-relaxed.pem") + .unwrap(); + server + .ctx() + .set_private_key_file("test/rsa-key-usage-relaxed-key.pem", SslFiletype::PEM) + .unwrap(); + server.ctx().set_options(SslOptions::NO_TLSV1_3); + server.ctx().set_cipher_list(CIPHER).unwrap(); + server + .ctx() + .set_min_proto_version(Some(SslVersion::TLS1_2)) + .unwrap(); + server + .ctx() + .set_max_proto_version(Some(SslVersion::TLS1_2)) + .unwrap(); + + let configure_client = |server: &Server| { + let mut client = server.client(); + client.ctx().set_verify(SslVerifyMode::PEER); + client + .ctx() + .set_ca_file("test/rsa-key-usage-relaxed-ca.pem") + .unwrap(); + client.ctx().set_options(SslOptions::NO_TLSV1_3); + client.ctx().set_cipher_list(CIPHER).unwrap(); + client + .ctx() + .set_min_proto_version(Some(SslVersion::TLS1_2)) + .unwrap(); + client + .ctx() + .set_max_proto_version(Some(SslVersion::TLS1_2)) + .unwrap(); + client + }; + + if cfg!(feature = "relax-rsa-key-usage") { + let server = server.build(); + configure_client(&server).connect(); + } else { + server.should_error(); + let server = server.build(); + configure_client(&server).connect_err(); + } +} diff --git a/boring/test/generate-rsa-key-usage-relaxed-certs.sh b/boring/test/generate-rsa-key-usage-relaxed-certs.sh new file mode 100755 index 000000000..bdc2a5b0b --- /dev/null +++ b/boring/test/generate-rsa-key-usage-relaxed-certs.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# Generates the certificates used by `boring/src/ssl/test/verify.rs` for the +# `rsa_key_usage_mismatch` test. +# +# The resulting leaf certificate has an RSA key and a keyUsage extension that +# only asserts keyEncipherment (no digitalSignature). When BoringSSL enforces +# RSA keyUsage, an ECDHE-RSA cipher suite therefore fails with +# KEY_USAGE_BIT_INCORRECT. When enforcement is relaxed, the handshake succeeds. +set -euo pipefail + +cd "$(dirname "$0")" +NAME=rsa-key-usage-relaxed + +# CA key and self-signed certificate. The CA key is deleted at the end; only +# the CA certificate is needed by the test. +openssl genrsa -out "${NAME}-ca-key.pem" 2048 +openssl req -new -x509 \ + -key "${NAME}-ca-key.pem" \ + -out "${NAME}-ca.pem" \ + -days 3650 \ + -subj "/CN=Relax RSA Key Usage Test CA" \ + -addext "basicConstraints=critical,CA:TRUE" \ + -addext "keyUsage=critical,keyCertSign,cRLSign" + +# Leaf key and certificate request. +openssl genrsa -out "${NAME}-key.pem" 2048 +openssl req -new \ + -key "${NAME}-key.pem" \ + -out "${NAME}.csr" \ + -subj "/CN=localhost" + +# Leaf certificate with keyUsage=keyEncipherment only. The missing +# digitalSignature bit is what triggers the RSA keyUsage check failure. +openssl x509 -req \ + -in "${NAME}.csr" \ + -CA "${NAME}-ca.pem" \ + -CAkey "${NAME}-ca-key.pem" \ + -CAcreateserial \ + -out "${NAME}.pem" \ + -days 365 \ + -extfile <(printf '%s\n' \ + 'keyUsage=critical,keyEncipherment' \ + 'extendedKeyUsage=serverAuth' \ + 'subjectAltName=DNS:localhost') + +rm -f "${NAME}.csr" "${NAME}.srl" "${NAME}-ca-key.pem" diff --git a/boring/test/rsa-key-usage-relaxed-ca.pem b/boring/test/rsa-key-usage-relaxed-ca.pem new file mode 100644 index 000000000..3afe33b9d --- /dev/null +++ b/boring/test/rsa-key-usage-relaxed-ca.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDPTCCAiWgAwIBAgIUVyjdxXDX1AIoOdOTwFCvxMZtiikwDQYJKoZIhvcNAQEL +BQAwJjEkMCIGA1UEAwwbUmVsYXggUlNBIEtleSBVc2FnZSBUZXN0IENBMB4XDTI2 +MDYyNjE4NDMxMloXDTM2MDYyMzE4NDMxMlowJjEkMCIGA1UEAwwbUmVsYXggUlNB +IEtleSBVc2FnZSBUZXN0IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEA50Yu0DxDUJZ5G+nGy5QXajf7m6z/IlJgmz8lcmNiRmiIGMMdN9HMyLe4Mljk +qOl2aNfqLsSFeXg9KamhoScnQatQl5o4cf5kTePOYk9SkYuK86bcfHFJQLcPAjb4 +XBvEHnpCr2LpPFGVGuOlG7d1I7zJ0Fz9a6hThjN2+1Nl7qnwLZM1f+45oD9bBmno +MObfLQDIC5Ds0pE0Iv+jPPoQqTzhkwRdsSWiSXojmuWsrS2kW7B/OUHxnB8zlEMZ +S2GoECKVEugRJUwXayyaJac9ygL895M9S49Ikvth18eyfmSu+A8usMG2PEklGCa2 +Yz0ij3P5KmgEXnHHuIydsxITbQIDAQABo2MwYTAdBgNVHQ4EFgQUj8ngJAk3fVY/ +KHlgDC2AFB1Kz5gwHwYDVR0jBBgwFoAUj8ngJAk3fVY/KHlgDC2AFB1Kz5gwDwYD +VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggEB +AJStEky4C9/28O8MQz7+MaupQn7MORVEWnYm2+afET0O6S+sLmqU6dEuzPXAxZit +x72Hw+g3i3XHwPtr2+WOpppTSA4fWuQzntcTaJErs4nImP9oVDs7wACaR2ITMuQ9 +jIwdBhaO3D1RBpKLsKYNyDbhvrprIscZBGxmmqOZJpcejsNCUwAAi3qcmGloZUJb +kX/+c3mYN5qGw62X2nQhTmm4sGfe4hRloAoaPFmezF+9HIKIYQ92gXRDEgDEImbj +39pqNdWfJ6zpWWrqqRH2il93BZ3qFk8904Z+3WBxKYhDJ0f8WBA4ECV4LCWaRL7t +RMbshIkpD5rfFuIjx1/ApuQ= +-----END CERTIFICATE----- diff --git a/boring/test/rsa-key-usage-relaxed-key.pem b/boring/test/rsa-key-usage-relaxed-key.pem new file mode 100644 index 000000000..32b1f025d --- /dev/null +++ b/boring/test/rsa-key-usage-relaxed-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCtBcpDMwAouuCD +SdO4rtWQkT/5IImPGLIX0qofIZq54eur64RPnh7FqbI+G87iD8/xXaEBup84fhT9 +yvsvxMDFYd06CUsrEcTEy3bfDHtfCo7dnELCj2A7tq5baxru2qASvqYCGEGeBiqM +qETjAn3BnsU1BHUCGaGA9bVnpwUEoKaBaFplXSonQnphdiXzad3ilIpGcWmJU/8i +z+NzgJrNmKSJL2F4OwIvvb+LNEJT2IW0U4RCXMIT+SMbgJ1MwRfH/XrU9fviRv9l +pjaXNlVonPjE1IRwAoZGagdPRso3naCiPWlHyJGpii3qoCd/dcCLk72xYV1tJVji +UFR3b7x3AgMBAAECggEACAyljX012cGGNkDYX/H2TyOKxQNgAKZUi07BUvGWo0B8 +XPn37cSX7JzuO/0LD2iX6eims0bSAsOX//t/2xoiIXXPlwsdf+o8x1NPDYFJpzEP +6Son9BcXEhuKGghAXQSAWqpSXPVMpVZUh7bS6+UFtEqlSVo3Ow1IENnmpq4xku2I +x03zwXLdCPdleUBr8zPHRWaGNg2x1jpVxHR7N5KHTfhz0ylIMDBfThseHUuMkkY9 +VlTQNW5jd/9Zr8kxbe6J5/o699QL6I3a/ACmlXbyO6g6PSUl39dYfOQPTCeIFKrc +FyZvEeVT9ZZwSnKbVr+kM+k/uzdURKOj7IE68yF+VQKBgQDf8TQw4Wn7PVVnZGgw +PvkAKiKp0OE+NqWu7p+NhRE0x6nUcqXOX1zm1EDu2GcFGSegGb+DcQYVcU7DhWlo +7PZEPaH3ddgRBXte/nhP0Meaxz5FRdMGoI4ywv/uB4jOeJbw/gIkfkyBCgpplYbv +H/BOBjN+4f6HiGlcSwl/bR8KvQKBgQDFyooQb8CrF7vxPAxagU/JWzCp1xVKjtzU +Z5oRWjNBNH/PzXfDTQWpDefHXJ30Z8cKwHy5TUqIL8g2jW85AXYk8rgbIBtRk/DI +izHJWqTydrOaeTvbUrao+xIvSOhxgcLt90aadOAk47E+bnbXBGFOEfvM8KF2QVeb +x8PQ+UTxQwKBgQC53N5dR2kHvYL5egtDJ7DQIyh72sJnOUHP63r/IRcDwEdC7RiS +LPHVHwr5cSAnyhXqOhSKSi8rcsxVWJABJtLKFoEr+mGm1u7rC7bdP8G6w2z6X5Zi +pLUAinmRnC0+eDWGtLsggLaMTsIPmavRIaf3igwJXhY7dMtFb33lhbLC3QKBgCY4 +VSWH8rsdAvxClkCG7FwEewrWvQ6DPLjurB7eRzk6Y9hL4/ChWY6pWTh09TDdPOEf +APrtrJFUamPgQLXLSoEpRdo4Ag9pfwXBoAVAts8DkQEwnBhti05r9b+dXw1P/dLu +DX6bRxTZys49mklCV2s2nmmjtg+b4MoBeB1RjbjxAoGAEsxMZu1hZbynDf/j6Q9J +JvZWzBGbiGbEXJQPB0ZlBDSWXeGyMYv1pw35YRXBa+jk1+fP4IWRF3UABY8tchmt +M4X9veVRUyznd7GwqhrwgvvUK1EW6qURDVObeMe6/Bdm09lu3G2NhFWZP7DM3F/5 +/okcZc7DZOZCUlcHgiIg0y0= +-----END PRIVATE KEY----- diff --git a/boring/test/rsa-key-usage-relaxed.pem b/boring/test/rsa-key-usage-relaxed.pem new file mode 100644 index 000000000..f9cf94e51 --- /dev/null +++ b/boring/test/rsa-key-usage-relaxed.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDRTCCAi2gAwIBAgIUCmRSuBn+jTOljHtas3Mut6KyiFAwDQYJKoZIhvcNAQEL +BQAwJjEkMCIGA1UEAwwbUmVsYXggUlNBIEtleSBVc2FnZSBUZXN0IENBMB4XDTI2 +MDYyNjE4NDMxMloXDTI3MDYyNjE4NDMxMlowFDESMBAGA1UEAwwJbG9jYWxob3N0 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArQXKQzMAKLrgg0nTuK7V +kJE/+SCJjxiyF9KqHyGaueHrq+uET54examyPhvO4g/P8V2hAbqfOH4U/cr7L8TA +xWHdOglLKxHExMt23wx7XwqO3ZxCwo9gO7auW2sa7tqgEr6mAhhBngYqjKhE4wJ9 +wZ7FNQR1AhmhgPW1Z6cFBKCmgWhaZV0qJ0J6YXYl82nd4pSKRnFpiVP/Is/jc4Ca +zZikiS9heDsCL72/izRCU9iFtFOEQlzCE/kjG4CdTMEXx/161PX74kb/ZaY2lzZV +aJz4xNSEcAKGRmoHT0bKN52goj1pR8iRqYot6qAnf3XAi5O9sWFdbSVY4lBUd2+8 +dwIDAQABo30wezAOBgNVHQ8BAf8EBAMCBSAwEwYDVR0lBAwwCgYIKwYBBQUHAwEw +FAYDVR0RBA0wC4IJbG9jYWxob3N0MB0GA1UdDgQWBBR4WbrbwW1ZzEwF0fRUB+XA +LF4WGzAfBgNVHSMEGDAWgBSPyeAkCTd9Vj8oeWAMLYAUHUrPmDANBgkqhkiG9w0B +AQsFAAOCAQEAz336T8K625TdEkvqQ/B/bbkW5zv2y/mu1G+iW7aSfYNdOsH1fVFl +kFpFJsv2dUoV82fKzk33RcXDlc9o3y4KzTeTPaEZb4ZCe8TleWkjdZYa7/hRj6GR +bc7mniRawJVtdCKfAvg+SgiPwSt18s1BCMd4B/5XIQivgd+ksYvtIFRdn15OtNZi +Z91m1rSUHSgJLqHtxSNYwbxz3yw1/gm+Xfvj1+HZe4gTjfqjLIyhRJti1botWMaR +4XYy1W/WAbJ1avvKNfjkAoBZAYXguperASS/eZ3d7hH+0RVrBQSp1bde9/utChvE +5ls7xa9vwSLCKoXtAXGiaSePA5XDAkTGrg== +-----END CERTIFICATE-----