Repository: rustls/rustls (language: rust)

Issue:
Support `certificate_authorities` extension in ClientHello  
**Checklist**
* [x] I've searched the issue tracker for similar requests

**Is your feature request related to a problem? Please describe.**
The TLS 1.3 [`certificate_authorities`](https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.4) extension in ClientHello can be used by the client to communicate to the server what CAs it recognizes, helping the server present a certificate acceptable to the client. Currently, this is not supported in `rustls`.

**Describe the solution you'd like**
We can add this extension to the `ClientHello` type (e.g., as the `.certificate_authorites_extension()` method), and add a default function to the `ServerCertVerifier` trait that asks for CA names that the server cert verifier recognizes. With these changes, custom `ServerCertVerifier`s on the client side can communicate their recognized CAs to the server during TLS handshake, and custom cert resolvers on the server side can take this information into account when picking a cert/key.

I've already started prototyping this, and if the maintainers are OK with the proposed solution, I can open a PR for this feature.



Discussion / hints:
The issue, as I understand, with this extension is that it involves transmitting about 15KB of data (for the WebPKI) in the client hello. That is pretty impractical.

There are some proposals in the TLS working group for alternatives to this, but they're not making quick progress.
Hi @ctz. Thanks for the quick reply. 

Just so we are on the same page, the 15KB number comes from ~100 root CAs * ~150 bytes per root CA?

I agree that this is not very practical for WebPKI, and I'm not proposing for rustls to start sending this extension by default. The proposal is to let custom cert verifiers and cert resolvers propose/check this extension.

In our use case, we want to let clients talk to our public server, and not have to rely on public root CAs for validation of the server.
Ah, yes that would be acceptable. FWIW this can already happen for client auth -- see https://docs.rs/rustls/latest/rustls/server/danger/trait.ClientCertVerifier.html#tymethod.root_hint_subjects so adding it for server auth seems reasonable.
Great. I'll work on the PR then.

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index e9628282116..5b4725d709e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -141,6 +141,45 @@ dependencies = [
  "itoa",
 ]
 
+[[package]]
+name = "asn1-rs"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048"
+dependencies = [
+ "asn1-rs-derive",
+ "asn1-rs-impl",
+ "displaydoc",
+ "nom",
+ "num-traits",
+ "rusticata-macros",
+ "thiserror 1.0.69",
+ "time",
+]
+
+[[package]]
+name = "asn1-rs-derive"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.90",
+ "synstructure",
+]
+
+[[package]]
+name = "asn1-rs-impl"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.90",
+]
+
 [[package]]
 name = "asn1_derive"
 version = "0.20.0"
@@ -750,6 +789,20 @@ dependencies = [
  "zeroize",
 ]
 
+[[package]]
+name = "der-parser"
+version = "9.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553"
+dependencies = [
+ "asn1-rs",
+ "displaydoc",
+ "nom",
+ "num-bigint",
+ "num-traits",
+ "rusticata-macros",
+]
+
 [[package]]
 name = "deranged"
 version = "0.3.11"
@@ -1753,6 +1806,15 @@ dependencies = [
  "memchr",
 ]
 
+[[package]]
+name = "oid-registry"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9"
+dependencies = [
+ "asn1-rs",
+]
+
 [[package]]
 name = "once_cell"
 version = "1.20.2"
@@ -2200,6 +2262,15 @@ dependencies = [
  "semver",
 ]
 
+[[package]]
+name = "rusticata-macros"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632"
+dependencies = [
+ "nom",
+]
+
 [[package]]
 name = "rustix"
 version = "0.38.41"
@@ -2254,6 +2325,7 @@ dependencies = [
  "subtle",
  "time",
  "webpki-roots",
+ "x509-parser",
  "zeroize",
  "zlib-rs",
 ]
@@ -2641,10 +2713,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
 dependencies = [
  "deranged",
+ "itoa",
  "num-conv",
  "powerfmt",
  "serde",
  "time-core",
+ "time-macros",
 ]
 
 [[package]]
@@ -2653,6 +2727,16 @@ version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
 
+[[package]]
+name = "time-macros"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
 [[package]]
 name = "tinystr"
 version = "0.7.6"
@@ -3154,6 +3238,23 @@ dependencies = [
  "zeroize",
 ]
 
+[[package]]
+name = "x509-parser"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69"
+dependencies = [
+ "asn1-rs",
+ "data-encoding",
+ "der-parser",
+ "lazy_static",
+ "nom",
+ "oid-registry",
+ "rusticata-macros",
+ "thiserror 1.0.69",
+ "time",
+]
+
 [[package]]
 name = "yasna"
 version = "0.5.2"
diff --git a/Cargo.toml b/Cargo.toml
index 48eadaed51d..ffce81a5bd2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -87,6 +87,7 @@ tokio = { version = "1.34", features = ["io-util", "macros", "net", "rt"]}
 webpki = { package = "rustls-webpki", version = "0.102.8", features = ["alloc"], default-features = false }
 webpki-roots = "0.26"
 x25519-dalek = "2"
+x509-parser = "0.16"
 zeroize = "1.7"
 zlib-rs = "0.4"
 
diff --git a/rustls/Cargo.toml b/rustls/Cargo.toml
index 59662a00e4c..7c4976ccf52 100644
--- a/rustls/Cargo.toml
+++ b/rustls/Cargo.toml
@@ -59,6 +59,7 @@ serde = { workspace = true }
 serde_json = { workspace = true }
 time = { workspace = true }
 webpki-roots = { workspace = true }
+x509-parser = { workspace = true }
 
 [[bench]]
 name = "benchmarks"
diff --git a/rustls/src/client/hs.rs b/rustls/src/client/hs.rs
index 30a97b669d4..732bd08d063 100644
--- a/rustls/src/client/hs.rs
+++ b/rustls/src/client/hs.rs
@@ -271,6 +271,12 @@ fn emit_client_hello_for_retry(
         ClientExtension::CertificateStatusRequest(CertificateStatusRequest::build_ocsp()),
     ];
 
+    if support_tls13 {
+        if let Some(cas_extension) = config.verifier.root_hint_subjects() {
+            exts.push(ClientExtension::AuthorityNames(cas_extension.to_owned()));
+        }
+    }
+
     // Send the ECPointFormat extension only if we are proposing ECDHE
     if config
         .provider
diff --git a/rustls/src/msgs/handshake.rs b/rustls/src/msgs/handshake.rs
index 619d0127878..38e211016ec 100644
--- a/rustls/src/msgs/handshake.rs
+++ b/rustls/src/msgs/handshake.rs
@@ -571,6 +571,7 @@ pub enum ClientExtension {
     CertificateCompressionAlgorithms(Vec<CertificateCompressionAlgorithm>),
     EncryptedClientHello(EncryptedClientHello),
     EncryptedClientHelloOuterExtensions(Vec<ExtensionType>),
+    AuthorityNames(Vec<DistinguishedName>),
     Unknown(UnknownExtension),
 }
 
@@ -600,6 +601,7 @@ impl ClientExtension {
             Self::EncryptedClientHelloOuterExtensions(_) => {
                 ExtensionType::EncryptedClientHelloOuterExtensions
             }
+            Self::AuthorityNames(_) => ExtensionType::CertificateAuthorities,
             Self::Unknown(ref r) => r.typ,
         }
     }
@@ -634,6 +636,7 @@ impl Codec<'_> for ClientExtension {
             Self::CertificateCompressionAlgorithms(ref r) => r.encode(nested.buf),
             Self::EncryptedClientHello(ref r) => r.encode(nested.buf),
             Self::EncryptedClientHelloOuterExtensions(ref r) => r.encode(nested.buf),
+            Self::AuthorityNames(ref r) => r.encode(nested.buf),
             Self::Unknown(ref r) => r.encode(nested.buf),
         }
     }
@@ -682,6 +685,7 @@ impl Codec<'_> for ClientExtension {
             ExtensionType::EncryptedClientHelloOuterExtensions => {
                 Self::EncryptedClientHelloOuterExtensions(Vec::read(&mut sub)?)
             }
+            ExtensionType::CertificateAuthorities => Self::AuthorityNames(Vec::read(&mut sub)?),
             _ => Self::Unknown(UnknownExtension::read(typ, &mut sub)),
         };
 
@@ -1159,6 +1163,13 @@ impl ClientHelloPayload {
             false
         }
     }
+
+    pub(crate) fn certificate_authorities_extension(&self) -> Option<&[DistinguishedName]> {
+        match self.find_extension(ExtensionType::CertificateAuthorities)? {
+            ClientExtension::AuthorityNames(ext) => Some(ext),
+            _ => unreachable!("extension type checked"),
+        }
+    }
 }
 
 #[derive(Clone, Debug)]
diff --git a/rustls/src/server/handy.rs b/rustls/src/server/handy.rs
index 4c601d9df22..ba2570abfdd 100644
--- a/rustls/src/server/handy.rs
+++ b/rustls/src/server/handy.rs
@@ -312,7 +312,8 @@ mod sni_resolver {
                     alpn: None,
                     server_cert_types: None,
                     client_cert_types: None,
-                    cipher_suites: &[]
+                    cipher_suites: &[],
+                    certificate_authorities: None,
                 })
                 .is_none());
         }
@@ -330,7 +331,8 @@ mod sni_resolver {
                     alpn: None,
                     server_cert_types: None,
                     client_cert_types: None,
-                    cipher_suites: &[]
+                    cipher_suites: &[],
+                    certificate_authorities: None,
                 })
                 .is_none());
         }
diff --git a/rustls/src/server/hs.rs b/rustls/src/server/hs.rs
index 2eee2d9da71..2d2a45cc575 100644
--- a/rustls/src/server/hs.rs
+++ b/rustls/src/server/hs.rs
@@ -398,6 +398,11 @@ impl ExpectClientHello {
         sig_schemes
             .retain(|scheme| suites::compatible_sigscheme_for_suites(*scheme, &client_suites));
 
+        // We adhere to the TLS 1.2 RFC by not exposing this to the cert resolver if TLS version is 1.2
+        let certificate_authorities = match version {
+            ProtocolVersion::TLSv1_2 => None,
+            _ => client_hello.certificate_authorities_extension(),
+        };
         // Choose a certificate.
         let certkey = {
             let client_hello = ClientHello {
@@ -407,6 +412,7 @@ impl ExpectClientHello {
                 client_cert_types: client_hello.server_certificate_extension(),
                 server_cert_types: client_hello.client_certificate_extension(),
                 cipher_suites: &client_hello.cipher_suites,
+                certificate_authorities,
             };
             trace!("Resolving server certificate: {client_hello:#?}");
 
diff --git a/rustls/src/server/server_conn.rs b/rustls/src/server/server_conn.rs
index c286eab5350..ad46cdf28f2 100644
--- a/rustls/src/server/server_conn.rs
+++ b/rustls/src/server/server_conn.rs
@@ -32,7 +32,7 @@ use crate::time_provider::TimeProvider;
 use crate::vecbuf::ChunkVecBuffer;
 #[cfg(feature = "std")]
 use crate::WantsVerifier;
-use crate::{compress, sign, verify, versions, KeyLog, WantsVersions};
+use crate::{compress, sign, verify, versions, DistinguishedName, KeyLog, WantsVersions};
 
 /// A trait for the ability to store server session data.
 ///
@@ -140,6 +140,10 @@ pub struct ClientHello<'a> {
     pub(super) server_cert_types: Option<&'a [CertificateType]>,
     pub(super) client_cert_types: Option<&'a [CertificateType]>,
     pub(super) cipher_suites: &'a [CipherSuite],
+    /// The [certificate_authorities] extension, if it was sent by the client.
+    ///
+    /// [certificate_authorities]: https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.4
+    pub(super) certificate_authorities: Option<&'a [DistinguishedName]>,
 }
 
 impl<'a> ClientHello<'a> {
@@ -202,6 +206,15 @@ impl<'a> ClientHello<'a> {
     pub fn client_cert_types(&self) -> Option<&'a [CertificateType]> {
         self.client_cert_types
     }
+
+    /// Get the [certificate_authorities] extension sent by the client.
+    ///
+    /// Returns `None` if the client did not send this extension.
+    ///
+    /// [certificate_authorities]: https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.4
+    pub fn certificate_authorities(&self) -> Option<&'a [DistinguishedName]> {
+        self.certificate_authorities
+    }
 }
 
 /// Common configuration for a set of server sessions.
@@ -920,6 +933,7 @@ impl Accepted {
             server_cert_types: payload.server_certificate_extension(),
             client_cert_types: payload.client_certificate_extension(),
             cipher_suites: &payload.cipher_suites,
+            certificate_authorities: payload.certificate_authorities_extension(),
         };
 
         trace!("Accepted::client_hello(): {ch:#?}");
diff --git a/rustls/src/verify.rs b/rustls/src/verify.rs
index dee10f0b813..ce07e3a0eb0 100644
--- a/rustls/src/verify.rs
+++ b/rustls/src/verify.rs
@@ -142,6 +142,16 @@ pub trait ServerCertVerifier: Debug + Send + Sync {
     fn requires_raw_public_keys(&self) -> bool {
         false
     }
+
+    /// Return the [`DistinguishedName`]s of certificate authorities that this verifier trusts.
+    ///
+    /// If specified, will be sent as the [`certificate_authorities`] extension in ClientHello.
+    /// Note that this is only applicable to TLS 1.3.
+    ///
+    /// [`certificate_authorities`]: https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.4
+    fn root_hint_subjects(&self) -> Option<&[DistinguishedName]> {
+        None
+    }
 }
 
 /// Something that can verify a client certificate chain


Test patch:
diff --git a/rustls/tests/common/mod.rs b/rustls/tests/common/mod.rs
index 4c8e09393ec..21cf4e53524 100644
--- a/rustls/tests/common/mod.rs
+++ b/rustls/tests/common/mod.rs
@@ -323,6 +323,13 @@ impl KeyType {
         }
     }
 
+    pub fn ca_cert(&self) -> CertificateDer<'_> {
+        self.get_chain()
+            .into_iter()
+            .next_back()
+            .expect("cert chain cannot be empty")
+    }
+
     pub fn get_chain(&self) -> Vec<CertificateDer<'static>> {
         CertificateDer::pem_slice_iter(self.bytes_for("end.fullchain"))
             .map(|result| result.unwrap())
@@ -385,7 +392,7 @@ impl KeyType {
         )))
     }
 
-    pub fn get_certified_key(&self) -> Result<Arc<CertifiedKey>, Error> {
+    pub fn certified_key_with_raw_pub_key(&self) -> Result<Arc<CertifiedKey>, Error> {
         let private_key = provider::default_provider()
             .key_provider
             .load_private_key(self.get_key())?;
@@ -399,6 +406,13 @@ impl KeyType {
         )))
     }
 
+    pub fn certified_key_with_cert_chain(&self) -> Result<Arc<CertifiedKey>, Error> {
+        let private_key = provider::default_provider()
+            .key_provider
+            .load_private_key(self.get_key())?;
+        Ok(Arc::new(CertifiedKey::new(self.get_chain(), private_key)))
+    }
+
     fn get_crl(&self, role: &str, r#type: &str) -> CertificateRevocationListDer<'static> {
         CertificateRevocationListDer::from_pem_slice(
             self.bytes_for(&format!("{role}.{type}.crl.pem")),
@@ -408,25 +422,13 @@ impl KeyType {
 
     pub fn ca_distinguished_name(&self) -> &'static [u8] {
         match self {
-            KeyType::Rsa2048 => {
-                &b"0\x1f1\x1d0\x1b\x06\x03U\x04\x03\x0c\x14ponytown RSA 2048 CA"[..]
-            }
-            KeyType::Rsa3072 => {
-                &b"0\x1f1\x1d0\x1b\x06\x03U\x04\x03\x0c\x14ponytown RSA 3072 CA"[..]
-            }
-            KeyType::Rsa4096 => {
-                &b"0\x1f1\x1d0\x1b\x06\x03U\x04\x03\x0c\x14ponytown RSA 4096 CA"[..]
-            }
-            KeyType::EcdsaP256 => {
-                &b"0\x211\x1f0\x1d\x06\x03U\x04\x03\x0c\x16ponytown ECDSA p256 CA"[..]
-            }
-            KeyType::EcdsaP384 => {
-                &b"0\x211\x1f0\x1d\x06\x03U\x04\x03\x0c\x16ponytown ECDSA p384 CA"[..]
-            }
-            KeyType::EcdsaP521 => {
-                &b"0\x211\x1f0\x1d\x06\x03U\x04\x03\x0c\x16ponytown ECDSA p521 CA"[..]
-            }
-            KeyType::Ed25519 => &b"0\x1c1\x1a0\x18\x06\x03U\x04\x03\x0c\x11ponytown EdDSA CA"[..],
+            KeyType::Rsa2048 => b"0\x1f1\x1d0\x1b\x06\x03U\x04\x03\x0c\x14ponytown RSA 2048 CA",
+            KeyType::Rsa3072 => b"0\x1f1\x1d0\x1b\x06\x03U\x04\x03\x0c\x14ponytown RSA 3072 CA",
+            KeyType::Rsa4096 => b"0\x1f1\x1d0\x1b\x06\x03U\x04\x03\x0c\x14ponytown RSA 4096 CA",
+            KeyType::EcdsaP256 => b"0\x211\x1f0\x1d\x06\x03U\x04\x03\x0c\x16ponytown ECDSA p256 CA",
+            KeyType::EcdsaP384 => b"0\x211\x1f0\x1d\x06\x03U\x04\x03\x0c\x16ponytown ECDSA p384 CA",
+            KeyType::EcdsaP521 => b"0\x211\x1f0\x1d\x06\x03U\x04\x03\x0c\x16ponytown ECDSA p521 CA",
+            KeyType::Ed25519 => b"0\x1c1\x1a0\x18\x06\x03U\x04\x03\x0c\x11ponytown EdDSA CA",
         }
     }
 }
@@ -573,7 +575,8 @@ pub fn make_server_config_with_client_verifier(
 pub fn make_server_config_with_raw_key_support(kt: KeyType) -> ServerConfig {
     let mut client_verifier = MockClientVerifier::new(|| Ok(ClientCertVerified::assertion()), kt);
     let server_cert_resolver = Arc::new(AlwaysResolvesServerRawPublicKeys::new(
-        kt.get_certified_key().unwrap(),
+        kt.certified_key_with_raw_pub_key()
+            .unwrap(),
     ));
     client_verifier.expect_raw_public_keys = true;
     // We don't support tls1.2 for Raw Public Keys, hence the version is hard-coded.
diff --git a/rustls/tests/server_cert_verifier.rs b/rustls/tests/server_cert_verifier.rs
index b0327d4bf11..8b5b03a9783 100644
--- a/rustls/tests/server_cert_verifier.rs
+++ b/rustls/tests/server_cert_verifier.rs
@@ -8,11 +8,25 @@ mod common;
 use std::sync::Arc;
 
 use common::{
-    do_handshake, do_handshake_until_both_error, make_client_config_with_versions,
-    make_pair_for_arc_configs, make_server_config, ErrorFromPeer, MockServerVerifier,
-    ALL_KEY_TYPES,
+    client_config_builder, client_config_builder_with_versions, do_handshake,
+    do_handshake_until_both_error, do_handshake_until_error, make_client_config_with_versions,
+    make_pair_for_arc_configs, make_server_config, server_config_builder, transfer_altered,
+    Altered, ErrorFromPeer, KeyType, MockServerVerifier, ALL_KEY_TYPES,
 };
-use rustls::{AlertDescription, Error, InvalidMessage};
+use pki_types::{CertificateDer, ServerName};
+use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
+use rustls::client::WebPkiServerVerifier;
+use rustls::internal::msgs::handshake::{ClientExtension, HandshakePayload};
+use rustls::internal::msgs::message::{Message, MessagePayload};
+use rustls::server::{ClientHello, ResolvesServerCert};
+use rustls::sign::CertifiedKey;
+use rustls::version::{TLS12, TLS13};
+use rustls::{
+    AlertDescription, CertificateError, DigitallySignedStruct, DistinguishedName, Error,
+    InvalidMessage, RootCertStore,
+};
+use x509_parser::prelude::FromDer;
+use x509_parser::x509::X509Name;
 
 #[test]
 fn client_can_override_certificate_verification() {
@@ -152,3 +166,218 @@ fn client_can_override_certificate_verification_and_offer_no_signature_schemes()
         }
     }
 }
+
+#[test]
+fn cas_extension_in_client_hello_if_server_verifier_requests_it() {
+    let server_config = Arc::new(make_server_config(KeyType::Rsa2048));
+
+    let mut root_cert_store = RootCertStore::empty();
+    root_cert_store
+        .add(KeyType::Rsa2048.ca_cert())
+        .unwrap();
+
+    let server_verifier = WebPkiServerVerifier::builder_with_provider(
+        Arc::new(root_cert_store),
+        Arc::new(provider::default_provider()),
+    )
+    .build()
+    .unwrap();
+    let cas_sending_server_verifier = Arc::new(ServerCertVerifierWithCasExt {
+        verifier: server_verifier.clone(),
+        ca_names: vec![KeyType::Rsa2048
+            .ca_distinguished_name()
+            .to_vec()
+            .into()],
+    });
+
+    for (protocol_version, cas_extension_expected) in [(&TLS12, false), (&TLS13, true)] {
+        let client_config = Arc::new(
+            client_config_builder_with_versions(&[protocol_version])
+                .dangerous()
+                .with_custom_certificate_verifier(cas_sending_server_verifier.clone())
+                .with_no_client_auth(),
+        );
+
+        let expect_cas_extension = |msg: &mut Message<'_>| -> Altered {
+            if let MessagePayload::Handshake { parsed, .. } = &msg.payload {
+                if let HandshakePayload::ClientHello(ch) = &parsed.payload {
+                    assert_eq!(
+                        ch.extensions
+                            .iter()
+                            .any(|ext| matches!(ext, ClientExtension::AuthorityNames(_))),
+                        cas_extension_expected
+                    );
+                    println!("cas extension expectation met! cas_extension_expected: {cas_extension_expected}");
+                }
+            }
+            Altered::InPlace
+        };
+
+        let (client, server) = make_pair_for_arc_configs(&client_config, &server_config);
+        let (mut client, mut server) = (client.into(), server.into());
+        transfer_altered(&mut client, expect_cas_extension, &mut server);
+    }
+}
+
+#[test]
+fn client_can_request_certain_trusted_cas() {
+    // These keys have CAs with different names, which our test needs.
+    // They also share the same sigalgs, so the server won't pick one over the other based on sigalgs.
+    let key_types = [KeyType::Rsa2048, KeyType::Rsa3072, KeyType::Rsa4096];
+    let cert_resolver = ResolvesCertChainByCaName(
+        key_types
+            .iter()
+            .map(|kt| {
+                (
+                    kt.ca_distinguished_name()
+                        .to_vec()
+                        .into(),
+                    kt.certified_key_with_cert_chain()
+                        .unwrap(),
+                )
+            })
+            .collect(),
+    );
+
+    let server_config = Arc::new(
+        server_config_builder()
+            .with_no_client_auth()
+            .with_cert_resolver(Arc::new(cert_resolver.clone())),
+    );
+
+    let mut cas_unaware_error_count = 0;
+
+    for key_type in key_types {
+        let mut root_store = RootCertStore::empty();
+        root_store
+            .add(key_type.ca_cert())
+            .unwrap();
+        let server_verifier = WebPkiServerVerifier::builder_with_provider(
+            Arc::new(root_store),
+            Arc::new(provider::default_provider()),
+        )
+        .build()
+        .unwrap();
+
+        let cas_sending_server_verifier = Arc::new(ServerCertVerifierWithCasExt {
+            verifier: server_verifier.clone(),
+            ca_names: vec![DistinguishedName::from(
+                key_type
+                    .ca_distinguished_name()
+                    .to_vec(),
+            )],
+        });
+
+        let cas_sending_client_config = client_config_builder()
+            .dangerous()
+            .with_custom_certificate_verifier(cas_sending_server_verifier)
+            .with_no_client_auth();
+
+        let (mut client, mut server) =
+            make_pair_for_arc_configs(&Arc::new(cas_sending_client_config), &server_config);
+        do_handshake(&mut client, &mut server);
+
+        let cas_unaware_client_config = client_config_builder()
+            .dangerous()
+            .with_custom_certificate_verifier(server_verifier)
+            .with_no_client_auth();
+
+        let (mut client, mut server) =
+            make_pair_for_arc_configs(&Arc::new(cas_unaware_client_config), &server_config);
+
+        cas_unaware_error_count += do_handshake_until_error(&mut client, &mut server)
+            .inspect_err(|e| {
+                assert!(matches!(
+                    e,
+                    ErrorFromPeer::Client(Error::InvalidCertificate(
+                        CertificateError::UnknownIssuer
+                    ))
+                ))
+            })
+            .is_err() as usize;
+
+        println!("key type {key_type:?} success!");
+    }
+
+    // For cas_unaware clients, all of them should fail except one that happens to
+    // have the cert the server sends
+    assert_eq!(cas_unaware_error_count, key_types.len() - 1);
+}
+
+#[derive(Debug, Clone)]
+pub struct ResolvesCertChainByCaName(Vec<(DistinguishedName, Arc<CertifiedKey>)>);
+
+impl ResolvesServerCert for ResolvesCertChainByCaName {
+    fn resolve(&self, client_hello: ClientHello<'_>) -> Option<Arc<CertifiedKey>> {
+        let Some(cas_extension) = client_hello.certificate_authorities() else {
+            println!("ResolvesCertChainByCaName: no CAs extension in ClientHello, returning default cert");
+            return Some(self.0[0].1.clone());
+        };
+        for (name, certified_key) in self.0.iter() {
+            let name = X509Name::from_der(name.as_ref())
+                .unwrap()
+                .1;
+            if cas_extension.iter().any(|ca_name| {
+                X509Name::from_der(ca_name.as_ref()).is_ok_and(|(_, ca_name)| ca_name == name)
+            }) {
+                println!("ResolvesCertChainByCaName: found matching CA name: {name}");
+                return Some(certified_key.clone());
+            }
+        }
+        println!("ResolvesCertChainByCaName: no matching CA name found, returning default Cert");
+        Some(self.0[0].1.clone())
+    }
+}
+
+#[derive(Debug)]
+struct ServerCertVerifierWithCasExt {
+    verifier: Arc<dyn ServerCertVerifier>,
+    ca_names: Vec<DistinguishedName>,
+}
+
+impl ServerCertVerifier for ServerCertVerifierWithCasExt {
+    fn verify_server_cert(
+        &self,
+        end_entity: &CertificateDer<'_>,
+        intermediates: &[CertificateDer<'_>],
+        server_name: &ServerName<'_>,
+        ocsp_response: &[u8],
+        now: pki_types::UnixTime,
+    ) -> Result<ServerCertVerified, Error> {
+        self.verifier
+            .verify_server_cert(end_entity, intermediates, server_name, ocsp_response, now)
+    }
+
+    fn verify_tls12_signature(
+        &self,
+        message: &[u8],
+        cert: &CertificateDer<'_>,
+        dss: &DigitallySignedStruct,
+    ) -> Result<HandshakeSignatureValid, Error> {
+        self.verifier
+            .verify_tls12_signature(message, cert, dss)
+    }
+
+    fn verify_tls13_signature(
+        &self,
+        message: &[u8],
+        cert: &CertificateDer<'_>,
+        dss: &DigitallySignedStruct,
+    ) -> Result<HandshakeSignatureValid, Error> {
+        self.verifier
+            .verify_tls13_signature(message, cert, dss)
+    }
+
+    fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
+        self.verifier.supported_verify_schemes()
+    }
+
+    fn requires_raw_public_keys(&self) -> bool {
+        self.verifier.requires_raw_public_keys()
+    }
+
+    fn root_hint_subjects(&self) -> Option<&[DistinguishedName]> {
+        println!("ServerCertVerifierWithCasExt::root_hint_subjects() called!");
+        Some(&self.ca_names)
+    }
+}