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::{Identity, SelectedCredential, SignatureScheme};
10use crate::enums::{ApplicationProtocol, CertificateType};
11use crate::log::{debug, trace};
12use crate::msgs::{
13 CertificateChain, ExtensionType, MaybeEmpty, ServerExtensions, SessionId, SizedPayload,
14};
15use crate::sync::Arc;
16use crate::verify::DistinguishedName;
17#[cfg(feature = "webpki")]
18pub use crate::webpki::{
19 ServerVerifierBuilder, VerifierBuilderError, WebPkiServerVerifier,
20 verify_identity_signed_by_trust_anchor, verify_server_name,
21};
22use crate::{Tls12CipherSuite, Tls13CipherSuite, compress};
23
24mod config;
25pub use config::{
26 ClientConfig, ClientCredentialResolver, ClientSessionKey, ClientSessionStore,
27 CredentialRequest, Resumption, Tls12Resumption, WantsClientCert,
28};
29
30mod connection;
31#[cfg(feature = "std")]
32pub use connection::{ClientConnection, ClientConnectionBuilder, WriteEarlyData};
33pub use connection::{
34 ClientConnectionData, EarlyDataError, MayEncryptEarlyData, UnbufferedClientConnection,
35};
36
37mod ech;
38pub use ech::{EchConfig, EchGreaseConfig, EchMode, EchStatus};
39
40mod handy;
41#[cfg(any(feature = "std", feature = "hashbrown"))]
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
53pub mod danger {
55 pub use super::config::danger::{DangerousClientConfig, DangerousClientConfigBuilder};
56 pub use crate::verify::{
57 HandshakeSignatureValid, PeerVerified, ServerIdentity, ServerVerifier,
58 SignatureVerificationInput,
59 };
60}
61
62#[cfg(test)]
63mod test;
64
65pub(crate) struct Retrieved<T> {
66 pub(crate) value: T,
67 retrieved_at: UnixTime,
68}
69
70impl<T> Retrieved<T> {
71 pub(crate) fn new(value: T, retrieved_at: UnixTime) -> Self {
72 Self {
73 value,
74 retrieved_at,
75 }
76 }
77
78 pub(crate) fn map<M>(&self, f: impl FnOnce(&T) -> Option<&M>) -> Option<Retrieved<&M>> {
79 Some(Retrieved {
80 value: f(&self.value)?,
81 retrieved_at: self.retrieved_at,
82 })
83 }
84}
85
86impl Retrieved<&Tls13ClientSessionValue> {
87 pub(crate) fn obfuscated_ticket_age(&self) -> u32 {
88 let age_secs = self
89 .retrieved_at
90 .as_secs()
91 .saturating_sub(self.value.common.epoch);
92 let age_millis = age_secs as u32 * 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#[derive(Debug)]
118pub struct Tls13ClientSessionValue {
119 suite: &'static Tls13CipherSuite,
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 Tls13ClientSessionValue {
128 pub(crate) fn new(
129 input: Tls13ClientSessionInput,
130 ticket: Arc<SizedPayload<'static, u16, MaybeEmpty>>,
131 secret: &[u8],
132 time_now: UnixTime,
133 lifetime: Duration,
134 age_add: u32,
135 max_early_data_size: u32,
136 ) -> Self {
137 Self {
138 suite: input.suite,
139 secret: Zeroizing::new(secret.to_vec().into()),
140 age_add,
141 max_early_data_size,
142 common: ClientSessionCommon::new(ticket, time_now, lifetime, input.peer_identity),
143 quic_params: input
144 .quic_params
145 .unwrap_or_else(|| SizedPayload::from(Payload::new(Vec::new()))),
146 }
147 }
148
149 pub(crate) fn secret(&self) -> &[u8] {
150 self.secret.bytes()
151 }
152
153 pub fn max_early_data_size(&self) -> u32 {
155 self.max_early_data_size
156 }
157
158 pub fn suite(&self) -> &'static Tls13CipherSuite {
160 self.suite
161 }
162
163 #[doc(hidden)]
165 pub fn rewind_epoch(&mut self, delta: u32) {
166 self.common.epoch -= delta as u64;
167 }
168
169 #[doc(hidden)]
171 pub fn _private_set_max_early_data_size(&mut self, new: u32) {
172 self.max_early_data_size = new;
173 }
174
175 pub fn quic_params(&self) -> Vec<u8> {
177 self.quic_params.to_vec()
178 }
179}
180
181impl Deref for Tls13ClientSessionValue {
182 type Target = ClientSessionCommon;
183
184 fn deref(&self) -> &Self::Target {
185 &self.common
186 }
187}
188
189#[derive(Clone)]
191pub(crate) struct Tls13ClientSessionInput {
192 pub(crate) suite: &'static Tls13CipherSuite,
193 pub(crate) peer_identity: Identity<'static>,
194 pub(crate) quic_params: Option<SizedPayload<'static, u16, MaybeEmpty>>,
195}
196
197#[derive(Debug, Clone)]
199pub struct Tls12ClientSessionValue {
200 suite: &'static Tls12CipherSuite,
201 pub(crate) session_id: SessionId,
202 master_secret: Zeroizing<[u8; 48]>,
203 extended_ms: bool,
204 #[doc(hidden)]
205 pub(crate) common: ClientSessionCommon,
206}
207
208impl Tls12ClientSessionValue {
209 pub(crate) fn new(
210 suite: &'static Tls12CipherSuite,
211 session_id: SessionId,
212 ticket: Arc<SizedPayload<'static, u16, MaybeEmpty>>,
213 master_secret: &[u8; 48],
214 peer_identity: Identity<'static>,
215 time_now: UnixTime,
216 lifetime: Duration,
217 extended_ms: bool,
218 ) -> Self {
219 Self {
220 suite,
221 session_id,
222 master_secret: Zeroizing::new(*master_secret),
223 extended_ms,
224 common: ClientSessionCommon::new(ticket, time_now, lifetime, peer_identity),
225 }
226 }
227
228 pub(crate) fn master_secret(&self) -> &[u8; 48] {
229 &self.master_secret
230 }
231
232 pub(crate) fn ticket(&self) -> Arc<SizedPayload<'static, u16, MaybeEmpty>> {
233 self.common.ticket.clone()
234 }
235
236 pub(crate) fn extended_ms(&self) -> bool {
237 self.extended_ms
238 }
239
240 pub(crate) fn suite(&self) -> &'static Tls12CipherSuite {
241 self.suite
242 }
243
244 #[doc(hidden)]
246 pub fn rewind_epoch(&mut self, delta: u32) {
247 self.common.epoch -= delta as u64;
248 }
249}
250
251impl Deref for Tls12ClientSessionValue {
252 type Target = ClientSessionCommon;
253
254 fn deref(&self) -> &Self::Target {
255 &self.common
256 }
257}
258
259#[derive(Debug, Clone)]
261pub struct ClientSessionCommon {
262 pub(crate) ticket: Arc<SizedPayload<'static, u16>>,
263 pub(crate) epoch: u64,
264 lifetime: Duration,
265 peer_identity: Arc<Identity<'static>>,
266}
267
268impl ClientSessionCommon {
269 pub(crate) fn new(
270 ticket: Arc<SizedPayload<'static, u16>>,
271 time_now: UnixTime,
272 lifetime: Duration,
273 peer_identity: Identity<'static>,
274 ) -> Self {
275 Self {
276 ticket,
277 epoch: time_now.as_secs(),
278 lifetime: Ord::min(lifetime, MAX_TICKET_LIFETIME),
279 peer_identity: Arc::new(peer_identity),
280 }
281 }
282
283 pub(crate) fn peer_identity(&self) -> &Identity<'static> {
284 &self.peer_identity
285 }
286
287 pub(crate) fn ticket(&self) -> &[u8] {
288 (*self.ticket).bytes()
289 }
290}
291
292#[derive(Debug)]
293struct ServerCertDetails {
294 cert_chain: CertificateChain<'static>,
295 ocsp_response: Vec<u8>,
296}
297
298impl ServerCertDetails {
299 fn new(cert_chain: CertificateChain<'static>, ocsp_response: Vec<u8>) -> Self {
300 Self {
301 cert_chain,
302 ocsp_response,
303 }
304 }
305}
306
307struct ClientHelloDetails {
308 alpn_protocols: Vec<ApplicationProtocol<'static>>,
309 sent_extensions: Vec<ExtensionType>,
310 extension_order_seed: u16,
311 offered_cert_compression: bool,
312}
313
314impl ClientHelloDetails {
315 fn new(alpn_protocols: Vec<ApplicationProtocol<'static>>, extension_order_seed: u16) -> Self {
316 Self {
317 alpn_protocols,
318 sent_extensions: Vec::new(),
319 extension_order_seed,
320 offered_cert_compression: false,
321 }
322 }
323
324 fn server_sent_unsolicited_extensions(
325 &self,
326 received_exts: &ServerExtensions<'_>,
327 allowed_unsolicited: &[ExtensionType],
328 ) -> bool {
329 let mut extensions = received_exts.collect_used();
330 extensions.extend(
331 received_exts
332 .unknown_extensions
333 .iter()
334 .map(|ext| ExtensionType::from(*ext)),
335 );
336 for ext_type in extensions {
337 if !self.sent_extensions.contains(&ext_type) && !allowed_unsolicited.contains(&ext_type)
338 {
339 trace!("Unsolicited extension {ext_type:?}");
340 return true;
341 }
342 }
343
344 false
345 }
346}
347
348enum ClientAuthDetails {
349 Empty { auth_context_tls13: Option<Vec<u8>> },
351 Verify {
353 credentials: SelectedCredential,
354 auth_context_tls13: Option<Vec<u8>>,
355 compressor: Option<&'static dyn compress::CertCompressor>,
356 },
357}
358
359impl ClientAuthDetails {
360 fn resolve(
361 negotiated_type: CertificateType,
362 resolver: &dyn ClientCredentialResolver,
363 root_hint_subjects: Option<&[DistinguishedName]>,
364 signature_schemes: &[SignatureScheme],
365 auth_context_tls13: Option<Vec<u8>>,
366 compressor: Option<&'static dyn compress::CertCompressor>,
367 ) -> Self {
368 let server_hello = CredentialRequest {
369 negotiated_type,
370 root_hint_subjects: root_hint_subjects.unwrap_or_default(),
371 signature_schemes,
372 };
373
374 if let Some(credentials) = resolver.resolve(&server_hello) {
375 debug!("Attempting client auth");
376 return Self::Verify {
377 credentials,
378 auth_context_tls13,
379 compressor,
380 };
381 }
382
383 debug!("Client auth requested but no cert/sigscheme available");
384 Self::Empty { auth_context_tls13 }
385 }
386}
387
388static MAX_TICKET_LIFETIME: Duration = Duration::from_secs(7 * 24 * 60 * 60);