webpki/
verify_cert.rs

1// Copyright 2015 Brian Smith.
2//
3// Permission to use, copy, modify, and/or distribute this software for any
4// purpose with or without fee is hereby granted, provided that the above
5// copyright notice and this permission notice appear in all copies.
6//
7// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
10// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
15use core::default::Default;
16use core::ops::ControlFlow;
17
18use crate::{
19    cert::{Cert, EndEntityOrCa},
20    der, public_values_eq, signed_data, subject_name, time, CertRevocationList, Error,
21    SignatureAlgorithm, TrustAnchor,
22};
23
24pub(crate) struct ChainOptions<'a> {
25    pub(crate) eku: KeyUsage,
26    pub(crate) supported_sig_algs: &'a [&'a SignatureAlgorithm],
27    pub(crate) trust_anchors: &'a [TrustAnchor<'a>],
28    pub(crate) intermediate_certs: &'a [&'a [u8]],
29    pub(crate) crls: &'a [&'a dyn CertRevocationList],
30}
31
32pub(crate) fn build_chain(opts: &ChainOptions, cert: &Cert, time: time::Time) -> Result<(), Error> {
33    build_chain_inner(opts, cert, time, 0, &mut Budget::default()).map_err(|e| match e {
34        ControlFlow::Break(err) => err,
35        ControlFlow::Continue(err) => err,
36    })
37}
38
39fn build_chain_inner(
40    opts: &ChainOptions,
41    cert: &Cert,
42    time: time::Time,
43    sub_ca_count: usize,
44    budget: &mut Budget,
45) -> Result<(), ControlFlow<Error, Error>> {
46    let used_as_ca = used_as_ca(&cert.ee_or_ca);
47
48    check_issuer_independent_properties(cert, time, used_as_ca, sub_ca_count, opts.eku.inner)?;
49
50    // TODO: HPKP checks.
51
52    match used_as_ca {
53        UsedAsCa::Yes => {
54            const MAX_SUB_CA_COUNT: usize = 6;
55
56            if sub_ca_count >= MAX_SUB_CA_COUNT {
57                return Err(Error::MaximumPathDepthExceeded.into());
58            }
59        }
60        UsedAsCa::No => {
61            assert_eq!(0, sub_ca_count);
62        }
63    }
64
65    let result = loop_while_non_fatal_error(
66        Error::UnknownIssuer,
67        opts.trust_anchors,
68        |trust_anchor: &TrustAnchor| {
69            let trust_anchor_subject = untrusted::Input::from(trust_anchor.subject);
70            if !public_values_eq(cert.issuer, trust_anchor_subject) {
71                return Err(Error::UnknownIssuer.into());
72            }
73
74            // TODO: check_distrust(trust_anchor_subject, trust_anchor_spki)?;
75
76            check_signed_chain(
77                opts.supported_sig_algs,
78                cert,
79                trust_anchor,
80                opts.crls,
81                budget,
82            )?;
83
84            check_signed_chain_name_constraints(cert, trust_anchor, budget)?;
85
86            Ok(())
87        },
88    );
89
90    let err = match result {
91        Ok(()) => return Ok(()),
92        // Fatal errors should halt further path building.
93        res @ Err(ControlFlow::Break(_)) => return res,
94        // Non-fatal errors should be carried forward as the default_error for subsequent
95        // loop_while_non_fatal_error processing and only returned once all other path-building
96        // options have been exhausted.
97        Err(ControlFlow::Continue(err)) => err,
98    };
99
100    loop_while_non_fatal_error(err, opts.intermediate_certs, |cert_der| {
101        let potential_issuer =
102            Cert::from_der(untrusted::Input::from(cert_der), EndEntityOrCa::Ca(cert))?;
103
104        if !public_values_eq(potential_issuer.subject, cert.issuer) {
105            return Err(Error::UnknownIssuer.into());
106        }
107
108        // Prevent loops; see RFC 4158 section 5.2.
109        let mut prev = cert;
110        loop {
111            if public_values_eq(potential_issuer.spki.value(), prev.spki.value())
112                && public_values_eq(potential_issuer.subject, prev.subject)
113            {
114                return Err(Error::UnknownIssuer.into());
115            }
116            match &prev.ee_or_ca {
117                EndEntityOrCa::EndEntity => {
118                    break;
119                }
120                EndEntityOrCa::Ca(child_cert) => {
121                    prev = child_cert;
122                }
123            }
124        }
125
126        let next_sub_ca_count = match used_as_ca {
127            UsedAsCa::No => sub_ca_count,
128            UsedAsCa::Yes => sub_ca_count + 1,
129        };
130
131        budget.consume_build_chain_call()?;
132        build_chain_inner(opts, &potential_issuer, time, next_sub_ca_count, budget)
133    })
134}
135
136fn check_signed_chain(
137    supported_sig_algs: &[&SignatureAlgorithm],
138    cert_chain: &Cert,
139    trust_anchor: &TrustAnchor,
140    crls: &[&dyn CertRevocationList],
141    budget: &mut Budget,
142) -> Result<(), ControlFlow<Error, Error>> {
143    let mut spki_value = untrusted::Input::from(trust_anchor.spki);
144    let mut issuer_subject = untrusted::Input::from(trust_anchor.subject);
145    let mut issuer_key_usage = None; // TODO(XXX): Consider whether to track TrustAnchor KU.
146    let mut cert = cert_chain;
147    loop {
148        signed_data::verify_signed_data(supported_sig_algs, spki_value, &cert.signed_data, budget)?;
149
150        if !crls.is_empty() {
151            check_crls(
152                supported_sig_algs,
153                cert,
154                issuer_subject,
155                spki_value,
156                issuer_key_usage,
157                crls,
158                budget,
159            )?;
160        }
161
162        match &cert.ee_or_ca {
163            EndEntityOrCa::Ca(child_cert) => {
164                spki_value = cert.spki.value();
165                issuer_subject = cert.subject;
166                issuer_key_usage = cert.key_usage;
167                cert = child_cert;
168            }
169            EndEntityOrCa::EndEntity => {
170                break;
171            }
172        }
173    }
174
175    Ok(())
176}
177
178fn check_signed_chain_name_constraints(
179    cert_chain: &Cert,
180    trust_anchor: &TrustAnchor,
181    budget: &mut Budget,
182) -> Result<(), ControlFlow<Error, Error>> {
183    let mut cert = cert_chain;
184    let mut name_constraints = trust_anchor
185        .name_constraints
186        .as_ref()
187        .map(|der| untrusted::Input::from(der));
188
189    loop {
190        untrusted::read_all_optional(name_constraints, Error::BadDer, |value| {
191            subject_name::check_name_constraints(value, cert, budget)
192        })?;
193
194        match &cert.ee_or_ca {
195            EndEntityOrCa::Ca(child_cert) => {
196                name_constraints = cert.name_constraints;
197                cert = child_cert;
198            }
199            EndEntityOrCa::EndEntity => {
200                break;
201            }
202        }
203    }
204
205    Ok(())
206}
207
208pub(crate) struct Budget {
209    signatures: usize,
210    build_chain_calls: usize,
211    name_constraint_comparisons: usize,
212}
213
214impl Budget {
215    #[inline]
216    pub(crate) fn consume_signature(&mut self) -> Result<(), Error> {
217        self.signatures = self
218            .signatures
219            .checked_sub(1)
220            .ok_or(Error::MaximumSignatureChecksExceeded)?;
221        Ok(())
222    }
223
224    #[inline]
225    fn consume_build_chain_call(&mut self) -> Result<(), Error> {
226        self.build_chain_calls = self
227            .build_chain_calls
228            .checked_sub(1)
229            .ok_or(Error::MaximumPathBuildCallsExceeded)?;
230        Ok(())
231    }
232
233    #[inline]
234    pub(crate) fn consume_name_constraint_comparison(&mut self) -> Result<(), Error> {
235        self.name_constraint_comparisons = self
236            .name_constraint_comparisons
237            .checked_sub(1)
238            .ok_or(Error::MaximumNameConstraintComparisonsExceeded)?;
239        Ok(())
240    }
241}
242
243impl Default for Budget {
244    fn default() -> Self {
245        Self {
246            // This limit is taken from the remediation for golang CVE-2018-16875.  However,
247            // note that golang subsequently implemented AKID matching due to this limit
248            // being hit in real applications (see <https://github.com/spiffe/spire/issues/1004>).
249            // So this may actually be too aggressive.
250            signatures: 100,
251
252            // This limit is taken from NSS libmozpkix, see:
253            // <https://github.com/nss-dev/nss/blob/bb4a1d38dd9e92923525ac6b5ed0288479f3f3fc/lib/mozpkix/lib/pkixbuild.cpp#L381-L393>
254            build_chain_calls: 200_000,
255
256            // This limit is taken from golang crypto/x509's default, see:
257            // <https://github.com/golang/go/blob/ac17bb6f13979f2ab9fcd45f0758b43ed72d0973/src/crypto/x509/verify.go#L588-L592>
258            name_constraint_comparisons: 250_000,
259        }
260    }
261}
262
263// Zero-sized marker type representing positive assertion that revocation status was checked
264// for a certificate and the result was that the certificate is not revoked.
265struct CertNotRevoked(());
266
267impl CertNotRevoked {
268    // Construct a CertNotRevoked marker.
269    fn assertion() -> Self {
270        Self(())
271    }
272}
273
274fn check_crls(
275    supported_sig_algs: &[&SignatureAlgorithm],
276    cert: &Cert,
277    issuer_subject: untrusted::Input,
278    issuer_spki: untrusted::Input,
279    issuer_ku: Option<untrusted::Input>,
280    crls: &[&dyn CertRevocationList],
281    budget: &mut Budget,
282) -> Result<Option<CertNotRevoked>, Error> {
283    assert!(public_values_eq(cert.issuer, issuer_subject));
284
285    let crl = match crls
286        .iter()
287        .find(|candidate_crl| candidate_crl.issuer() == cert.issuer())
288    {
289        Some(crl) => crl,
290        None => return Ok(None),
291    };
292
293    // Verify the CRL signature with the issuer SPKI.
294    // TODO(XXX): consider whether we can refactor so this happens once up-front, instead
295    //            of per-lookup.
296    //            https://github.com/rustls/webpki/issues/81
297    // Note: The `verify_signature` method is part of a public trait in the exported API.
298    //       We can't add a budget argument to that fn in a semver compatible way and so must
299    //       consume signature budget here before calling verify_signature.
300    budget.consume_signature()?;
301    crl.verify_signature(supported_sig_algs, issuer_spki.as_slice_less_safe())
302        .map_err(crl_signature_err)?;
303
304    // Verify that if the issuer has a KeyUsage bitstring it asserts cRLSign.
305    KeyUsageMode::CrlSign.check(issuer_ku)?;
306
307    // Try to find the cert serial in the verified CRL contents.
308    let cert_serial = cert.serial.as_slice_less_safe();
309    match crl.find_serial(cert_serial)? {
310        None => Ok(Some(CertNotRevoked::assertion())),
311        Some(_) => Err(Error::CertRevoked),
312    }
313}
314
315// When verifying CRL signed data we want to disambiguate the context of possible errors by mapping
316// them to CRL specific variants that a consumer can use to tell the issue was with the CRL's
317// signature, not a certificate.
318fn crl_signature_err(err: Error) -> Error {
319    match err {
320        Error::UnsupportedSignatureAlgorithm => Error::UnsupportedCrlSignatureAlgorithm,
321        Error::UnsupportedSignatureAlgorithmForPublicKey => {
322            Error::UnsupportedCrlSignatureAlgorithmForPublicKey
323        }
324        Error::InvalidSignatureForPublicKey => Error::InvalidCrlSignatureForPublicKey,
325        _ => err,
326    }
327}
328
329fn check_issuer_independent_properties(
330    cert: &Cert,
331    time: time::Time,
332    used_as_ca: UsedAsCa,
333    sub_ca_count: usize,
334    eku: ExtendedKeyUsage,
335) -> Result<(), Error> {
336    // TODO: check_distrust(trust_anchor_subject, trust_anchor_spki)?;
337    // TODO: Check signature algorithm like mozilla::pkix.
338    // TODO: Check SPKI like mozilla::pkix.
339    // TODO: check for active distrust like mozilla::pkix.
340
341    // For cert validation, we ignore the KeyUsage extension. For CA
342    // certificates, BasicConstraints.cA makes KeyUsage redundant. Firefox
343    // and other common browsers do not check KeyUsage for end-entities,
344    // though it would be kind of nice to ensure that a KeyUsage without
345    // the keyEncipherment bit could not be used for RSA key exchange.
346
347    cert.validity
348        .read_all(Error::BadDer, |value| check_validity(value, time))?;
349    untrusted::read_all_optional(cert.basic_constraints, Error::BadDer, |value| {
350        check_basic_constraints(value, used_as_ca, sub_ca_count)
351    })?;
352    untrusted::read_all_optional(cert.eku, Error::BadDer, |value| eku.check(value))?;
353
354    Ok(())
355}
356
357// https://tools.ietf.org/html/rfc5280#section-4.1.2.5
358fn check_validity(input: &mut untrusted::Reader, time: time::Time) -> Result<(), Error> {
359    let not_before = der::time_choice(input)?;
360    let not_after = der::time_choice(input)?;
361
362    if not_before > not_after {
363        return Err(Error::InvalidCertValidity);
364    }
365    if time < not_before {
366        return Err(Error::CertNotValidYet);
367    }
368    if time > not_after {
369        return Err(Error::CertExpired);
370    }
371
372    // TODO: mozilla::pkix allows the TrustDomain to check not_before and
373    // not_after, to enforce things like a maximum validity period. We should
374    // do something similar.
375
376    Ok(())
377}
378
379#[derive(Clone, Copy, PartialEq)]
380enum UsedAsCa {
381    Yes,
382    No,
383}
384
385fn used_as_ca(ee_or_ca: &EndEntityOrCa) -> UsedAsCa {
386    match ee_or_ca {
387        EndEntityOrCa::EndEntity => UsedAsCa::No,
388        EndEntityOrCa::Ca(..) => UsedAsCa::Yes,
389    }
390}
391
392// https://tools.ietf.org/html/rfc5280#section-4.2.1.9
393fn check_basic_constraints(
394    input: Option<&mut untrusted::Reader>,
395    used_as_ca: UsedAsCa,
396    sub_ca_count: usize,
397) -> Result<(), Error> {
398    let (is_ca, path_len_constraint) = match input {
399        Some(input) => {
400            let is_ca = der::optional_boolean(input)?;
401
402            // https://bugzilla.mozilla.org/show_bug.cgi?id=985025: RFC 5280
403            // says that a certificate must not have pathLenConstraint unless
404            // it is a CA certificate, but some real-world end-entity
405            // certificates have pathLenConstraint.
406            let path_len_constraint = if !input.at_end() {
407                let value = der::small_nonnegative_integer(input)?;
408                Some(usize::from(value))
409            } else {
410                None
411            };
412
413            (is_ca, path_len_constraint)
414        }
415        None => (false, None),
416    };
417
418    match (used_as_ca, is_ca, path_len_constraint) {
419        (UsedAsCa::No, true, _) => Err(Error::CaUsedAsEndEntity),
420        (UsedAsCa::Yes, false, _) => Err(Error::EndEntityUsedAsCa),
421        (UsedAsCa::Yes, true, Some(len)) if sub_ca_count > len => {
422            Err(Error::PathLenConstraintViolated)
423        }
424        _ => Ok(()),
425    }
426}
427
428/// The expected key usage of a certificate.
429///
430/// This type represents the expected key usage of an end entity certificate. Although for most
431/// kinds of certificates the extended key usage extension is optional (and so certificates
432/// not carrying a particular value in the EKU extension are acceptable). If the extension
433/// is present, the certificate MUST only be used for one of the purposes indicated.
434///
435/// <https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.12>
436#[derive(Clone, Copy)]
437pub struct KeyUsage {
438    inner: ExtendedKeyUsage,
439}
440
441impl KeyUsage {
442    /// Construct a new [`KeyUsage`] as appropriate for server certificate authentication.
443    ///
444    /// As specified in <https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.12>, this does not require the certificate to specify the eKU extension.
445    pub const fn server_auth() -> Self {
446        Self {
447            inner: ExtendedKeyUsage::RequiredIfPresent(EKU_SERVER_AUTH),
448        }
449    }
450
451    /// Construct a new [`KeyUsage`] as appropriate for client certificate authentication.
452    ///
453    /// As specified in <>, this does not require the certificate to specify the eKU extension.
454    pub const fn client_auth() -> Self {
455        Self {
456            inner: ExtendedKeyUsage::RequiredIfPresent(EKU_CLIENT_AUTH),
457        }
458    }
459
460    /// Construct a new [`KeyUsage`] requiring a certificate to support the specified OID.
461    pub const fn required(oid: &'static [u8]) -> Self {
462        Self {
463            inner: ExtendedKeyUsage::Required(KeyPurposeId::new(oid)),
464        }
465    }
466}
467
468/// Extended Key Usage (EKU) of a certificate.
469#[derive(Clone, Copy)]
470enum ExtendedKeyUsage {
471    /// The certificate must contain the specified [`KeyPurposeId`] as EKU.
472    Required(KeyPurposeId),
473
474    /// If the certificate has EKUs, then the specified [`KeyPurposeId`] must be included.
475    RequiredIfPresent(KeyPurposeId),
476}
477
478impl ExtendedKeyUsage {
479    // https://tools.ietf.org/html/rfc5280#section-4.2.1.12
480    fn check(&self, input: Option<&mut untrusted::Reader>) -> Result<(), Error> {
481        let input = match (input, self) {
482            (Some(input), _) => input,
483            (None, Self::RequiredIfPresent(_)) => return Ok(()),
484            (None, Self::Required(_)) => return Err(Error::RequiredEkuNotFound),
485        };
486
487        loop {
488            let value = der::expect_tag_and_get_value(input, der::Tag::OID)?;
489            if self.key_purpose_id_equals(value) {
490                input.skip_to_end();
491                break;
492            }
493
494            if input.at_end() {
495                return Err(Error::RequiredEkuNotFound);
496            }
497        }
498
499        Ok(())
500    }
501
502    fn key_purpose_id_equals(&self, value: untrusted::Input<'_>) -> bool {
503        public_values_eq(
504            match self {
505                ExtendedKeyUsage::Required(eku) => *eku,
506                ExtendedKeyUsage::RequiredIfPresent(eku) => *eku,
507            }
508            .oid_value,
509            value,
510        )
511    }
512}
513
514/// An OID value indicating an Extended Key Usage (EKU) key purpose.
515#[derive(Clone, Copy)]
516struct KeyPurposeId {
517    oid_value: untrusted::Input<'static>,
518}
519
520impl KeyPurposeId {
521    /// Construct a new [`KeyPurposeId`].
522    ///
523    /// `oid` is the OBJECT IDENTIFIER in bytes.
524    const fn new(oid: &'static [u8]) -> Self {
525        Self {
526            oid_value: untrusted::Input::from(oid),
527        }
528    }
529}
530
531impl PartialEq<Self> for KeyPurposeId {
532    fn eq(&self, other: &Self) -> bool {
533        public_values_eq(self.oid_value, other.oid_value)
534    }
535}
536
537impl Eq for KeyPurposeId {}
538
539// id-pkix            OBJECT IDENTIFIER ::= { 1 3 6 1 5 5 7 }
540// id-kp              OBJECT IDENTIFIER ::= { id-pkix 3 }
541
542// id-kp-serverAuth   OBJECT IDENTIFIER ::= { id-kp 1 }
543#[allow(clippy::identity_op)] // TODO: Make this clearer
544const EKU_SERVER_AUTH: KeyPurposeId = KeyPurposeId::new(&[(40 * 1) + 3, 6, 1, 5, 5, 7, 3, 1]);
545
546// id-kp-clientAuth   OBJECT IDENTIFIER ::= { id-kp 2 }
547#[allow(clippy::identity_op)] // TODO: Make this clearer
548const EKU_CLIENT_AUTH: KeyPurposeId = KeyPurposeId::new(&[(40 * 1) + 3, 6, 1, 5, 5, 7, 3, 2]);
549
550// https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.3
551#[repr(u8)]
552#[derive(Clone, Copy)]
553enum KeyUsageMode {
554    // DigitalSignature = 0,
555    // ContentCommitment = 1,
556    // KeyEncipherment = 2,
557    // DataEncipherment = 3,
558    // KeyAgreement = 4,
559    // CertSign = 5,
560    CrlSign = 6,
561    // EncipherOnly = 7,
562    // DecipherOnly = 8,
563}
564
565impl KeyUsageMode {
566    // https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.3
567    fn check(self, input: Option<untrusted::Input>) -> Result<(), Error> {
568        let bit_string = match input {
569            Some(input) => input,
570            // While RFC 5280 requires KeyUsage be present, historically the absence of a KeyUsage
571            // has been treated as "Any Usage". We follow that convention here and assume the absence
572            // of KeyUsage implies the required_ku_bit_if_present we're checking for.
573            None => return Ok(()),
574        };
575
576        let flags = der::bit_string_flags(&mut untrusted::Reader::new(bit_string))?;
577        #[allow(clippy::as_conversions)] // u8 always fits in usize.
578        match flags.bit_set(self as usize) {
579            true => Ok(()),
580            false => Err(Error::IssuerNotCrlSigner),
581        }
582    }
583}
584
585fn loop_while_non_fatal_error<V>(
586    default_error: Error,
587    values: V,
588    mut f: impl FnMut(V::Item) -> Result<(), ControlFlow<Error, Error>>,
589) -> Result<(), ControlFlow<Error, Error>>
590where
591    V: IntoIterator,
592{
593    let mut error = default_error;
594    for v in values {
595        match f(v) {
596            Ok(()) => return Ok(()),
597            // Fatal errors should halt further looping.
598            res @ Err(ControlFlow::Break(_)) => return res,
599            // Non-fatal errors should be ranked by specificity and only returned
600            // once all other path-building options have been exhausted.
601            Err(ControlFlow::Continue(new_error)) => error = error.most_specific(new_error),
602        }
603    }
604    Err(error.into())
605}
606
607#[cfg(test)]
608mod tests {
609    use super::*;
610
611    #[cfg(feature = "alloc")]
612    use crate::test_utils::{make_end_entity, make_issuer};
613
614    #[test]
615    fn eku_key_purpose_id() {
616        assert!(ExtendedKeyUsage::RequiredIfPresent(EKU_SERVER_AUTH)
617            .key_purpose_id_equals(EKU_SERVER_AUTH.oid_value))
618    }
619
620    #[cfg(feature = "alloc")]
621    enum TrustAnchorIsActualIssuer {
622        Yes,
623        No,
624    }
625
626    #[cfg(feature = "alloc")]
627    fn build_degenerate_chain(
628        intermediate_count: usize,
629        trust_anchor_is_actual_issuer: TrustAnchorIsActualIssuer,
630        budget: Option<Budget>,
631    ) -> ControlFlow<Error, Error> {
632        let ca_cert = make_issuer("Bogus Subject", None);
633        let ca_cert_der = ca_cert.serialize_der().unwrap();
634
635        let mut intermediates = Vec::with_capacity(intermediate_count);
636        let mut issuer = ca_cert;
637        for _ in 0..intermediate_count {
638            let intermediate = make_issuer("Bogus Subject", None);
639            let intermediate_der = intermediate.serialize_der_with_signer(&issuer).unwrap();
640            intermediates.push(intermediate_der);
641            issuer = intermediate;
642        }
643
644        if let TrustAnchorIsActualIssuer::No = trust_anchor_is_actual_issuer {
645            intermediates.pop();
646        }
647
648        verify_chain(
649            &ca_cert_der,
650            &intermediates,
651            &make_end_entity(&issuer),
652            budget,
653        )
654        .unwrap_err()
655    }
656
657    #[test]
658    #[cfg(feature = "alloc")]
659    fn test_too_many_signatures() {
660        assert!(matches!(
661            build_degenerate_chain(5, TrustAnchorIsActualIssuer::Yes, None),
662            ControlFlow::Break(Error::MaximumSignatureChecksExceeded)
663        ));
664    }
665
666    #[test]
667    #[cfg(feature = "alloc")]
668    fn test_too_many_path_calls() {
669        assert!(matches!(
670            build_degenerate_chain(
671                10,
672                TrustAnchorIsActualIssuer::No,
673                Some(Budget {
674                    // Crafting a chain that will expend the build chain calls budget without
675                    // first expending the signature checks budget is tricky, so we artificially
676                    // inflate the signature limit to make this test easier to write.
677                    signatures: usize::MAX,
678                    ..Budget::default()
679                })
680            ),
681            ControlFlow::Break(Error::MaximumPathBuildCallsExceeded)
682        ));
683    }
684
685    #[cfg(feature = "alloc")]
686    fn build_linear_chain(chain_length: usize) -> Result<(), ControlFlow<Error, Error>> {
687        let ca_cert = make_issuer(format!("Bogus Subject {chain_length}"), None);
688        let ca_cert_der = ca_cert.serialize_der().unwrap();
689
690        let mut intermediates = Vec::with_capacity(chain_length);
691        let mut issuer = ca_cert;
692        for i in 0..chain_length {
693            let intermediate = make_issuer(format!("Bogus Subject {i}"), None);
694            let intermediate_der = intermediate.serialize_der_with_signer(&issuer).unwrap();
695            intermediates.push(intermediate_der);
696            issuer = intermediate;
697        }
698
699        verify_chain(
700            &ca_cert_der,
701            &intermediates,
702            &make_end_entity(&issuer),
703            None,
704        )
705    }
706
707    #[test]
708    #[cfg(feature = "alloc")]
709    fn longest_allowed_path() {
710        assert!(build_linear_chain(1).is_ok());
711        assert!(build_linear_chain(2).is_ok());
712        assert!(build_linear_chain(3).is_ok());
713        assert!(build_linear_chain(4).is_ok());
714        assert!(build_linear_chain(5).is_ok());
715        assert!(build_linear_chain(6).is_ok());
716    }
717
718    #[test]
719    #[cfg(feature = "alloc")]
720    fn path_too_long() {
721        assert!(matches!(
722            build_linear_chain(7),
723            Err(ControlFlow::Continue(Error::MaximumPathDepthExceeded))
724        ));
725    }
726
727    #[test]
728    #[cfg(feature = "alloc")]
729    fn name_constraint_budget() {
730        // Issue a trust anchor that imposes name constraints. The constraint should match
731        // the end entity certificate SAN.
732        let ca_cert = make_issuer(
733            "Constrained Root",
734            Some(rcgen::NameConstraints {
735                permitted_subtrees: vec![rcgen::GeneralSubtree::DnsName(".com".into())],
736                excluded_subtrees: vec![],
737            }),
738        );
739        let ca_cert_der = ca_cert.serialize_der().unwrap();
740
741        // Create a series of intermediate issuers. We'll only use one in the actual built path,
742        // helping demonstrate that the name constraint budget is not expended checking certificates
743        // that are not part of the path we compute.
744        const NUM_INTERMEDIATES: usize = 5;
745        let mut intermediates = Vec::with_capacity(NUM_INTERMEDIATES);
746        for i in 0..NUM_INTERMEDIATES {
747            intermediates.push(make_issuer(format!("Intermediate {i}"), None));
748        }
749
750        // Each intermediate should be issued by the trust anchor.
751        let mut intermediates_der = Vec::with_capacity(NUM_INTERMEDIATES);
752        for intermediate in &intermediates {
753            intermediates_der.push(intermediate.serialize_der_with_signer(&ca_cert).unwrap());
754        }
755
756        // Create an end-entity cert that is issued by the last of the intermediates.
757        let ee_cert = make_end_entity(intermediates.last().unwrap());
758
759        // We use a custom budget to make it easier to write a test, otherwise it is tricky to
760        // stuff enough names/constraints into the potential chains while staying within the path
761        // depth limit and the build chain call limit.
762        let passing_budget = Budget {
763            // One comparison against the intermediate's distinguished name.
764            // One comparison against the EE's distinguished name.
765            // One comparison against the EE's SAN.
766            //  = 3 total comparisons.
767            name_constraint_comparisons: 3,
768            ..Budget::default()
769        };
770
771        // Validation should succeed with the name constraint comparison budget allocated above.
772        // This shows that we're not consuming budget on unused intermediates: we didn't budget
773        // enough comparisons for that to pass the overall chain building.
774        assert!(verify_chain(
775            &ca_cert_der,
776            &intermediates_der,
777            &ee_cert,
778            Some(passing_budget),
779        )
780        .is_ok());
781
782        let failing_budget = Budget {
783            // See passing_budget: 2 comparisons is not sufficient.
784            name_constraint_comparisons: 2,
785            ..Budget::default()
786        };
787        // Validation should fail when the budget is smaller than the number of comparisons performed
788        // on the validated path. This demonstrates we properly fail path building when too many
789        // name constraint comparisons occur.
790        let result = verify_chain(
791            &ca_cert_der,
792            &intermediates_der,
793            &ee_cert,
794            Some(failing_budget),
795        );
796
797        assert!(matches!(
798            result,
799            Err(ControlFlow::Break(
800                Error::MaximumNameConstraintComparisonsExceeded
801            ))
802        ));
803    }
804
805    #[cfg(feature = "alloc")]
806    fn verify_chain(
807        trust_anchor_der: &[u8],
808        intermediates_der: &[Vec<u8>],
809        ee_cert_der: &[u8],
810        budget: Option<Budget>,
811    ) -> Result<(), ControlFlow<Error, Error>> {
812        use crate::ECDSA_P256_SHA256;
813        use crate::{EndEntityCert, Time};
814
815        let anchors = &[TrustAnchor::try_from_cert_der(trust_anchor_der).unwrap()];
816        let time = Time::from_seconds_since_unix_epoch(0x1fed_f00d);
817        let cert = EndEntityCert::try_from(ee_cert_der).unwrap();
818        let intermediates_der = intermediates_der
819            .iter()
820            .map(|x| x.as_ref())
821            .collect::<Vec<_>>();
822
823        build_chain_inner(
824            &ChainOptions {
825                eku: KeyUsage::server_auth(),
826                supported_sig_algs: &[&ECDSA_P256_SHA256],
827                trust_anchors: anchors,
828                intermediate_certs: &intermediates_der,
829                crls: &[],
830            },
831            cert.inner(),
832            time,
833            0,
834            &mut budget.unwrap_or_default(),
835        )
836    }
837}