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