rustls/webpki/
client_verifier.rs

1use alloc::sync::Arc;
2use alloc::vec::Vec;
3
4use pki_types::{CertificateDer, CertificateRevocationListDer, UnixTime};
5use webpki::{CertRevocationList, ExpirationPolicy, RevocationCheckDepth, UnknownStatusPolicy};
6
7use super::{pki_error, VerifierBuilderError};
8#[cfg(doc)]
9use crate::crypto;
10use crate::crypto::{CryptoProvider, WebPkiSupportedAlgorithms};
11#[cfg(doc)]
12use crate::server::ServerConfig;
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 rustls::RootCertStore;
205/// # use rustls::server::WebPkiClientVerifier;
206/// # let roots = RootCertStore::empty();
207/// let client_verifier = WebPkiClientVerifier::builder(roots.into())
208///   .build()
209///   .unwrap();
210/// # }
211/// ```
212///
213/// Or, to allow clients presenting a client certificate authenticated by a trusted CA, or
214/// anonymous clients that present no client certificate:
215/// ```no_run
216/// # #[cfg(any(feature = "ring", feature = "aws_lc_rs"))] {
217/// # use rustls::RootCertStore;
218/// # use rustls::server::WebPkiClientVerifier;
219/// # let roots = RootCertStore::empty();
220/// let client_verifier = WebPkiClientVerifier::builder(roots.into())
221///   .allow_unauthenticated()
222///   .build()
223///   .unwrap();
224/// # }
225/// ```
226///
227/// If you wish to disable advertising client authentication:
228/// ```no_run
229/// # use rustls::RootCertStore;
230/// # use rustls::server::WebPkiClientVerifier;
231/// # let roots = RootCertStore::empty();
232/// let client_verifier = WebPkiClientVerifier::no_client_auth();
233/// ```
234///
235/// You can also configure the client verifier to check for certificate revocation with
236/// client certificate revocation lists (CRLs):
237/// ```no_run
238/// # #[cfg(any(feature = "ring", feature = "aws_lc_rs"))] {
239/// # use rustls::RootCertStore;
240/// # use rustls::server::{WebPkiClientVerifier};
241/// # let roots = RootCertStore::empty();
242/// # let crls = Vec::new();
243/// let client_verifier = WebPkiClientVerifier::builder(roots.into())
244///   .with_crls(crls)
245///   .build()
246///   .unwrap();
247/// # }
248/// ```
249///
250/// [^1]: <https://github.com/rustls/webpki>
251#[derive(Debug)]
252pub struct WebPkiClientVerifier {
253    roots: Arc<RootCertStore>,
254    root_hint_subjects: Vec<DistinguishedName>,
255    crls: Vec<CertRevocationList<'static>>,
256    revocation_check_depth: RevocationCheckDepth,
257    unknown_revocation_policy: UnknownStatusPolicy,
258    revocation_expiration_policy: ExpirationPolicy,
259    anonymous_policy: AnonymousClientPolicy,
260    supported_algs: WebPkiSupportedAlgorithms,
261}
262
263impl WebPkiClientVerifier {
264    /// Create a builder for the `webpki` client certificate verifier configuration using
265    /// the [process-default `CryptoProvider`][CryptoProvider#using-the-per-process-default-cryptoprovider].
266    ///
267    /// Client certificate authentication will be offered by the server, and client certificates
268    /// will be verified using the trust anchors found in the provided `roots`. If you
269    /// wish to disable client authentication use [`WebPkiClientVerifier::no_client_auth()`] instead.
270    ///
271    /// Use [`Self::builder_with_provider`] if you wish to specify an explicit provider.
272    ///
273    /// For more information, see the [`ClientCertVerifierBuilder`] documentation.
274    pub fn builder(roots: Arc<RootCertStore>) -> ClientCertVerifierBuilder {
275        Self::builder_with_provider(
276            roots,
277            Arc::clone(CryptoProvider::get_default_or_install_from_crate_features()),
278        )
279    }
280
281    /// Create a builder for the `webpki` client certificate verifier configuration using
282    /// a specified [`CryptoProvider`].
283    ///
284    /// Client certificate authentication will be offered by the server, and client certificates
285    /// will be verified using the trust anchors found in the provided `roots`. If you
286    /// wish to disable client authentication use [WebPkiClientVerifier::no_client_auth()] instead.
287    ///
288    /// The cryptography used comes from the specified [`CryptoProvider`].
289    ///
290    /// For more information, see the [`ClientCertVerifierBuilder`] documentation.
291    pub fn builder_with_provider(
292        roots: Arc<RootCertStore>,
293        provider: Arc<CryptoProvider>,
294    ) -> ClientCertVerifierBuilder {
295        ClientCertVerifierBuilder::new(roots, provider.signature_verification_algorithms)
296    }
297
298    /// Create a new `WebPkiClientVerifier` that disables client authentication. The server will
299    /// not offer client authentication and anonymous clients will be accepted.
300    ///
301    /// This is in contrast to using `WebPkiClientVerifier::builder().allow_unauthenticated().build()`,
302    /// which will produce a verifier that will offer client authentication, but not require it.
303    pub fn no_client_auth() -> Arc<dyn ClientCertVerifier> {
304        Arc::new(NoClientAuth {})
305    }
306
307    /// Construct a new `WebpkiClientVerifier`.
308    ///
309    /// * `roots` is a list of trust anchors to use for certificate validation.
310    /// * `root_hint_subjects` is a list of distinguished names to use for hinting acceptable
311    ///   certificate authority subjects to a client.
312    /// * `crls` is a `Vec` of owned certificate revocation lists (CRLs) to use for
313    ///   client certificate validation.
314    /// * `revocation_check_depth` controls which certificates have their revocation status checked
315    ///   when `crls` are provided.
316    /// * `unknown_revocation_policy` controls how certificates with an unknown revocation status
317    ///   are handled when `crls` are provided.
318    /// * `anonymous_policy` controls whether client authentication is required, or if anonymous
319    ///   clients can connect.
320    /// * `supported_algs` specifies which signature verification algorithms should be used.
321    pub(crate) fn new(
322        roots: Arc<RootCertStore>,
323        root_hint_subjects: Vec<DistinguishedName>,
324        crls: Vec<CertRevocationList<'static>>,
325        revocation_check_depth: RevocationCheckDepth,
326        unknown_revocation_policy: UnknownStatusPolicy,
327        revocation_expiration_policy: ExpirationPolicy,
328        anonymous_policy: AnonymousClientPolicy,
329        supported_algs: WebPkiSupportedAlgorithms,
330    ) -> Self {
331        Self {
332            roots,
333            root_hint_subjects,
334            crls,
335            revocation_check_depth,
336            unknown_revocation_policy,
337            revocation_expiration_policy,
338            anonymous_policy,
339            supported_algs,
340        }
341    }
342}
343
344impl ClientCertVerifier for WebPkiClientVerifier {
345    fn offer_client_auth(&self) -> bool {
346        true
347    }
348
349    fn client_auth_mandatory(&self) -> bool {
350        match self.anonymous_policy {
351            AnonymousClientPolicy::Allow => false,
352            AnonymousClientPolicy::Deny => true,
353        }
354    }
355
356    fn root_hint_subjects(&self) -> &[DistinguishedName] {
357        &self.root_hint_subjects
358    }
359
360    fn verify_client_cert(
361        &self,
362        end_entity: &CertificateDer<'_>,
363        intermediates: &[CertificateDer<'_>],
364        now: UnixTime,
365    ) -> Result<ClientCertVerified, Error> {
366        let cert = ParsedCertificate::try_from(end_entity)?;
367
368        let crl_refs = self.crls.iter().collect::<Vec<_>>();
369
370        let revocation = if self.crls.is_empty() {
371            None
372        } else {
373            Some(
374                webpki::RevocationOptionsBuilder::new(&crl_refs)
375                    // Note: safe to unwrap here - new is only fallible if no CRLs are provided
376                    //       and we verify this above.
377                    .unwrap()
378                    .with_depth(self.revocation_check_depth)
379                    .with_status_policy(self.unknown_revocation_policy)
380                    .with_expiration_policy(self.revocation_expiration_policy)
381                    .build(),
382            )
383        };
384
385        cert.0
386            .verify_for_usage(
387                self.supported_algs.all,
388                &self.roots.roots,
389                intermediates,
390                now,
391                webpki::KeyUsage::client_auth(),
392                revocation,
393                None,
394            )
395            .map_err(pki_error)
396            .map(|_| ClientCertVerified::assertion())
397    }
398
399    fn verify_tls12_signature(
400        &self,
401        message: &[u8],
402        cert: &CertificateDer<'_>,
403        dss: &DigitallySignedStruct,
404    ) -> Result<HandshakeSignatureValid, Error> {
405        verify_tls12_signature(message, cert, dss, &self.supported_algs)
406    }
407
408    fn verify_tls13_signature(
409        &self,
410        message: &[u8],
411        cert: &CertificateDer<'_>,
412        dss: &DigitallySignedStruct,
413    ) -> Result<HandshakeSignatureValid, Error> {
414        verify_tls13_signature(message, cert, dss, &self.supported_algs)
415    }
416
417    fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
418        self.supported_algs.supported_schemes()
419    }
420}
421
422/// Controls how the [WebPkiClientVerifier] handles anonymous clients.
423#[derive(Debug, Clone, Copy, PartialEq, Eq)]
424pub(crate) enum AnonymousClientPolicy {
425    /// Clients that do not present a client certificate are allowed.
426    Allow,
427    /// Clients that do not present a client certificate are denied.
428    Deny,
429}
430
431#[cfg(test)]
432#[macro_rules_attribute::apply(test_for_each_provider)]
433mod tests {
434    use std::prelude::v1::*;
435    use std::sync::Arc;
436    use std::{format, println, vec};
437
438    use pki_types::pem::PemObject;
439    use pki_types::{CertificateDer, CertificateRevocationListDer};
440
441    use super::{provider, WebPkiClientVerifier};
442    use crate::server::VerifierBuilderError;
443    use crate::RootCertStore;
444
445    fn load_crls(crls_der: &[&[u8]]) -> Vec<CertificateRevocationListDer<'static>> {
446        crls_der
447            .iter()
448            .map(|pem_bytes| CertificateRevocationListDer::from_pem_slice(pem_bytes).unwrap())
449            .collect()
450    }
451
452    fn test_crls() -> Vec<CertificateRevocationListDer<'static>> {
453        load_crls(&[
454            include_bytes!("../../../test-ca/ecdsa-p256/client.revoked.crl.pem").as_slice(),
455            include_bytes!("../../../test-ca/rsa-2048/client.revoked.crl.pem").as_slice(),
456        ])
457    }
458
459    fn load_roots(roots_der: &[&[u8]]) -> Arc<RootCertStore> {
460        let mut roots = RootCertStore::empty();
461        roots_der.iter().for_each(|der| {
462            roots
463                .add(CertificateDer::from(der.to_vec()))
464                .unwrap()
465        });
466        roots.into()
467    }
468
469    fn test_roots() -> Arc<RootCertStore> {
470        load_roots(&[
471            include_bytes!("../../../test-ca/ecdsa-p256/ca.der").as_slice(),
472            include_bytes!("../../../test-ca/rsa-2048/ca.der").as_slice(),
473        ])
474    }
475
476    #[test]
477    fn test_client_verifier_no_auth() {
478        // We should be able to build a verifier that turns off client authentication.
479        WebPkiClientVerifier::no_client_auth();
480    }
481
482    #[test]
483    fn test_client_verifier_required_auth() {
484        // We should be able to build a verifier that requires client authentication, and does
485        // no revocation checking.
486        let builder = WebPkiClientVerifier::builder_with_provider(
487            test_roots(),
488            provider::default_provider().into(),
489        );
490        // The builder should be Debug.
491        println!("{:?}", builder);
492        builder.build().unwrap();
493    }
494
495    #[test]
496    fn test_client_verifier_optional_auth() {
497        // We should be able to build a verifier that allows client authentication, and anonymous
498        // access, and does no revocation checking.
499        let builder = WebPkiClientVerifier::builder_with_provider(
500            test_roots(),
501            provider::default_provider().into(),
502        )
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 = WebPkiClientVerifier::builder_with_provider(
515            test_roots(),
516            provider::default_provider().into(),
517        );
518        // The builder should be Debug.
519        println!("{:?}", builder);
520        builder.build().unwrap();
521    }
522
523    #[test]
524    fn test_client_verifier_without_crls_opptional_auth() {
525        // We should be able to build a verifier that allows client authentication,
526        // and anonymous access, that does no revocation checking.
527        let builder = WebPkiClientVerifier::builder_with_provider(
528            test_roots(),
529            provider::default_provider().into(),
530        )
531        .allow_unauthenticated();
532        // The builder should be Debug.
533        println!("{:?}", builder);
534        builder.build().unwrap();
535    }
536
537    #[test]
538    fn test_with_invalid_crls() {
539        // Trying to build a client verifier with invalid CRLs should error at build time.
540        let result = WebPkiClientVerifier::builder_with_provider(
541            test_roots(),
542            provider::default_provider().into(),
543        )
544        .with_crls(vec![CertificateRevocationListDer::from(vec![0xFF])])
545        .build();
546        assert!(matches!(result, Err(VerifierBuilderError::InvalidCrl(_))));
547    }
548
549    #[test]
550    fn test_with_crls_multiple_calls() {
551        // We should be able to call `with_crls` on a client verifier multiple times.
552        let initial_crls = test_crls();
553        let extra_crls =
554            load_crls(&[
555                include_bytes!("../../../test-ca/eddsa/client.revoked.crl.pem").as_slice(),
556            ]);
557        let builder = WebPkiClientVerifier::builder_with_provider(
558            test_roots(),
559            provider::default_provider().into(),
560        )
561        .with_crls(initial_crls.clone())
562        .with_crls(extra_crls.clone());
563
564        // There should be the expected number of crls.
565        assert_eq!(builder.crls.len(), initial_crls.len() + extra_crls.len());
566        // The builder should be Debug.
567        println!("{:?}", builder);
568        builder.build().unwrap();
569    }
570
571    #[test]
572    fn test_client_verifier_with_crls_required_auth_implicit() {
573        // We should be able to build a verifier that requires client authentication, and that does
574        // revocation checking with CRLs, and that does not allow any anonymous access.
575        let builder = WebPkiClientVerifier::builder_with_provider(
576            test_roots(),
577            provider::default_provider().into(),
578        )
579        .with_crls(test_crls());
580        // The builder should be Debug.
581        println!("{:?}", builder);
582        builder.build().unwrap();
583    }
584
585    #[test]
586    fn test_client_verifier_with_crls_optional_auth() {
587        // We should be able to build a verifier that supports client authentication, that does
588        // revocation checking with CRLs, and that allows anonymous access.
589        let builder = WebPkiClientVerifier::builder_with_provider(
590            test_roots(),
591            provider::default_provider().into(),
592        )
593        .with_crls(test_crls())
594        .allow_unauthenticated();
595        // The builder should be Debug.
596        println!("{:?}", builder);
597        builder.build().unwrap();
598    }
599
600    #[test]
601    fn test_client_verifier_ee_only() {
602        // We should be able to build a client verifier that only checks EE revocation status.
603        let builder = WebPkiClientVerifier::builder_with_provider(
604            test_roots(),
605            provider::default_provider().into(),
606        )
607        .with_crls(test_crls())
608        .only_check_end_entity_revocation();
609        // The builder should be Debug.
610        println!("{:?}", builder);
611        builder.build().unwrap();
612    }
613
614    #[test]
615    fn test_client_verifier_allow_unknown() {
616        // We should be able to build a client verifier that allows unknown revocation status
617        let builder = WebPkiClientVerifier::builder_with_provider(
618            test_roots(),
619            provider::default_provider().into(),
620        )
621        .with_crls(test_crls())
622        .allow_unknown_revocation_status();
623        // The builder should be Debug.
624        println!("{:?}", builder);
625        builder.build().unwrap();
626    }
627
628    #[test]
629    fn test_client_verifier_enforce_expiration() {
630        // We should be able to build a client verifier that allows unknown revocation status
631        let builder = WebPkiClientVerifier::builder_with_provider(
632            test_roots(),
633            provider::default_provider().into(),
634        )
635        .with_crls(test_crls())
636        .enforce_revocation_expiration();
637        // The builder should be Debug.
638        println!("{:?}", builder);
639        builder.build().unwrap();
640    }
641
642    #[test]
643    fn test_builder_no_roots() {
644        // Trying to create a client verifier builder with no trust anchors should fail at build time
645        let result = WebPkiClientVerifier::builder_with_provider(
646            RootCertStore::empty().into(),
647            provider::default_provider().into(),
648        )
649        .build();
650        assert!(matches!(result, Err(VerifierBuilderError::NoRootAnchors)));
651    }
652
653    #[test]
654    fn smoke() {
655        let all = vec![
656            VerifierBuilderError::NoRootAnchors,
657            VerifierBuilderError::InvalidCrl(crate::CertRevocationListError::ParseError),
658        ];
659
660        for err in all {
661            let _ = format!("{:?}", err);
662            let _ = format!("{}", err);
663        }
664    }
665}