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 | |
15 | use core::default::Default; |
16 | use core::ops::ControlFlow; |
17 | |
18 | use pki_types::{CertificateDer, SignatureVerificationAlgorithm, TrustAnchor, UnixTime}; |
19 | |
20 | use crate::cert::Cert; |
21 | use crate::crl::RevocationOptions; |
22 | use crate::der::{self, FromDer}; |
23 | use crate::end_entity::EndEntityCert; |
24 | use crate::error::Error; |
25 | use crate::{public_values_eq, signed_data, subject_name}; |
26 | |
27 | // Use `'a` for lifetimes that we don't care about, `'p` for lifetimes that become a part of |
28 | // the `VerifiedPath`. |
29 | pub(crate) struct ChainOptions<'a, 'p> { |
30 | pub(crate) eku: KeyUsage, |
31 | pub(crate) supported_sig_algs: &'a [&'a dyn SignatureVerificationAlgorithm], |
32 | pub(crate) trust_anchors: &'p [TrustAnchor<'p>], |
33 | pub(crate) intermediate_certs: &'p [CertificateDer<'p>], |
34 | pub(crate) revocation: Option<RevocationOptions<'a>>, |
35 | } |
36 | |
37 | impl<'a, 'p: 'a> ChainOptions<'a, 'p> { |
38 | pub(crate) fn build_chain( |
39 | &self, |
40 | end_entity: &'p EndEntityCert<'p>, |
41 | time: UnixTime, |
42 | verify_path: Option<&dyn Fn(&VerifiedPath<'_>) -> Result<(), Error>>, |
43 | ) -> Result<VerifiedPath<'p>, Error> { |
44 | let mut path = PartialPath::new(end_entity); |
45 | match self.build_chain_inner(&mut path, time, verify_path, 0, &mut Budget::default()) { |
46 | Ok(anchor) => Ok(VerifiedPath::new(end_entity, anchor, path)), |
47 | Err(ControlFlow::Break(err)) | Err(ControlFlow::Continue(err)) => Err(err), |
48 | } |
49 | } |
50 | |
51 | fn build_chain_inner( |
52 | &self, |
53 | path: &mut PartialPath<'p>, |
54 | time: UnixTime, |
55 | verify_path: Option<&dyn Fn(&VerifiedPath<'_>) -> Result<(), Error>>, |
56 | sub_ca_count: usize, |
57 | budget: &mut Budget, |
58 | ) -> Result<&'p TrustAnchor<'p>, ControlFlow<Error, Error>> { |
59 | let role = path.node().role(); |
60 | |
61 | check_issuer_independent_properties(path.head(), time, role, sub_ca_count, self.eku.inner)?; |
62 | |
63 | // TODO: HPKP checks. |
64 | |
65 | let result = loop_while_non_fatal_error( |
66 | Error::UnknownIssuer, |
67 | self.trust_anchors, |
68 | |trust_anchor: &TrustAnchor| { |
69 | let trust_anchor_subject = untrusted::Input::from(trust_anchor.subject.as_ref()); |
70 | if !public_values_eq(path.head().issuer, trust_anchor_subject) { |
71 | return Err(Error::UnknownIssuer.into()); |
72 | } |
73 | |
74 | // TODO: check_distrust(trust_anchor_subject, trust_anchor_spki)?; |
75 | |
76 | let node = path.node(); |
77 | self.check_signed_chain(&node, trust_anchor, budget)?; |
78 | check_signed_chain_name_constraints(&node, trust_anchor, budget)?; |
79 | |
80 | let verify = match verify_path { |
81 | Some(verify) => verify, |
82 | None => return Ok(trust_anchor), |
83 | }; |
84 | |
85 | let candidate = VerifiedPath { |
86 | end_entity: path.end_entity, |
87 | intermediates: Intermediates::Borrowed(&path.intermediates[..path.used]), |
88 | anchor: trust_anchor, |
89 | }; |
90 | |
91 | match verify(&candidate) { |
92 | Ok(()) => Ok(trust_anchor), |
93 | Err(err) => Err(ControlFlow::Continue(err)), |
94 | } |
95 | }, |
96 | ); |
97 | |
98 | let err = match result { |
99 | Ok(anchor) => return Ok(anchor), |
100 | // Fatal errors should halt further path building. |
101 | res @ Err(ControlFlow::Break(_)) => return res, |
102 | // Non-fatal errors should be carried forward as the default_error for subsequent |
103 | // loop_while_non_fatal_error processing and only returned once all other path-building |
104 | // options have been exhausted. |
105 | Err(ControlFlow::Continue(err)) => err, |
106 | }; |
107 | |
108 | loop_while_non_fatal_error(err, self.intermediate_certs, |cert_der| { |
109 | let potential_issuer = Cert::from_der(untrusted::Input::from(cert_der))?; |
110 | if !public_values_eq(potential_issuer.subject, path.head().issuer) { |
111 | return Err(Error::UnknownIssuer.into()); |
112 | } |
113 | |
114 | // Prevent loops; see RFC 4158 section 5.2. |
115 | if path.node().iter().any(|prev| { |
116 | public_values_eq(potential_issuer.spki, prev.cert.spki) |
117 | && public_values_eq(potential_issuer.subject, prev.cert.subject) |
118 | }) { |
119 | return Err(Error::UnknownIssuer.into()); |
120 | } |
121 | |
122 | let next_sub_ca_count = match role { |
123 | Role::EndEntity => sub_ca_count, |
124 | Role::Issuer => sub_ca_count + 1, |
125 | }; |
126 | |
127 | budget.consume_build_chain_call()?; |
128 | path.push(potential_issuer)?; |
129 | let result = self.build_chain_inner(path, time, verify_path, next_sub_ca_count, budget); |
130 | if result.is_err() { |
131 | path.pop(); |
132 | } |
133 | |
134 | result |
135 | }) |
136 | } |
137 | |
138 | fn check_signed_chain( |
139 | &self, |
140 | path: &PathNode<'_>, |
141 | trust_anchor: &TrustAnchor, |
142 | budget: &mut Budget, |
143 | ) -> Result<(), ControlFlow<Error, Error>> { |
144 | let mut spki_value = untrusted::Input::from(trust_anchor.subject_public_key_info.as_ref()); |
145 | let mut issuer_subject = untrusted::Input::from(trust_anchor.subject.as_ref()); |
146 | let mut issuer_key_usage = None; // TODO(XXX): Consider whether to track TrustAnchor KU. |
147 | for path in path.iter() { |
148 | signed_data::verify_signed_data( |
149 | self.supported_sig_algs, |
150 | spki_value, |
151 | &path.cert.signed_data, |
152 | budget, |
153 | )?; |
154 | |
155 | if let Some(revocation_opts) = &self.revocation { |
156 | revocation_opts.check( |
157 | &path, |
158 | issuer_subject, |
159 | spki_value, |
160 | issuer_key_usage, |
161 | self.supported_sig_algs, |
162 | budget, |
163 | )?; |
164 | } |
165 | |
166 | spki_value = path.cert.spki; |
167 | issuer_subject = path.cert.subject; |
168 | issuer_key_usage = path.cert.key_usage; |
169 | } |
170 | |
171 | Ok(()) |
172 | } |
173 | } |
174 | |
175 | /// Path from end-entity certificate to trust anchor that's been verified. |
176 | /// |
177 | /// See [`EndEntityCert::verify_for_usage()`] for more details on what verification entails. |
178 | pub struct VerifiedPath<'p> { |
179 | end_entity: &'p EndEntityCert<'p>, |
180 | intermediates: Intermediates<'p>, |
181 | anchor: &'p TrustAnchor<'p>, |
182 | } |
183 | |
184 | impl<'p> VerifiedPath<'p> { |
185 | fn new( |
186 | end_entity: &'p EndEntityCert<'p>, |
187 | anchor: &'p TrustAnchor<'p>, |
188 | partial: PartialPath<'p>, |
189 | ) -> Self { |
190 | Self { |
191 | end_entity, |
192 | intermediates: Intermediates::Owned { |
193 | certs: partial.intermediates, |
194 | used: partial.used, |
195 | }, |
196 | anchor, |
197 | } |
198 | } |
199 | |
200 | /// Yields a (double-ended) iterator over the intermediate certificates in this path. |
201 | pub fn intermediate_certificates(&'p self) -> IntermediateIterator<'p> { |
202 | IntermediateIterator { |
203 | intermediates: self.intermediates.as_ref(), |
204 | } |
205 | } |
206 | |
207 | /// Yields the end-entity certificate for this path. |
208 | pub fn end_entity(&self) -> &'p EndEntityCert<'p> { |
209 | self.end_entity |
210 | } |
211 | |
212 | /// Yields the trust anchor for this path. |
213 | pub fn anchor(&self) -> &'p TrustAnchor<'p> { |
214 | self.anchor |
215 | } |
216 | } |
217 | |
218 | /// Iterator over a path's intermediate certificates. |
219 | /// |
220 | /// Implements [`DoubleEndedIterator`] so it can be traversed in both directions. |
221 | pub struct IntermediateIterator<'a> { |
222 | /// Invariant: all of these `Option`s are `Some`. |
223 | intermediates: &'a [Option<Cert<'a>>], |
224 | } |
225 | |
226 | impl<'a> Iterator for IntermediateIterator<'a> { |
227 | type Item = &'a Cert<'a>; |
228 | |
229 | fn next(&mut self) -> Option<Self::Item> { |
230 | match self.intermediates.split_first() { |
231 | Some((head: &Option |
232 | self.intermediates = tail; |
233 | Some(head.as_ref().unwrap()) |
234 | } |
235 | None => None, |
236 | } |
237 | } |
238 | } |
239 | |
240 | impl<'a> DoubleEndedIterator for IntermediateIterator<'a> { |
241 | fn next_back(&mut self) -> Option<Self::Item> { |
242 | match self.intermediates.split_last() { |
243 | Some((head: &Option |
244 | self.intermediates = tail; |
245 | Some(head.as_ref().unwrap()) |
246 | } |
247 | None => None, |
248 | } |
249 | } |
250 | } |
251 | |
252 | #[allow(clippy::large_enum_variant)] |
253 | enum Intermediates<'a> { |
254 | Owned { |
255 | certs: [Option<Cert<'a>>; MAX_SUB_CA_COUNT], |
256 | used: usize, |
257 | }, |
258 | Borrowed(&'a [Option<Cert<'a>>]), |
259 | } |
260 | |
261 | impl<'a> AsRef<[Option<Cert<'a>>]> for Intermediates<'a> { |
262 | fn as_ref(&self) -> &[Option<Cert<'a>>] { |
263 | match self { |
264 | Intermediates::Owned { certs: &[Option |
265 | Intermediates::Borrowed(certs: &&[Option |
266 | } |
267 | } |
268 | } |
269 | |
270 | fn check_signed_chain_name_constraints( |
271 | path: &PathNode<'_>, |
272 | trust_anchor: &TrustAnchor, |
273 | budget: &mut Budget, |
274 | ) -> Result<(), ControlFlow<Error, Error>> { |
275 | let mut name_constraints: Option> = trust_anchorOption<&Der<'_>> |
276 | .name_constraints |
277 | .as_ref() |
278 | .map(|der: &Der<'_>| untrusted::Input::from(bytes:der.as_ref())); |
279 | |
280 | for path: PathNode<'_> in path.iter() { |
281 | untrusted::read_all_optional(input:name_constraints, incomplete_read:Error::BadDer, |value: Option<&mut Reader<'_>>| { |
282 | subject_name::check_name_constraints(constraints:value, &path, budget) |
283 | })?; |
284 | |
285 | name_constraints = path.cert.name_constraints; |
286 | } |
287 | |
288 | Ok(()) |
289 | } |
290 | |
291 | pub(crate) struct Budget { |
292 | signatures: usize, |
293 | build_chain_calls: usize, |
294 | name_constraint_comparisons: usize, |
295 | } |
296 | |
297 | impl Budget { |
298 | #[inline] |
299 | pub(crate) fn consume_signature(&mut self) -> Result<(), Error> { |
300 | self.signatures = self |
301 | .signatures |
302 | .checked_sub(1) |
303 | .ok_or(Error::MaximumSignatureChecksExceeded)?; |
304 | Ok(()) |
305 | } |
306 | |
307 | #[inline] |
308 | fn consume_build_chain_call(&mut self) -> Result<(), Error> { |
309 | self.build_chain_calls = self |
310 | .build_chain_calls |
311 | .checked_sub(1) |
312 | .ok_or(Error::MaximumPathBuildCallsExceeded)?; |
313 | Ok(()) |
314 | } |
315 | |
316 | #[inline] |
317 | pub(crate) fn consume_name_constraint_comparison(&mut self) -> Result<(), Error> { |
318 | self.name_constraint_comparisons = self |
319 | .name_constraint_comparisons |
320 | .checked_sub(1) |
321 | .ok_or(Error::MaximumNameConstraintComparisonsExceeded)?; |
322 | Ok(()) |
323 | } |
324 | } |
325 | |
326 | impl Default for Budget { |
327 | fn default() -> Self { |
328 | Self { |
329 | // This limit is taken from the remediation for golang CVE-2018-16875. However, |
330 | // note that golang subsequently implemented AKID matching due to this limit |
331 | // being hit in real applications (see <https://github.com/spiffe/spire/issues/1004>). |
332 | // So this may actually be too aggressive. |
333 | signatures: 100, |
334 | |
335 | // This limit is taken from mozilla::pkix, see: |
336 | // <https://github.com/nss-dev/nss/blob/bb4a1d38dd9e92923525ac6b5ed0288479f3f3fc/lib/mozpkix/lib/pkixbuild.cpp#L381-L393> |
337 | build_chain_calls: 200_000, |
338 | |
339 | // This limit is taken from golang crypto/x509's default, see: |
340 | // <https://github.com/golang/go/blob/ac17bb6f13979f2ab9fcd45f0758b43ed72d0973/src/crypto/x509/verify.go#L588-L592> |
341 | name_constraint_comparisons: 250_000, |
342 | } |
343 | } |
344 | } |
345 | |
346 | fn check_issuer_independent_properties( |
347 | cert: &Cert, |
348 | time: UnixTime, |
349 | role: Role, |
350 | sub_ca_count: usize, |
351 | eku: ExtendedKeyUsage, |
352 | ) -> Result<(), Error> { |
353 | // TODO: check_distrust(trust_anchor_subject, trust_anchor_spki)?; |
354 | // TODO: Check signature algorithm like mozilla::pkix. |
355 | // TODO: Check SPKI like mozilla::pkix. |
356 | // TODO: check for active distrust like mozilla::pkix. |
357 | |
358 | // For cert validation, we ignore the KeyUsage extension. For CA |
359 | // certificates, BasicConstraints.cA makes KeyUsage redundant. Firefox |
360 | // and other common browsers do not check KeyUsage for end-entities, |
361 | // though it would be kind of nice to ensure that a KeyUsage without |
362 | // the keyEncipherment bit could not be used for RSA key exchange. |
363 | |
364 | cert.validity |
365 | .read_all(incomplete_read:Error::BadDer, |value: &mut Reader<'_>| check_validity(input:value, time))?; |
366 | untrusted::read_all_optional(input:cert.basic_constraints, incomplete_read:Error::BadDer, |value: Option<&mut Reader<'_>>| { |
367 | check_basic_constraints(input:value, role, sub_ca_count) |
368 | })?; |
369 | untrusted::read_all_optional(input:cert.eku, incomplete_read:Error::BadDer, |value: Option<&mut Reader<'_>>| eku.check(input:value))?; |
370 | |
371 | Ok(()) |
372 | } |
373 | |
374 | // https://tools.ietf.org/html/rfc5280#section-4.1.2.5 |
375 | fn check_validity(input: &mut untrusted::Reader, time: UnixTime) -> Result<(), Error> { |
376 | let not_before: UnixTime = UnixTime::from_der(reader:input)?; |
377 | let not_after: UnixTime = UnixTime::from_der(reader:input)?; |
378 | |
379 | if not_before > not_after { |
380 | return Err(Error::InvalidCertValidity); |
381 | } |
382 | if time < not_before { |
383 | return Err(Error::CertNotValidYet); |
384 | } |
385 | if time > not_after { |
386 | return Err(Error::CertExpired); |
387 | } |
388 | |
389 | // TODO: mozilla::pkix allows the TrustDomain to check not_before and |
390 | // not_after, to enforce things like a maximum validity period. We should |
391 | // do something similar. |
392 | |
393 | Ok(()) |
394 | } |
395 | |
396 | // https://tools.ietf.org/html/rfc5280#section-4.2.1.9 |
397 | fn check_basic_constraints( |
398 | input: Option<&mut untrusted::Reader>, |
399 | role: Role, |
400 | sub_ca_count: usize, |
401 | ) -> Result<(), Error> { |
402 | let (is_ca, path_len_constraint) = match input { |
403 | Some(input) => { |
404 | let is_ca = bool::from_der(input)?; |
405 | |
406 | // https://bugzilla.mozilla.org/show_bug.cgi?id=985025: RFC 5280 |
407 | // says that a certificate must not have pathLenConstraint unless |
408 | // it is a CA certificate, but some real-world end-entity |
409 | // certificates have pathLenConstraint. |
410 | let path_len_constraint = if !input.at_end() { |
411 | Some(usize::from(u8::from_der(input)?)) |
412 | } else { |
413 | None |
414 | }; |
415 | |
416 | (is_ca, path_len_constraint) |
417 | } |
418 | None => (false, None), |
419 | }; |
420 | |
421 | match (role, is_ca, path_len_constraint) { |
422 | (Role::EndEntity, true, _) => Err(Error::CaUsedAsEndEntity), |
423 | (Role::Issuer, false, _) => Err(Error::EndEntityUsedAsCa), |
424 | (Role::Issuer, true, Some(len)) if sub_ca_count > len => { |
425 | Err(Error::PathLenConstraintViolated) |
426 | } |
427 | _ => Ok(()), |
428 | } |
429 | } |
430 | |
431 | /// The expected key usage of a certificate. |
432 | /// |
433 | /// This type represents the expected key usage of an end entity certificate. Although for most |
434 | /// kinds of certificates the extended key usage extension is optional (and so certificates |
435 | /// not carrying a particular value in the EKU extension are acceptable). If the extension |
436 | /// is present, the certificate MUST only be used for one of the purposes indicated. |
437 | /// |
438 | /// <https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.12> |
439 | #[derive(Clone, Copy)] |
440 | pub struct KeyUsage { |
441 | inner: ExtendedKeyUsage, |
442 | } |
443 | |
444 | impl KeyUsage { |
445 | /// Construct a new [`KeyUsage`] as appropriate for server certificate authentication. |
446 | /// |
447 | /// 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. |
448 | pub const fn server_auth() -> Self { |
449 | Self { |
450 | inner: ExtendedKeyUsage::RequiredIfPresent(EKU_SERVER_AUTH), |
451 | } |
452 | } |
453 | |
454 | /// Construct a new [`KeyUsage`] as appropriate for client certificate authentication. |
455 | /// |
456 | /// As specified in <>, this does not require the certificate to specify the eKU extension. |
457 | pub const fn client_auth() -> Self { |
458 | Self { |
459 | inner: ExtendedKeyUsage::RequiredIfPresent(EKU_CLIENT_AUTH), |
460 | } |
461 | } |
462 | |
463 | /// Construct a new [`KeyUsage`] requiring a certificate to support the specified OID. |
464 | pub const fn required(oid: &'static [u8]) -> Self { |
465 | Self { |
466 | inner: ExtendedKeyUsage::Required(KeyPurposeId::new(oid)), |
467 | } |
468 | } |
469 | } |
470 | |
471 | /// Extended Key Usage (EKU) of a certificate. |
472 | #[derive(Clone, Copy)] |
473 | enum ExtendedKeyUsage { |
474 | /// The certificate must contain the specified [`KeyPurposeId`] as EKU. |
475 | Required(KeyPurposeId), |
476 | |
477 | /// If the certificate has EKUs, then the specified [`KeyPurposeId`] must be included. |
478 | RequiredIfPresent(KeyPurposeId), |
479 | } |
480 | |
481 | impl ExtendedKeyUsage { |
482 | // https://tools.ietf.org/html/rfc5280#section-4.2.1.12 |
483 | fn check(&self, input: Option<&mut untrusted::Reader>) -> Result<(), Error> { |
484 | let input = match (input, self) { |
485 | (Some(input), _) => input, |
486 | (None, Self::RequiredIfPresent(_)) => return Ok(()), |
487 | (None, Self::Required(_)) => return Err(Error::RequiredEkuNotFound), |
488 | }; |
489 | |
490 | loop { |
491 | let value = der::expect_tag(input, der::Tag::OID)?; |
492 | if self.key_purpose_id_equals(value) { |
493 | input.skip_to_end(); |
494 | break; |
495 | } |
496 | |
497 | if input.at_end() { |
498 | return Err(Error::RequiredEkuNotFound); |
499 | } |
500 | } |
501 | |
502 | Ok(()) |
503 | } |
504 | |
505 | fn key_purpose_id_equals(&self, value: untrusted::Input<'_>) -> bool { |
506 | public_values_eq( |
507 | match self { |
508 | ExtendedKeyUsage::Required(eku) => *eku, |
509 | ExtendedKeyUsage::RequiredIfPresent(eku) => *eku, |
510 | } |
511 | .oid_value, |
512 | value, |
513 | ) |
514 | } |
515 | } |
516 | |
517 | /// An OID value indicating an Extended Key Usage (EKU) key purpose. |
518 | #[derive(Clone, Copy)] |
519 | struct KeyPurposeId { |
520 | oid_value: untrusted::Input<'static>, |
521 | } |
522 | |
523 | impl KeyPurposeId { |
524 | /// Construct a new [`KeyPurposeId`]. |
525 | /// |
526 | /// `oid` is the OBJECT IDENTIFIER in bytes. |
527 | const fn new(oid: &'static [u8]) -> Self { |
528 | Self { |
529 | oid_value: untrusted::Input::from(bytes:oid), |
530 | } |
531 | } |
532 | } |
533 | |
534 | impl PartialEq<Self> for KeyPurposeId { |
535 | fn eq(&self, other: &Self) -> bool { |
536 | public_values_eq(self.oid_value, b:other.oid_value) |
537 | } |
538 | } |
539 | |
540 | impl Eq for KeyPurposeId {} |
541 | |
542 | // id-pkix OBJECT IDENTIFIER ::= { 1 3 6 1 5 5 7 } |
543 | // id-kp OBJECT IDENTIFIER ::= { id-pkix 3 } |
544 | |
545 | // id-kp-serverAuth OBJECT IDENTIFIER ::= { id-kp 1 } |
546 | const EKU_SERVER_AUTH: KeyPurposeId = KeyPurposeId::new(&oid!(1, 3, 6, 1, 5, 5, 7, 3, 1)); |
547 | |
548 | // id-kp-clientAuth OBJECT IDENTIFIER ::= { id-kp 2 } |
549 | const EKU_CLIENT_AUTH: KeyPurposeId = KeyPurposeId::new(&oid!(1, 3, 6, 1, 5, 5, 7, 3, 2)); |
550 | |
551 | fn loop_while_non_fatal_error<'a, V: 'a>( |
552 | default_error: Error, |
553 | values: V, |
554 | mut f: impl FnMut(V::Item) -> Result<&'a TrustAnchor<'a>, ControlFlow<Error, Error>>, |
555 | ) -> Result<&'a TrustAnchor<'a>, ControlFlow<Error, Error>> |
556 | where |
557 | V: IntoIterator, |
558 | { |
559 | let mut error: Error = default_error; |
560 | for v: |
561 | match f(v) { |
562 | Ok(anchor: &TrustAnchor<'_>) => return Ok(anchor), |
563 | // Fatal errors should halt further looping. |
564 | res: Result<&TrustAnchor<'_>, …> @ Err(ControlFlow::Break(_)) => return res, |
565 | // Non-fatal errors should be ranked by specificity and only returned |
566 | // once all other path-building options have been exhausted. |
567 | Err(ControlFlow::Continue(new_error: Error)) => error = error.most_specific(new_error), |
568 | } |
569 | } |
570 | Err(error.into()) |
571 | } |
572 | |
573 | /// A path for consideration in path building. |
574 | /// |
575 | /// This represents a partial path because it does not yet contain the trust anchor. It stores |
576 | /// the end-entity certificates, and an array of intermediate certificates. |
577 | pub(crate) struct PartialPath<'a> { |
578 | end_entity: &'a EndEntityCert<'a>, |
579 | /// Intermediate certificates, in order from end-entity to trust anchor. |
580 | /// |
581 | /// Invariant: all values below `used` are `Some`. |
582 | intermediates: [Option<Cert<'a>>; MAX_SUB_CA_COUNT], |
583 | /// The number of `Some` values in `intermediates`. |
584 | /// |
585 | /// The next `Cert` passed to `push()` will be placed at `intermediates[used]`. |
586 | /// If this value is 0, the path contains only the end-entity certificate. |
587 | used: usize, |
588 | } |
589 | |
590 | impl<'a> PartialPath<'a> { |
591 | pub(crate) fn new(end_entity: &'a EndEntityCert<'a>) -> Self { |
592 | Self { |
593 | end_entity, |
594 | intermediates: Default::default(), |
595 | used: 0, |
596 | } |
597 | } |
598 | |
599 | pub(crate) fn push(&mut self, cert: Cert<'a>) -> Result<(), ControlFlow<Error, Error>> { |
600 | if self.used >= MAX_SUB_CA_COUNT { |
601 | return Err(Error::MaximumPathDepthExceeded.into()); |
602 | } |
603 | |
604 | self.intermediates[self.used] = Some(cert); |
605 | self.used += 1; |
606 | Ok(()) |
607 | } |
608 | |
609 | fn pop(&mut self) { |
610 | debug_assert!(self.used > 0); |
611 | if self.used == 0 { |
612 | return; |
613 | } |
614 | |
615 | self.used -= 1; |
616 | self.intermediates[self.used] = None; |
617 | } |
618 | |
619 | pub(crate) fn node(&self) -> PathNode<'_> { |
620 | PathNode { |
621 | path: self, |
622 | index: self.used, |
623 | cert: self.head(), |
624 | } |
625 | } |
626 | |
627 | /// Current head of the path. |
628 | pub(crate) fn head(&self) -> &Cert<'a> { |
629 | self.get(self.used) |
630 | } |
631 | |
632 | /// Get the certificate at index `idx` in the path. |
633 | /// |
634 | // `idx` must be in the range `0..=self.used`; `idx` 0 thus yields the `end_entity`, |
635 | // while subsequent indexes yield the intermediate at `self.intermediates[idx - 1]`. |
636 | fn get(&self, idx: usize) -> &Cert<'a> { |
637 | match idx { |
638 | 0 => self.end_entity, |
639 | _ => self.intermediates[idx - 1].as_ref().unwrap(), |
640 | } |
641 | } |
642 | } |
643 | |
644 | const MAX_SUB_CA_COUNT: usize = 6; |
645 | |
646 | pub(crate) struct PathNode<'a> { |
647 | /// The path we're iterating. |
648 | path: &'a PartialPath<'a>, |
649 | /// The index of the current node in the path (input for `path.get()`). |
650 | index: usize, |
651 | /// The [`Cert`] at `index`. |
652 | pub(crate) cert: &'a Cert<'a>, |
653 | } |
654 | |
655 | impl<'a> PathNode<'a> { |
656 | pub(crate) fn iter(&self) -> PathIter<'a> { |
657 | PathIter { |
658 | path: self.path, |
659 | next: Some(self.index), |
660 | } |
661 | } |
662 | |
663 | pub(crate) fn role(&self) -> Role { |
664 | match self.index { |
665 | 0 => Role::EndEntity, |
666 | _ => Role::Issuer, |
667 | } |
668 | } |
669 | } |
670 | |
671 | pub(crate) struct PathIter<'a> { |
672 | path: &'a PartialPath<'a>, |
673 | next: Option<usize>, |
674 | } |
675 | |
676 | impl<'a> Iterator for PathIter<'a> { |
677 | type Item = PathNode<'a>; |
678 | |
679 | fn next(&mut self) -> Option<Self::Item> { |
680 | let next: usize = self.next?; |
681 | self.next = match next { |
682 | 0 => None, |
683 | _ => Some(next - 1), |
684 | }; |
685 | |
686 | Some(PathNode { |
687 | path: self.path, |
688 | index: next, |
689 | cert: self.path.get(idx:next), |
690 | }) |
691 | } |
692 | } |
693 | |
694 | #[derive(Clone, Copy, PartialEq)] |
695 | pub(crate) enum Role { |
696 | Issuer, |
697 | EndEntity, |
698 | } |
699 | |
700 | #[cfg(all(test, feature = "alloc", any(feature = "ring", feature = "aws_lc_rs")))] |
701 | mod tests { |
702 | use super::*; |
703 | use crate::test_utils::{issuer_params, make_end_entity, make_issuer}; |
704 | use crate::trust_anchor::anchor_from_trusted_cert; |
705 | |
706 | #[test] |
707 | fn eku_key_purpose_id() { |
708 | assert!(ExtendedKeyUsage::RequiredIfPresent(EKU_SERVER_AUTH) |
709 | .key_purpose_id_equals(EKU_SERVER_AUTH.oid_value)) |
710 | } |
711 | |
712 | #[cfg(feature = "alloc")] |
713 | enum ChainTrustAnchor { |
714 | NotInChain, |
715 | InChain, |
716 | } |
717 | |
718 | fn build_degenerate_chain( |
719 | intermediate_count: usize, |
720 | trust_anchor: ChainTrustAnchor, |
721 | ) -> ControlFlow<Error, Error> { |
722 | let ca_cert = make_issuer("Bogus Subject"); |
723 | let ca_cert_der = CertificateDer::from(ca_cert.serialize_der().unwrap()); |
724 | |
725 | let mut intermediates = Vec::with_capacity(intermediate_count + 1); |
726 | if let ChainTrustAnchor::InChain = trust_anchor { |
727 | intermediates.push(CertificateDer::from(ca_cert_der.to_vec())); |
728 | } |
729 | |
730 | let mut issuer = ca_cert; |
731 | for _ in 0..intermediate_count { |
732 | let intermediate = make_issuer("Bogus Subject"); |
733 | let intermediate_der = intermediate.serialize_der_with_signer(&issuer).unwrap(); |
734 | intermediates.push(CertificateDer::from(intermediate_der)); |
735 | issuer = intermediate; |
736 | } |
737 | |
738 | let trust_anchor = match trust_anchor { |
739 | ChainTrustAnchor::InChain => { |
740 | let unused_anchor = make_issuer("Bogus Trust Anchor"); |
741 | CertificateDer::from(unused_anchor.serialize_der().unwrap()) |
742 | } |
743 | ChainTrustAnchor::NotInChain => ca_cert_der, |
744 | }; |
745 | |
746 | let ee_der = make_end_entity(&issuer); |
747 | let ee_cert = EndEntityCert::try_from(&ee_der).unwrap(); |
748 | verify_chain( |
749 | &[anchor_from_trusted_cert(&trust_anchor).unwrap()], |
750 | &intermediates, |
751 | &ee_cert, |
752 | None, |
753 | None, |
754 | ) |
755 | .map(|_| ()) |
756 | .unwrap_err() |
757 | } |
758 | |
759 | #[test] |
760 | fn test_too_many_signatures() { |
761 | assert!(matches!( |
762 | build_degenerate_chain(5, ChainTrustAnchor::NotInChain), |
763 | ControlFlow::Break(Error::MaximumSignatureChecksExceeded) |
764 | )); |
765 | } |
766 | |
767 | #[test] |
768 | fn test_too_many_path_calls() { |
769 | assert!(matches!( |
770 | dbg!(build_degenerate_chain(10, ChainTrustAnchor::InChain)), |
771 | ControlFlow::Break(Error::MaximumPathBuildCallsExceeded) |
772 | )); |
773 | } |
774 | |
775 | fn build_linear_chain(chain_length: usize) -> Result<(), ControlFlow<Error, Error>> { |
776 | let ca_cert = make_issuer(format!("Bogus Subject{chain_length} ")); |
777 | let ca_cert_der = CertificateDer::from(ca_cert.serialize_der().unwrap()); |
778 | let anchor = anchor_from_trusted_cert(&ca_cert_der).unwrap(); |
779 | let anchors = &[anchor.clone()]; |
780 | |
781 | let mut intermediates = Vec::with_capacity(chain_length); |
782 | let mut issuer = ca_cert; |
783 | for i in 0..chain_length { |
784 | let intermediate = make_issuer(format!("Bogus Subject{i} ")); |
785 | let intermediate_der = intermediate.serialize_der_with_signer(&issuer).unwrap(); |
786 | intermediates.push(CertificateDer::from(intermediate_der)); |
787 | issuer = intermediate; |
788 | } |
789 | |
790 | let ee_der = make_end_entity(&issuer); |
791 | let ee_cert = EndEntityCert::try_from(&ee_der).unwrap(); |
792 | |
793 | let expected_chain = |path: &VerifiedPath<'_>| { |
794 | assert_eq!(path.anchor().subject, anchor.subject); |
795 | assert!(public_values_eq(path.end_entity().subject, ee_cert.subject)); |
796 | assert_eq!(path.intermediate_certificates().count(), chain_length); |
797 | |
798 | let intermediate_certs = intermediates |
799 | .iter() |
800 | .map(|der| Cert::from_der(untrusted::Input::from(der.as_ref())).unwrap()) |
801 | .collect::<Vec<_>>(); |
802 | |
803 | for (cert, expected) in path |
804 | .intermediate_certificates() |
805 | .rev() |
806 | .zip(intermediate_certs.iter()) |
807 | { |
808 | assert!(public_values_eq(cert.subject, expected.subject)); |
809 | assert_eq!(cert.der(), expected.der()); |
810 | } |
811 | |
812 | for (cert, expected) in path |
813 | .intermediate_certificates() |
814 | .zip(intermediate_certs.iter().rev()) |
815 | { |
816 | assert!(public_values_eq(cert.subject, expected.subject)); |
817 | assert_eq!(cert.der(), expected.der()); |
818 | } |
819 | |
820 | Ok(()) |
821 | }; |
822 | |
823 | verify_chain( |
824 | anchors, |
825 | &intermediates, |
826 | &ee_cert, |
827 | Some(&expected_chain), |
828 | None, |
829 | ) |
830 | .map(|_| ()) |
831 | } |
832 | |
833 | #[test] |
834 | fn longest_allowed_path() { |
835 | assert!(build_linear_chain(1).is_ok()); |
836 | assert!(build_linear_chain(2).is_ok()); |
837 | assert!(build_linear_chain(3).is_ok()); |
838 | assert!(build_linear_chain(4).is_ok()); |
839 | assert!(build_linear_chain(5).is_ok()); |
840 | assert!(build_linear_chain(6).is_ok()); |
841 | } |
842 | |
843 | #[test] |
844 | fn path_too_long() { |
845 | assert!(matches!( |
846 | build_linear_chain(7), |
847 | Err(ControlFlow::Continue(Error::MaximumPathDepthExceeded)) |
848 | )); |
849 | } |
850 | |
851 | #[test] |
852 | fn name_constraint_budget() { |
853 | // Issue a trust anchor that imposes name constraints. The constraint should match |
854 | // the end entity certificate SAN. |
855 | let mut ca_cert_params = issuer_params("Constrained Root"); |
856 | ca_cert_params.name_constraints = Some(rcgen::NameConstraints { |
857 | permitted_subtrees: vec![rcgen::GeneralSubtree::DnsName(".com".into())], |
858 | excluded_subtrees: vec![], |
859 | }); |
860 | let ca_cert = rcgen::Certificate::from_params(ca_cert_params).unwrap(); |
861 | let ca_cert_der = CertificateDer::from(ca_cert.serialize_der().unwrap()); |
862 | let anchors = &[anchor_from_trusted_cert(&ca_cert_der).unwrap()]; |
863 | |
864 | // Create a series of intermediate issuers. We'll only use one in the actual built path, |
865 | // helping demonstrate that the name constraint budget is not expended checking certificates |
866 | // that are not part of the path we compute. |
867 | const NUM_INTERMEDIATES: usize = 5; |
868 | let mut intermediates = Vec::with_capacity(NUM_INTERMEDIATES); |
869 | for i in 0..NUM_INTERMEDIATES { |
870 | intermediates.push(make_issuer(format!("Intermediate{i} "))); |
871 | } |
872 | |
873 | // Each intermediate should be issued by the trust anchor. |
874 | let mut intermediates_der = Vec::with_capacity(NUM_INTERMEDIATES); |
875 | for intermediate in &intermediates { |
876 | intermediates_der.push(CertificateDer::from( |
877 | intermediate.serialize_der_with_signer(&ca_cert).unwrap(), |
878 | )); |
879 | } |
880 | |
881 | // Create an end-entity cert that is issued by the last of the intermediates. |
882 | let ee_der = make_end_entity(intermediates.last().unwrap()); |
883 | let ee_cert = EndEntityCert::try_from(&ee_der).unwrap(); |
884 | |
885 | // We use a custom budget to make it easier to write a test, otherwise it is tricky to |
886 | // stuff enough names/constraints into the potential chains while staying within the path |
887 | // depth limit and the build chain call limit. |
888 | let passing_budget = Budget { |
889 | // One comparison against the intermediate's distinguished name. |
890 | // One comparison against the EE's distinguished name. |
891 | // One comparison against the EE's SAN. |
892 | // = 3 total comparisons. |
893 | name_constraint_comparisons: 3, |
894 | ..Budget::default() |
895 | }; |
896 | |
897 | // Validation should succeed with the name constraint comparison budget allocated above. |
898 | // This shows that we're not consuming budget on unused intermediates: we didn't budget |
899 | // enough comparisons for that to pass the overall chain building. |
900 | let path = verify_chain( |
901 | anchors, |
902 | &intermediates_der, |
903 | &ee_cert, |
904 | None, |
905 | Some(passing_budget), |
906 | ) |
907 | .unwrap(); |
908 | assert_eq!(path.anchor().subject, anchors.first().unwrap().subject); |
909 | |
910 | let failing_budget = Budget { |
911 | // See passing_budget: 2 comparisons is not sufficient. |
912 | name_constraint_comparisons: 2, |
913 | ..Budget::default() |
914 | }; |
915 | // Validation should fail when the budget is smaller than the number of comparisons performed |
916 | // on the validated path. This demonstrates we properly fail path building when too many |
917 | // name constraint comparisons occur. |
918 | let result = verify_chain( |
919 | anchors, |
920 | &intermediates_der, |
921 | &ee_cert, |
922 | None, |
923 | Some(failing_budget), |
924 | ); |
925 | |
926 | assert!(matches!( |
927 | result, |
928 | Err(ControlFlow::Break( |
929 | Error::MaximumNameConstraintComparisonsExceeded |
930 | )) |
931 | )); |
932 | } |
933 | |
934 | #[test] |
935 | fn test_reject_candidate_path() { |
936 | /* |
937 | This test builds a PKI like the following diagram depicts. We first verify |
938 | that we can build a path EE -> B -> A -> TA. Next we supply a custom path verification |
939 | function that rejects the B->A path, and verify that we build a path EE -> B -> C -> TA. |
940 | |
941 | ┌───────────┐ |
942 | │ │ |
943 | │ TA │ |
944 | │ │ |
945 | └───┬───┬───┘ |
946 | │ │ |
947 | │ │ |
948 | ┌────────┐◄┘ └──►┌────────┐ |
949 | │ │ │ │ |
950 | │ A │ │ C │ |
951 | │ │ │ │ |
952 | └────┬───┘ └───┬────┘ |
953 | │ │ |
954 | │ │ |
955 | │ ┌─────────┐ │ |
956 | └──►│ │◄──┘ |
957 | │ B │ |
958 | │ │ |
959 | └────┬────┘ |
960 | │ |
961 | │ |
962 | │ |
963 | ┌────▼────┐ |
964 | │ │ |
965 | │ EE │ |
966 | │ │ |
967 | └─────────┘ |
968 | */ |
969 | |
970 | // Create a trust anchor, and use it to issue two distinct intermediate certificates, each |
971 | // with a unique subject and keypair. |
972 | let trust_anchor = make_issuer("Trust Anchor"); |
973 | let trust_anchor_der = CertificateDer::from(trust_anchor.serialize_der().unwrap()); |
974 | let trust_anchor_cert = |
975 | Cert::from_der(untrusted::Input::from(trust_anchor_der.as_ref())).unwrap(); |
976 | let trust_anchors = &[anchor_from_trusted_cert(&trust_anchor_der).unwrap()]; |
977 | |
978 | let intermediate_a = make_issuer("Intermediate A"); |
979 | let intermediate_a_der = CertificateDer::from( |
980 | intermediate_a |
981 | .serialize_der_with_signer(&trust_anchor) |
982 | .unwrap(), |
983 | ); |
984 | let intermediate_a_cert = |
985 | Cert::from_der(untrusted::Input::from(intermediate_a_der.as_ref())).unwrap(); |
986 | |
987 | let intermediate_c = make_issuer("Intermediate C"); |
988 | let intermediate_c_der = CertificateDer::from( |
989 | intermediate_c |
990 | .serialize_der_with_signer(&trust_anchor) |
991 | .unwrap(), |
992 | ); |
993 | let intermediate_c_cert = |
994 | Cert::from_der(untrusted::Input::from(intermediate_c_der.as_ref())).unwrap(); |
995 | |
996 | // Next, create an intermediate that is issued by both of the intermediates above. |
997 | // Both should share the same subject, and key pair, but will differ in the issuer. |
998 | let intermediate_b_key = rcgen::KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256).unwrap(); |
999 | let mut intermediate_b_params = issuer_params("Intermediate"); |
1000 | intermediate_b_params.key_pair = Some(intermediate_b_key); |
1001 | let intermediate_b = rcgen::Certificate::from_params(intermediate_b_params).unwrap(); |
1002 | |
1003 | let intermediate_b_a_der = CertificateDer::from( |
1004 | intermediate_b |
1005 | .serialize_der_with_signer(&intermediate_a) |
1006 | .unwrap(), |
1007 | ); |
1008 | let intermediate_b_c_der = CertificateDer::from( |
1009 | intermediate_b |
1010 | .serialize_der_with_signer(&intermediate_c) |
1011 | .unwrap(), |
1012 | ); |
1013 | |
1014 | let intermediates = &[ |
1015 | intermediate_a_der.clone(), |
1016 | intermediate_c_der.clone(), |
1017 | intermediate_b_a_der.clone(), |
1018 | intermediate_b_c_der.clone(), |
1019 | ]; |
1020 | |
1021 | // Create an end entity certificate signed by the keypair of the intermediates created above. |
1022 | let ee = make_end_entity(&intermediate_b); |
1023 | let ee_cert = &EndEntityCert::try_from(&ee).unwrap(); |
1024 | |
1025 | // We should be able to create a valid path from EE to trust anchor. |
1026 | let path = verify_chain(trust_anchors, intermediates, ee_cert, None, None).unwrap(); |
1027 | let path_intermediates = path.intermediate_certificates().collect::<Vec<_>>(); |
1028 | |
1029 | // We expect that without applying any additional constraints, that the path will be |
1030 | // EE -> intermediate_b_a -> intermediate_a -> trust_anchor. |
1031 | assert_eq!(path_intermediates.len(), 2); |
1032 | assert_eq!( |
1033 | path_intermediates[0].issuer(), |
1034 | intermediate_a_cert.subject() |
1035 | ); |
1036 | assert_eq!(path_intermediates[1].issuer(), trust_anchor_cert.subject()); |
1037 | |
1038 | // Now, we'll create a function that will reject the intermediate_b_a path. |
1039 | let expected_chain = |path: &VerifiedPath<'_>| { |
1040 | for intermediate in path.intermediate_certificates() { |
1041 | // Reject any intermediates issued by intermediate A. |
1042 | if intermediate.issuer() == intermediate_a_cert.subject() { |
1043 | return Err(Error::UnknownIssuer); |
1044 | } |
1045 | } |
1046 | |
1047 | Ok(()) |
1048 | }; |
1049 | |
1050 | // We should still be able to build a valid path. |
1051 | let path = verify_chain( |
1052 | trust_anchors, |
1053 | intermediates, |
1054 | ee_cert, |
1055 | Some(&expected_chain), |
1056 | None, |
1057 | ) |
1058 | .unwrap(); |
1059 | let path_intermediates = path.intermediate_certificates().collect::<Vec<_>>(); |
1060 | |
1061 | // We expect that the path will now be |
1062 | // EE -> intermediate_b_c -> intermediate_c -> trust_anchor. |
1063 | assert_eq!(path_intermediates.len(), 2); |
1064 | assert_eq!( |
1065 | path_intermediates[0].issuer(), |
1066 | intermediate_c_cert.subject() |
1067 | ); |
1068 | assert_eq!(path_intermediates[1].issuer(), trust_anchor_cert.subject()); |
1069 | } |
1070 | |
1071 | fn verify_chain<'a>( |
1072 | trust_anchors: &'a [TrustAnchor<'a>], |
1073 | intermediate_certs: &'a [CertificateDer<'a>], |
1074 | ee_cert: &'a EndEntityCert<'a>, |
1075 | verify_path: Option<&dyn Fn(&VerifiedPath<'_>) -> Result<(), Error>>, |
1076 | budget: Option<Budget>, |
1077 | ) -> Result<VerifiedPath<'a>, ControlFlow<Error, Error>> { |
1078 | use core::time::Duration; |
1079 | |
1080 | let time = UnixTime::since_unix_epoch(Duration::from_secs(0x1fed_f00d)); |
1081 | let mut path = PartialPath::new(ee_cert); |
1082 | let opts = ChainOptions { |
1083 | eku: KeyUsage::server_auth(), |
1084 | supported_sig_algs: crate::ALL_VERIFICATION_ALGS, |
1085 | trust_anchors, |
1086 | intermediate_certs, |
1087 | revocation: None, |
1088 | }; |
1089 | |
1090 | match opts.build_chain_inner( |
1091 | &mut path, |
1092 | time, |
1093 | verify_path, |
1094 | 0, |
1095 | &mut budget.unwrap_or_default(), |
1096 | ) { |
1097 | Ok(anchor) => Ok(VerifiedPath::new(ee_cert, anchor, path)), |
1098 | Err(err) => Err(err), |
1099 | } |
1100 | } |
1101 | } |
1102 |
Definitions
- ChainOptions
- eku
- supported_sig_algs
- trust_anchors
- intermediate_certs
- revocation
- build_chain
- build_chain_inner
- check_signed_chain
- VerifiedPath
- end_entity
- intermediates
- anchor
- new
- intermediate_certificates
- end_entity
- anchor
- IntermediateIterator
- intermediates
- Item
- next
- next_back
- Intermediates
- Owned
- certs
- used
- Borrowed
- as_ref
- certs
- used
- check_signed_chain_name_constraints
- Budget
- signatures
- build_chain_calls
- name_constraint_comparisons
- consume_signature
- consume_build_chain_call
- consume_name_constraint_comparison
- default
- check_issuer_independent_properties
- check_validity
- check_basic_constraints
- KeyUsage
- inner
- server_auth
- client_auth
- required
- ExtendedKeyUsage
- Required
- RequiredIfPresent
- check
- key_purpose_id_equals
- KeyPurposeId
- oid_value
- new
- eq
- loop_while_non_fatal_error
- PartialPath
- end_entity
- intermediates
- used
- new
- push
- pop
- node
- head
- get
- PathNode
- path
- index
- cert
- iter
- role
- PathIter
- path
- next
- Item
- next
- Role
- Issuer
Learn Rust with the experts
Find out more