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