Skip to main content

rustls/client/
mod.rs

1use alloc::vec::Vec;
2use core::ops::Deref;
3use core::time::Duration;
4
5use pki_types::UnixTime;
6use zeroize::Zeroizing;
7
8use crate::crypto::cipher::Payload;
9use crate::crypto::{CipherSuite, CryptoProvider, Identity, SelectedCredential, SignatureScheme};
10use crate::enums::{ApplicationProtocol, CertificateType};
11use crate::error::{ApiMisuse, Error, InvalidMessage};
12use crate::msgs::{
13    CertificateChain, Codec, ExtensionType, MaybeEmpty, NewSessionTicketPayloadTls13, Reader,
14    SessionId, SizedPayload,
15};
16use crate::sync::Arc;
17use crate::tls13::Tls13ProtocolSuite;
18use crate::tracing::{debug, trace};
19use crate::verify::{DistinguishedName, VerifiedIdentity};
20#[cfg(feature = "webpki")]
21pub use crate::webpki::{
22    ServerVerifierBuilder, VerifierBuilderError, WebPkiServerVerifier,
23    verify_identity_signed_by_trust_anchor, verify_server_name,
24};
25use crate::{Tls12CipherSuite, compress};
26
27mod config;
28pub use config::{
29    ClientConfig, ClientCredentialResolver, ClientSessionKey, ClientSessionStore,
30    CredentialRequest, Resumption, TicketRequest, Tls12Resumption, WantsClientCert,
31};
32
33mod connection;
34pub use connection::{ClientConnection, ClientConnectionBuilder, ClientSide, WriteEarlyData};
35
36mod ech;
37pub use ech::{EchConfig, EchGreaseConfig, EchMode, EchStatus};
38
39mod handy;
40pub use handy::ClientSessionMemoryCache;
41
42mod hs;
43pub(crate) use hs::ClientHandler;
44
45mod tls12;
46pub(crate) use tls12::TLS12_HANDLER;
47
48mod tls13;
49pub(crate) use tls13::TLS13_HANDLER;
50
51/// Dangerous configuration that should be audited and used with extreme care.
52pub mod danger {
53    pub use super::config::danger::{DangerousClientConfig, DangerousClientConfigBuilder};
54    pub use crate::verify::{
55        HandshakeSignatureValid, ServerIdentity, ServerVerifier, SignatureVerificationInput,
56    };
57}
58
59#[cfg(test)]
60mod test;
61
62pub(crate) struct Retrieved<T> {
63    pub(crate) value: T,
64    retrieved_at: UnixTime,
65}
66
67impl<T> Retrieved<T> {
68    pub(crate) fn new(value: T, retrieved_at: UnixTime) -> Self {
69        Self {
70            value,
71            retrieved_at,
72        }
73    }
74
75    pub(crate) fn map<M>(&self, f: impl FnOnce(&T) -> Option<&M>) -> Option<Retrieved<&M>> {
76        Some(Retrieved {
77            value: f(&self.value)?,
78            retrieved_at: self.retrieved_at,
79        })
80    }
81}
82
83impl Retrieved<&Tls13Session> {
84    pub(crate) fn obfuscated_ticket_age(&self) -> u32 {
85        let age_secs = self
86            .retrieved_at
87            .as_secs()
88            .saturating_sub(self.value.common.epoch);
89        // nb. tickets have an upper age limit of ~7 days, well short of the 49 days here
90        let age_millis = u32::try_from(age_secs)
91            .unwrap_or(u32::MAX)
92            .saturating_mul(1000);
93        age_millis.wrapping_add(self.value.age_add)
94    }
95}
96
97impl<T: Deref<Target = ClientSessionCommon>> Retrieved<T> {
98    pub(crate) fn has_expired(&self) -> bool {
99        let common = &*self.value;
100        common.lifetime != Duration::ZERO
101            && common
102                .epoch
103                .saturating_add(common.lifetime.as_secs())
104                < self.retrieved_at.as_secs()
105    }
106}
107
108impl<T> Deref for Retrieved<T> {
109    type Target = T;
110
111    fn deref(&self) -> &Self::Target {
112        &self.value
113    }
114}
115
116/// A stored TLS 1.3 client session value.
117#[derive(Debug)]
118pub struct Tls13Session {
119    suite: Tls13ProtocolSuite,
120    secret: Zeroizing<SizedPayload<'static, u8>>,
121    pub(crate) age_add: u32,
122    max_early_data_size: u32,
123    pub(crate) common: ClientSessionCommon,
124    quic_params: SizedPayload<'static, u16, MaybeEmpty>,
125}
126
127impl Tls13Session {
128    /// Decode a ticket from the given bytes.
129    #[cfg(test)]
130    pub fn from_slice(bytes: &[u8], provider: &CryptoProvider) -> Result<Self, Error> {
131        Reader::new(bytes).all("Tls13Session", |reader| {
132            let suite = CipherSuite::read(reader)?;
133            let suite = provider
134                .tls13_cipher_suites
135                .iter()
136                .find(|s| s.common.suite == suite)
137                .ok_or(ApiMisuse::ResumingFromUnknownCipherSuite(suite))?;
138
139            Ok(Self {
140                suite: Tls13ProtocolSuite::Tcp(suite),
141                secret: Zeroizing::new(SizedPayload::<u8>::read(reader)?.into_owned()),
142                age_add: u32::read(reader)?,
143                max_early_data_size: u32::read(reader)?,
144                common: ClientSessionCommon::read(reader)?,
145                quic_params: SizedPayload::<u16, MaybeEmpty>::read(reader)?.into_owned(),
146            })
147        })
148    }
149
150    pub(crate) fn new(
151        ticket: &NewSessionTicketPayloadTls13,
152        input: Tls13ClientSessionInput,
153        secret: &[u8],
154        time_now: UnixTime,
155    ) -> Self {
156        Self {
157            suite: input.suite,
158            secret: Zeroizing::new(secret.to_vec().into()),
159            age_add: ticket.age_add,
160            max_early_data_size: ticket
161                .extensions
162                .max_early_data_size
163                .unwrap_or_default(),
164            common: ClientSessionCommon::new(
165                ticket.ticket.clone(),
166                time_now,
167                ticket.lifetime,
168                input.peer_identity,
169            ),
170            quic_params: input
171                .quic_params
172                .unwrap_or_else(|| SizedPayload::from(Payload::new(Vec::new()))),
173        }
174    }
175
176    /// Encode this ticket into `buf` for persistence.
177    pub fn encode(&self, buf: &mut Vec<u8>) {
178        self.suite
179            .suite()
180            .common
181            .suite
182            .encode(buf);
183        self.secret.encode(buf);
184        buf.extend_from_slice(&self.age_add.to_be_bytes());
185        buf.extend_from_slice(&self.max_early_data_size.to_be_bytes());
186        self.common.encode(buf);
187        self.quic_params.encode(buf);
188    }
189
190    /// Test only: replace `max_early_data_size` with `new`
191    #[doc(hidden)]
192    pub fn _reset_max_early_data_size(&mut self, expected: u32, desired: u32) {
193        assert_eq!(
194            self.max_early_data_size, expected,
195            "max_early_data_size was not expected value"
196        );
197        self.max_early_data_size = desired;
198    }
199
200    /// Test only: rewind epoch by `delta` seconds.
201    #[doc(hidden)]
202    pub fn rewind_epoch(&mut self, delta: u32) {
203        self.common.epoch -= delta as u64;
204    }
205}
206
207impl Deref for Tls13Session {
208    type Target = ClientSessionCommon;
209
210    fn deref(&self) -> &Self::Target {
211        &self.common
212    }
213}
214
215/// A "template" for future TLS1.3 client session values.
216#[derive(Clone)]
217pub(crate) struct Tls13ClientSessionInput {
218    pub(crate) suite: Tls13ProtocolSuite,
219    pub(crate) peer_identity: VerifiedIdentity<'static>,
220    pub(crate) quic_params: Option<SizedPayload<'static, u16, MaybeEmpty>>,
221}
222
223/// A stored TLS 1.2 client session value.
224#[derive(Debug, Clone)]
225pub struct Tls12Session {
226    suite: &'static Tls12CipherSuite,
227    pub(crate) session_id: SessionId,
228    master_secret: Zeroizing<[u8; 48]>,
229    extended_ms: bool,
230    #[doc(hidden)]
231    pub(crate) common: ClientSessionCommon,
232}
233
234impl Tls12Session {
235    /// Decode a ticket from the given bytes.
236    pub fn from_slice(bytes: &[u8], provider: &CryptoProvider) -> Result<Self, Error> {
237        Reader::new(bytes).all("Tls12Session", |reader| {
238            let suite = CipherSuite::read(reader)?;
239            let suite = provider
240                .tls12_cipher_suites
241                .iter()
242                .find(|s| s.common.suite == suite)
243                .ok_or(ApiMisuse::ResumingFromUnknownCipherSuite(suite))?;
244
245            Ok(Self {
246                suite: *suite,
247                session_id: SessionId::read(reader)?,
248                master_secret: Zeroizing::new(
249                    reader
250                        .take_array("MasterSecret")
251                        .copied()?,
252                ),
253                extended_ms: matches!(u8::read(reader)?, 1),
254                common: ClientSessionCommon::read(reader)?,
255            })
256        })
257    }
258
259    pub(crate) fn new(
260        suite: &'static Tls12CipherSuite,
261        session_id: SessionId,
262        ticket: Arc<SizedPayload<'static, u16, MaybeEmpty>>,
263        master_secret: &[u8; 48],
264        peer_identity: VerifiedIdentity<'static>,
265        time_now: UnixTime,
266        lifetime: Duration,
267        extended_ms: bool,
268    ) -> Self {
269        Self {
270            suite,
271            session_id,
272            master_secret: Zeroizing::new(*master_secret),
273            extended_ms,
274            common: ClientSessionCommon::new(ticket, time_now, lifetime, peer_identity),
275        }
276    }
277
278    /// Encode this ticket into `buf` for persistence.
279    pub fn encode(&self, buf: &mut Vec<u8>) {
280        self.suite.common.suite.encode(buf);
281        self.session_id.encode(buf);
282        buf.extend_from_slice(&*self.master_secret);
283        buf.push(self.extended_ms as u8);
284        self.common.encode(buf);
285    }
286
287    /// Test only: rewind epoch by `delta` seconds.
288    #[doc(hidden)]
289    pub fn rewind_epoch(&mut self, delta: u32) {
290        self.common.epoch -= delta as u64;
291    }
292}
293
294impl Deref for Tls12Session {
295    type Target = ClientSessionCommon;
296
297    fn deref(&self) -> &Self::Target {
298        &self.common
299    }
300}
301
302/// Common data for stored client sessions.
303#[derive(Debug, Clone)]
304pub struct ClientSessionCommon {
305    pub(crate) ticket: Arc<SizedPayload<'static, u16>>,
306    pub(crate) epoch: u64,
307    lifetime: Duration,
308    peer_identity: Arc<VerifiedIdentity<'static>>,
309}
310
311impl ClientSessionCommon {
312    pub(crate) fn new(
313        ticket: Arc<SizedPayload<'static, u16>>,
314        time_now: UnixTime,
315        lifetime: Duration,
316        peer_identity: VerifiedIdentity<'static>,
317    ) -> Self {
318        Self {
319            ticket,
320            epoch: time_now.as_secs(),
321            lifetime: Ord::min(lifetime, MAX_TICKET_LIFETIME),
322            peer_identity: Arc::new(peer_identity),
323        }
324    }
325
326    pub(crate) fn peer_identity(&self) -> &Identity<'static> {
327        &self.peer_identity
328    }
329
330    pub(crate) fn ticket(&self) -> &[u8] {
331        (*self.ticket).bytes()
332    }
333}
334
335impl<'a> Codec<'a> for ClientSessionCommon {
336    fn encode(&self, bytes: &mut Vec<u8>) {
337        self.ticket.encode(bytes);
338        bytes.extend_from_slice(&self.epoch.to_be_bytes());
339        bytes.extend_from_slice(&self.lifetime.as_secs().to_be_bytes());
340        self.peer_identity.encode(bytes);
341    }
342
343    fn read(r: &mut Reader<'a>) -> Result<Self, InvalidMessage> {
344        Ok(Self {
345            ticket: Arc::new(SizedPayload::read(r)?.into_owned()),
346            epoch: u64::read(r)?,
347            lifetime: Duration::from_secs(u64::read(r)?),
348            peer_identity: Arc::new(VerifiedIdentity::assertion(Identity::read(r)?.into_owned())),
349        })
350    }
351}
352
353#[derive(Debug)]
354struct ServerCertDetails {
355    cert_chain: CertificateChain<'static>,
356    ocsp_response: Vec<u8>,
357}
358
359impl ServerCertDetails {
360    fn new(cert_chain: CertificateChain<'static>, ocsp_response: Vec<u8>) -> Self {
361        Self {
362            cert_chain,
363            ocsp_response,
364        }
365    }
366}
367
368struct ClientHelloDetails {
369    alpn_protocols: Vec<ApplicationProtocol<'static>>,
370    sent_extensions: Vec<ExtensionType>,
371    extension_order_seed: u16,
372    offered_cert_compression: bool,
373    offered_cipher_suites: Vec<CipherSuite>,
374}
375
376impl ClientHelloDetails {
377    fn new(alpn_protocols: Vec<ApplicationProtocol<'static>>, extension_order_seed: u16) -> Self {
378        Self {
379            alpn_protocols,
380            sent_extensions: Vec::new(),
381            extension_order_seed,
382            offered_cert_compression: false,
383            offered_cipher_suites: Vec::new(),
384        }
385    }
386
387    fn server_sent_unsolicited_extensions(
388        &self,
389        received_exts: impl Iterator<Item = ExtensionType>,
390        allowed_unsolicited: &[ExtensionType],
391    ) -> bool {
392        for ext_type in received_exts {
393            if !self.sent_extensions.contains(&ext_type) && !allowed_unsolicited.contains(&ext_type)
394            {
395                trace!("Unsolicited extension {ext_type:?}");
396                return true;
397            }
398        }
399
400        false
401    }
402}
403
404enum ClientAuthDetails {
405    /// Send an empty `Certificate` and no `CertificateVerify`.
406    Empty { auth_context_tls13: Option<Vec<u8>> },
407    /// Send a non-empty `Certificate` and a `CertificateVerify`.
408    Verify {
409        credentials: SelectedCredential,
410        auth_context_tls13: Option<Vec<u8>>,
411        compressor: Option<&'static dyn compress::CertCompressor>,
412    },
413}
414
415impl ClientAuthDetails {
416    fn resolve(
417        negotiated_type: CertificateType,
418        resolver: &dyn ClientCredentialResolver,
419        root_hint_subjects: Option<&[DistinguishedName]>,
420        signature_schemes: &[SignatureScheme],
421        auth_context_tls13: Option<Vec<u8>>,
422        compressor: Option<&'static dyn compress::CertCompressor>,
423    ) -> Self {
424        let server_hello = CredentialRequest {
425            negotiated_type,
426            root_hint_subjects: root_hint_subjects.unwrap_or_default(),
427            signature_schemes,
428        };
429
430        if let Some(credentials) = resolver.resolve(&server_hello) {
431            debug!("Attempting client auth");
432            return Self::Verify {
433                credentials,
434                auth_context_tls13,
435                compressor,
436            };
437        }
438
439        debug!("Client auth requested but no cert/sigscheme available");
440        Self::Empty { auth_context_tls13 }
441    }
442}
443
444static MAX_TICKET_LIFETIME: Duration = Duration::from_secs(7 * 24 * 60 * 60);