Skip to content

Commit dcad240

Browse files
cpuctz
authored andcommitted
verify_cert: enforce maximum number of signatures.
Pathbuilding complexity can be quadratic, particularly when the set of intermediates all have subjects matching a trust anchor. In these cases we need to bound the number of expensive signature validation operations that are performed to avoid a DoS on CPU usage. This commit implements a simple maximum signature check limit inspired by the approach taken in the Golang x509 package. No more than 100 signatures will be evaluated while pathbuilding. This limit works in practice for Go when processing real world certificate chains and so should be appropriate for our use case as well.
1 parent 989ef02 commit dcad240

File tree

2 files changed

+85
-4
lines changed

2 files changed

+85
-4
lines changed

src/error.rs

+6
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ pub enum Error {
9393
/// invalid labels.
9494
MalformedNameConstraint,
9595

96+
/// The maximum number of signature checks has been reached. Path complexity is too great.
97+
MaximumSignatureChecksExceeded,
98+
9699
/// The certificate violates one or more name constraints.
97100
NameConstraintViolation,
98101

@@ -220,6 +223,9 @@ impl Error {
220223
Error::BadDerTime => 2,
221224
Error::BadDer => 1,
222225

226+
// Special case error - not subject to ranking.
227+
Error::MaximumSignatureChecksExceeded => 0,
228+
223229
// Default catch all error - should be renamed in the future.
224230
Error::UnknownIssuer => 0,
225231
}

src/verify_cert.rs

+79-4
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,15 @@ pub(crate) struct ChainOptions<'a> {
2727
}
2828

2929
pub(crate) fn build_chain(opts: &ChainOptions, cert: &Cert, time: time::Time) -> Result<(), Error> {
30-
build_chain_inner(opts, cert, time, 0)
30+
build_chain_inner(opts, cert, time, 0, &mut 0_usize)
3131
}
3232

3333
fn build_chain_inner(
3434
opts: &ChainOptions,
3535
cert: &Cert,
3636
time: time::Time,
3737
sub_ca_count: usize,
38+
signatures: &mut usize,
3839
) -> Result<(), Error> {
3940
let used_as_ca = used_as_ca(&cert.ee_or_ca);
4041

@@ -87,7 +88,13 @@ fn build_chain_inner(
8788

8889
// TODO: check_distrust(trust_anchor_subject, trust_anchor_spki)?;
8990

90-
check_signatures(opts.supported_sig_algs, cert, trust_anchor, opts.crls)?;
91+
check_signatures(
92+
opts.supported_sig_algs,
93+
cert,
94+
trust_anchor,
95+
opts.crls,
96+
signatures,
97+
)?;
9198

9299
Ok(())
93100
},
@@ -133,7 +140,7 @@ fn build_chain_inner(
133140
UsedAsCa::Yes => sub_ca_count + 1,
134141
};
135142

136-
build_chain_inner(opts, &potential_issuer, time, next_sub_ca_count)
143+
build_chain_inner(opts, &potential_issuer, time, next_sub_ca_count, signatures)
137144
})
138145
}
139146

@@ -142,12 +149,17 @@ fn check_signatures(
142149
cert_chain: &Cert,
143150
trust_anchor: &TrustAnchor,
144151
crls: &[&dyn CertRevocationList],
152+
signatures: &mut usize,
145153
) -> Result<(), Error> {
146154
let mut spki_value = untrusted::Input::from(trust_anchor.spki);
147155
let mut issuer_subject = untrusted::Input::from(trust_anchor.subject);
148156
let mut issuer_key_usage = None; // TODO(XXX): Consider whether to track TrustAnchor KU.
149157
let mut cert = cert_chain;
150158
loop {
159+
*signatures += 1;
160+
if *signatures > 100 {
161+
return Err(Error::MaximumSignatureChecksExceeded);
162+
}
151163
signed_data::verify_signed_data(supported_sig_algs, spki_value, &cert.signed_data)?;
152164

153165
if !crls.is_empty() {
@@ -487,7 +499,7 @@ impl KeyUsageMode {
487499
fn loop_while_non_fatal_error<V>(
488500
default_error: Error,
489501
values: V,
490-
f: impl Fn(V::Item) -> Result<(), Error>,
502+
mut f: impl FnMut(V::Item) -> Result<(), Error>,
491503
) -> Result<(), Error>
492504
where
493505
V: IntoIterator,
@@ -496,6 +508,7 @@ where
496508
for v in values {
497509
match f(v) {
498510
Ok(()) => return Ok(()),
511+
err @ Err(Error::MaximumSignatureChecksExceeded) => return err,
499512
Err(new_error) => error = error.most_specific(new_error),
500513
}
501514
}
@@ -511,4 +524,66 @@ mod tests {
511524
assert!(ExtendedKeyUsage::RequiredIfPresent(EKU_SERVER_AUTH)
512525
.key_purpose_id_equals(EKU_SERVER_AUTH.oid_value))
513526
}
527+
528+
#[test]
529+
#[cfg(feature = "alloc")]
530+
fn test_too_many_signatures() {
531+
use crate::ECDSA_P256_SHA256;
532+
use crate::{EndEntityCert, Time};
533+
534+
let alg = &rcgen::PKCS_ECDSA_P256_SHA256;
535+
536+
let make_issuer = || {
537+
let mut ca_params = rcgen::CertificateParams::new(Vec::new());
538+
ca_params
539+
.distinguished_name
540+
.push(rcgen::DnType::OrganizationName, "Bogus Subject");
541+
ca_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
542+
ca_params.key_usages = vec![
543+
rcgen::KeyUsagePurpose::KeyCertSign,
544+
rcgen::KeyUsagePurpose::DigitalSignature,
545+
rcgen::KeyUsagePurpose::CrlSign,
546+
];
547+
ca_params.alg = alg;
548+
rcgen::Certificate::from_params(ca_params).unwrap()
549+
};
550+
551+
let ca_cert = make_issuer();
552+
let ca_cert_der = ca_cert.serialize_der().unwrap();
553+
554+
let mut intermediates = Vec::with_capacity(101);
555+
let mut issuer = ca_cert;
556+
for _ in 0..101 {
557+
let intermediate = make_issuer();
558+
let intermediate_der = intermediate.serialize_der_with_signer(&issuer).unwrap();
559+
intermediates.push(intermediate_der);
560+
issuer = intermediate;
561+
}
562+
563+
let mut ee_params = rcgen::CertificateParams::new(vec!["example.com".to_string()]);
564+
ee_params.is_ca = rcgen::IsCa::ExplicitNoCa;
565+
ee_params.alg = alg;
566+
let ee_cert = rcgen::Certificate::from_params(ee_params).unwrap();
567+
let ee_cert_der = ee_cert.serialize_der_with_signer(&issuer).unwrap();
568+
569+
let anchors = &[TrustAnchor::try_from_cert_der(&ca_cert_der).unwrap()];
570+
let time = Time::from_seconds_since_unix_epoch(0x1fed_f00d);
571+
let cert = EndEntityCert::try_from(&ee_cert_der[..]).unwrap();
572+
let intermediates_der: Vec<&[u8]> = intermediates.iter().map(|x| x.as_ref()).collect();
573+
let intermediate_certs: &[&[u8]] = intermediates_der.as_ref();
574+
575+
let result = build_chain(
576+
&ChainOptions {
577+
eku: KeyUsage::server_auth(),
578+
supported_sig_algs: &[&ECDSA_P256_SHA256],
579+
trust_anchors: anchors,
580+
intermediate_certs,
581+
crls: &[],
582+
},
583+
cert.inner(),
584+
time,
585+
);
586+
587+
assert!(matches!(result, Err(Error::MaximumSignatureChecksExceeded)));
588+
}
514589
}

0 commit comments

Comments
 (0)