rustls/webpki/
client_verifier.rs

1use alloc::vec::Vec;
2
3use pki_types::CertificateRevocationListDer;
4use webpki::{
5    CertRevocationList, ExpirationPolicy, ExtendedKeyUsage, RevocationCheckDepth,
6    UnknownStatusPolicy,
7};
8
9use super::{VerifierBuilderError, pki_error};
10#[cfg(doc)]
11use crate::ConfigBuilder;
12#[cfg(doc)]
13use crate::crypto;
14use crate::crypto::{CryptoProvider, Identity, WebPkiSupportedAlgorithms};
15use crate::enums::SignatureScheme;
16use crate::error::ApiMisuse;
17#[cfg(doc)]
18use crate::server::ServerConfig;
19use crate::sync::Arc;
20use crate::verify::{
21    ClientIdentity, ClientVerifier, HandshakeSignatureValid, NoClientAuth, PeerVerified,
22    SignatureVerificationInput,
23};
24use crate::webpki::parse_crls;
25use crate::webpki::verify::{ParsedCertificate, verify_tls12_signature, verify_tls13_signature};
26use crate::{DistinguishedName, Error, RootCertStore};
27
28/// A builder for configuring a `webpki` client certificate verifier.
29///
30/// For more information, see the [`WebPkiClientVerifier`] documentation.
31#[derive(Debug, Clone)]
32pub struct ClientVerifierBuilder {
33    roots: Arc<RootCertStore>,
34    root_hint_subjects: Vec<DistinguishedName>,
35    crls: Vec<CertificateRevocationListDer<'static>>,
36    revocation_check_depth: RevocationCheckDepth,
37    unknown_revocation_policy: UnknownStatusPolicy,
38    revocation_expiration_policy: ExpirationPolicy,
39    anon_policy: AnonymousClientPolicy,
40    supported_algs: WebPkiSupportedAlgorithms,
41}
42
43impl ClientVerifierBuilder {
44    pub(crate) fn new(
45        roots: Arc<RootCertStore>,
46        supported_algs: WebPkiSupportedAlgorithms,
47    ) -> Self {
48        Self {
49            root_hint_subjects: roots.subjects(),
50            roots,
51            crls: Vec::new(),
52            anon_policy: AnonymousClientPolicy::Deny,
53            revocation_check_depth: RevocationCheckDepth::Chain,
54            unknown_revocation_policy: UnknownStatusPolicy::Deny,
55            revocation_expiration_policy: ExpirationPolicy::Ignore,
56            supported_algs,
57        }
58    }
59
60    /// Clear the list of trust anchor hint subjects.
61    ///
62    /// By default, the client cert verifier will use the subjects provided by the root cert
63    /// store configured for client authentication. Calling this function will remove these
64    /// hint subjects, indicating the client should make a free choice of which certificate
65    /// to send.
66    ///
67    /// See [`ClientVerifier::root_hint_subjects`] for more information on
68    /// circumstances where you may want to clear the default hint subjects.
69    pub fn clear_root_hint_subjects(mut self) -> Self {
70        self.root_hint_subjects = Vec::default();
71        self
72    }
73
74    /// Add additional [`DistinguishedName`]s to the list of trust anchor hint subjects.
75    ///
76    /// By default, the client cert verifier will use the subjects provided by the root cert
77    /// store configured for client authentication. Calling this function will add to these
78    /// existing hint subjects. Calling this function with empty `subjects` will have no
79    /// effect.
80    ///
81    /// See [`ClientVerifier::root_hint_subjects`] for more information on
82    /// circumstances where you may want to override the default hint subjects.
83    pub fn add_root_hint_subjects(
84        mut self,
85        subjects: impl IntoIterator<Item = DistinguishedName>,
86    ) -> Self {
87        self.root_hint_subjects.extend(subjects);
88        self
89    }
90
91    /// Verify the revocation state of presented client certificates against the provided
92    /// certificate revocation lists (CRLs). Calling `with_crls` multiple times appends the
93    /// given CRLs to the existing collection.
94    ///
95    /// By default all certificates in the verified chain built from the presented client
96    /// certificate to a trust anchor will have their revocation status checked. Calling
97    /// [`only_check_end_entity_revocation`][Self::only_check_end_entity_revocation] will
98    /// change this behavior to only check the end entity client certificate.
99    ///
100    /// By default if a certificate's revocation status can not be determined using the
101    /// configured CRLs, it will be treated as an error. Calling
102    /// [`allow_unknown_revocation_status`][Self::allow_unknown_revocation_status] will change
103    /// this behavior to allow unknown revocation status.
104    pub fn with_crls(
105        mut self,
106        crls: impl IntoIterator<Item = CertificateRevocationListDer<'static>>,
107    ) -> Self {
108        self.crls.extend(crls);
109        self
110    }
111
112    /// Only check the end entity certificate revocation status when using CRLs.
113    ///
114    /// If CRLs are provided using [`with_crls`][Self::with_crls] only check the end entity
115    /// certificate's revocation status. Overrides the default behavior of checking revocation
116    /// status for each certificate in the verified chain built to a trust anchor
117    /// (excluding the trust anchor itself).
118    ///
119    /// If no CRLs are provided then this setting has no effect. Neither the end entity certificate
120    /// or any intermediates will have revocation status checked.
121    pub fn only_check_end_entity_revocation(mut self) -> Self {
122        self.revocation_check_depth = RevocationCheckDepth::EndEntity;
123        self
124    }
125
126    /// Allow unauthenticated clients to connect.
127    ///
128    /// Clients that offer a client certificate issued by a trusted root, and clients that offer no
129    /// client certificate will be allowed to connect.
130    pub fn allow_unauthenticated(mut self) -> Self {
131        self.anon_policy = AnonymousClientPolicy::Allow;
132        self
133    }
134
135    /// Allow unknown certificate revocation status when using CRLs.
136    ///
137    /// If CRLs are provided with [`with_crls`][Self::with_crls] and it isn't possible to
138    /// determine the revocation status of a certificate, do not treat it as an error condition.
139    /// Overrides the default behavior where unknown revocation status is considered an error.
140    ///
141    /// If no CRLs are provided then this setting has no effect as revocation status checks
142    /// are not performed.
143    pub fn allow_unknown_revocation_status(mut self) -> Self {
144        self.unknown_revocation_policy = UnknownStatusPolicy::Allow;
145        self
146    }
147
148    /// Enforce the CRL nextUpdate field (i.e. expiration)
149    ///
150    /// If CRLs are provided with [`with_crls`][Self::with_crls] and the verification time is
151    /// beyond the time in the CRL nextUpdate field, it is expired and treated as an error condition.
152    /// Overrides the default behavior where expired CRLs are not treated as an error condition.
153    ///
154    /// If no CRLs are provided then this setting has no effect as revocation status checks
155    /// are not performed.
156    pub fn enforce_revocation_expiration(mut self) -> Self {
157        self.revocation_expiration_policy = ExpirationPolicy::Enforce;
158        self
159    }
160
161    /// Build a client certificate verifier. The built verifier will be used for the server to offer
162    /// client certificate authentication, to control how offered client certificates are validated,
163    /// and to determine what to do with anonymous clients that do not respond to the client
164    /// certificate authentication offer with a client certificate.
165    ///
166    /// If `with_signature_verification_algorithms` was not called on the builder, a default set of
167    /// signature verification algorithms is used, controlled by the selected [`CryptoProvider`].
168    ///
169    /// Once built, the provided `Arc<dyn ClientVerifier>` can be used with a Rustls
170    /// [`ServerConfig`] to configure client certificate validation using
171    /// [`with_client_cert_verifier`][ConfigBuilder<ClientConfig, WantsVerifier>::with_client_cert_verifier].
172    ///
173    /// # Errors
174    /// This function will return a [`VerifierBuilderError`] if:
175    /// 1. No trust anchors have been provided.
176    /// 2. DER encoded CRLs have been provided that can not be parsed successfully.
177    pub fn build(self) -> Result<Arc<dyn ClientVerifier>, VerifierBuilderError> {
178        if self.roots.is_empty() {
179            return Err(VerifierBuilderError::NoRootAnchors);
180        }
181
182        Ok(Arc::new(WebPkiClientVerifier::new(
183            self.roots,
184            Arc::from(self.root_hint_subjects),
185            parse_crls(self.crls)?,
186            self.revocation_check_depth,
187            self.unknown_revocation_policy,
188            self.revocation_expiration_policy,
189            self.anon_policy,
190            self.supported_algs,
191        )))
192    }
193}
194
195/// A client certificate verifier that uses the `webpki` crate[^1] to perform client certificate
196/// validation.
197///
198/// It must be created via the [`WebPkiClientVerifier::builder()`] or
199/// [`WebPkiClientVerifier::builder_with_provider()`] functions.
200///
201/// Once built, the provided `Arc<dyn ClientVerifier>` can be used with a Rustls [`ServerConfig`]
202/// to configure client certificate validation using [`with_client_cert_verifier`][ConfigBuilder<ClientConfig, WantsVerifier>::with_client_cert_verifier].
203///
204/// Example:
205///
206/// To require all clients present a client certificate issued by a trusted CA:
207/// ```no_run
208/// # #[cfg(any(feature = "ring", feature = "aws-lc-rs"))] {
209/// # use rustls::RootCertStore;
210/// # use rustls::server::WebPkiClientVerifier;
211/// # let roots = RootCertStore::empty();
212/// let client_verifier = WebPkiClientVerifier::builder(roots.into())
213///   .build()
214///   .unwrap();
215/// # }
216/// ```
217///
218/// Or, to allow clients presenting a client certificate authenticated by a trusted CA, or
219/// anonymous clients that present no client certificate:
220/// ```no_run
221/// # #[cfg(any(feature = "ring", feature = "aws-lc-rs"))] {
222/// # use rustls::RootCertStore;
223/// # use rustls::server::WebPkiClientVerifier;
224/// # let roots = RootCertStore::empty();
225/// let client_verifier = WebPkiClientVerifier::builder(roots.into())
226///   .allow_unauthenticated()
227///   .build()
228///   .unwrap();
229/// # }
230/// ```
231///
232/// If you wish to disable advertising client authentication:
233/// ```no_run
234/// # use rustls::RootCertStore;
235/// # use rustls::server::WebPkiClientVerifier;
236/// # let roots = RootCertStore::empty();
237/// let client_verifier = WebPkiClientVerifier::no_client_auth();
238/// ```
239///
240/// You can also configure the client verifier to check for certificate revocation with
241/// client certificate revocation lists (CRLs):
242/// ```no_run
243/// # #[cfg(any(feature = "ring", feature = "aws-lc-rs"))] {
244/// # use rustls::RootCertStore;
245/// # use rustls::server::{WebPkiClientVerifier};
246/// # let roots = RootCertStore::empty();
247/// # let crls = Vec::new();
248/// let client_verifier = WebPkiClientVerifier::builder(roots.into())
249///   .with_crls(crls)
250///   .build()
251///   .unwrap();
252/// # }
253/// ```
254///
255/// [^1]: <https://github.com/rustls/webpki>
256#[derive(Debug)]
257pub struct WebPkiClientVerifier {
258    roots: Arc<RootCertStore>,
259    root_hint_subjects: Arc<[DistinguishedName]>,
260    eku_validator: ExtendedKeyUsage,
261    crls: Vec<CertRevocationList<'static>>,
262    revocation_check_depth: RevocationCheckDepth,
263    unknown_revocation_policy: UnknownStatusPolicy,
264    revocation_expiration_policy: ExpirationPolicy,
265    anonymous_policy: AnonymousClientPolicy,
266    supported_algs: WebPkiSupportedAlgorithms,
267}
268
269impl WebPkiClientVerifier {
270    /// Create a builder for the `webpki` client certificate verifier configuration using
271    /// the [process-default `CryptoProvider`][CryptoProvider#using-the-per-process-default-cryptoprovider].
272    ///
273    /// Client certificate authentication will be offered by the server, and client certificates
274    /// will be verified using the trust anchors found in the provided `roots`. If you
275    /// wish to disable client authentication use [`WebPkiClientVerifier::no_client_auth()`] instead.
276    ///
277    /// Use [`Self::builder_with_provider`] if you wish to specify an explicit provider.
278    ///
279    /// For more information, see the [`ClientVerifierBuilder`] documentation.
280    pub fn builder(roots: Arc<RootCertStore>) -> ClientVerifierBuilder {
281        Self::builder_with_provider(
282            roots,
283            CryptoProvider::get_default_or_install_from_crate_features(),
284        )
285    }
286
287    /// Create a builder for the `webpki` client certificate verifier configuration using
288    /// a specified [`CryptoProvider`].
289    ///
290    /// Client certificate authentication will be offered by the server, and client certificates
291    /// will be verified using the trust anchors found in the provided `roots`. If you
292    /// wish to disable client authentication use [WebPkiClientVerifier::no_client_auth()] instead.
293    ///
294    /// The cryptography used comes from the specified [`CryptoProvider`].
295    ///
296    /// For more information, see the [`ClientVerifierBuilder`] documentation.
297    pub fn builder_with_provider(
298        roots: Arc<RootCertStore>,
299        provider: &CryptoProvider,
300    ) -> ClientVerifierBuilder {
301        ClientVerifierBuilder::new(roots, provider.signature_verification_algorithms)
302    }
303
304    /// Create a new `WebPkiClientVerifier` that disables client authentication. The server will
305    /// not offer client authentication and anonymous clients will be accepted.
306    ///
307    /// This is in contrast to using `WebPkiClientVerifier::builder().allow_unauthenticated().build()`,
308    /// which will produce a verifier that will offer client authentication, but not require it.
309    pub fn no_client_auth() -> Arc<dyn ClientVerifier> {
310        Arc::new(NoClientAuth {})
311    }
312
313    /// Construct a new `WebpkiClientVerifier`.
314    ///
315    /// * `roots` is a list of trust anchors to use for certificate validation.
316    /// * `root_hint_subjects` is a list of distinguished names to use for hinting acceptable
317    ///   certificate authority subjects to a client.
318    /// * `crls` is a `Vec` of owned certificate revocation lists (CRLs) to use for
319    ///   client certificate validation.
320    /// * `revocation_check_depth` controls which certificates have their revocation status checked
321    ///   when `crls` are provided.
322    /// * `unknown_revocation_policy` controls how certificates with an unknown revocation status
323    ///   are handled when `crls` are provided.
324    /// * `anonymous_policy` controls whether client authentication is required, or if anonymous
325    ///   clients can connect.
326    /// * `supported_algs` specifies which signature verification algorithms should be used.
327    pub(crate) fn new(
328        roots: Arc<RootCertStore>,
329        root_hint_subjects: Arc<[DistinguishedName]>,
330        crls: Vec<CertRevocationList<'static>>,
331        revocation_check_depth: RevocationCheckDepth,
332        unknown_revocation_policy: UnknownStatusPolicy,
333        revocation_expiration_policy: ExpirationPolicy,
334        anonymous_policy: AnonymousClientPolicy,
335        supported_algs: WebPkiSupportedAlgorithms,
336    ) -> Self {
337        Self {
338            roots,
339            root_hint_subjects,
340            eku_validator: ExtendedKeyUsage::client_auth(),
341            crls,
342            revocation_check_depth,
343            unknown_revocation_policy,
344            revocation_expiration_policy,
345            anonymous_policy,
346            supported_algs,
347        }
348    }
349}
350
351impl ClientVerifier for WebPkiClientVerifier {
352    fn verify_identity(&self, identity: &ClientIdentity<'_>) -> Result<PeerVerified, Error> {
353        let certificates = match identity.identity {
354            Identity::X509(certificates) => certificates,
355            Identity::RawPublicKey(_) => {
356                return Err(ApiMisuse::UnverifiableCertificateType.into());
357            }
358        };
359
360        let cert = ParsedCertificate::try_from(&certificates.end_entity)?;
361        let crl_refs = self.crls.iter().collect::<Vec<_>>();
362        let revocation = if self.crls.is_empty() {
363            None
364        } else {
365            Some(
366                webpki::RevocationOptionsBuilder::new(&crl_refs)
367                    // Note: safe to unwrap here - new is only fallible if no CRLs are provided
368                    //       and we verify this above.
369                    .unwrap()
370                    .with_depth(self.revocation_check_depth)
371                    .with_status_policy(self.unknown_revocation_policy)
372                    .with_expiration_policy(self.revocation_expiration_policy)
373                    .build(),
374            )
375        };
376
377        cert.0
378            .verify_for_usage(
379                self.supported_algs.all,
380                &self.roots.roots,
381                &certificates.intermediates,
382                identity.now,
383                &self.eku_validator,
384                revocation,
385                None,
386            )
387            .map_err(pki_error)
388            .map(|_| PeerVerified::assertion())
389    }
390
391    fn verify_tls12_signature(
392        &self,
393        input: &SignatureVerificationInput<'_>,
394    ) -> Result<HandshakeSignatureValid, Error> {
395        verify_tls12_signature(input, &self.supported_algs)
396    }
397
398    fn verify_tls13_signature(
399        &self,
400        input: &SignatureVerificationInput<'_>,
401    ) -> Result<HandshakeSignatureValid, Error> {
402        verify_tls13_signature(input, &self.supported_algs)
403    }
404
405    fn root_hint_subjects(&self) -> Arc<[DistinguishedName]> {
406        self.root_hint_subjects.clone()
407    }
408
409    fn client_auth_mandatory(&self) -> bool {
410        match self.anonymous_policy {
411            AnonymousClientPolicy::Allow => false,
412            AnonymousClientPolicy::Deny => true,
413        }
414    }
415
416    fn offer_client_auth(&self) -> bool {
417        true
418    }
419
420    fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
421        self.supported_algs.supported_schemes()
422    }
423}
424
425/// Controls how the [WebPkiClientVerifier] handles anonymous clients.
426#[derive(Debug, Clone, Copy, PartialEq, Eq)]
427pub(crate) enum AnonymousClientPolicy {
428    /// Clients that do not present a client certificate are allowed.
429    Allow,
430    /// Clients that do not present a client certificate are denied.
431    Deny,
432}
433
434#[cfg(test)]
435#[macro_rules_attribute::apply(test_for_each_provider)]
436mod tests {
437    use std::prelude::v1::*;
438    use std::{format, println, vec};
439
440    use pki_types::pem::PemObject;
441    use pki_types::{CertificateDer, CertificateRevocationListDer};
442
443    use super::{WebPkiClientVerifier, provider};
444    use crate::RootCertStore;
445    use crate::error::CertRevocationListError;
446    use crate::server::VerifierBuilderError;
447    use crate::sync::Arc;
448
449    fn load_crls(crls_der: &[&[u8]]) -> Vec<CertificateRevocationListDer<'static>> {
450        crls_der
451            .iter()
452            .map(|pem_bytes| CertificateRevocationListDer::from_pem_slice(pem_bytes).unwrap())
453            .collect()
454    }
455
456    fn test_crls() -> Vec<CertificateRevocationListDer<'static>> {
457        load_crls(&[
458            include_bytes!("../../../test-ca/ecdsa-p256/client.revoked.crl.pem").as_slice(),
459            include_bytes!("../../../test-ca/rsa-2048/client.revoked.crl.pem").as_slice(),
460        ])
461    }
462
463    fn load_roots(roots_der: &[&[u8]]) -> Arc<RootCertStore> {
464        let mut roots = RootCertStore::empty();
465        roots_der.iter().for_each(|der| {
466            roots
467                .add(CertificateDer::from(der.to_vec()))
468                .unwrap()
469        });
470        roots.into()
471    }
472
473    fn test_roots() -> Arc<RootCertStore> {
474        load_roots(&[
475            include_bytes!("../../../test-ca/ecdsa-p256/ca.der").as_slice(),
476            include_bytes!("../../../test-ca/rsa-2048/ca.der").as_slice(),
477        ])
478    }
479
480    #[test]
481    fn test_client_verifier_no_auth() {
482        // We should be able to build a verifier that turns off client authentication.
483        WebPkiClientVerifier::no_client_auth();
484    }
485
486    #[test]
487    fn test_client_verifier_required_auth() {
488        // We should be able to build a verifier that requires client authentication, and does
489        // no revocation checking.
490        let builder =
491            WebPkiClientVerifier::builder_with_provider(test_roots(), &provider::DEFAULT_PROVIDER);
492        // The builder should be Debug.
493        println!("{builder:?}");
494        builder.build().unwrap();
495    }
496
497    #[test]
498    fn test_client_verifier_optional_auth() {
499        // We should be able to build a verifier that allows client authentication, and anonymous
500        // access, and does no revocation checking.
501        let builder =
502            WebPkiClientVerifier::builder_with_provider(test_roots(), &provider::DEFAULT_PROVIDER)
503                .allow_unauthenticated();
504        // The builder should be Debug.
505        println!("{builder:?}");
506        builder.build().unwrap();
507    }
508
509    #[test]
510    fn test_client_verifier_without_crls_required_auth() {
511        // We should be able to build a verifier that requires client authentication, and does
512        // no revocation checking, that hasn't been configured to determine how to handle
513        // unauthenticated clients yet.
514        let builder =
515            WebPkiClientVerifier::builder_with_provider(test_roots(), &provider::DEFAULT_PROVIDER);
516        // The builder should be Debug.
517        println!("{builder:?}");
518        builder.build().unwrap();
519    }
520
521    #[test]
522    fn test_client_verifier_without_crls_optional_auth() {
523        // We should be able to build a verifier that allows client authentication,
524        // and anonymous access, that does no revocation checking.
525        let builder =
526            WebPkiClientVerifier::builder_with_provider(test_roots(), &provider::DEFAULT_PROVIDER)
527                .allow_unauthenticated();
528        // The builder should be Debug.
529        println!("{builder:?}");
530        builder.build().unwrap();
531    }
532
533    #[test]
534    fn test_with_invalid_crls() {
535        // Trying to build a client verifier with invalid CRLs should error at build time.
536        let result =
537            WebPkiClientVerifier::builder_with_provider(test_roots(), &provider::DEFAULT_PROVIDER)
538                .with_crls(vec![CertificateRevocationListDer::from(vec![0xFF])])
539                .build();
540        assert!(matches!(result, Err(VerifierBuilderError::InvalidCrl(_))));
541    }
542
543    #[test]
544    fn test_with_crls_multiple_calls() {
545        // We should be able to call `with_crls` on a client verifier multiple times.
546        let initial_crls = test_crls();
547        let extra_crls =
548            load_crls(&[
549                include_bytes!("../../../test-ca/eddsa/client.revoked.crl.pem").as_slice(),
550            ]);
551        let builder =
552            WebPkiClientVerifier::builder_with_provider(test_roots(), &provider::DEFAULT_PROVIDER)
553                .with_crls(initial_crls.clone())
554                .with_crls(extra_crls.clone());
555
556        // There should be the expected number of crls.
557        assert_eq!(builder.crls.len(), initial_crls.len() + extra_crls.len());
558        // The builder should be Debug.
559        println!("{builder:?}");
560        builder.build().unwrap();
561    }
562
563    #[test]
564    fn test_client_verifier_with_crls_required_auth_implicit() {
565        // We should be able to build a verifier that requires client authentication, and that does
566        // revocation checking with CRLs, and that does not allow any anonymous access.
567        let builder =
568            WebPkiClientVerifier::builder_with_provider(test_roots(), &provider::DEFAULT_PROVIDER)
569                .with_crls(test_crls());
570        // The builder should be Debug.
571        println!("{builder:?}");
572        builder.build().unwrap();
573    }
574
575    #[test]
576    fn test_client_verifier_with_crls_optional_auth() {
577        // We should be able to build a verifier that supports client authentication, that does
578        // revocation checking with CRLs, and that allows anonymous access.
579        let builder =
580            WebPkiClientVerifier::builder_with_provider(test_roots(), &provider::DEFAULT_PROVIDER)
581                .with_crls(test_crls())
582                .allow_unauthenticated();
583        // The builder should be Debug.
584        println!("{builder:?}");
585        builder.build().unwrap();
586    }
587
588    #[test]
589    fn test_client_verifier_ee_only() {
590        // We should be able to build a client verifier that only checks EE revocation status.
591        let builder =
592            WebPkiClientVerifier::builder_with_provider(test_roots(), &provider::DEFAULT_PROVIDER)
593                .with_crls(test_crls())
594                .only_check_end_entity_revocation();
595        // The builder should be Debug.
596        println!("{builder:?}");
597        builder.build().unwrap();
598    }
599
600    #[test]
601    fn test_client_verifier_allow_unknown() {
602        // We should be able to build a client verifier that allows unknown revocation status
603        let builder =
604            WebPkiClientVerifier::builder_with_provider(test_roots(), &provider::DEFAULT_PROVIDER)
605                .with_crls(test_crls())
606                .allow_unknown_revocation_status();
607        // The builder should be Debug.
608        println!("{builder:?}");
609        builder.build().unwrap();
610    }
611
612    #[test]
613    fn test_client_verifier_enforce_expiration() {
614        // We should be able to build a client verifier that allows unknown revocation status
615        let builder =
616            WebPkiClientVerifier::builder_with_provider(test_roots(), &provider::DEFAULT_PROVIDER)
617                .with_crls(test_crls())
618                .enforce_revocation_expiration();
619        // The builder should be Debug.
620        println!("{builder:?}");
621        builder.build().unwrap();
622    }
623
624    #[test]
625    fn test_builder_no_roots() {
626        // Trying to create a client verifier builder with no trust anchors should fail at build time
627        let result = WebPkiClientVerifier::builder_with_provider(
628            RootCertStore::empty().into(),
629            &provider::DEFAULT_PROVIDER,
630        )
631        .build();
632        assert!(matches!(result, Err(VerifierBuilderError::NoRootAnchors)));
633    }
634
635    #[test]
636    fn smoke() {
637        let all = vec![
638            VerifierBuilderError::NoRootAnchors,
639            VerifierBuilderError::InvalidCrl(CertRevocationListError::ParseError),
640        ];
641
642        for err in all {
643            let _ = format!("{err:?}");
644            let _ = format!("{err}");
645        }
646    }
647}