Skip to main content

hns_browser_loopback_proxy/chromium/
certificate.rs

1//! Per-generation local TLS identities for scoped CONNECT termination.
2
3use crate::{NormalizedHost, ProxyInstanceId};
4use base64::Engine as _;
5use base64::engine::general_purpose::STANDARD;
6use hns_dane::extract_spki_der;
7use rcgen::{
8    BasicConstraints, CertificateParams, DistinguishedName, DnType, IsCa, Issuer, KeyPair,
9    KeyUsagePurpose, PublicKeyData,
10};
11use ring::digest::{SHA1_FOR_LEGACY_USE_ONLY, SHA256, digest};
12use rustls::ServerConfig;
13use rustls::crypto::ring::sign::any_ecdsa_type;
14use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
15use rustls::server::{ClientHello, ResolvesServerCert};
16use rustls::sign::CertifiedKey;
17use std::collections::HashMap;
18use std::fmt;
19use std::sync::atomic::{AtomicBool, Ordering};
20use std::sync::{Arc, Mutex, MutexGuard};
21use subtle::ConstantTimeEq;
22use thiserror::Error;
23use time::{Duration, OffsetDateTime};
24use zeroize::Zeroizing;
25
26const MAX_CACHED_IDENTITIES: usize = 128;
27const MAX_PRESENTED_CERTIFICATE_DER_BYTES: usize = 64 * 1024;
28const MAX_LOCAL_CA_PRIVATE_KEY_DER_BYTES: usize = 16 * 1024;
29const LOCAL_LEAF_VALIDITY_DAYS: i64 = 90;
30const LOCAL_CA_VALIDITY_DAYS: i64 = 3_650;
31const LOCAL_CA_COMMON_NAME: &str = "HNS DANE Browser Local CA";
32
33#[derive(Clone)]
34pub struct LocalCertificateAuthority {
35    inner: Arc<LocalCertificateAuthorityInner>,
36}
37
38struct LocalCertificateAuthorityInner {
39    issuer: Issuer<'static, KeyPair>,
40    certificate_der: Vec<u8>,
41    certificate_sha1: [u8; 20],
42    certificate_sha256: [u8; 32],
43}
44
45pub struct GeneratedLocalCertificateAuthority {
46    authority: LocalCertificateAuthority,
47    private_key_der: Zeroizing<Vec<u8>>,
48}
49
50impl GeneratedLocalCertificateAuthority {
51    pub fn authority(&self) -> &LocalCertificateAuthority {
52        &self.authority
53    }
54
55    /// Explicit secret export for the native host's mode-0600 per-install
56    /// persistence boundary.
57    pub fn private_key_der(&self) -> &[u8] {
58        &self.private_key_der
59    }
60}
61
62impl fmt::Debug for GeneratedLocalCertificateAuthority {
63    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
64        formatter
65            .debug_struct("GeneratedLocalCertificateAuthority")
66            .field("authority", &self.authority)
67            .field("private_key_der", &"[REDACTED]")
68            .finish()
69    }
70}
71
72#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
73pub enum LocalCertificateAuthorityError {
74    #[error("local CA certificate is empty or exceeds the size bound")]
75    InvalidCertificateSize,
76    #[error("local CA private key is empty or exceeds the size bound")]
77    InvalidPrivateKeySize,
78    #[error("unable to generate the local CA")]
79    Generation,
80    #[error("unable to parse the local CA private key")]
81    InvalidPrivateKey,
82    #[error("unable to parse the local CA certificate")]
83    InvalidCertificate,
84    #[error("the local CA certificate does not match its private key")]
85    KeyMismatch,
86}
87
88impl LocalCertificateAuthority {
89    pub fn generate() -> Result<GeneratedLocalCertificateAuthority, LocalCertificateAuthorityError>
90    {
91        let key = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256)
92            .map_err(|_| LocalCertificateAuthorityError::Generation)?;
93        let private_key_der = Zeroizing::new(key.serialize_der());
94        let params = local_ca_params();
95        let certificate = params
96            .self_signed(&key)
97            .map_err(|_| LocalCertificateAuthorityError::Generation)?;
98        let certificate_der = certificate.der().as_ref().to_vec();
99        let authority = Self::from_parts(params, key, certificate_der)?;
100        Ok(GeneratedLocalCertificateAuthority {
101            authority,
102            private_key_der,
103        })
104    }
105
106    pub fn from_der(
107        certificate_der: Vec<u8>,
108        private_key_der: &[u8],
109    ) -> Result<Self, LocalCertificateAuthorityError> {
110        validate_ca_material_sizes(&certificate_der, private_key_der)?;
111        let private_key = rustls::pki_types::PrivatePkcs8KeyDer::from(private_key_der);
112        let key =
113            KeyPair::from_pkcs8_der_and_sign_algo(&private_key, &rcgen::PKCS_ECDSA_P256_SHA256)
114                .map_err(|_| LocalCertificateAuthorityError::InvalidPrivateKey)?;
115        let certificate_spki = extract_spki_der(&certificate_der)
116            .map_err(|_| LocalCertificateAuthorityError::InvalidCertificate)?;
117        let key_spki = key.subject_public_key_info();
118        if certificate_spki.len() != key_spki.len()
119            || !bool::from(certificate_spki.ct_eq(&key_spki))
120        {
121            return Err(LocalCertificateAuthorityError::KeyMismatch);
122        }
123        let certificate = rustls::pki_types::CertificateDer::from(certificate_der.clone());
124        let issuer = Issuer::from_ca_cert_der(&certificate, key)
125            .map_err(|_| LocalCertificateAuthorityError::InvalidCertificate)?;
126        Ok(Self::from_issuer(issuer, certificate_der))
127    }
128
129    pub fn certificate_der(&self) -> &[u8] {
130        &self.inner.certificate_der
131    }
132
133    pub fn certificate_pem(&self) -> String {
134        let encoded = STANDARD.encode(self.certificate_der());
135        let mut pem = String::from("-----BEGIN CERTIFICATE-----\n");
136        for (index, character) in encoded.chars().enumerate() {
137            pem.push(character);
138            if (index + 1) % 64 == 0 {
139                pem.push('\n');
140            }
141        }
142        if !encoded.len().is_multiple_of(64) {
143            pem.push('\n');
144        }
145        pem.push_str("-----END CERTIFICATE-----\n");
146        pem
147    }
148
149    pub fn certificate_sha256_hex(&self) -> String {
150        self.inner
151            .certificate_sha256
152            .iter()
153            .map(|byte| format!("{byte:02x}"))
154            .collect()
155    }
156
157    /// SHA-1 is exposed only as the identifier required by macOS and Windows
158    /// certificate-store removal commands. It is never used for trust.
159    pub fn certificate_sha1_hex(&self) -> String {
160        self.inner
161            .certificate_sha1
162            .iter()
163            .map(|byte| format!("{byte:02x}"))
164            .collect()
165    }
166
167    fn from_parts(
168        params: CertificateParams,
169        key: KeyPair,
170        certificate_der: Vec<u8>,
171    ) -> Result<Self, LocalCertificateAuthorityError> {
172        validate_ca_material_sizes(&certificate_der, key.serialized_der())?;
173        Ok(Self::from_issuer(Issuer::new(params, key), certificate_der))
174    }
175
176    fn from_issuer(issuer: Issuer<'static, KeyPair>, certificate_der: Vec<u8>) -> Self {
177        let sha1_fingerprint = digest(&SHA1_FOR_LEGACY_USE_ONLY, &certificate_der);
178        let mut certificate_sha1 = [0_u8; 20];
179        certificate_sha1.copy_from_slice(sha1_fingerprint.as_ref());
180        let fingerprint = digest(&SHA256, &certificate_der);
181        let mut certificate_sha256 = [0_u8; 32];
182        certificate_sha256.copy_from_slice(fingerprint.as_ref());
183        Self {
184            inner: Arc::new(LocalCertificateAuthorityInner {
185                issuer,
186                certificate_der,
187                certificate_sha1,
188                certificate_sha256,
189            }),
190        }
191    }
192
193    fn issuer(&self) -> &Issuer<'static, KeyPair> {
194        &self.inner.issuer
195    }
196}
197
198impl PartialEq for LocalCertificateAuthority {
199    fn eq(&self, other: &Self) -> bool {
200        bool::from(
201            self.inner
202                .certificate_sha256
203                .ct_eq(&other.inner.certificate_sha256),
204        )
205    }
206}
207
208impl Eq for LocalCertificateAuthority {}
209
210impl fmt::Debug for LocalCertificateAuthority {
211    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
212        formatter
213            .debug_struct("LocalCertificateAuthority")
214            .field("certificate_sha256", &"[REDACTED]")
215            .finish()
216    }
217}
218
219fn validate_ca_material_sizes(
220    certificate_der: &[u8],
221    private_key_der: &[u8],
222) -> Result<(), LocalCertificateAuthorityError> {
223    if certificate_der.is_empty() || certificate_der.len() > MAX_PRESENTED_CERTIFICATE_DER_BYTES {
224        return Err(LocalCertificateAuthorityError::InvalidCertificateSize);
225    }
226    if private_key_der.is_empty() || private_key_der.len() > MAX_LOCAL_CA_PRIVATE_KEY_DER_BYTES {
227        return Err(LocalCertificateAuthorityError::InvalidPrivateKeySize);
228    }
229    Ok(())
230}
231
232fn local_ca_params() -> CertificateParams {
233    let now = OffsetDateTime::now_utc();
234    let mut distinguished_name = DistinguishedName::new();
235    distinguished_name.push(DnType::CommonName, LOCAL_CA_COMMON_NAME);
236    let mut params = CertificateParams::default();
237    params.not_before = now - Duration::days(1);
238    params.not_after = now + Duration::days(LOCAL_CA_VALIDITY_DAYS);
239    params.distinguished_name = distinguished_name;
240    params.is_ca = IsCa::Ca(BasicConstraints::Constrained(0));
241    params.key_usages = vec![
242        KeyUsagePurpose::DigitalSignature,
243        KeyUsagePurpose::KeyCertSign,
244        KeyUsagePurpose::CrlSign,
245    ];
246    params
247}
248
249/// Opaque SHA-256 equality token for one generated local certificate.
250///
251/// Raw digest bytes and DER authorization are deliberately not exposed.
252/// Diagnostics redact the token so it never enters ordinary logging.
253#[derive(Clone, Copy)]
254pub struct CertificateSha256([u8; 32]);
255
256impl CertificateSha256 {
257    /// Compares a bounded DER certificate to this pin in constant time after
258    /// hashing. Oversized, attacker-controlled challenge data fails closed.
259    pub(crate) fn matches_der(&self, certificate_der: &[u8]) -> bool {
260        if certificate_der.len() > MAX_PRESENTED_CERTIFICATE_DER_BYTES {
261            return false;
262        }
263        let candidate = digest(&SHA256, certificate_der);
264        self.0.ct_eq(candidate.as_ref()).into()
265    }
266
267    fn from_der(certificate_der: &[u8]) -> Self {
268        let fingerprint = digest(&SHA256, certificate_der);
269        let mut bytes = [0_u8; 32];
270        bytes.copy_from_slice(fingerprint.as_ref());
271        Self(bytes)
272    }
273}
274
275impl PartialEq for CertificateSha256 {
276    fn eq(&self, other: &Self) -> bool {
277        self.0.ct_eq(&other.0).into()
278    }
279}
280
281impl Eq for CertificateSha256 {}
282
283impl fmt::Debug for CertificateSha256 {
284    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
285        formatter.write_str("CertificateSha256([REDACTED])")
286    }
287}
288
289/// Generation- and exact-host-bound certificate pin for browser trust hooks.
290#[derive(Clone, Eq, PartialEq)]
291pub struct LocalCertificatePin {
292    instance: ProxyInstanceId,
293    host: NormalizedHost,
294    certificate_sha256: CertificateSha256,
295}
296
297impl LocalCertificatePin {
298    /// Exposes the proxy instance (session and generation) that created this pin.
299    pub fn instance(&self) -> &ProxyInstanceId {
300        &self.instance
301    }
302
303    /// Explicitly exposes the canonical exact host covered by this pin.
304    pub fn host(&self) -> &NormalizedHost {
305        &self.host
306    }
307
308    /// Exposes the certificate's opaque SHA-256 equality token.
309    pub fn certificate_sha256(&self) -> &CertificateSha256 {
310        &self.certificate_sha256
311    }
312
313    /// Verifies DER only inside the active store. Keeping this non-public
314    /// prevents a copied pin from becoming stale authorization after stop.
315    pub(crate) fn matches_der(&self, certificate_der: &[u8]) -> bool {
316        self.certificate_sha256.matches_der(certificate_der)
317    }
318}
319
320impl fmt::Debug for LocalCertificatePin {
321    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
322        formatter
323            .debug_struct("LocalCertificatePin")
324            .field("instance", &"[REDACTED]")
325            .field("host", &"[REDACTED]")
326            .field("certificate_sha256", &"[REDACTED]")
327            .finish()
328    }
329}
330
331#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
332pub(crate) enum CertificateError {
333    #[error("the local TLS identity store is inactive")]
334    Inactive,
335    #[error("the local TLS identity cache is full of active leases")]
336    CapacityExhausted,
337    #[error("the local TLS identity store is unavailable")]
338    StoreUnavailable,
339    #[error("unable to generate a local TLS certificate")]
340    CertificateGeneration,
341    #[error("unable to load the generated local TLS signing key")]
342    SigningKey,
343    #[error("unable to configure the local TLS server")]
344    ServerConfiguration,
345}
346
347/// A lease keeps its cache entry alive until the CONNECT connection finishes.
348pub(crate) struct IdentityLease {
349    identity: Arc<LocalTlsIdentity>,
350}
351
352impl IdentityLease {
353    pub(crate) fn server_config(&self) -> Arc<ServerConfig> {
354        Arc::clone(&self.identity.server_config)
355    }
356
357    pub(crate) fn pin(&self) -> &LocalCertificatePin {
358        &self.identity.pin
359    }
360}
361
362impl fmt::Debug for IdentityLease {
363    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
364        formatter.write_str("IdentityLease([REDACTED])")
365    }
366}
367
368struct LocalTlsIdentity {
369    server_config: Arc<ServerConfig>,
370    pin: LocalCertificatePin,
371    // Retaining public certificate bytes is harmless and lets the store avoid
372    // recovering them through rustls internals when validating local pins.
373    #[cfg(test)]
374    certificate_der: Vec<u8>,
375}
376
377struct CacheEntry {
378    identity: Arc<LocalTlsIdentity>,
379    last_access: u128,
380}
381
382#[derive(Default)]
383struct StoreState {
384    entries: HashMap<NormalizedHost, CacheEntry>,
385    access_counter: u128,
386}
387
388impl StoreState {
389    fn next_access(&mut self) -> u128 {
390        if self.access_counter == u128::MAX {
391            let mut oldest_first: Vec<_> = self
392                .entries
393                .iter()
394                .map(|(host, entry)| (host.clone(), entry.last_access))
395                .collect();
396            oldest_first.sort_by_key(|(_, last_access)| *last_access);
397            for (rank, (host, _)) in oldest_first.into_iter().enumerate() {
398                if let Some(entry) = self.entries.get_mut(&host) {
399                    entry.last_access = rank as u128;
400                }
401            }
402            self.access_counter = self.entries.len() as u128;
403        }
404        self.access_counter += 1;
405        self.access_counter
406    }
407}
408
409/// Exact-host identity cache owned by one proxy generation.
410pub(crate) struct LocalTlsIdentityStore {
411    instance: ProxyInstanceId,
412    certificate_authority: Option<LocalCertificateAuthority>,
413    active: Arc<AtomicBool>,
414    state: Mutex<StoreState>,
415}
416
417impl LocalTlsIdentityStore {
418    #[cfg(test)]
419    pub(crate) fn new(instance: ProxyInstanceId) -> Self {
420        Self::with_certificate_authority(instance, None)
421    }
422
423    pub(crate) fn with_certificate_authority(
424        instance: ProxyInstanceId,
425        certificate_authority: Option<LocalCertificateAuthority>,
426    ) -> Self {
427        Self {
428            instance,
429            certificate_authority,
430            active: Arc::new(AtomicBool::new(true)),
431            state: Mutex::new(StoreState::default()),
432        }
433    }
434
435    pub(crate) fn prepare(&self, host: &NormalizedHost) -> Result<IdentityLease, CertificateError> {
436        if !self.active.load(Ordering::Acquire) {
437            return Err(CertificateError::Inactive);
438        }
439        let mut state = self
440            .state
441            .lock()
442            .map_err(|_| CertificateError::StoreUnavailable)?;
443        if !self.active.load(Ordering::Acquire) {
444            return Err(CertificateError::Inactive);
445        }
446
447        let access = state.next_access();
448        if let Some(entry) = state.entries.get_mut(host) {
449            entry.last_access = access;
450            if !self.active.load(Ordering::Acquire) {
451                return Err(CertificateError::Inactive);
452            }
453            return Ok(IdentityLease {
454                identity: Arc::clone(&entry.identity),
455            });
456        }
457
458        let eviction = if state.entries.len() < MAX_CACHED_IDENTITIES {
459            None
460        } else {
461            Some(
462                state
463                    .entries
464                    .iter()
465                    .filter(|(_, entry)| Arc::strong_count(&entry.identity) == 1)
466                    .min_by_key(|(_, entry)| entry.last_access)
467                    .map(|(candidate, _)| candidate.clone())
468                    .ok_or(CertificateError::CapacityExhausted)?,
469            )
470        };
471
472        // Generation happens while holding the state lock. This deliberately
473        // serializes same-host generation and makes prepare/deactivate linear.
474        // The eviction is committed only after generation succeeds.
475        let identity = Arc::new(generate_identity(
476            self.instance.clone(),
477            host.clone(),
478            Arc::clone(&self.active),
479            self.certificate_authority.as_ref(),
480        )?);
481        if !self.active.load(Ordering::Acquire) {
482            return Err(CertificateError::Inactive);
483        }
484        if let Some(eviction) = eviction {
485            state.entries.remove(&eviction);
486        }
487        state.entries.insert(
488            host.clone(),
489            CacheEntry {
490                identity: Arc::clone(&identity),
491                last_access: access,
492            },
493        );
494        if !self.active.load(Ordering::Acquire) {
495            state.entries.remove(host);
496            return Err(CertificateError::Inactive);
497        }
498        Ok(IdentityLease { identity })
499    }
500
501    pub(crate) fn pin(&self, host: &NormalizedHost) -> Option<LocalCertificatePin> {
502        if !self.active.load(Ordering::Acquire) {
503            return None;
504        }
505        let mut state = self.state.lock().ok()?;
506        if !self.active.load(Ordering::Acquire) {
507            return None;
508        }
509        let access = state.next_access();
510        let entry = state.entries.get_mut(host)?;
511        entry.last_access = access;
512        Some(entry.identity.pin.clone())
513    }
514
515    pub(crate) fn matches_der(&self, host: &NormalizedHost, certificate_der: &[u8]) -> bool {
516        if certificate_der.len() > MAX_PRESENTED_CERTIFICATE_DER_BYTES {
517            return false;
518        }
519        self.pin(host)
520            .is_some_and(|pin| pin.matches_der(certificate_der))
521    }
522
523    pub(crate) fn deactivate(&self) {
524        // Resolver revocation is immediate and never waits behind certificate
525        // generation. The mutex is needed only to release cached identities.
526        self.active.store(false, Ordering::Release);
527        let mut state = lock_recover(&self.state);
528        state.entries.clear();
529    }
530}
531
532impl fmt::Debug for LocalTlsIdentityStore {
533    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
534        let entry_count = self
535            .state
536            .lock()
537            .map(|state| state.entries.len())
538            .unwrap_or_default();
539        formatter
540            .debug_struct("LocalTlsIdentityStore")
541            .field("instance", &"[REDACTED]")
542            .field(
543                "certificate_authority",
544                &self.certificate_authority.as_ref().map(|_| "[REDACTED]"),
545            )
546            .field("active", &self.active.load(Ordering::Acquire))
547            .field("entry_count", &entry_count)
548            .finish()
549    }
550}
551
552impl Drop for LocalTlsIdentityStore {
553    fn drop(&mut self) {
554        self.deactivate();
555    }
556}
557
558#[derive(Clone)]
559struct ExactSniResolver {
560    expected_host: NormalizedHost,
561    certified_key: Arc<CertifiedKey>,
562    active: Arc<AtomicBool>,
563}
564
565impl ExactSniResolver {
566    fn allows(&self, presented_sni: Option<&str>) -> bool {
567        self.active.load(Ordering::Acquire)
568            && presented_sni
569                .and_then(|name| NormalizedHost::parse(name).ok())
570                .is_some_and(|name| name == self.expected_host)
571    }
572}
573
574impl fmt::Debug for ExactSniResolver {
575    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
576        formatter
577            .debug_struct("ExactSniResolver")
578            .field("expected_host", &"[REDACTED]")
579            .field("active", &self.active.load(Ordering::Acquire))
580            .finish()
581    }
582}
583
584impl ResolvesServerCert for ExactSniResolver {
585    fn resolve(&self, client_hello: ClientHello<'_>) -> Option<Arc<CertifiedKey>> {
586        self.allows(client_hello.server_name())
587            .then(|| Arc::clone(&self.certified_key))
588    }
589}
590
591fn generate_identity(
592    instance: ProxyInstanceId,
593    host: NormalizedHost,
594    active: Arc<AtomicBool>,
595    certificate_authority: Option<&LocalCertificateAuthority>,
596) -> Result<LocalTlsIdentity, CertificateError> {
597    let mut params = rcgen::CertificateParams::new(vec![host.as_str().to_owned()])
598        .map_err(|_| CertificateError::CertificateGeneration)?;
599    let now = OffsetDateTime::now_utc();
600    params.not_before = now - Duration::minutes(5);
601    params.not_after = now + Duration::days(LOCAL_LEAF_VALIDITY_DAYS);
602    params.key_usages = vec![rcgen::KeyUsagePurpose::DigitalSignature];
603    params.extended_key_usages = vec![rcgen::ExtendedKeyUsagePurpose::ServerAuth];
604
605    // rcgen retains its PKCS#8 serialization alongside the ring key. Its
606    // zeroize feature makes this guard erase that serialized state on every
607    // success, error, and unwind path after key generation.
608    let key_pair = Zeroizing::new(
609        rcgen::KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256)
610            .map_err(|_| CertificateError::CertificateGeneration)?,
611    );
612    let certificate = if let Some(certificate_authority) = certificate_authority {
613        params.signed_by(&*key_pair, certificate_authority.issuer())
614    } else {
615        params.self_signed(&*key_pair)
616    }
617    .map_err(|_| CertificateError::CertificateGeneration)?;
618    let certificate_der = certificate.der().as_ref().to_vec();
619    if certificate_der.len() > MAX_PRESENTED_CERTIFICATE_DER_BYTES {
620        return Err(CertificateError::CertificateGeneration);
621    }
622
623    // rustls/ring parses the borrowed PKCS#8 bytes into its signing-key
624    // representation. The temporary serialization is erased at scope exit.
625    let private_key_der = Zeroizing::new(key_pair.serialize_der());
626    let borrowed_key = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(private_key_der.as_slice()));
627    let signing_key = any_ecdsa_type(&borrowed_key).map_err(|_| CertificateError::SigningKey)?;
628    let certified_key = Arc::new(CertifiedKey::new(
629        vec![CertificateDer::from(certificate_der.clone())],
630        signing_key,
631    ));
632    certified_key
633        .keys_match()
634        .map_err(|_| CertificateError::SigningKey)?;
635
636    let resolver = Arc::new(ExactSniResolver {
637        expected_host: host.clone(),
638        certified_key,
639        active,
640    });
641    let mut server_config =
642        ServerConfig::builder_with_provider(Arc::new(rustls::crypto::ring::default_provider()))
643            .with_protocol_versions(&[&rustls::version::TLS13, &rustls::version::TLS12])
644            .map_err(|_| CertificateError::ServerConfiguration)?
645            .with_no_client_auth()
646            .with_cert_resolver(resolver);
647    server_config.alpn_protocols = vec![b"http/1.1".to_vec()];
648    // Every CONNECT is deliberately a fresh, single-request TLS connection.
649    // Disabling resumption prevents per-host session-cache amplification and
650    // ensures the exact current-generation certificate participates in every
651    // handshake seen by a native browser trust hook.
652    server_config.session_storage = Arc::new(rustls::server::NoServerSessionStorage {});
653    server_config.send_tls13_tickets = 0;
654
655    let pin = LocalCertificatePin {
656        instance,
657        host,
658        certificate_sha256: CertificateSha256::from_der(&certificate_der),
659    };
660    Ok(LocalTlsIdentity {
661        server_config: Arc::new(server_config),
662        pin,
663        #[cfg(test)]
664        certificate_der,
665    })
666}
667
668fn lock_recover<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
669    mutex
670        .lock()
671        .unwrap_or_else(|poisoned| poisoned.into_inner())
672}
673
674#[cfg(test)]
675mod tests {
676    use super::*;
677    use crate::ProxySessionId;
678    use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
679    use rustls::pki_types::{ServerName, UnixTime};
680    use rustls::{
681        ClientConfig, ClientConnection, DigitallySignedStruct, Error as RustlsError,
682        ServerConnection, SignatureScheme, SupportedProtocolVersion,
683    };
684    use std::sync::Barrier;
685    use std::thread;
686
687    fn instance(generation: u64) -> ProxyInstanceId {
688        ProxyInstanceId::new(ProxySessionId::generate().unwrap(), generation)
689    }
690
691    fn host(name: &str) -> NormalizedHost {
692        NormalizedHost::parse(name).unwrap()
693    }
694
695    fn entry_count(store: &LocalTlsIdentityStore) -> usize {
696        lock_recover(&store.state).entries.len()
697    }
698
699    #[test]
700    fn identities_are_fresh_across_stores_and_reused_within_one_store() {
701        let shared_instance = instance(7);
702        let first = LocalTlsIdentityStore::new(shared_instance.clone());
703        let second = LocalTlsIdentityStore::new(shared_instance);
704        let canonical = host("WELCOME.");
705        let equivalent = host("welcome");
706
707        let first_lease = first.prepare(&canonical).unwrap();
708        let reused_lease = first.prepare(&equivalent).unwrap();
709        let second_lease = second.prepare(&equivalent).unwrap();
710
711        assert!(Arc::ptr_eq(&first_lease.identity, &reused_lease.identity));
712        assert_eq!(first_lease.pin(), reused_lease.pin());
713        assert_ne!(
714            first_lease.pin().certificate_sha256(),
715            second_lease.pin().certificate_sha256()
716        );
717        assert_eq!(first_lease.pin().host(), &equivalent);
718        assert_eq!(entry_count(&first), 1);
719    }
720
721    #[test]
722    fn pins_match_only_the_exact_host_certificate_and_active_generation() {
723        let store = LocalTlsIdentityStore::new(instance(11));
724        let welcome = host("welcome");
725        let subdomain = host("www.welcome");
726        let lease = store.prepare(&welcome).unwrap();
727        let certificate_der = lease.identity.certificate_der.clone();
728
729        assert!(lease.pin().matches_der(&certificate_der));
730        assert!(store.matches_der(&welcome, &certificate_der));
731        assert!(!store.matches_der(&subdomain, &certificate_der));
732        assert!(!store.matches_der(&welcome, b"not the certificate"));
733        assert!(!store.matches_der(
734            &welcome,
735            &vec![0_u8; MAX_PRESENTED_CERTIFICATE_DER_BYTES + 1]
736        ));
737
738        store.deactivate();
739        assert_eq!(entry_count(&store), 0);
740        assert!(store.pin(&welcome).is_none());
741        assert!(!store.matches_der(&welcome, &certificate_der));
742        assert!(matches!(
743            store.prepare(&welcome),
744            Err(CertificateError::Inactive)
745        ));
746    }
747
748    #[test]
749    fn local_ca_material_round_trips_and_rejects_a_mismatched_key() {
750        let generated = LocalCertificateAuthority::generate().unwrap();
751        let authority = generated.authority().clone();
752        let reloaded = LocalCertificateAuthority::from_der(
753            authority.certificate_der().to_vec(),
754            generated.private_key_der(),
755        )
756        .unwrap();
757        assert_eq!(authority, reloaded);
758        assert_eq!(
759            authority.certificate_sha256_hex(),
760            reloaded.certificate_sha256_hex()
761        );
762        assert!(
763            authority
764                .certificate_pem()
765                .starts_with("-----BEGIN CERTIFICATE-----\n")
766        );
767        assert!(
768            authority
769                .certificate_pem()
770                .ends_with("-----END CERTIFICATE-----\n")
771        );
772
773        let other = LocalCertificateAuthority::generate().unwrap();
774        assert_eq!(
775            LocalCertificateAuthority::from_der(
776                authority.certificate_der().to_vec(),
777                other.private_key_der(),
778            )
779            .unwrap_err(),
780            LocalCertificateAuthorityError::KeyMismatch
781        );
782    }
783
784    #[test]
785    fn ca_signed_leaf_validates_for_only_its_exact_host() {
786        let generated = LocalCertificateAuthority::generate().unwrap();
787        let authority = generated.authority().clone();
788        let store = LocalTlsIdentityStore::with_certificate_authority(
789            instance(19),
790            Some(authority.clone()),
791        );
792        let lease = store.prepare(&host("welcome")).unwrap();
793
794        assert_eq!(
795            trusted_handshake(
796                lease.server_config(),
797                "welcome",
798                &rustls::version::TLS13,
799                &authority,
800            )
801            .unwrap(),
802            Some(b"http/1.1".to_vec())
803        );
804        assert!(
805            trusted_handshake(
806                lease.server_config(),
807                "other.welcome",
808                &rustls::version::TLS13,
809                &authority,
810            )
811            .is_err()
812        );
813    }
814
815    #[test]
816    fn diagnostics_redact_hosts_generation_tokens_and_fingerprints() {
817        let store = LocalTlsIdentityStore::new(instance(23));
818        let private_host = host("private-history.welcome");
819        let lease = store.prepare(&private_host).unwrap();
820        let pin_debug = format!("{:?}", lease.pin());
821        let digest_debug = format!("{:?}", lease.pin().certificate_sha256());
822        let lease_debug = format!("{lease:?}");
823        let store_debug = format!("{store:?}");
824
825        for diagnostic in [pin_debug, digest_debug, lease_debug, store_debug] {
826            assert!(!diagnostic.contains(private_host.as_str()));
827            assert!(!diagnostic.contains(store.instance.session().as_str()));
828            assert!(diagnostic.contains("REDACTED") || diagnostic.contains("entry_count"));
829        }
830    }
831
832    #[test]
833    fn cache_evicts_the_least_recent_idle_identity_only() {
834        let store = LocalTlsIdentityStore::new(instance(31));
835        let hosts: Vec<_> = (0..MAX_CACHED_IDENTITIES)
836            .map(|index| host(&format!("host-{index}.welcome")))
837            .collect();
838        for candidate in &hosts {
839            drop(store.prepare(candidate).unwrap());
840        }
841        assert_eq!(entry_count(&store), MAX_CACHED_IDENTITIES);
842
843        // Refresh the oldest entry so the second-created entry becomes LRU.
844        drop(store.prepare(&hosts[0]).unwrap());
845        let replacement = host("replacement.welcome");
846        drop(store.prepare(&replacement).unwrap());
847
848        assert_eq!(entry_count(&store), MAX_CACHED_IDENTITIES);
849        assert!(store.pin(&hosts[0]).is_some());
850        assert!(store.pin(&hosts[1]).is_none());
851        assert!(store.pin(&replacement).is_some());
852    }
853
854    #[test]
855    fn cache_fails_closed_when_every_identity_is_leased() {
856        let store = LocalTlsIdentityStore::new(instance(37));
857        let hosts: Vec<_> = (0..MAX_CACHED_IDENTITIES)
858            .map(|index| host(&format!("leased-{index}.welcome")))
859            .collect();
860        let mut leases: Vec<_> = hosts
861            .iter()
862            .map(|candidate| store.prepare(candidate).unwrap())
863            .collect();
864        let extra = host("extra.welcome");
865
866        assert!(matches!(
867            store.prepare(&extra),
868            Err(CertificateError::CapacityExhausted)
869        ));
870        assert_eq!(entry_count(&store), MAX_CACHED_IDENTITIES);
871
872        drop(leases.remove(0));
873        assert!(store.prepare(&extra).is_ok());
874        assert_eq!(entry_count(&store), MAX_CACHED_IDENTITIES);
875    }
876
877    #[test]
878    fn deactivate_fails_closed_for_prepare_and_pin_racing_the_state_lock() {
879        let store = Arc::new(LocalTlsIdentityStore::new(instance(38)));
880        let cached_host = host("cached.welcome");
881        let pending_host = host("pending.welcome");
882        let retained_lease = store.prepare(&cached_host).unwrap();
883        let cached_certificate_der = retained_lease.identity.certificate_der.clone();
884
885        // Keep both lookups behind the state mutex until deactivate has made
886        // resolver revocation visible. Depending on scheduling, each lookup
887        // observes inactivity either before locking or immediately after it;
888        // neither path may publish a lease or pin after that transition.
889        let state = lock_recover(&store.state);
890        let lookup_gate = Arc::new(Barrier::new(3));
891        let prepare_thread = {
892            let store = Arc::clone(&store);
893            let lookup_gate = Arc::clone(&lookup_gate);
894            thread::spawn(move || {
895                lookup_gate.wait();
896                store
897                    .prepare(&pending_host)
898                    .map(|lease| lease.pin().clone())
899            })
900        };
901        let pin_thread = {
902            let store = Arc::clone(&store);
903            let lookup_gate = Arc::clone(&lookup_gate);
904            thread::spawn(move || {
905                lookup_gate.wait();
906                store.pin(&cached_host)
907            })
908        };
909        lookup_gate.wait();
910
911        let deactivate_thread = {
912            let store = Arc::clone(&store);
913            thread::spawn(move || store.deactivate())
914        };
915        while store.active.load(Ordering::Acquire) {
916            thread::yield_now();
917        }
918        drop(state);
919
920        assert!(matches!(
921            prepare_thread.join().unwrap(),
922            Err(CertificateError::Inactive)
923        ));
924        assert!(pin_thread.join().unwrap().is_none());
925        deactivate_thread.join().unwrap();
926
927        assert_eq!(entry_count(&store), 0);
928        assert!(store.pin(&host("cached.welcome")).is_none());
929        assert!(!store.matches_der(&host("cached.welcome"), &cached_certificate_der));
930        assert!(
931            handshake(
932                retained_lease.server_config(),
933                "cached.welcome",
934                &rustls::version::TLS13,
935                true,
936            )
937            .is_err()
938        );
939    }
940
941    #[test]
942    fn eviction_skips_a_retained_lru_lease_then_deactivation_revokes_it() {
943        let store = LocalTlsIdentityStore::new(instance(39));
944        let hosts: Vec<_> = (0..MAX_CACHED_IDENTITIES)
945            .map(|index| host(&format!("lease-aware-{index}.welcome")))
946            .collect();
947        let retained_lru = store.prepare(&hosts[0]).unwrap();
948        let retained_certificate_der = retained_lru.identity.certificate_der.clone();
949        for candidate in &hosts[1..] {
950            drop(store.prepare(candidate).unwrap());
951        }
952
953        let replacement = host("lease-aware-replacement.welcome");
954        drop(store.prepare(&replacement).unwrap());
955
956        assert_eq!(entry_count(&store), MAX_CACHED_IDENTITIES);
957        assert_eq!(store.pin(&hosts[0]).as_ref(), Some(retained_lru.pin()));
958        assert!(store.pin(&hosts[1]).is_none());
959        assert!(store.pin(&replacement).is_some());
960        assert!(store.matches_der(&hosts[0], &retained_certificate_der));
961
962        store.deactivate();
963
964        assert_eq!(entry_count(&store), 0);
965        assert!(retained_lru.pin().matches_der(&retained_certificate_der));
966        assert!(!store.matches_der(&hosts[0], &retained_certificate_der));
967        assert!(
968            handshake(
969                retained_lru.server_config(),
970                hosts[0].as_str(),
971                &rustls::version::TLS13,
972                true,
973            )
974            .is_err()
975        );
976    }
977
978    #[derive(Debug)]
979    struct AcceptCertificate;
980
981    impl ServerCertVerifier for AcceptCertificate {
982        fn verify_server_cert(
983            &self,
984            _end_entity: &CertificateDer<'_>,
985            _intermediates: &[CertificateDer<'_>],
986            _server_name: &ServerName<'_>,
987            _ocsp_response: &[u8],
988            _now: UnixTime,
989        ) -> Result<ServerCertVerified, RustlsError> {
990            Ok(ServerCertVerified::assertion())
991        }
992
993        fn verify_tls12_signature(
994            &self,
995            message: &[u8],
996            cert: &CertificateDer<'_>,
997            signature: &DigitallySignedStruct,
998        ) -> Result<HandshakeSignatureValid, RustlsError> {
999            rustls::crypto::verify_tls12_signature(
1000                message,
1001                cert,
1002                signature,
1003                &rustls::crypto::ring::default_provider().signature_verification_algorithms,
1004            )
1005        }
1006
1007        fn verify_tls13_signature(
1008            &self,
1009            message: &[u8],
1010            cert: &CertificateDer<'_>,
1011            signature: &DigitallySignedStruct,
1012        ) -> Result<HandshakeSignatureValid, RustlsError> {
1013            rustls::crypto::verify_tls13_signature(
1014                message,
1015                cert,
1016                signature,
1017                &rustls::crypto::ring::default_provider().signature_verification_algorithms,
1018            )
1019        }
1020
1021        fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
1022            rustls::crypto::ring::default_provider()
1023                .signature_verification_algorithms
1024                .supported_schemes()
1025        }
1026    }
1027
1028    fn handshake(
1029        server_config: Arc<ServerConfig>,
1030        sni: &str,
1031        version: &'static SupportedProtocolVersion,
1032        send_sni: bool,
1033    ) -> Result<Option<Vec<u8>>, RustlsError> {
1034        let mut client_config =
1035            ClientConfig::builder_with_provider(Arc::new(rustls::crypto::ring::default_provider()))
1036                .with_protocol_versions(&[version])
1037                .unwrap()
1038                .dangerous()
1039                .with_custom_certificate_verifier(Arc::new(AcceptCertificate))
1040                .with_no_client_auth();
1041        client_config.enable_sni = send_sni;
1042        client_config.alpn_protocols = vec![b"http/1.1".to_vec()];
1043        handshake_with_client_config(server_config, sni, Arc::new(client_config))
1044    }
1045
1046    fn trusted_handshake(
1047        server_config: Arc<ServerConfig>,
1048        sni: &str,
1049        version: &'static SupportedProtocolVersion,
1050        authority: &LocalCertificateAuthority,
1051    ) -> Result<Option<Vec<u8>>, RustlsError> {
1052        let mut roots = rustls::RootCertStore::empty();
1053        roots
1054            .add(CertificateDer::from(authority.certificate_der().to_vec()))
1055            .map_err(|error| RustlsError::General(error.to_string()))?;
1056        let mut client_config =
1057            ClientConfig::builder_with_provider(Arc::new(rustls::crypto::ring::default_provider()))
1058                .with_protocol_versions(&[version])
1059                .map_err(|error| RustlsError::General(error.to_string()))?
1060                .with_root_certificates(roots)
1061                .with_no_client_auth();
1062        client_config.alpn_protocols = vec![b"http/1.1".to_vec()];
1063        handshake_with_client_config(server_config, sni, Arc::new(client_config))
1064    }
1065
1066    fn handshake_with_client_config(
1067        server_config: Arc<ServerConfig>,
1068        sni: &str,
1069        client_config: Arc<ClientConfig>,
1070    ) -> Result<Option<Vec<u8>>, RustlsError> {
1071        let server_name = ServerName::try_from(sni.to_owned()).unwrap();
1072        let mut client = ClientConnection::new(client_config, server_name)?;
1073        let mut server = ServerConnection::new(server_config)?;
1074
1075        for _ in 0..32 {
1076            let mut client_wire = Vec::new();
1077            while client.wants_write() {
1078                client
1079                    .write_tls(&mut client_wire)
1080                    .map_err(|_| in_memory_io_error())?;
1081            }
1082            if !client_wire.is_empty() {
1083                server
1084                    .read_tls(&mut client_wire.as_slice())
1085                    .map_err(|_| in_memory_io_error())?;
1086                server.process_new_packets()?;
1087            }
1088
1089            let mut server_wire = Vec::new();
1090            while server.wants_write() {
1091                server
1092                    .write_tls(&mut server_wire)
1093                    .map_err(|_| in_memory_io_error())?;
1094            }
1095            if !server_wire.is_empty() {
1096                client
1097                    .read_tls(&mut server_wire.as_slice())
1098                    .map_err(|_| in_memory_io_error())?;
1099                client.process_new_packets()?;
1100            }
1101
1102            if !client.is_handshaking() && !server.is_handshaking() {
1103                return Ok(client.alpn_protocol().map(<[u8]>::to_vec));
1104            }
1105        }
1106        Err(RustlsError::General(
1107            "in-memory TLS handshake did not finish".to_owned(),
1108        ))
1109    }
1110
1111    fn in_memory_io_error() -> RustlsError {
1112        RustlsError::General("in-memory TLS handshake I/O failed".to_owned())
1113    }
1114
1115    #[test]
1116    fn resolver_requires_exact_canonical_sni_and_supports_tls12_tls13_http11() {
1117        let store = LocalTlsIdentityStore::new(instance(41));
1118        let welcome = host("welcome");
1119        let lease = store.prepare(&welcome).unwrap();
1120        assert!(!lease.server_config().session_storage.can_cache());
1121        assert_eq!(lease.server_config().send_tls13_tickets, 0);
1122
1123        for version in [&rustls::version::TLS12, &rustls::version::TLS13] {
1124            assert_eq!(
1125                handshake(lease.server_config(), "welcome", version, true).unwrap(),
1126                Some(b"http/1.1".to_vec())
1127            );
1128        }
1129        assert_eq!(
1130            handshake(
1131                lease.server_config(),
1132                "WELCOME.",
1133                &rustls::version::TLS13,
1134                true,
1135            )
1136            .unwrap(),
1137            Some(b"http/1.1".to_vec())
1138        );
1139        assert!(
1140            handshake(
1141                lease.server_config(),
1142                "other.welcome",
1143                &rustls::version::TLS13,
1144                true
1145            )
1146            .is_err()
1147        );
1148        assert!(
1149            handshake(
1150                lease.server_config(),
1151                "welcome",
1152                &rustls::version::TLS13,
1153                false
1154            )
1155            .is_err()
1156        );
1157
1158        store.deactivate();
1159        assert!(
1160            handshake(
1161                lease.server_config(),
1162                "welcome",
1163                &rustls::version::TLS13,
1164                true
1165            )
1166            .is_err()
1167        );
1168    }
1169}