Skip to main content

rustls/client/
config.rs

1use alloc::vec::Vec;
2use core::any::Any;
3use core::fmt;
4use core::hash::{Hash, Hasher};
5use core::marker::PhantomData;
6
7#[cfg(feature = "webpki")]
8use pki_types::PrivateKeyDer;
9use pki_types::{FipsStatus, ServerName, UnixTime};
10
11use super::ech::EchMode;
12use super::handy::{ClientSessionMemoryCache, FailResolveClientCert, NoClientSessionStorage};
13use super::{Tls12Session, Tls13Session};
14use crate::builder::{ConfigBuilder, WantsVerifier};
15use crate::client::connection::ClientConnectionBuilder;
16#[cfg(doc)]
17use crate::crypto;
18use crate::crypto::kx::NamedGroup;
19use crate::crypto::{CipherSuite, CryptoProvider, SelectedCredential, SignatureScheme, hash};
20#[cfg(feature = "webpki")]
21use crate::crypto::{Credentials, Identity, SingleCredential};
22use crate::enums::{ApplicationProtocol, CertificateType, ProtocolVersion};
23use crate::error::{ApiMisuse, Error};
24use crate::key_log::NoKeyLog;
25use crate::suites::SupportedCipherSuite;
26use crate::sync::Arc;
27use crate::time_provider::{DefaultTimeProvider, TimeProvider};
28#[cfg(feature = "webpki")]
29use crate::webpki::{self, WebPkiServerVerifier};
30use crate::{DistinguishedName, DynHasher, KeyLog, compress, verify};
31
32/// Common configuration for (typically) all connections made by a program.
33///
34/// Making one of these is cheap, though one of the inputs may be expensive: gathering trust roots
35/// from the operating system to add to the [`RootCertStore`] passed to `with_root_certificates()`
36/// (the rustls-native-certs crate is often used for this) may take on the order of a few hundred
37/// milliseconds.
38///
39/// These must be created via the [`ClientConfig::builder()`] or [`ClientConfig::builder_with_details()`]
40/// function.
41///
42/// Note that using [`ConfigBuilder<ClientConfig, WantsVersions>::with_ech()`] will produce a common
43/// configuration specific to the provided [`crate::client::EchConfig`] that may not be appropriate
44/// for all connections made by the program. In this case the configuration should only be shared
45/// by connections intended for domains that offer the provided [`crate::client::EchConfig`] in
46/// their DNS zone.
47///
48/// # Defaults
49///
50/// * [`ClientConfig::max_fragment_size`]: the default is `None` (meaning 16kB).
51/// * [`ClientConfig::resumption`]: supports resumption with up to 256 server names, using session
52///   ids or tickets, with a max of eight tickets per server.
53/// * [`ClientConfig::alpn_protocols`]: the default is empty -- no ALPN protocol is negotiated.
54/// * [`ClientConfig::key_log`]: key material is not logged.
55/// * [`ClientConfig::cert_decompressors`]: depends on the crate features, see [`compress::default_cert_decompressors()`].
56/// * [`ClientConfig::cert_compressors`]: depends on the crate features, see [`compress::default_cert_compressors()`].
57/// * [`ClientConfig::cert_compression_cache`]: caches the most recently used 4 compressions
58///
59/// [`RootCertStore`]: crate::RootCertStore
60#[derive(Clone, Debug)]
61pub struct ClientConfig {
62    /// Which ALPN protocols we include in our client hello.
63    /// If empty, no ALPN extension is sent.
64    pub alpn_protocols: Vec<ApplicationProtocol<'static>>,
65
66    /// Whether to check the selected ALPN was offered.
67    ///
68    /// The default is true.
69    pub check_selected_alpn: bool,
70
71    /// How and when the client can resume a previous session.
72    ///
73    /// # Sharing `resumption` between `ClientConfig`s
74    /// In a program using many `ClientConfig`s it may improve resumption rates
75    /// (which has a significant impact on connection performance) if those
76    /// configs share a single `Resumption`.
77    ///
78    /// However, resumption is only allowed between two `ClientConfig`s if their
79    /// `client_auth_cert_resolver` (ie, potential client authentication credentials)
80    /// and `verifier` (ie, server certificate verification settings):
81    ///
82    /// - are the same type (determined by hashing their `TypeId`), and
83    /// - input the same data into [`ServerVerifier::hash_config()`] and
84    ///   [`ClientCredentialResolver::hash_config()`].
85    ///
86    /// To illustrate, imagine two `ClientConfig`s `A` and `B`.  `A` fully validates
87    /// the server certificate, `B` does not.  If `A` and `B` shared a resumption store,
88    /// it would be possible for a session originated by `B` to be inserted into the
89    /// store, and then resumed by `A`.  This would give a false impression to the user
90    /// of `A` that the server certificate is fully validated.
91    ///
92    /// [`ServerVerifier::hash_config()`]: verify::ServerVerifier::hash_config()
93    pub resumption: Resumption,
94
95    /// The maximum size of plaintext input to be emitted in a single TLS record.
96    /// A value of None is equivalent to the [TLS maximum] of 16 kB.
97    ///
98    /// rustls enforces an arbitrary minimum of 32 bytes for this field.
99    /// Out of range values are reported as errors when initializing a connection.
100    ///
101    /// Setting this value to a little less than the TCP MSS may improve latency
102    /// for stream-y workloads.
103    ///
104    /// [TLS maximum]: https://datatracker.ietf.org/doc/html/rfc8446#section-5.1
105    pub max_fragment_size: Option<usize>,
106
107    /// Whether to send the Server Name Indication (SNI) extension
108    /// during the client handshake.
109    ///
110    /// The default is true.
111    pub enable_sni: bool,
112
113    /// How to output key material for debugging.  The default
114    /// does nothing.
115    pub key_log: Arc<dyn KeyLog>,
116
117    /// Allows traffic secrets to be extracted after the handshake,
118    /// e.g. for kTLS setup.
119    pub enable_secret_extraction: bool,
120
121    /// Whether to send data on the first flight ("early data") in
122    /// TLS 1.3 handshakes.
123    ///
124    /// The default is false.
125    pub enable_early_data: bool,
126
127    /// If set to `true`, requires the server to support the extended
128    /// master secret extraction method defined in [RFC 7627].
129    ///
130    /// The default is `true` if the configured [`CryptoProvider`] is FIPS-compliant,
131    /// false otherwise.
132    ///
133    /// It must be set to `true` to meet FIPS requirement mentioned in section
134    /// **D.Q Transition of the TLS 1.2 KDF to Support the Extended Master
135    /// Secret** from [FIPS 140-3 IG.pdf].
136    ///
137    /// [RFC 7627]: https://datatracker.ietf.org/doc/html/rfc7627
138    /// [FIPS 140-3 IG.pdf]: https://csrc.nist.gov/csrc/media/Projects/cryptographic-module-validation-program/documents/fips%20140-3/FIPS%20140-3%20IG.pdf
139    pub require_ems: bool,
140
141    /// Request a specific number of TLS 1.3 session tickets via [RFC 9149].
142    ///
143    /// Set to `None` to disable sending the extension (the default).
144    ///
145    /// [RFC 9149]: https://datatracker.ietf.org/doc/html/rfc9149
146    pub send_ticket_request: Option<TicketRequest>,
147
148    /// Items that affect the fundamental security properties of a connection.
149    pub(super) domain: SecurityDomain,
150
151    /// How to decompress the server's certificate chain.
152    ///
153    /// If this is non-empty, the [RFC8779] certificate compression
154    /// extension is offered, and any compressed certificates are
155    /// transparently decompressed during the handshake.
156    ///
157    /// This only applies to TLS1.3 connections.  It is ignored for
158    /// TLS1.2 connections.
159    ///
160    /// [RFC8779]: https://datatracker.ietf.org/doc/rfc8879/
161    pub cert_decompressors: Vec<&'static dyn compress::CertDecompressor>,
162
163    /// How to compress the client's certificate chain.
164    ///
165    /// If a server supports this extension, and advertises support
166    /// for one of the compression algorithms included here, the
167    /// client certificate will be compressed according to [RFC8779].
168    ///
169    /// This only applies to TLS1.3 connections.  It is ignored for
170    /// TLS1.2 connections.
171    ///
172    /// [RFC8779]: https://datatracker.ietf.org/doc/rfc8879/
173    pub cert_compressors: Vec<&'static dyn compress::CertCompressor>,
174
175    /// Caching for compressed certificates.
176    ///
177    /// This is optional: [`compress::CompressionCache::Disabled`] gives
178    /// a cache that does no caching.
179    pub cert_compression_cache: Arc<compress::CompressionCache>,
180
181    /// How to offer Encrypted Client Hello (ECH). The default is to not offer ECH.
182    pub(super) ech_mode: Option<EchMode>,
183}
184
185impl ClientConfig {
186    /// Create a builder for a client configuration with a specific [`CryptoProvider`].
187    ///
188    /// This will use the provider's configured ciphersuites.
189    ///
190    /// For more information, see the [`ConfigBuilder`] documentation.
191    pub fn builder(provider: Arc<CryptoProvider>) -> ConfigBuilder<Self, WantsVerifier> {
192        Self::builder_with_details(provider, Arc::new(DefaultTimeProvider))
193    }
194
195    /// Create a builder for a client configuration with no default implementation details.
196    ///
197    /// This API must be used by `no_std` users.
198    ///
199    /// You must provide a specific [`TimeProvider`].
200    ///
201    /// You must provide a specific [`CryptoProvider`].
202    ///
203    /// For more information, see the [`ConfigBuilder`] documentation.
204    pub fn builder_with_details(
205        provider: Arc<CryptoProvider>,
206        time_provider: Arc<dyn TimeProvider>,
207    ) -> ConfigBuilder<Self, WantsVerifier> {
208        ConfigBuilder {
209            state: WantsVerifier {
210                client_ech_mode: None,
211            },
212            provider,
213            time_provider,
214            side: PhantomData,
215        }
216    }
217
218    /// Create a new client connection builder for the given server name.
219    ///
220    /// The `ClientConfig` controls how the client behaves;
221    /// `name` is the name of server we want to talk to.
222    pub fn connect(self: &Arc<Self>, server_name: ServerName<'static>) -> ClientConnectionBuilder {
223        ClientConnectionBuilder {
224            config: self.clone(),
225            name: server_name,
226            alpn_protocols: None,
227        }
228    }
229
230    /// Access configuration options whose use is dangerous and requires
231    /// extra care.
232    pub fn dangerous(&mut self) -> danger::DangerousClientConfig<'_> {
233        danger::DangerousClientConfig { cfg: self }
234    }
235
236    /// Return the FIPS validation status for connections made with this configuration.
237    ///
238    /// This is different from [`CryptoProvider::fips()`]: [`CryptoProvider::fips()`]
239    /// is concerned only with cryptography, whereas this _also_ covers TLS-level
240    /// configuration that NIST recommends, as well as ECH HPKE suites if applicable.
241    pub fn fips(&self) -> FipsStatus {
242        if !self.require_ems {
243            return FipsStatus::Unvalidated;
244        }
245
246        let status = self.domain.provider.fips();
247        match &self.ech_mode {
248            Some(ech) => Ord::min(status, ech.fips()),
249            None => status,
250        }
251    }
252
253    /// Return the crypto provider used to construct this client configuration.
254    pub fn provider(&self) -> &Arc<CryptoProvider> {
255        &self.domain.provider
256    }
257
258    /// Return the resolver for this client configuration.
259    ///
260    /// This is the object that determines which credentials to use for client
261    /// authentication.
262    pub fn resolver(&self) -> &Arc<dyn ClientCredentialResolver> {
263        &self.domain.client_auth_cert_resolver
264    }
265
266    /// Return the verifier for this client configuration.
267    ///
268    /// This is the object that determines how server certificates are verified.
269    pub fn verifier(&self) -> &Arc<dyn verify::ServerVerifier> {
270        &self.domain.verifier
271    }
272
273    pub(crate) fn supports_version(&self, v: ProtocolVersion) -> bool {
274        self.domain.provider.supports_version(v)
275    }
276
277    pub(super) fn find_cipher_suite(&self, suite: CipherSuite) -> Option<SupportedCipherSuite> {
278        self.domain
279            .provider
280            .iter_cipher_suites()
281            .find(|&scs| scs.suite() == suite)
282    }
283
284    pub(super) fn current_time(&self) -> Result<UnixTime, Error> {
285        self.domain
286            .time_provider
287            .current_time()
288            .ok_or(Error::FailedToGetCurrentTime)
289    }
290
291    /// A hash which partitions this config's use of the [`Self::resumption`] store.
292    pub(super) fn config_hash(&self) -> [u8; 32] {
293        self.domain.config_hash
294    }
295}
296
297struct HashAdapter<'a>(&'a mut dyn hash::Context);
298
299impl Hasher for HashAdapter<'_> {
300    fn finish(&self) -> u64 {
301        // SAFETY: this is private to `SecurityDomain::new`, which guarantees `hash::Output`
302        // is at least 32 bytes.
303        u64::from_be_bytes(
304            self.0.fork_finish().as_ref()[..8]
305                .try_into()
306                .unwrap(),
307        )
308    }
309
310    fn write(&mut self, bytes: &[u8]) {
311        self.0.update(bytes)
312    }
313}
314
315/// Client session data store for possible future resumption.
316///
317/// All data in this interface should be treated as **highly sensitive**, containing enough key
318/// material to break all security of the corresponding session.
319///
320/// `set_`, `insert_`, `remove_` and `take_` operations are mutating; this isn't
321/// expressed in the type system to allow implementations freedom in
322/// how to achieve interior mutability.  `Mutex` is a common choice.
323pub trait ClientSessionStore: fmt::Debug + Send + Sync {
324    /// Remember what `NamedGroup` the given server chose.
325    fn set_kx_hint(&self, key: ClientSessionKey<'static>, group: NamedGroup);
326
327    /// Value most recently passed to `set_kx_hint` for the given `key`.
328    ///
329    /// If `None` is returned, the caller chooses the first configured group, and an extra round
330    /// trip might happen if that choice is unsatisfactory to the server.
331    fn kx_hint(&self, key: &ClientSessionKey<'_>) -> Option<NamedGroup>;
332
333    /// Remember a TLS1.2 session, allowing resumption of this connection in the future.
334    ///
335    /// At most one of these per session key can be remembered at a time.
336    fn set_tls12_session(&self, key: ClientSessionKey<'static>, value: Tls12Session);
337
338    /// Get the most recently saved TLS1.2 session for `key` provided to `set_tls12_session`.
339    fn tls12_session(&self, key: &ClientSessionKey<'_>) -> Option<Tls12Session>;
340
341    /// Remove and forget any saved TLS1.2 session for `key`.
342    fn remove_tls12_session(&self, key: &ClientSessionKey<'static>);
343
344    /// Remember a TLS1.3 ticket, allowing resumption of this connection in the future.
345    ///
346    /// This can be called multiple times for a given session, allowing multiple independent tickets
347    /// to be valid at once.  The number of times this is called is controlled by the server, so
348    /// implementations of this trait should apply a reasonable bound of how many items are stored
349    /// simultaneously.
350    fn insert_tls13_ticket(&self, key: ClientSessionKey<'static>, value: Tls13Session);
351
352    /// Return a TLS1.3 ticket previously provided to `insert_tls13_ticket()`.
353    ///
354    /// Implementations of this trait must return each value provided to `insert_tls13_ticket()` _at most once_.
355    fn take_tls13_ticket(&self, key: &ClientSessionKey<'static>) -> Option<Tls13Session>;
356}
357
358/// Identifies a security context and server in the [`ClientSessionStore`] interface.
359#[derive(Clone, Debug, Eq, Hash, PartialEq)]
360#[non_exhaustive]
361pub struct ClientSessionKey<'a> {
362    /// A hash to partition the client storage between different security domains.
363    pub config_hash: [u8; 32],
364
365    /// Transport-level identity of the server.
366    pub server_name: ServerName<'a>,
367}
368
369impl ClientSessionKey<'_> {
370    /// Copy the value to own its contents.
371    pub fn to_owned(&self) -> ClientSessionKey<'static> {
372        let Self {
373            config_hash,
374            server_name,
375        } = self;
376        ClientSessionKey {
377            config_hash: *config_hash,
378            server_name: server_name.to_owned(),
379        }
380    }
381}
382
383/// A trait for the ability to choose a certificate chain and
384/// private key for the purposes of client authentication.
385pub trait ClientCredentialResolver: fmt::Debug + Send + Sync {
386    /// Resolve a client certificate chain/private key to use as the client's identity.
387    ///
388    /// The `SelectedCredential` returned from this method contains an identity and a
389    /// one-time-use [`Signer`] wrapping the private key. This is usually obtained via a
390    /// [`Credentials`], on which an implementation can call [`Credentials::signer()`].
391    /// An implementation can either store long-lived [`Credentials`] values, or instantiate
392    /// them as needed using one of its constructors.
393    ///
394    /// Return `None` to continue the handshake without any client
395    /// authentication.  The server may reject the handshake later
396    /// if it requires authentication.
397    ///
398    /// [RFC 5280 A.1]: https://www.rfc-editor.org/rfc/rfc5280#appendix-A.1
399    ///
400    /// [`Credentials`]: crate::crypto::Credentials
401    /// [`Credentials::signer()`]: crate::crypto::Credentials::signer
402    /// [`Signer`]: crate::crypto::Signer
403    fn resolve(&self, request: &CredentialRequest<'_>) -> Option<SelectedCredential>;
404
405    /// Returns which [`CertificateType`]s this resolver supports.
406    ///
407    /// Should return the empty slice if the resolver does not have any credentials to send.
408    /// Implementations should return the same value every time.
409    ///
410    /// See [RFC 7250](https://tools.ietf.org/html/rfc7250) for more information.
411    fn supported_certificate_types(&self) -> &'static [CertificateType];
412
413    /// Instance configuration should be input to `h`.
414    fn hash_config(&self, h: &mut dyn Hasher);
415}
416
417/// Context from the server to inform client credential selection.
418pub struct CredentialRequest<'a> {
419    pub(super) negotiated_type: CertificateType,
420    pub(super) root_hint_subjects: &'a [DistinguishedName],
421    pub(super) signature_schemes: &'a [SignatureScheme],
422}
423
424impl CredentialRequest<'_> {
425    /// List of certificate authority subject distinguished names provided by the server.
426    ///
427    /// If the list is empty, the client should send whatever certificate it has. The hints
428    /// are expected to be DER-encoded X.500 distinguished names, per [RFC 5280 A.1]. Note that
429    /// the encoding comes from the server and has not been validated by rustls.
430    ///
431    /// See [`DistinguishedName`] for more information on decoding with external crates like
432    /// `x509-parser`.
433    ///
434    /// [`DistinguishedName`]: crate::DistinguishedName
435    pub fn root_hint_subjects(&self) -> &[DistinguishedName] {
436        self.root_hint_subjects
437    }
438
439    /// Get the compatible signature schemes.
440    pub fn signature_schemes(&self) -> &[SignatureScheme] {
441        self.signature_schemes
442    }
443
444    /// The negotiated certificate type.
445    ///
446    /// If the server does not support [RFC 7250], this will be `CertificateType::X509`.
447    ///
448    /// [RFC 7250]: https://tools.ietf.org/html/rfc7250
449    pub fn negotiated_type(&self) -> CertificateType {
450        self.negotiated_type
451    }
452}
453
454/// Items that affect the fundamental security properties of a connection.
455///
456/// This is its own type because `config_hash` depends on the other fields:
457/// fields therefore should not be mutated, but an entire object created
458/// through [`Self::new`] for any edits.
459#[derive(Clone, Debug)]
460pub(super) struct SecurityDomain {
461    /// Provides the current system time
462    time_provider: Arc<dyn TimeProvider>,
463
464    /// Source of randomness and other crypto.
465    provider: Arc<CryptoProvider>,
466
467    /// How to verify the server certificate chain.
468    verifier: Arc<dyn verify::ServerVerifier>,
469
470    /// How to decide what client auth certificate/keys to use.
471    client_auth_cert_resolver: Arc<dyn ClientCredentialResolver>,
472
473    config_hash: [u8; 32],
474}
475
476impl SecurityDomain {
477    pub(crate) fn new(
478        provider: Arc<CryptoProvider>,
479        client_auth_cert_resolver: Arc<dyn ClientCredentialResolver + 'static>,
480        verifier: Arc<dyn verify::ServerVerifier + 'static>,
481        time_provider: Arc<dyn TimeProvider + 'static>,
482    ) -> Self {
483        // Use a hash function that outputs at least 32 bytes.
484        let hash = provider
485            .iter_cipher_suites()
486            .map(|cs| cs.hash_provider())
487            .find(|h| h.output_len() >= 32)
488            .expect("no suitable cipher suite available (with |H| >= 32)"); // this is -- in practice -- all cipher suites
489
490        let mut h = hash.start();
491        let mut adapter = HashAdapter(h.as_mut());
492
493        // Include TypeId of impl, so two different types with different non-configured
494        // behavior do not collide even if their `hash_config()`s are the same.
495        client_auth_cert_resolver
496            .type_id()
497            .hash(&mut DynHasher(&mut adapter));
498        client_auth_cert_resolver.hash_config(&mut adapter);
499
500        verifier
501            .type_id()
502            .hash(&mut DynHasher(&mut adapter));
503        verifier.hash_config(&mut adapter);
504
505        time_provider
506            .type_id()
507            .hash(&mut DynHasher(&mut adapter));
508
509        let config_hash = h.finish().as_ref()[..32]
510            .try_into()
511            .unwrap();
512
513        Self {
514            time_provider,
515            provider,
516            verifier,
517            client_auth_cert_resolver,
518            config_hash,
519        }
520    }
521
522    fn with_verifier(&self, verifier: Arc<dyn verify::ServerVerifier + 'static>) -> Self {
523        let Self {
524            time_provider,
525            provider,
526            verifier: _,
527            client_auth_cert_resolver,
528            config_hash: _,
529        } = self;
530        Self::new(
531            provider.clone(),
532            client_auth_cert_resolver.clone(),
533            verifier,
534            time_provider.clone(),
535        )
536    }
537}
538
539/// Configuration for how/when a client is allowed to resume a previous session.
540#[derive(Clone, Debug)]
541pub struct Resumption {
542    /// How we store session data or tickets. The default is to use an in-memory
543    /// [super::handy::ClientSessionMemoryCache].
544    pub(super) store: Arc<dyn ClientSessionStore>,
545
546    /// What mechanism is used for resuming a TLS 1.2 session.
547    pub(super) tls12_resumption: Tls12Resumption,
548}
549
550impl Resumption {
551    /// Create a new `Resumption` that stores data for the given number of sessions in memory.
552    ///
553    /// This is the default `Resumption` choice, and enables resuming a TLS 1.2 session with
554    /// a session id or RFC 5077 ticket.
555    pub fn in_memory_sessions(num: usize) -> Self {
556        Self {
557            store: Arc::new(ClientSessionMemoryCache::new(num)),
558            tls12_resumption: Tls12Resumption::SessionIdOrTickets,
559        }
560    }
561
562    /// Use a custom [`ClientSessionStore`] implementation to store sessions.
563    ///
564    /// By default, enables resuming a TLS 1.2 session with a session id or RFC 5077 ticket.
565    pub fn store(store: Arc<dyn ClientSessionStore>) -> Self {
566        Self {
567            store,
568            tls12_resumption: Tls12Resumption::SessionIdOrTickets,
569        }
570    }
571
572    /// Disable all use of session resumption.
573    pub fn disabled() -> Self {
574        Self {
575            store: Arc::new(NoClientSessionStorage),
576            tls12_resumption: Tls12Resumption::Disabled,
577        }
578    }
579
580    /// Configure whether TLS 1.2 sessions may be resumed, and by what mechanism.
581    ///
582    /// This is meaningless if you've disabled resumption entirely, which is the case in `no-std`
583    /// contexts.
584    pub fn tls12_resumption(mut self, tls12: Tls12Resumption) -> Self {
585        self.tls12_resumption = tls12;
586        self
587    }
588}
589
590impl Default for Resumption {
591    /// Create an in-memory session store resumption with up to 256 server names, allowing
592    /// a TLS 1.2 session to resume with a session id or RFC 5077 ticket.
593    fn default() -> Self {
594        Self::in_memory_sessions(256)
595    }
596}
597
598/// What mechanisms to support for resuming a TLS 1.2 session.
599#[non_exhaustive]
600#[derive(Clone, Copy, Debug, PartialEq)]
601pub enum Tls12Resumption {
602    /// Disable 1.2 resumption.
603    Disabled,
604    /// Support 1.2 resumption using session ids only.
605    SessionIdOnly,
606    /// Support 1.2 resumption using session ids or RFC 5077 tickets.
607    ///
608    /// See[^1] for why you might like to disable RFC 5077 by instead choosing the `SessionIdOnly`
609    /// option. Note that TLS 1.3 tickets do not have those issues.
610    ///
611    /// [^1]: <https://words.filippo.io/we-need-to-talk-about-session-tickets/>
612    SessionIdOrTickets,
613}
614
615/// Number of TLS 1.3 session tickets to request via the [RFC 9149]
616/// `ticket_request` extension.
617///
618/// [RFC 9149]: https://datatracker.ietf.org/doc/html/rfc9149
619#[expect(clippy::exhaustive_structs)]
620#[derive(Clone, Copy, Debug, PartialEq)]
621pub struct TicketRequest {
622    /// Tickets desired when the server negotiates a new connection.
623    ///
624    /// RFC 9149 recommends setting this to the desired number of tickets
625    /// and `resumption_count` to 0 for initial connections.
626    pub new_session_count: u8,
627
628    /// Tickets desired when the server resumes using a presented ticket.
629    ///
630    /// A value of 1 is a good default for primed caches. Clients racing
631    /// multiple connections may want a higher value.
632    pub resumption_count: u8,
633}
634
635impl ConfigBuilder<ClientConfig, WantsVerifier> {
636    /// Choose how to verify server certificates.
637    ///
638    /// Using this function does not configure revocation.  If you wish to
639    /// configure revocation, instead use:
640    ///
641    /// ```diff
642    /// - .with_root_certificates(root_store)
643    /// + .with_webpki_verifier(
644    /// +   WebPkiServerVerifier::builder(root_store, crypto_provider)
645    /// +   .with_crls(...)
646    /// +   .build()?
647    /// + )
648    /// ```
649    #[cfg(feature = "webpki")]
650    pub fn with_root_certificates(
651        self,
652        root_store: impl Into<Arc<webpki::RootCertStore>>,
653    ) -> ConfigBuilder<ClientConfig, WantsClientCert> {
654        let algorithms = self
655            .provider
656            .signature_verification_algorithms;
657        self.with_webpki_verifier(
658            WebPkiServerVerifier::new_without_revocation(root_store, algorithms).into(),
659        )
660    }
661
662    /// Choose how to verify server certificates using a webpki verifier.
663    ///
664    /// See [`webpki::WebPkiServerVerifier::builder`] for more information.
665    #[cfg(feature = "webpki")]
666    pub fn with_webpki_verifier(
667        self,
668        verifier: Arc<WebPkiServerVerifier>,
669    ) -> ConfigBuilder<ClientConfig, WantsClientCert> {
670        ConfigBuilder {
671            state: WantsClientCert {
672                verifier,
673                client_ech_mode: self.state.client_ech_mode,
674            },
675            provider: self.provider,
676            time_provider: self.time_provider,
677            side: PhantomData,
678        }
679    }
680
681    /// Enable Encrypted Client Hello (ECH) in the given mode.
682    ///
683    /// This requires TLS 1.3 as the only supported protocol version to meet the requirement
684    /// to support ECH.  At the end, the config building process will return an error if either
685    /// TLS1.3 _is not_ supported by the provider, or TLS1.2 _is_ supported.
686    ///
687    /// The `ClientConfig` that will be produced by this builder will be specific to the provided
688    /// [`crate::client::EchConfig`] and may not be appropriate for all connections made by the program.
689    /// In this case the configuration should only be shared by connections intended for domains
690    /// that offer the provided [`crate::client::EchConfig`] in their DNS zone.
691    pub fn with_ech(mut self, mode: EchMode) -> Self {
692        self.state.client_ech_mode = Some(mode);
693        self
694    }
695
696    /// Access configuration options whose use is dangerous and requires
697    /// extra care.
698    pub fn dangerous(self) -> danger::DangerousClientConfigBuilder {
699        danger::DangerousClientConfigBuilder { cfg: self }
700    }
701}
702
703/// A config builder state where the caller needs to supply whether and how to provide a client
704/// certificate.
705///
706/// For more information, see the [`ConfigBuilder`] documentation.
707#[derive(Clone)]
708pub struct WantsClientCert {
709    verifier: Arc<dyn verify::ServerVerifier>,
710    client_ech_mode: Option<EchMode>,
711}
712
713impl ConfigBuilder<ClientConfig, WantsClientCert> {
714    /// Sets a single certificate chain and matching private key for use
715    /// in client authentication.
716    ///
717    /// `cert_chain` is a vector of DER-encoded certificates.
718    /// `key_der` is a DER-encoded private key as PKCS#1, PKCS#8, or SEC1. The
719    /// `aws-lc-rs` and `ring` [`CryptoProvider`]s support
720    /// all three encodings, but other `CryptoProviders` may not.
721    ///
722    /// This function fails if `key_der` is invalid.
723    #[cfg(feature = "webpki")]
724    pub fn with_client_auth_cert(
725        self,
726        identity: Arc<Identity<'static>>,
727        key_der: PrivateKeyDer<'static>,
728    ) -> Result<ClientConfig, Error> {
729        let credentials = Credentials::from_der(identity, key_der, &self.provider)?;
730        self.with_client_credential_resolver(Arc::new(SingleCredential::from(credentials)))
731    }
732
733    /// Do not support client auth.
734    pub fn with_no_client_auth(self) -> Result<ClientConfig, Error> {
735        self.with_client_credential_resolver(Arc::new(FailResolveClientCert {}))
736    }
737
738    /// Sets a custom [`ClientCredentialResolver`].
739    pub fn with_client_credential_resolver(
740        self,
741        client_auth_cert_resolver: Arc<dyn ClientCredentialResolver>,
742    ) -> Result<ClientConfig, Error> {
743        self.provider.consistency_check()?;
744
745        if self.state.client_ech_mode.is_some() {
746            match (
747                self.provider
748                    .tls12_cipher_suites
749                    .is_empty(),
750                self.provider
751                    .tls13_cipher_suites
752                    .is_empty(),
753            ) {
754                (_, true) => return Err(ApiMisuse::EchRequiresTls13Support.into()),
755                (false, _) => return Err(ApiMisuse::EchForbidsTls12Support.into()),
756                (true, false) => {}
757            };
758        }
759
760        let require_ems = !matches!(self.provider.fips(), FipsStatus::Unvalidated);
761        Ok(ClientConfig {
762            alpn_protocols: Vec::new(),
763            check_selected_alpn: true,
764            resumption: Resumption::default(),
765            max_fragment_size: None,
766            enable_sni: true,
767            key_log: Arc::new(NoKeyLog {}),
768            enable_secret_extraction: false,
769            enable_early_data: false,
770            require_ems,
771            send_ticket_request: None,
772            domain: SecurityDomain::new(
773                self.provider,
774                client_auth_cert_resolver,
775                self.state.verifier,
776                self.time_provider,
777            ),
778            cert_decompressors: compress::default_cert_decompressors().to_vec(),
779            cert_compressors: compress::default_cert_compressors().to_vec(),
780            cert_compression_cache: Arc::new(compress::CompressionCache::default()),
781            ech_mode: self.state.client_ech_mode,
782        })
783    }
784}
785
786/// Container for unsafe APIs
787pub(super) mod danger {
788    use core::marker::PhantomData;
789
790    use crate::client::WantsClientCert;
791    use crate::client::config::ClientConfig;
792    use crate::sync::Arc;
793    use crate::verify::ServerVerifier;
794    use crate::{ConfigBuilder, WantsVerifier};
795
796    /// Accessor for dangerous configuration options.
797    #[derive(Debug)]
798    pub struct DangerousClientConfig<'a> {
799        /// The underlying ClientConfig
800        pub(super) cfg: &'a mut ClientConfig,
801    }
802
803    impl DangerousClientConfig<'_> {
804        /// Overrides the default `ServerVerifier` with something else.
805        pub fn set_certificate_verifier(&mut self, verifier: Arc<dyn ServerVerifier>) {
806            self.cfg.domain = self.cfg.domain.with_verifier(verifier);
807        }
808    }
809
810    /// Accessor for dangerous configuration options.
811    #[derive(Debug)]
812    pub struct DangerousClientConfigBuilder {
813        /// The underlying ClientConfigBuilder
814        pub(super) cfg: ConfigBuilder<ClientConfig, WantsVerifier>,
815    }
816
817    impl DangerousClientConfigBuilder {
818        /// Set a custom certificate verifier.
819        pub fn with_custom_certificate_verifier(
820            self,
821            verifier: Arc<dyn ServerVerifier>,
822        ) -> ConfigBuilder<ClientConfig, WantsClientCert> {
823            ConfigBuilder {
824                state: WantsClientCert {
825                    verifier,
826                    client_ech_mode: self.cfg.state.client_ech_mode,
827                },
828                provider: self.cfg.provider,
829                time_provider: self.cfg.time_provider,
830                side: PhantomData,
831            }
832        }
833    }
834}