1use std::sync::Arc;
10
11use arc_swap::ArcSwapOption;
12use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
13use rustls::pki_types::{CertificateDer, CertificateRevocationListDer, ServerName, UnixTime};
14use rustls::server::WebPkiClientVerifier;
15use rustls::server::danger::{ClientCertVerified, ClientCertVerifier};
16use rustls::{DigitallySignedStruct, DistinguishedName, RootCertStore, SignatureScheme};
17
18use crate::cache::{CrlCache, CrlSourceId};
19
20type CrlFingerprint = Vec<usize>;
27
28fn fingerprint(crls: &[Arc<CertificateRevocationListDer<'static>>]) -> CrlFingerprint {
29 crls.iter().map(|arc| Arc::as_ptr(arc) as usize).collect()
30}
31
32fn build_owned(
33 crls: &[Arc<CertificateRevocationListDer<'static>>],
34) -> Vec<CertificateRevocationListDer<'static>> {
35 crls.iter().map(|arc| (**arc).clone()).collect()
36}
37
38struct CachedClient {
39 fingerprint: CrlFingerprint,
40 verifier: Arc<dyn ClientCertVerifier>,
41}
42
43struct CachedServer {
44 fingerprint: CrlFingerprint,
45 verifier: Arc<dyn ServerCertVerifier>,
46}
47
48pub struct RefreshableClientCertVerifier {
52 cache: Arc<CrlCache>,
53 sources: Vec<CrlSourceId>,
54 cas: Arc<RootCertStore>,
55 allow_unauthenticated: bool,
56 root_hint_subjects: Vec<DistinguishedName>,
57 cached: ArcSwapOption<CachedClient>,
58}
59
60impl std::fmt::Debug for RefreshableClientCertVerifier {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 f.debug_struct("RefreshableClientCertVerifier")
63 .field("sources", &self.sources)
64 .field("allow_unauthenticated", &self.allow_unauthenticated)
65 .finish_non_exhaustive()
66 }
67}
68
69impl RefreshableClientCertVerifier {
70 #[must_use]
71 pub fn new(
72 cache: Arc<CrlCache>,
73 sources: Vec<CrlSourceId>,
74 cas: Arc<RootCertStore>,
75 allow_unauthenticated: bool,
76 ) -> Arc<Self> {
77 let root_hint_subjects = cas.subjects();
78 Arc::new(Self {
79 cache,
80 sources,
81 cas,
82 allow_unauthenticated,
83 root_hint_subjects,
84 cached: ArcSwapOption::from(None),
85 })
86 }
87
88 fn build_inner(&self) -> Result<Arc<dyn ClientCertVerifier>, rustls::Error> {
89 let crls = self
90 .cache
91 .snapshot(&self.sources)
92 .map_err(|e| rustls::Error::General(format!("crl unavailable: {e}")))?;
93 let fp = fingerprint(&crls);
94 if let Some(hit) = self.cached.load_full()
95 && hit.fingerprint == fp
96 {
97 return Ok(Arc::clone(&hit.verifier));
98 }
99 let mut builder = WebPkiClientVerifier::builder(Arc::clone(&self.cas));
100 if !crls.is_empty() {
101 builder = builder.with_crls(build_owned(&crls));
102 }
103 if self.allow_unauthenticated {
104 builder = builder.allow_unauthenticated();
105 }
106 let verifier =
107 builder.build().map_err(|e| rustls::Error::General(format!("verifier build: {e}")))?;
108 let verifier: Arc<dyn ClientCertVerifier> = verifier;
109 self
110 .cached
111 .store(Some(Arc::new(CachedClient { fingerprint: fp, verifier: Arc::clone(&verifier) })));
112 Ok(verifier)
113 }
114}
115
116impl ClientCertVerifier for RefreshableClientCertVerifier {
117 fn offer_client_auth(&self) -> bool {
118 true
119 }
120
121 fn client_auth_mandatory(&self) -> bool {
122 !self.allow_unauthenticated
123 }
124
125 fn root_hint_subjects(&self) -> &[DistinguishedName] {
126 &self.root_hint_subjects
127 }
128
129 fn verify_client_cert(
130 &self,
131 end_entity: &CertificateDer<'_>,
132 intermediates: &[CertificateDer<'_>],
133 now: UnixTime,
134 ) -> Result<ClientCertVerified, rustls::Error> {
135 self.build_inner()?.verify_client_cert(end_entity, intermediates, now)
136 }
137
138 fn verify_tls12_signature(
139 &self,
140 message: &[u8],
141 cert: &CertificateDer<'_>,
142 dss: &DigitallySignedStruct,
143 ) -> Result<HandshakeSignatureValid, rustls::Error> {
144 rustls::crypto::verify_tls12_signature(
147 message,
148 cert,
149 dss,
150 &rustls::crypto::CryptoProvider::get_default()
151 .expect("rustls crypto provider installed at boot")
152 .signature_verification_algorithms,
153 )
154 }
155
156 fn verify_tls13_signature(
157 &self,
158 message: &[u8],
159 cert: &CertificateDer<'_>,
160 dss: &DigitallySignedStruct,
161 ) -> Result<HandshakeSignatureValid, rustls::Error> {
162 rustls::crypto::verify_tls13_signature(
163 message,
164 cert,
165 dss,
166 &rustls::crypto::CryptoProvider::get_default()
167 .expect("rustls crypto provider installed at boot")
168 .signature_verification_algorithms,
169 )
170 }
171
172 fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
173 rustls::crypto::CryptoProvider::get_default()
174 .expect("rustls crypto provider installed at boot")
175 .signature_verification_algorithms
176 .supported_schemes()
177 }
178}
179
180pub struct RefreshableServerCertVerifier {
185 cache: Arc<CrlCache>,
186 sources: Vec<CrlSourceId>,
187 cas: Arc<RootCertStore>,
188 cached: ArcSwapOption<CachedServer>,
189}
190
191impl std::fmt::Debug for RefreshableServerCertVerifier {
192 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
193 f.debug_struct("RefreshableServerCertVerifier")
194 .field("sources", &self.sources)
195 .finish_non_exhaustive()
196 }
197}
198
199impl RefreshableServerCertVerifier {
200 #[must_use]
201 pub fn new(
202 cache: Arc<CrlCache>,
203 sources: Vec<CrlSourceId>,
204 cas: Arc<RootCertStore>,
205 ) -> Arc<Self> {
206 Arc::new(Self { cache, sources, cas, cached: ArcSwapOption::from(None) })
207 }
208
209 fn build_inner(&self) -> Result<Arc<dyn ServerCertVerifier>, rustls::Error> {
210 let crls = self
211 .cache
212 .snapshot(&self.sources)
213 .map_err(|e| rustls::Error::General(format!("crl unavailable: {e}")))?;
214 let fp = fingerprint(&crls);
215 if let Some(hit) = self.cached.load_full()
216 && hit.fingerprint == fp
217 {
218 return Ok(Arc::clone(&hit.verifier));
219 }
220 let mut builder = rustls::client::WebPkiServerVerifier::builder(Arc::clone(&self.cas));
221 if !crls.is_empty() {
222 builder = builder.with_crls(build_owned(&crls));
223 }
224 let inner =
225 builder.build().map_err(|e| rustls::Error::General(format!("verifier build: {e}")))?;
226 let verifier: Arc<dyn ServerCertVerifier> = inner;
227 self
228 .cached
229 .store(Some(Arc::new(CachedServer { fingerprint: fp, verifier: Arc::clone(&verifier) })));
230 Ok(verifier)
231 }
232}
233
234impl ServerCertVerifier for RefreshableServerCertVerifier {
235 fn verify_server_cert(
236 &self,
237 end_entity: &CertificateDer<'_>,
238 intermediates: &[CertificateDer<'_>],
239 server_name: &ServerName<'_>,
240 ocsp_response: &[u8],
241 now: UnixTime,
242 ) -> Result<ServerCertVerified, rustls::Error> {
243 self.build_inner()?.verify_server_cert(
244 end_entity,
245 intermediates,
246 server_name,
247 ocsp_response,
248 now,
249 )
250 }
251
252 fn verify_tls12_signature(
253 &self,
254 message: &[u8],
255 cert: &CertificateDer<'_>,
256 dss: &DigitallySignedStruct,
257 ) -> Result<HandshakeSignatureValid, rustls::Error> {
258 rustls::crypto::verify_tls12_signature(
259 message,
260 cert,
261 dss,
262 &rustls::crypto::CryptoProvider::get_default()
263 .expect("rustls crypto provider installed at boot")
264 .signature_verification_algorithms,
265 )
266 }
267
268 fn verify_tls13_signature(
269 &self,
270 message: &[u8],
271 cert: &CertificateDer<'_>,
272 dss: &DigitallySignedStruct,
273 ) -> Result<HandshakeSignatureValid, rustls::Error> {
274 rustls::crypto::verify_tls13_signature(
275 message,
276 cert,
277 dss,
278 &rustls::crypto::CryptoProvider::get_default()
279 .expect("rustls crypto provider installed at boot")
280 .signature_verification_algorithms,
281 )
282 }
283
284 fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
285 rustls::crypto::CryptoProvider::get_default()
286 .expect("rustls crypto provider installed at boot")
287 .signature_verification_algorithms
288 .supported_schemes()
289 }
290}
291
292#[cfg(test)]
293mod tests {
294 use std::sync::Arc;
295 use std::sync::atomic::{AtomicUsize, Ordering};
296
297 use async_trait::async_trait;
298 use rustls::RootCertStore;
299
300 use super::*;
301 use crate::cache::{CrlCache, CrlFetchFailure, CrlFetcher, CrlSourceId};
302
303 struct StaticFetcher {
304 bytes: Vec<u8>,
305 count: AtomicUsize,
306 }
307
308 #[async_trait]
309 impl CrlFetcher for StaticFetcher {
310 async fn fetch(&self, _src: &CrlSourceId) -> Result<Vec<u8>, String> {
311 self.count.fetch_add(1, Ordering::SeqCst);
312 Ok(self.bytes.clone())
313 }
314 }
315
316 struct FailingFetcher;
317
318 #[async_trait]
319 impl CrlFetcher for FailingFetcher {
320 async fn fetch(&self, _src: &CrlSourceId) -> Result<Vec<u8>, String> {
321 Err("test failure".into())
322 }
323 }
324
325 fn install_crypto_once() {
326 let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
329 }
330
331 fn ca_only_root_store() -> (Arc<RootCertStore>, rcgen::Issuer<'static, rcgen::KeyPair>) {
332 use rcgen::{BasicConstraints, CertificateParams, IsCa, Issuer, KeyPair, KeyUsagePurpose};
333 let mut params = CertificateParams::new(vec!["test ca".into()]).expect("ca params");
334 params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
335 params.key_usages = vec![
336 KeyUsagePurpose::KeyCertSign,
337 KeyUsagePurpose::DigitalSignature,
338 KeyUsagePurpose::CrlSign,
339 ];
340 let key = KeyPair::generate().expect("ca key");
341 let cert = params.clone().self_signed(&key).expect("self-sign ca");
342 let cert_der = cert.der().clone();
343 let issuer = Issuer::new(params, key);
344 let mut store = RootCertStore::empty();
345 store.add(cert_der).expect("add ca");
346 (Arc::new(store), issuer)
347 }
348
349 fn fixture_crl(issuer: &rcgen::Issuer<'_, rcgen::KeyPair>, revoked: &[u64]) -> Vec<u8> {
350 use rcgen::{
351 CertificateRevocationListParams, KeyIdMethod, RevocationReason, RevokedCertParams,
352 SerialNumber,
353 };
354 let now = time::OffsetDateTime::now_utc();
355 let params = CertificateRevocationListParams {
356 this_update: now,
357 next_update: now + time::Duration::hours(24),
358 crl_number: SerialNumber::from(1u64),
359 issuing_distribution_point: None,
360 revoked_certs: revoked
361 .iter()
362 .map(|s| RevokedCertParams {
363 serial_number: SerialNumber::from(*s),
364 revocation_time: now,
365 reason_code: Some(RevocationReason::KeyCompromise),
366 invalidity_date: None,
367 })
368 .collect(),
369 key_identifier_method: KeyIdMethod::Sha256,
370 };
371 params.signed_by(issuer).expect("sign crl").der().as_ref().to_vec()
372 }
373
374 #[tokio::test(flavor = "multi_thread")]
375 async fn client_verifier_builds_with_empty_cache_when_no_sources() {
376 install_crypto_once();
377 let (cas, _issuer) = ca_only_root_store();
378 let fetcher = Arc::new(StaticFetcher { bytes: vec![], count: AtomicUsize::new(0) });
379 let cache = CrlCache::new(fetcher);
380 let v = RefreshableClientCertVerifier::new(cache, Vec::new(), cas, false);
381 assert!(v.build_inner().is_ok());
382 assert!(v.client_auth_mandatory());
383 }
384
385 #[tokio::test(flavor = "multi_thread")]
386 async fn client_verifier_propagates_reject_unavailable() {
387 install_crypto_once();
388 let (cas, _issuer) = ca_only_root_store();
389 let cache = CrlCache::new(Arc::new(FailingFetcher));
390 let src = CrlSourceId::Url("https://crl.example/down".into());
391 let _ = cache.ensure_loaded(&[(src.clone(), CrlFetchFailure::Reject)]);
392 let v = RefreshableClientCertVerifier::new(cache, vec![src], cas, false);
393 let err = v.build_inner().expect_err("reject unavailable must fail");
394 match err {
395 rustls::Error::General(msg) => assert!(msg.contains("crl unavailable"), "{msg}"),
396 other => panic!("unexpected: {other:?}"),
397 }
398 }
399
400 #[tokio::test(flavor = "multi_thread")]
401 async fn server_verifier_builds_with_real_crl_bytes() {
402 install_crypto_once();
403 let (cas, issuer) = ca_only_root_store();
404 let bytes = fixture_crl(&issuer, &[42]);
405 let fetcher = Arc::new(StaticFetcher { bytes, count: AtomicUsize::new(0) });
406 let cache = CrlCache::new(fetcher);
407 let src = CrlSourceId::Url("https://crl.example/with-revoke".into());
408 cache.ensure_loaded(&[(src.clone(), CrlFetchFailure::Tolerate)]).expect("load");
409 let v = RefreshableServerCertVerifier::new(cache, vec![src], cas);
410 assert!(v.build_inner().is_ok());
411 }
412
413 struct SwapFetcher {
417 calls: AtomicUsize,
418 first: Vec<u8>,
419 second: Vec<u8>,
420 }
421
422 #[async_trait]
423 impl CrlFetcher for SwapFetcher {
424 async fn fetch(&self, _src: &CrlSourceId) -> Result<Vec<u8>, String> {
425 let n = self.calls.fetch_add(1, Ordering::SeqCst);
426 Ok(if n == 0 { self.first.clone() } else { self.second.clone() })
427 }
428 }
429
430 #[tokio::test(flavor = "multi_thread")]
431 async fn client_verifier_reuses_cached_inner_until_crl_arc_changes() {
432 install_crypto_once();
433 let (cas, issuer) = ca_only_root_store();
434 let bytes_v1 = fixture_crl(&issuer, &[1]);
435 let bytes_v2 = fixture_crl(&issuer, &[1, 2]);
436 let fetcher =
437 Arc::new(SwapFetcher { calls: AtomicUsize::new(0), first: bytes_v1, second: bytes_v2 });
438 let cache = CrlCache::new(fetcher);
439 let src = CrlSourceId::Url("https://crl.example/cached".into());
440 cache.ensure_loaded(&[(src.clone(), CrlFetchFailure::Tolerate)]).expect("load v1");
441 let v = RefreshableClientCertVerifier::new(cache.clone(), vec![src.clone()], cas, false);
442 let a = v.build_inner().expect("build a");
443 let b = v.build_inner().expect("build b");
444 assert!(Arc::ptr_eq(&a, &b), "cache hit reuses the same inner verifier Arc");
445
446 cache.ensure_loaded(&[(src, CrlFetchFailure::Tolerate)]).expect("refresh to v2");
449 let c = v.build_inner().expect("build c");
450 assert!(!Arc::ptr_eq(&b, &c), "post-refresh forces a rebuild");
451 }
452
453 #[tokio::test(flavor = "multi_thread")]
454 async fn allow_unauthenticated_flips_mandatory_flag() {
455 install_crypto_once();
456 let (cas, _issuer) = ca_only_root_store();
457 let fetcher = Arc::new(StaticFetcher { bytes: vec![], count: AtomicUsize::new(0) });
458 let cache = CrlCache::new(fetcher);
459 let v_mandatory =
460 RefreshableClientCertVerifier::new(Arc::clone(&cache), Vec::new(), Arc::clone(&cas), false);
461 let v_request = RefreshableClientCertVerifier::new(cache, Vec::new(), cas, true);
462 assert!(v_mandatory.client_auth_mandatory());
463 assert!(!v_request.client_auth_mandatory());
464 }
465}