portable_rustls/webpki/
client_verifier.rs

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