1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
use crate::der_parser::Oid;
use crate::oid_registry::OID_X509_EXT_BASIC_CONSTRAINTS;
use crate::unverified_chain::UnverifiedCertificateChain;
use crate::{PolicyEvaluationResult, PolicyFailureReason, ValidationPolicy};
/// id-ce-basicConstraints, RFC 5280 ยง4.2.1.9: 2.5.29.19.
fn basic_constraints_oid() -> Oid<'static> {
OID_X509_EXT_BASIC_CONSTRAINTS
}
/// A sub-policy of the [`RFC5280Policy`] that polices the basicConstraints extension.
///
/// [`RFC5280Policy`]: crate::rfc5280::RFC5280Policy
pub struct BasicConstraintsPolicy;
impl ValidationPolicy for BasicConstraintsPolicy {
fn verifying_critical_extensions(&self) -> Vec<Oid<'static>> {
vec![basic_constraints_oid()]
}
fn chain_meets_policy_requirements(
&self,
chain: &UnverifiedCertificateChain<'_>,
) -> PolicyEvaluationResult {
// The rules for BasicConstraints come from https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.9,
// but roughly can be summarised as:
//
// 0. If the cert is a v1 cert then shrug our shoulders, it can do whatever.
// 1. If basicConstraints is absent, the cert must not be used as an issuing certificate.
// 2. If basicConstraints is present and does not assert that this is a CA, this must not be used
// as an issuing certificate.
// 3. If basic constraints is present, and the CA bit is present, and there is a path length constraint,
// then this certificate may not have more sub CAs than the path length constraint allows.
//
// RFC 5280 also wants us to enforce key usage. Unfortunately, as a practical matter, browsers don't. That
// means that other implementations, like Go and webpki, also don't. To maximise compatibility, we don't either.
if chain.is_empty() {
// This is conceptually impossible, but we'll tolerate it.
return Err(PolicyFailureReason::new("empty certificate chain"));
}
let leaf = chain.leaf();
let leaf_is_v1 = leaf.tbs_certificate.version == crate::x509::X509Version::V1;
// We check for the special-case of a trust root being presented as the end entity cert. If that's what's
// happening, we require that this cert be marked as a CA.
if chain.len() == 1 && !leaf_is_v1 {
let basic_constraints = leaf
.tbs_certificate
.basic_constraints()
.map_err(|error| {
PolicyFailureReason::new(format!(
"error processing basic constraints for {:?}: {}",
leaf, error
))
})?;
return match basic_constraints {
Some(bc) if bc.value.ca => Ok(()),
_ => Err(PolicyFailureReason::new(format!(
"self-signed cert {:?} is not marked as a CA",
leaf
))),
};
}
// Now we check the chain.
let mut sub_ca_count: u32 = 0;
for i in 1..chain.len() {
let cert = &chain[i];
let is_v1 = cert.tbs_certificate.version == crate::x509::X509Version::V1;
if !is_v1 {
let basic_constraints = cert
.tbs_certificate
.basic_constraints()
.map_err(|error| {
PolicyFailureReason::new(format!(
"error processing basic constraints for {:?}: {}",
cert, error
))
})?;
match basic_constraints {
Some(bc) if bc.value.ca => {
// Is a CA, but either the max path length is at least as large as our current set of sub CAs,
// or there isn't one. Continue to the next cert.
if let Some(max_path_length) = bc.value.path_len_constraint {
// Is a CA, but the max path length is smaller than the number of sub CAs we have.
if max_path_length < sub_ca_count {
return Err(PolicyFailureReason::new(format!(
"CA {:?} has maximum path length {}, but chain has {} subCAs",
cert, max_path_length, sub_ca_count
)));
}
}
}
_ => {
return Err(PolicyFailureReason::new(format!(
"certificate {:?} is not marked as a CA",
cert
)));
}
}
}
// Is a v1 cert. Basic constraints don't apply here. Continue to the next cert.
// Note that we _do_ include this in the path length, in case there are basic constraints further along
// the path.
if cert.issuer() != cert.subject() {
// only non-self-issued certificates count against the maxPathLength limit
//
// RFC Section 4.2.1.9. Basic Constraints
// [...]
// The pathLenConstraint field is meaningful only if the cA boolean is
// asserted and the key usage extension, if present, asserts the
// keyCertSign bit (Section 4.2.1.3). In this case, it gives the
// maximum number of non-self-issued intermediate certificates that may
// follow this certificate in a valid certification path. (Note: The
// last certificate in the certification path is not an intermediate
// certificate, and is not included in this limit. Usually, the last
// certificate is an end entity certificate, but it can be a CA
// certificate.) A pathLenConstraint of zero indicates that no non-
// self-issued intermediate CA certificates may follow in a valid
// certification path. Where it appears, the pathLenConstraint field
// MUST be greater than or equal to zero. Where pathLenConstraint does
// not appear, no limit is imposed.
// [...]
// https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.9
sub_ca_count += 1;
}
}
Ok(())
}
}