Skip to main content

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