Skip to main content

simoom_tls/tls/
config.rs

1//! Server and client configuration: certificates and SNI, client authentication, ALPN, session
2//! tickets with rotating keys, and the time source.
3
4use super::{CipherSuite, Error, NamedGroup, Result};
5use crate::crypto::gcm::AesGcm;
6use crate::crypto::rng;
7use crate::keys::PrivateKey;
8use crate::x509::{Certificate, RootStore};
9#[cfg(feature = "client")]
10use std::collections::{HashMap, VecDeque};
11use std::sync::{Arc, Mutex};
12
13/// Wall-clock time in Unix milliseconds. The protocol code never reads a clock itself: ticket
14/// ages and certificate validity use this. Tests inject fixed times.
15pub trait TimeSource: Send + Sync {
16    fn unix_millis(&self) -> u64;
17}
18
19/// The operating system clock.
20#[derive(Debug, Default, Clone, Copy)]
21pub struct SystemClock;
22
23impl TimeSource for SystemClock {
24    fn unix_millis(&self) -> u64 {
25        std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_millis() as u64).unwrap_or(0)
26    }
27}
28
29/// A certificate chain (leaf first, DER) with its private key.
30#[derive(Clone, Debug)]
31pub struct CertifiedKey {
32    pub chain: Vec<Vec<u8>>,
33    pub key: PrivateKey,
34    /// The leaf's DNS names for SNI (its SAN entries, or else its common name), taken from
35    /// the one parse in [`CertifiedKey::new`].
36    names: Vec<String>,
37}
38
39impl CertifiedKey {
40    /// Checks that the key matches the leaf certificate.
41    pub fn new(chain: Vec<Vec<u8>>, key: PrivateKey) -> Result<CertifiedKey> {
42        let leaf = chain.first().ok_or_else(|| Error::Config("empty certificate chain".into()))?;
43        let cert = Certificate::from_der(leaf).map_err(|e| Error::Config(format!("bad certificate: {e}")))?;
44        match &cert.public_key {
45            Some(pk) if pk.same_as(&key.public_key()) => {
46                let names = if cert.has_san { cert.dns_names } else { cert.subject.common_name().map(|s| vec![s.to_ascii_lowercase()]).unwrap_or_default() };
47                Ok(CertifiedKey { chain, key, names })
48            }
49            Some(_) => Err(Error::Config("the private key does not match the certificate".into())),
50            None => Err(Error::Config("unsupported certificate key type".into())),
51        }
52    }
53
54    /// From PEM text: every CERTIFICATE block (leaf first) and the first private key.
55    pub fn from_pem(cert_pem: &[u8], key_pem: &[u8]) -> Result<CertifiedKey> {
56        let chain: Vec<Vec<u8>> = crate::pem::parse_pem(cert_pem)
57            .map_err(Error::Config)?
58            .into_iter()
59            .filter(|b| b.label == "CERTIFICATE")
60            .map(|b| b.der)
61            .collect();
62        let key = PrivateKey::from_pem(key_pem).map_err(Error::Config)?;
63        CertifiedKey::new(chain, key)
64    }
65
66    /// From DER: the chain and a key in PKCS#8, SEC1 or PKCS#1.
67    pub fn from_der(chain: Vec<Vec<u8>>, key_der: &[u8]) -> Result<CertifiedKey> {
68        let key = PrivateKey::from_der(key_der).map_err(Error::Config)?;
69        CertifiedKey::new(chain, key)
70    }
71
72    /// The leaf's DNS names used for SNI selection (SAN entries, or else the common name).
73    pub fn names(&self) -> &[String] {
74        &self.names
75    }
76}
77
78/// Client certificate authentication on a server.
79#[derive(Clone, Debug, Default)]
80pub enum ClientAuth {
81    /// No CertificateRequest.
82    #[default]
83    None,
84    /// Request a certificate; accept clients without one, validate the ones that send one.
85    Optional(RootStore),
86    /// Require a valid client certificate.
87    Required(RootStore),
88}
89
90/// Encrypts and decrypts session tickets with AES-256-GCM under keys that rotate every
91/// `rotate_secs`; the previous key stays valid for one more period.
92pub struct Ticketer {
93    keys: Mutex<TicketKeys>,
94    rotate_ms: u64,
95}
96
97struct TicketKey {
98    id: [u8; 8],
99    aead: AesGcm,
100    created_ms: u64,
101}
102
103struct TicketKeys {
104    current: Option<TicketKey>,
105    previous: Option<TicketKey>,
106}
107
108impl std::fmt::Debug for Ticketer {
109    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110        write!(f, "Ticketer(rotate every {} s)", self.rotate_ms / 1000)
111    }
112}
113
114impl Ticketer {
115    pub fn new(rotate_secs: u32) -> Ticketer {
116        Ticketer { keys: Mutex::new(TicketKeys { current: None, previous: None }), rotate_ms: rotate_secs.max(1) as u64 * 1000 }
117    }
118
119    fn fresh(now: u64) -> TicketKey {
120        let mut k = rng::array::<32>();
121        let key = TicketKey { id: rng::array(), aead: AesGcm::new(&k), created_ms: now };
122        crate::util::zeroize(&mut k);
123        key
124    }
125
126    fn rotate(&self, keys: &mut TicketKeys, now: u64) {
127        let stale = match &keys.current {
128            None => true,
129            Some(k) => now.saturating_sub(k.created_ms) >= self.rotate_ms,
130        };
131        if stale {
132            keys.previous = keys.current.take();
133            keys.current = Some(Self::fresh(now));
134            if let Some(p) = &keys.previous {
135                if now.saturating_sub(p.created_ms) >= 2 * self.rotate_ms {
136                    keys.previous = None;
137                }
138            }
139        }
140    }
141
142    /// key id (8) || nonce (12) || ciphertext || tag (16).
143    pub(crate) fn seal(&self, now: u64, plain: &[u8]) -> Vec<u8> {
144        let mut keys = self.keys.lock().unwrap_or_else(|e| e.into_inner());
145        self.rotate(&mut keys, now);
146        let k = keys.current.as_ref().expect("current key");
147        let nonce = rng::array::<12>();
148        let mut out = Vec::with_capacity(36 + plain.len());
149        out.extend_from_slice(&k.id);
150        out.extend_from_slice(&nonce);
151        let start = out.len();
152        out.extend_from_slice(plain);
153        let tag = k.aead.seal(&nonce, &k.id, &mut out[start..]);
154        out.extend_from_slice(&tag);
155        out
156    }
157
158    pub(crate) fn open(&self, now: u64, ticket: &[u8]) -> Option<Vec<u8>> {
159        if ticket.len() < 36 {
160            return None;
161        }
162        let mut keys = self.keys.lock().unwrap_or_else(|e| e.into_inner());
163        self.rotate(&mut keys, now);
164        let (id, rest) = ticket.split_at(8);
165        let (nonce, rest) = rest.split_at(12);
166        let (ct, tag) = rest.split_at(rest.len() - 16);
167        for k in [&keys.current, &keys.previous].into_iter().flatten() {
168            if k.id == id {
169                let mut pt = ct.to_vec();
170                return k.aead.open(nonce.try_into().unwrap(), &k.id, &mut pt, tag).then_some(pt);
171            }
172        }
173        None
174    }
175
176    /// Forces a new key now (for tests and operators).
177    pub fn rotate_now(&self, now_ms: u64) {
178        let mut keys = self.keys.lock().unwrap_or_else(|e| e.into_inner());
179        keys.previous = keys.current.take();
180        keys.current = Some(Self::fresh(now_ms));
181    }
182}
183
184/// Server configuration. A clone shares the certificates and the session ticket keys
185/// (they sit behind `Arc`s), so a TCP listener and a QUIC listener can use one parsed
186/// certificate with their own ALPN lists.
187#[derive(Clone)]
188pub struct ServerConfig {
189    pub(crate) default_cert: Option<Arc<CertifiedKey>>,
190    pub(crate) sni_certs: Vec<(String, Arc<CertifiedKey>)>,
191    /// ALPN protocols in preference order (empty: ALPN is ignored).
192    pub alpn: Vec<Vec<u8>>,
193    pub client_auth: ClientAuth,
194    pub suites: Vec<CipherSuite>,
195    pub groups: Vec<NamedGroup>,
196    /// Session tickets (None disables resumption).
197    pub ticketer: Option<Arc<Ticketer>>,
198    /// Ticket lifetime advertised to clients and enforced on resumption.
199    pub ticket_lifetime_secs: u32,
200    /// NewSessionTickets sent after each full handshake.
201    pub ticket_count: u8,
202    /// Reject clients whose SNI matches no configured name (unrecognized_name) instead of
203    /// using the default certificate.
204    pub strict_sni: bool,
205    /// Largest handshake message accepted.
206    pub max_handshake_message: usize,
207    pub time: Arc<dyn TimeSource>,
208    /// Lowest protocol version accepted (feature `tls12`; default TLS 1.2).
209    #[cfg(feature = "tls12")]
210    pub min_version: super::ProtocolVersion,
211    /// Highest protocol version offered (feature `tls12`; default TLS 1.3, chosen whenever the
212    /// client supports it).
213    #[cfg(feature = "tls12")]
214    pub max_version: super::ProtocolVersion,
215    /// TLS 1.2 cipher suites in preference order (feature `tls12`).
216    #[cfg(feature = "tls12")]
217    pub suites12: Vec<super::tls12::Tls12Suite>,
218    /// TLS 1.2 session-ID resumption for clients without ticket support (None disables it;
219    /// tickets use `ticketer`).
220    #[cfg(feature = "tls12")]
221    pub session_ids: Option<Arc<super::tls12::SessionIdCache>>,
222    /// The OCSP response stapled for clients that send `status_request` (feature
223    /// `revocation`). The caller fetches and refreshes it; each handshake reads the current one.
224    #[cfg(feature = "revocation")]
225    pub ocsp_staple: Option<crate::revocation::StapleSource>,
226    /// Revocation checks of client certificates (feature `revocation`): CRLs and online OCSP.
227    #[cfg(feature = "revocation")]
228    pub client_revocation: Option<crate::revocation::RevocationPolicy>,
229}
230
231impl std::fmt::Debug for ServerConfig {
232    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
233        f.debug_struct("ServerConfig").field("alpn", &self.alpn).field("suites", &self.suites).field("groups", &self.groups).finish()
234    }
235}
236
237impl ServerConfig {
238    /// Accept TLS 1.3 only. Without the `tls12` feature that is already the only version, so
239    /// this is a no-op; with it, it raises `min_version` to TLS 1.3. Lets code that must never
240    /// accept TLS 1.2 say so whatever features feature unification switched on.
241    pub fn require_tls13(&mut self) {
242        #[cfg(feature = "tls12")]
243        {
244            self.min_version = super::ProtocolVersion::Tls13;
245        }
246    }
247
248    /// A configuration serving one certificate chain; tickets on, no client authentication.
249    pub fn new(cert: CertifiedKey) -> ServerConfig {
250        ServerConfig {
251            default_cert: Some(Arc::new(cert)),
252            sni_certs: Vec::new(),
253            alpn: Vec::new(),
254            client_auth: ClientAuth::None,
255            suites: CipherSuite::default_order(),
256            groups: NamedGroup::ALL.to_vec(),
257            ticketer: Some(Arc::new(Ticketer::new(6 * 3600))),
258            ticket_lifetime_secs: 6 * 3600,
259            ticket_count: 1,
260            strict_sni: false,
261            max_handshake_message: 64 * 1024,
262            time: Arc::new(SystemClock),
263            #[cfg(feature = "tls12")]
264            min_version: super::ProtocolVersion::Tls12,
265            #[cfg(feature = "tls12")]
266            max_version: super::ProtocolVersion::Tls13,
267            #[cfg(feature = "tls12")]
268            suites12: super::tls12::Tls12Suite::default_order(),
269            #[cfg(feature = "tls12")]
270            session_ids: Some(Arc::new(super::tls12::SessionIdCache::new(1024))),
271            #[cfg(feature = "revocation")]
272            ocsp_staple: None,
273            #[cfg(feature = "revocation")]
274            client_revocation: None,
275        }
276    }
277
278    /// From PEM certificate chain and key.
279    pub fn from_pem(cert_pem: &[u8], key_pem: &[u8]) -> Result<ServerConfig> {
280        Ok(ServerConfig::new(CertifiedKey::from_pem(cert_pem, key_pem)?))
281    }
282
283    /// Adds a certificate selected by SNI for every DNS name in its leaf (wildcards included).
284    pub fn add_sni_cert(&mut self, cert: CertifiedKey) {
285        let cert = Arc::new(cert);
286        for n in cert.names() {
287            self.sni_certs.push((n.clone(), cert.clone()));
288        }
289    }
290
291    pub fn with_alpn(mut self, protocols: &[&[u8]]) -> ServerConfig {
292        self.alpn = protocols.iter().map(|p| p.to_vec()).collect();
293        self
294    }
295
296    pub(crate) fn resolve(&self, sni: Option<&str>) -> Option<Arc<CertifiedKey>> {
297        if let Some(name) = sni {
298            if let Some((_, c)) = self.sni_certs.iter().find(|(p, _)| crate::x509::dns_name_matches(p, name)) {
299                return Some(c.clone());
300            }
301            if self.strict_sni && !self.sni_certs.is_empty() {
302                return None;
303            }
304        }
305        self.default_cert.clone()
306    }
307}
308
309/// A resumable session remembered by a client.
310#[cfg(feature = "client")]
311#[derive(Clone, Debug)]
312pub(crate) struct StoredSession {
313    pub suite: CipherSuite,
314    pub psk: Vec<u8>,
315    pub ticket: Vec<u8>,
316    pub age_add: u32,
317    pub received_ms: u64,
318    pub lifetime_secs: u32,
319    pub alpn: Option<Vec<u8>>,
320}
321
322/// The client's session store: up to `per_server` tickets per server name, each used once.
323#[cfg(feature = "client")]
324pub struct SessionCache {
325    map: Mutex<HashMap<String, VecDeque<StoredSession>>>,
326    #[cfg(feature = "tls12")]
327    map12: Mutex<HashMap<String, super::tls12::ClientSession12>>,
328    max_servers: usize,
329    per_server: usize,
330}
331
332#[cfg(feature = "client")]
333impl std::fmt::Debug for SessionCache {
334    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
335        write!(f, "SessionCache({} servers)", self.map.lock().map(|m| m.len()).unwrap_or(0))
336    }
337}
338
339#[cfg(feature = "client")]
340impl SessionCache {
341    pub fn new(max_servers: usize) -> SessionCache {
342        SessionCache {
343            map: Mutex::new(HashMap::new()),
344            #[cfg(feature = "tls12")]
345            map12: Mutex::new(HashMap::new()),
346            max_servers: max_servers.max(1),
347            per_server: 4,
348        }
349    }
350
351    /// Stores a TLS 1.2 session (one per server name; a newer one replaces it).
352    #[cfg(feature = "tls12")]
353    pub(crate) fn put12(&self, server: &str, s: super::tls12::ClientSession12) {
354        let mut m = self.map12.lock().unwrap_or_else(|e| e.into_inner());
355        if !m.contains_key(server) && m.len() >= self.max_servers {
356            if let Some(k) = m.keys().next().cloned() {
357                m.remove(&k);
358            }
359        }
360        m.insert(server.to_string(), s);
361    }
362
363    /// The TLS 1.2 session for `server`, if still valid (it stays cached: TLS 1.2 tickets and
364    /// session IDs may be reused, unlike TLS 1.3 tickets).
365    #[cfg(feature = "tls12")]
366    pub(crate) fn get12(&self, server: &str, now: u64) -> Option<super::tls12::ClientSession12> {
367        let mut m = self.map12.lock().unwrap_or_else(|e| e.into_inner());
368        match m.get(server) {
369            Some(s) if now < s.received_ms + s.lifetime_secs as u64 * 1000 => Some(s.clone()),
370            Some(_) => {
371                m.remove(server);
372                None
373            }
374            None => None,
375        }
376    }
377
378
379    pub(crate) fn put(&self, server: &str, s: StoredSession) {
380        let mut m = self.map.lock().unwrap_or_else(|e| e.into_inner());
381        if !m.contains_key(server) && m.len() >= self.max_servers {
382            if let Some(k) = m.keys().next().cloned() {
383                m.remove(&k);
384            }
385        }
386        let q = m.entry(server.to_string()).or_default();
387        if q.len() >= self.per_server {
388            q.pop_front();
389        }
390        q.push_back(s);
391    }
392
393    pub(crate) fn take(&self, server: &str, now: u64) -> Option<StoredSession> {
394        let mut m = self.map.lock().unwrap_or_else(|e| e.into_inner());
395        let q = m.get_mut(server)?;
396        while let Some(s) = q.pop_back() {
397            if now < s.received_ms + s.lifetime_secs as u64 * 1000 {
398                return Some(s);
399            }
400        }
401        None
402    }
403
404    /// Number of stored tickets for `server`.
405    pub fn len_for(&self, server: &str) -> usize {
406        self.map.lock().map(|m| m.get(server).map_or(0, |q| q.len())).unwrap_or(0)
407    }
408}
409
410/// Client configuration.
411#[cfg(feature = "client")]
412pub struct ClientConfig {
413    /// Trust anchors for server certificates. Verification cannot be turned off; to trust a
414    /// self-signed server, add its certificate here.
415    pub roots: RootStore,
416    pub alpn: Vec<Vec<u8>>,
417    pub suites: Vec<CipherSuite>,
418    /// Groups in preference order; the first gets a key share in the ClientHello.
419    pub groups: Vec<NamedGroup>,
420    /// A certificate for servers that request one.
421    pub client_cert: Option<CertifiedKey>,
422    /// Session resumption (None disables it).
423    pub sessions: Option<Arc<SessionCache>>,
424    /// Send the server_name extension.
425    pub enable_sni: bool,
426    pub max_handshake_message: usize,
427    pub time: Arc<dyn TimeSource>,
428    /// Lowest protocol version offered (feature `tls12`; default TLS 1.2).
429    #[cfg(feature = "tls12")]
430    pub min_version: super::ProtocolVersion,
431    /// Highest protocol version offered (feature `tls12`; default TLS 1.3).
432    #[cfg(feature = "tls12")]
433    pub max_version: super::ProtocolVersion,
434    /// TLS 1.2 cipher suites in preference order (feature `tls12`).
435    #[cfg(feature = "tls12")]
436    pub suites12: Vec<super::tls12::Tls12Suite>,
437    /// Revocation checks of the server certificate (feature `revocation`): OCSP stapling
438    /// (`status_request`), CRLs and online OCSP. None checks nothing.
439    #[cfg(feature = "revocation")]
440    pub revocation: Option<crate::revocation::RevocationPolicy>,
441}
442
443#[cfg(feature = "client")]
444impl std::fmt::Debug for ClientConfig {
445    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
446        f.debug_struct("ClientConfig").field("roots", &self.roots).field("alpn", &self.alpn).finish()
447    }
448}
449
450#[cfg(feature = "client")]
451impl ClientConfig {
452    pub fn new(roots: RootStore) -> ClientConfig {
453        ClientConfig {
454            roots,
455            alpn: Vec::new(),
456            suites: CipherSuite::ALL.to_vec(),
457            groups: NamedGroup::ALL.to_vec(),
458            client_cert: None,
459            sessions: Some(Arc::new(SessionCache::new(256))),
460            enable_sni: true,
461            max_handshake_message: 64 * 1024,
462            time: Arc::new(SystemClock),
463            #[cfg(feature = "tls12")]
464            min_version: super::ProtocolVersion::Tls12,
465            #[cfg(feature = "tls12")]
466            max_version: super::ProtocolVersion::Tls13,
467            #[cfg(feature = "tls12")]
468            suites12: super::tls12::Tls12Suite::default_order(),
469            #[cfg(feature = "revocation")]
470            revocation: None,
471        }
472    }
473
474    pub fn with_alpn(mut self, protocols: &[&[u8]]) -> ClientConfig {
475        self.alpn = protocols.iter().map(|p| p.to_vec()).collect();
476        self
477    }
478}
479
480#[cfg(test)]
481mod tests {
482    use super::*;
483
484    // Bug 13: the leaf is parsed once, in CertifiedKey::new; names() and SNI use the result,
485    // and a cloned ServerConfig shares the parsed certificate instead of copying it.
486    #[test]
487    fn certificate_parsed_once_and_shared_by_clones() {
488        let ss = crate::certgen::self_signed_with_sans(&["a.example", "*.b.example"], 30);
489        let ck = CertifiedKey::new(vec![ss.cert_der.clone()], ss.key.clone()).unwrap();
490        assert_eq!(ck.names(), ["a.example".to_string(), "*.b.example".to_string()]);
491        let mut cfg = ServerConfig::new(ck.clone());
492        cfg.add_sni_cert(ck);
493        assert_eq!(cfg.sni_certs.len(), 2);
494        assert!(cfg.resolve(Some("x.b.example")).is_some());
495        let h3 = cfg.clone().with_alpn(&[b"h3"]);
496        assert!(Arc::ptr_eq(cfg.default_cert.as_ref().unwrap(), h3.default_cert.as_ref().unwrap()));
497        assert!(Arc::ptr_eq(cfg.ticketer.as_ref().unwrap(), h3.ticketer.as_ref().unwrap()));
498        assert!(cfg.alpn.is_empty() && h3.alpn == [b"h3".to_vec()]);
499    }
500}