Skip to main content

rustls/client/
mod.rs

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