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::crypto::cipher::Payload;
10use crate::crypto::{CipherSuite, CryptoProvider, Identity, SelectedCredential, SignatureScheme};
11use crate::enums::{ApplicationProtocol, CertificateType};
12use crate::error::{ApiMisuse, Error, InvalidMessage};
13use crate::msgs::{
14 CertificateChain, Codec, ExtensionType, MaybeEmpty, NewSessionTicketPayloadTls13, Reader,
15 SessionId, SizedPayload,
16};
17use crate::sync::Arc;
18use crate::tls13::Tls13ProtocolSuite;
19use crate::tracing::{debug, trace};
20use crate::verify::{DistinguishedName, VerifiedIdentity};
21#[cfg(feature = "webpki")]
22pub use crate::webpki::{
23 ServerVerifierBuilder, VerifierBuilderError, WebPkiServerVerifier,
24 verify_identity_signed_by_trust_anchor, verify_server_name,
25};
26use crate::{Tls12CipherSuite, compress};
27
28mod config;
29pub use config::{
30 ClientConfig, ClientCredentialResolver, ClientSessionKey, ClientSessionStore,
31 CredentialRequest, Resumption, TicketRequest, Tls12Resumption, WantsClientCert,
32};
33
34mod connection;
35pub use connection::{ClientConnectionBuilder, ClientHandshake, ClientSide, WriteEarlyData};
36
37mod ech;
38pub use ech::{EchConfig, EchGreaseConfig, EchMode, EchStatus};
39
40mod handy;
41pub use handy::ClientSessionMemoryCache;
42
43mod hs;
44pub(crate) use hs::{ClientHandler, ClientState};
45
46mod tls12;
47pub(crate) use tls12::TLS12_HANDLER;
48
49mod tls13;
50pub(crate) use tls13::TLS13_HANDLER;
51
52pub mod danger {
54 pub use super::config::danger::{DangerousClientConfig, DangerousClientConfigBuilder};
55 pub use crate::verify::{
56 HandshakeSignatureValid, ServerIdentity, ServerVerifier, SignatureVerificationInput,
57 };
58}
59
60#[cfg(test)]
61mod test;
62
63pub(crate) struct Retrieved<T> {
64 pub(crate) value: T,
65 retrieved_at: UnixTime,
66}
67
68impl<T> Retrieved<T> {
69 pub(crate) fn new(value: T, retrieved_at: UnixTime) -> Self {
70 Self {
71 value,
72 retrieved_at,
73 }
74 }
75
76 pub(crate) fn map<M>(&self, f: impl FnOnce(&T) -> Option<&M>) -> Option<Retrieved<&M>> {
77 Some(Retrieved {
78 value: f(&self.value)?,
79 retrieved_at: self.retrieved_at,
80 })
81 }
82}
83
84impl Retrieved<&Tls13Session> {
85 pub(crate) fn obfuscated_ticket_age(&self) -> u32 {
86 let age_secs = self
87 .retrieved_at
88 .as_secs()
89 .saturating_sub(self.value.common.epoch);
90 let age_millis = u32::try_from(age_secs)
92 .unwrap_or(u32::MAX)
93 .saturating_mul(1000);
94 age_millis.wrapping_add(self.value.age_add)
95 }
96}
97
98impl<T: Deref<Target = ClientSessionCommon>> Retrieved<T> {
99 pub(crate) fn has_expired(&self) -> bool {
100 let common = &*self.value;
101 common.lifetime != Duration::ZERO
102 && common
103 .epoch
104 .saturating_add(common.lifetime.as_secs())
105 < self.retrieved_at.as_secs()
106 }
107}
108
109impl<T> Deref for Retrieved<T> {
110 type Target = T;
111
112 fn deref(&self) -> &Self::Target {
113 &self.value
114 }
115}
116
117pub 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 #[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 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 #[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 #[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
215impl fmt::Debug for Tls13Session {
216 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
217 let Self {
218 suite,
219 secret: _,
220 age_add,
221 max_early_data_size,
222 common,
223 quic_params,
224 } = self;
225 f.debug_struct("Tls13Session")
226 .field("suite", suite)
227 .field("age_add", age_add)
228 .field("max_early_data_size", max_early_data_size)
229 .field("common", common)
230 .field("quic_params", quic_params)
231 .finish_non_exhaustive()
232 }
233}
234
235#[derive(Clone)]
237pub(crate) struct Tls13ClientSessionInput {
238 pub(crate) suite: Tls13ProtocolSuite,
239 pub(crate) peer_identity: VerifiedIdentity<'static>,
240 pub(crate) quic_params: Option<SizedPayload<'static, u16, MaybeEmpty>>,
241}
242
243#[derive(Clone)]
245pub struct Tls12Session {
246 suite: &'static Tls12CipherSuite,
247 pub(crate) session_id: SessionId,
248 master_secret: Zeroizing<[u8; 48]>,
249 extended_ms: bool,
250 #[doc(hidden)]
251 pub(crate) common: ClientSessionCommon,
252}
253
254impl Tls12Session {
255 pub fn from_slice(bytes: &[u8], provider: &CryptoProvider) -> Result<Self, Error> {
257 Reader::new(bytes).all("Tls12Session", |reader| {
258 let suite = CipherSuite::read(reader)?;
259 let suite = provider
260 .tls12_cipher_suites
261 .iter()
262 .find(|s| s.common.suite == suite)
263 .ok_or(ApiMisuse::ResumingFromUnknownCipherSuite(suite))?;
264
265 Ok(Self {
266 suite: *suite,
267 session_id: SessionId::read(reader)?,
268 master_secret: Zeroizing::new(
269 reader
270 .take_array("MasterSecret")
271 .copied()?,
272 ),
273 extended_ms: matches!(u8::read(reader)?, 1),
274 common: ClientSessionCommon::read(reader)?,
275 })
276 })
277 }
278
279 pub(crate) fn new(
280 suite: &'static Tls12CipherSuite,
281 session_id: SessionId,
282 ticket: Arc<SizedPayload<'static, u16, MaybeEmpty>>,
283 master_secret: &[u8; 48],
284 peer_identity: VerifiedIdentity<'static>,
285 time_now: UnixTime,
286 lifetime: Duration,
287 extended_ms: bool,
288 ) -> Self {
289 Self {
290 suite,
291 session_id,
292 master_secret: Zeroizing::new(*master_secret),
293 extended_ms,
294 common: ClientSessionCommon::new(ticket, time_now, lifetime, peer_identity),
295 }
296 }
297
298 pub fn encode(&self, buf: &mut Vec<u8>) {
300 self.suite.common.suite.encode(buf);
301 self.session_id.encode(buf);
302 buf.extend_from_slice(&*self.master_secret);
303 buf.push(self.extended_ms as u8);
304 self.common.encode(buf);
305 }
306
307 #[doc(hidden)]
309 pub fn rewind_epoch(&mut self, delta: u32) {
310 self.common.epoch -= delta as u64;
311 }
312}
313
314impl Deref for Tls12Session {
315 type Target = ClientSessionCommon;
316
317 fn deref(&self) -> &Self::Target {
318 &self.common
319 }
320}
321
322impl fmt::Debug for Tls12Session {
323 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
324 let Self {
325 suite,
326 session_id,
327 master_secret: _,
328 extended_ms,
329 common,
330 } = self;
331 f.debug_struct("Tls12Session")
332 .field("suite", suite)
333 .field("session_id", session_id)
334 .field("extended_ms", extended_ms)
335 .field("common", common)
336 .finish_non_exhaustive()
337 }
338}
339
340#[derive(Debug, Clone)]
342pub struct ClientSessionCommon {
343 pub(crate) ticket: Arc<SizedPayload<'static, u16>>,
344 pub(crate) epoch: u64,
345 lifetime: Duration,
346 peer_identity: Arc<VerifiedIdentity<'static>>,
347}
348
349impl ClientSessionCommon {
350 pub(crate) fn new(
351 ticket: Arc<SizedPayload<'static, u16>>,
352 time_now: UnixTime,
353 lifetime: Duration,
354 peer_identity: VerifiedIdentity<'static>,
355 ) -> Self {
356 Self {
357 ticket,
358 epoch: time_now.as_secs(),
359 lifetime: Ord::min(lifetime, MAX_TICKET_LIFETIME),
360 peer_identity: Arc::new(peer_identity),
361 }
362 }
363
364 pub(crate) fn peer_identity(&self) -> &Identity<'static> {
365 &self.peer_identity
366 }
367
368 pub(crate) fn ticket(&self) -> &[u8] {
369 (*self.ticket).bytes()
370 }
371}
372
373impl<'a> Codec<'a> for ClientSessionCommon {
374 fn encode(&self, bytes: &mut Vec<u8>) {
375 self.ticket.encode(bytes);
376 bytes.extend_from_slice(&self.epoch.to_be_bytes());
377 bytes.extend_from_slice(&self.lifetime.as_secs().to_be_bytes());
378 self.peer_identity.encode(bytes);
379 }
380
381 fn read(r: &mut Reader<'a>) -> Result<Self, InvalidMessage> {
382 Ok(Self {
383 ticket: Arc::new(SizedPayload::read(r)?.into_owned()),
384 epoch: u64::read(r)?,
385 lifetime: Duration::from_secs(u64::read(r)?),
386 peer_identity: Arc::new(VerifiedIdentity::assertion(Identity::read(r)?.into_owned())),
387 })
388 }
389}
390
391#[derive(Debug)]
392struct ServerCertDetails {
393 cert_chain: CertificateChain<'static>,
394 ocsp_response: Vec<u8>,
395}
396
397impl ServerCertDetails {
398 fn new(cert_chain: CertificateChain<'static>, ocsp_response: Vec<u8>) -> Self {
399 Self {
400 cert_chain,
401 ocsp_response,
402 }
403 }
404}
405
406struct ClientHelloDetails {
407 alpn_protocols: Vec<ApplicationProtocol<'static>>,
408 sent_extensions: Vec<ExtensionType>,
409 extension_order_seed: u16,
410 offered_cert_compression: bool,
411 offered_cipher_suites: Vec<CipherSuite>,
412}
413
414impl ClientHelloDetails {
415 fn new(alpn_protocols: Vec<ApplicationProtocol<'static>>, extension_order_seed: u16) -> Self {
416 Self {
417 alpn_protocols,
418 sent_extensions: Vec::new(),
419 extension_order_seed,
420 offered_cert_compression: false,
421 offered_cipher_suites: Vec::new(),
422 }
423 }
424
425 fn server_sent_unsolicited_extensions(
426 &self,
427 received_exts: impl Iterator<Item = ExtensionType>,
428 allowed_unsolicited: &[ExtensionType],
429 ) -> bool {
430 for ext_type in received_exts {
431 if !self.sent_extensions.contains(&ext_type) && !allowed_unsolicited.contains(&ext_type)
432 {
433 trace!("Unsolicited extension {ext_type:?}");
434 return true;
435 }
436 }
437
438 false
439 }
440}
441
442enum ClientAuthDetails {
443 Empty { auth_context_tls13: Option<Vec<u8>> },
445 Verify {
447 credentials: SelectedCredential,
448 auth_context_tls13: Option<Vec<u8>>,
449 compressor: Option<&'static dyn compress::CertCompressor>,
450 },
451}
452
453impl ClientAuthDetails {
454 fn resolve(
455 negotiated_type: CertificateType,
456 resolver: &dyn ClientCredentialResolver,
457 root_hint_subjects: Option<&[DistinguishedName]>,
458 signature_schemes: &[SignatureScheme],
459 auth_context_tls13: Option<Vec<u8>>,
460 compressor: Option<&'static dyn compress::CertCompressor>,
461 ) -> Self {
462 let server_hello = CredentialRequest {
463 negotiated_type,
464 root_hint_subjects: root_hint_subjects.unwrap_or_default(),
465 signature_schemes,
466 };
467
468 if let Some(credentials) = resolver.resolve(&server_hello) {
469 debug!("Attempting client auth");
470 return Self::Verify {
471 credentials,
472 auth_context_tls13,
473 compressor,
474 };
475 }
476
477 debug!("Client auth requested but no cert/sigscheme available");
478 Self::Empty { auth_context_tls13 }
479 }
480}
481
482static MAX_TICKET_LIFETIME: Duration = Duration::from_secs(7 * 24 * 60 * 60);
483
484#[cfg(test)]
485mod tests {
486 use alloc::format;
487
488 use pki_types::SubjectPublicKeyInfoDer;
489
490 use super::*;
491 use crate::crypto::{TEST_PROVIDER, tls12_suite};
492
493 #[test]
494 fn debug_of_session_types() {
495 let tls12 = Tls12Session::new(
496 tls12_suite(CipherSuite(0xff12), &TEST_PROVIDER),
497 SessionId::empty(),
498 Arc::new(SizedPayload::empty()),
499 &[0xa5; 48],
500 VerifiedIdentity::assertion(Identity::RawPublicKey(SubjectPublicKeyInfoDer::from(
501 &b"spki"[..],
502 ))),
503 UnixTime::since_unix_epoch(Duration::from_secs(1)),
504 Duration::from_secs(2),
505 true,
506 );
507 assert_eq!(
508 format!("{tls12:?}"),
509 "Tls12Session { suite: Tls12CipherSuite { suite: 0xff12, .. }, session_id: , extended_ms: true, common: ClientSessionCommon { ticket: , epoch: 1, lifetime: 2s, peer_identity: VerifiedIdentity(RawPublicKey(SubjectPublicKeyInfoDer(0x73706b69))) }, .. }"
510 );
511
512 let tls13 = Tls13Session::new(
513 &NewSessionTicketPayloadTls13::new(
514 Duration::from_secs(2),
515 3,
516 [4u8; 32],
517 Vec::from([5]),
518 ),
519 Tls13ClientSessionInput {
520 suite: Tls13ProtocolSuite::Tcp(TEST_PROVIDER.tls13_cipher_suites[0]),
521 peer_identity: VerifiedIdentity::assertion(Identity::RawPublicKey(
522 SubjectPublicKeyInfoDer::from(&b"spki"[..]),
523 )),
524 quic_params: None,
525 },
526 &[0xa5; 32],
527 UnixTime::since_unix_epoch(Duration::from_secs(1)),
528 );
529 assert_eq!(
530 format!("{tls13:?}"),
531 "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: , .. }"
532 );
533 }
534}