1use 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 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 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 res @ Err(ControlFlow::Break(_)) => return res,
94 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 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; 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 signatures: 100,
251
252 build_chain_calls: 200_000,
255
256 name_constraint_comparisons: 250_000,
259 }
260 }
261}
262
263struct CertNotRevoked(());
266
267impl CertNotRevoked {
268 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 budget.consume_signature()?;
301 crl.verify_signature(supported_sig_algs, issuer_spki.as_slice_less_safe())
302 .map_err(crl_signature_err)?;
303
304 KeyUsageMode::CrlSign.check(issuer_ku)?;
306
307 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
315fn 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 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
357fn 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 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
392fn 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 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#[derive(Clone, Copy)]
437pub struct KeyUsage {
438 inner: ExtendedKeyUsage,
439}
440
441impl KeyUsage {
442 pub const fn server_auth() -> Self {
446 Self {
447 inner: ExtendedKeyUsage::RequiredIfPresent(EKU_SERVER_AUTH),
448 }
449 }
450
451 pub const fn client_auth() -> Self {
455 Self {
456 inner: ExtendedKeyUsage::RequiredIfPresent(EKU_CLIENT_AUTH),
457 }
458 }
459
460 pub const fn required(oid: &'static [u8]) -> Self {
462 Self {
463 inner: ExtendedKeyUsage::Required(KeyPurposeId::new(oid)),
464 }
465 }
466}
467
468#[derive(Clone, Copy)]
470enum ExtendedKeyUsage {
471 Required(KeyPurposeId),
473
474 RequiredIfPresent(KeyPurposeId),
476}
477
478impl ExtendedKeyUsage {
479 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#[derive(Clone, Copy)]
516struct KeyPurposeId {
517 oid_value: untrusted::Input<'static>,
518}
519
520impl KeyPurposeId {
521 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#[allow(clippy::identity_op)] const EKU_SERVER_AUTH: KeyPurposeId = KeyPurposeId::new(&[(40 * 1) + 3, 6, 1, 5, 5, 7, 3, 1]);
545
546#[allow(clippy::identity_op)] const EKU_CLIENT_AUTH: KeyPurposeId = KeyPurposeId::new(&[(40 * 1) + 3, 6, 1, 5, 5, 7, 3, 2]);
549
550#[repr(u8)]
552#[derive(Clone, Copy)]
553enum KeyUsageMode {
554 CrlSign = 6,
561 }
564
565impl KeyUsageMode {
566 fn check(self, input: Option<untrusted::Input>) -> Result<(), Error> {
568 let bit_string = match input {
569 Some(input) => input,
570 None => return Ok(()),
574 };
575
576 let flags = der::bit_string_flags(&mut untrusted::Reader::new(bit_string))?;
577 #[allow(clippy::as_conversions)] 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 res @ Err(ControlFlow::Break(_)) => return res,
599 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 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 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 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 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 let ee_cert = make_end_entity(intermediates.last().unwrap());
758
759 let passing_budget = Budget {
763 name_constraint_comparisons: 3,
768 ..Budget::default()
769 };
770
771 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 name_constraint_comparisons: 2,
785 ..Budget::default()
786 };
787 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}