diff --git a/src/error.rs b/src/error.rs index 40649b57..3e20bf1b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -93,6 +93,9 @@ pub enum Error { /// invalid labels. MalformedNameConstraint, + /// The maximum number of signature checks has been reached. Path complexity is too great. + MaximumSignatureChecksExceeded, + /// The certificate violates one or more name constraints. NameConstraintViolation, @@ -203,7 +206,9 @@ impl Error { Error::RequiredEkuNotFound => 24, Error::NameConstraintViolation => 23, Error::PathLenConstraintViolated => 22, - Error::CaUsedAsEndEntity | Error::EndEntityUsedAsCa => 21, + Error::CaUsedAsEndEntity + | Error::EndEntityUsedAsCa + | Error::MaximumSignatureChecksExceeded => 21, Error::IssuerNotCrlSigner => 20, // Errors related to supported features used in an invalid way. diff --git a/src/verify_cert.rs b/src/verify_cert.rs index e2a2eace..79761a49 100644 --- a/src/verify_cert.rs +++ b/src/verify_cert.rs @@ -129,7 +129,7 @@ pub(crate) struct ChainOptions<'a> { } pub(crate) fn build_chain(opts: &ChainOptions, cert: &Cert, time: time::Time) -> Result<(), Error> { - build_chain_inner(opts, cert, time, 0) + build_chain_inner(opts, cert, time, 0, &mut 0_usize) } fn build_chain_inner( @@ -137,6 +137,7 @@ fn build_chain_inner( cert: &Cert, time: time::Time, sub_ca_count: usize, + signatures: &mut usize, ) -> Result<(), Error> { let used_as_ca = used_as_ca(&cert.ee_or_ca); @@ -189,7 +190,13 @@ fn build_chain_inner( // TODO: check_distrust(trust_anchor_subject, trust_anchor_spki)?; - check_signatures(opts.supported_sig_algs, cert, trust_anchor, opts.revocation)?; + check_signatures( + opts.supported_sig_algs, + cert, + trust_anchor, + opts.revocation, + signatures, + )?; Ok(()) }, @@ -233,7 +240,7 @@ fn build_chain_inner( UsedAsCa::Yes => sub_ca_count + 1, }; - build_chain_inner(opts, &potential_issuer, time, next_sub_ca_count) + build_chain_inner(opts, &potential_issuer, time, next_sub_ca_count, signatures) }) } @@ -242,12 +249,17 @@ fn check_signatures( cert_chain: &Cert, trust_anchor: &TrustAnchor, revocation: Option<RevocationOptions>, + signatures: &mut usize, ) -> Result<(), Error> { let mut spki_value = untrusted::Input::from(trust_anchor.spki); let mut issuer_subject = untrusted::Input::from(trust_anchor.subject); let mut issuer_key_usage = None; // TODO(XXX): Consider whether to track TrustAnchor KU. let mut cert = cert_chain; loop { + *signatures += 1; + if *signatures > 100 { + return Err(Error::MaximumSignatureChecksExceeded); + } signed_data::verify_signed_data(supported_sig_algs, spki_value, &cert.signed_data)?; if let Some(revocation_opts) = &revocation { @@ -643,7 +655,7 @@ impl KeyUsageMode { fn loop_while_non_fatal_error<V>( default_error: Error, values: V, - f: impl Fn(V::Item) -> Result<(), Error>, + mut f: impl FnMut(V::Item) -> Result<(), Error>, ) -> Result<(), Error> where V: IntoIterator, @@ -652,6 +664,9 @@ where for v in values { match f(v) { Ok(()) => return Ok(()), + Err(Error::MaximumSignatureChecksExceeded) => { + return Err(Error::MaximumSignatureChecksExceeded) + } Err(new_error) => error = error.most_specific(new_error), } } @@ -661,7 +676,7 @@ where #[cfg(test)] mod tests { use super::*; - use crate::BorrowedCertRevocationList; + use crate::{BorrowedCertRevocationList, EndEntityCert, Time, ECDSA_P256_SHA256}; #[test] fn eku_key_purpose_id() { @@ -773,4 +788,62 @@ mod tests { // cert has no CRL DPs. assert!(crl_authoritative(&crl, &ee)); } + + #[test] + fn test_too_many_signatures() { + let alg = &rcgen::PKCS_ECDSA_P256_SHA256; + + let make_issuer = || { + let mut ca_params = rcgen::CertificateParams::new(Vec::new()); + ca_params + .distinguished_name + .push(rcgen::DnType::OrganizationName, "Bogus Subject"); + ca_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained); + ca_params.key_usages = vec![ + rcgen::KeyUsagePurpose::KeyCertSign, + rcgen::KeyUsagePurpose::DigitalSignature, + rcgen::KeyUsagePurpose::CrlSign, + ]; + ca_params.alg = alg; + rcgen::Certificate::from_params(ca_params).unwrap() + }; + + let ca_cert = make_issuer(); + let ca_cert_der = ca_cert.serialize_der().unwrap(); + + let mut intermediates = Vec::with_capacity(101); + let mut issuer = ca_cert; + for _ in 0..101 { + let intermediate = make_issuer(); + let intermediate_der = intermediate.serialize_der_with_signer(&issuer).unwrap(); + intermediates.push(intermediate_der); + issuer = intermediate; + } + + let mut ee_params = rcgen::CertificateParams::new(vec!["example.com".to_string()]); + ee_params.is_ca = rcgen::IsCa::ExplicitNoCa; + ee_params.alg = alg; + let ee_cert = rcgen::Certificate::from_params(ee_params).unwrap(); + let ee_cert_der = ee_cert.serialize_der_with_signer(&issuer).unwrap(); + + let anchors = &[TrustAnchor::try_from_cert_der(&ca_cert_der).unwrap()]; + let time = Time::from_seconds_since_unix_epoch(0x1fed_f00d); + let cert = EndEntityCert::try_from(&ee_cert_der[..]).unwrap(); + let intermediates_der: Vec<&[u8]> = intermediates.iter().map(|x| x.as_ref()).collect(); + let intermediate_certs: &[&[u8]] = intermediates_der.as_ref(); + + let result = build_chain( + &ChainOptions { + eku: KeyUsage::server_auth(), + supported_sig_algs: &[ECDSA_P256_SHA256], + trust_anchors: anchors, + intermediate_certs, + revocation: None, + }, + cert.inner(), + time, + ); + + assert!(matches!(result, Err(Error::MaximumSignatureChecksExceeded))); + } }