1use std::{collections::HashSet, time::SystemTime};
4
5use x509_parser::x509::AlgorithmIdentifier;
6#[cfg(test)]
7use x509_parser::{
8 certificate::X509Certificate, prelude::FromDer, revocation_list::CertificateRevocationList,
9 time::ASN1Time,
10};
11
12use super::X509DataInfo;
13use super::pkix::X509DataSource;
14use crate::{
15 policy::{DsaKeyPolicy, ExtendedKeyPurpose, RsaKeyPolicy},
16 provider::X509SignatureAlgorithm,
17};
18
19#[derive(Debug, Clone)]
21pub struct X509ChainOptions<'a> {
22 pub utc_facts: Option<structured_pkix::temporal::LeapTable<'a>>,
25 pub trusted_certs: &'a [Vec<u8>],
27 pub verification_time: SystemTime,
29 pub max_chain_depth: usize,
31 pub check_crls: bool,
33 pub allowed_extended_key_usages: Option<&'a HashSet<ExtendedKeyPurpose>>,
35 pub rsa_keys: RsaKeyPolicy,
37 pub dsa_keys: DsaKeyPolicy,
39 pub certificate_signature_algorithms: Option<&'a crate::policy::CertificateSignatureAlgorithms>,
43}
44
45#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
47#[non_exhaustive]
48pub enum X509ChainError {
49 #[error("X.509 processing failed: {0}")]
51 Processing(#[source] structured_pkix::Error),
52 #[error("X.509 signature policy rejected authentication: {0}")]
54 Policy(#[from] crate::policy::PolicyViolation),
55 #[error("cryptographic provider rejected X.509 authentication: {0}")]
57 Provider(#[from] crate::provider::ProviderError),
58 #[error("certificate signature at chain position {0} is unsupported")]
60 UnsupportedSignature(usize),
61 #[error("maximum certificate chain depth must be greater than zero")]
63 InvalidDepth,
64 #[error("invalid {kind} DER: {message}")]
66 InvalidDer {
67 kind: &'static str,
69 message: String,
71 },
72 #[error("certificate at chain position {position} repeats extension {oid}")]
74 DuplicateExtension {
75 position: usize,
77 oid: String,
79 },
80 #[error("certificate chain does not terminate at a trusted certificate")]
82 UntrustedRoot,
83 #[error("certificate chain exceeds maximum depth of {0}")]
85 DepthExceeded(usize),
86 #[error("certificate path checking stopped at {0:?}")]
88 PathIncomplete(structured_pkix::path::Truncation),
89 #[error("certificate path policy constraints are not satisfied")]
91 CertificatePolicies,
92 #[error("certificate at chain position {0} is expired or not yet valid")]
94 CertificateNotValid(usize),
95 #[error("certificate at chain position {0} is not a CA")]
97 IssuerNotCa(usize),
98 #[error("certificate at chain position {position} exceeds path length constraint {limit}")]
100 PathLengthExceeded {
101 position: usize,
103 limit: u32,
105 },
106 #[error(
108 "certificate at chain position {position} violates name constraints from position {constraining_position}"
109 )]
110 NameConstraintViolation {
111 position: usize,
113 constraining_position: usize,
115 },
116 #[error("certificate at chain position {position} has unsupported critical extension {oid}")]
118 UnsupportedCriticalExtension {
119 position: usize,
121 oid: String,
123 },
124 #[error("certificate at chain position {position} has invalid NameConstraints placement")]
126 InvalidNameConstraints {
127 position: usize,
129 },
130 #[error("certificate at chain position {position} does not permit {required}")]
132 InvalidKeyUsage {
133 position: usize,
135 required: &'static str,
137 },
138 #[error("certificate signature at chain position {0} is invalid or unsupported")]
140 InvalidSignature(usize),
141 #[error("certificate issuer key at chain position {position} is rejected by policy: {source}")]
143 KeyPolicy {
144 position: usize,
146 source: crate::policy::PolicyViolation,
148 },
149 #[error("unsupported X.509 signature algorithm: {oid}")]
151 UnsupportedSignatureAlgorithm {
152 oid: String,
154 },
155 #[error("CRL {0} is invalid or cannot be authenticated")]
157 InvalidCrl(usize),
158 #[error("CRL evidence has incomplete reason coverage at chain position {0}")]
160 IncompleteCrlCoverage(usize),
161 #[error("CRL validation requires caller-supplied UTC facts")]
163 MissingUtcFacts,
164 #[error("UTC evidence cannot establish CRL {index} time: {reason:?}")]
166 CrlTimeUncertain {
167 index: usize,
169 reason: structured_pkix::temporal::Uncertainty,
171 },
172 #[error("certificate at chain position {0} is revoked")]
174 Revoked(usize),
175}
176
177impl X509ChainError {
178 pub(crate) fn is_execution_failure(&self) -> bool {
179 match self {
180 Self::Processing(error) => error.is_resource(),
181 Self::Policy(
182 crate::policy::PolicyViolation::ResourceLimit { .. }
183 | crate::policy::PolicyViolation::ResourceLimitExceeded { .. },
184 ) => true,
185 Self::Provider(crate::provider::ProviderError::Unsupported { .. }) => false,
186 Self::Provider(_) => true,
187 Self::PathIncomplete(reason) => !matches!(
188 reason,
189 structured_pkix::path::Truncation::Depth
190 | structured_pkix::path::Truncation::Paths
191 | structured_pkix::path::Truncation::Policies
192 ),
193 _ => false,
194 }
195 }
196}
197
198pub fn verify_x509_certificate_chain(
201 info: &X509DataInfo,
202 options: &X509ChainOptions<'_>,
203) -> Result<(), X509ChainError> {
204 verify_x509_certificate_chain_with_resources(
205 info,
206 options,
207 &crate::policy::ResourcePolicy::default(),
208 crate::provider::default_provider(),
209 )
210}
211
212pub fn verify_x509_certificate_chain_with_resources(
216 info: &X509DataInfo,
217 options: &X509ChainOptions<'_>,
218 resources: &crate::policy::ResourcePolicy,
219 provider: &dyn crate::provider::CryptoProvider,
220) -> Result<(), X509ChainError> {
221 resources.validate()?;
222 let session = super::pkix::CertificateSession::new(resources);
223 let count = info
226 .certificates
227 .len()
228 .checked_add(info.crls.len())
229 .and_then(|count| count.checked_add(options.trusted_certs.len()))
230 .ok_or(crate::policy::PolicyViolation::ResourceLimitExceeded {
231 resource: crate::policy::resource_name::X509_WORK,
232 maximum: resources.max_x509_work,
233 })?;
234 session
235 .memory()
236 .charge_work(count as u64)
237 .map_err(|reason| {
238 session.error(structured_pkix::Error::resource(
239 reason,
240 "certificate.evidence",
241 0,
242 ))
243 })?;
244 let mut remaining = resources.max_external_resource_total_bytes;
245 for bytes in info
246 .certificates
247 .iter()
248 .chain(&info.crls)
249 .chain(options.trusted_certs)
250 {
251 if bytes.len() > resources.max_external_resource_bytes {
252 return Err(crate::policy::PolicyViolation::ResourceLimitExceeded {
253 resource: crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES,
254 maximum: resources.max_external_resource_bytes,
255 }
256 .into());
257 }
258 if bytes.len() > remaining {
259 return Err(crate::policy::PolicyViolation::ResourceLimitExceeded {
260 resource: crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
261 maximum: resources.max_external_resource_total_bytes,
262 }
263 .into());
264 }
265 remaining -= bytes.len();
266 }
267 verify_x509_certificate_chain_in_session(info, options, provider, &[], &session).map(|_| ())
268}
269
270pub(crate) fn verify_x509_certificate_chain_in_session(
271 info: &impl X509DataSource,
272 options: &X509ChainOptions<'_>,
273 provider: &dyn crate::provider::CryptoProvider,
274 additional_crls: &[Vec<u8>],
275 session: &super::pkix::CertificateSession,
276) -> Result<super::pkix::ValidatedX509Key, X509ChainError> {
277 if options.max_chain_depth == 0 {
278 return Err(X509ChainError::InvalidDepth);
279 }
280 if info.chain().is_empty() {
281 return Err(X509ChainError::UntrustedRoot);
282 }
283
284 let storage_error = |reason| {
285 session.error(structured_pkix::Error::resource(
286 reason,
287 "certificate.path",
288 0,
289 ))
290 };
291 let mut path_der = session.memory().vector();
292 path_der
293 .try_reserve_exact(info.chain().len())
294 .map_err(storage_error)?;
295 for &index in info.chain() {
296 let bytes = info
297 .certificate(index)
298 .ok_or(X509ChainError::UntrustedRoot)?;
299 path_der.try_push(bytes).map_err(storage_error)?;
300 }
301
302 let last = session.read(
303 path_der
304 .last()
305 .copied()
306 .ok_or(X509ChainError::UntrustedRoot)?,
307 )?;
308 let trusted_anchors =
309 session
310 .read_pool(options.trusted_certs)
311 .map_err(|error| match error {
312 X509ChainError::Processing(error) if !error.is_resource() => {
313 X509ChainError::InvalidDer {
314 kind: "certificate",
315 message: error.to_string(),
316 }
317 }
318 error => error,
319 })?;
320 let embedded_anchor = trusted_anchors
321 .iter()
322 .any(|cert| cert.as_bytes() == last.as_bytes());
323 if embedded_anchor {
324 return validate_path(
325 &path_der,
326 info,
327 options,
328 (provider, session),
329 additional_crls,
330 );
331 }
332
333 let replace_untrusted_root = if path_der.len() > 1
336 && session.source_names_equal(last.tbs().subject(), last.tbs().issuer())?
337 && shared_certificate_signature_outcome(
338 &last,
339 &last,
340 provider,
341 options.certificate_signature_algorithms,
342 session,
343 )? == structured_pkix::path::Verified::Yes
344 {
345 let child = session.read(path_der[path_der.len() - 2])?;
346 session.source_names_equal(child.tbs().issuer(), last.tbs().subject())?
347 && shared_certificate_signature_outcome(
348 &child,
349 &last,
350 provider,
351 options.certificate_signature_algorithms,
352 session,
353 )? == structured_pkix::path::Verified::Yes
354 } else {
355 false
356 };
357 let candidate_base = if replace_untrusted_root {
358 &path_der[..path_der.len() - 1]
359 } else {
360 &path_der[..]
361 };
362 let candidate_child = session.read(
363 candidate_base
364 .last()
365 .copied()
366 .ok_or(X509ChainError::UntrustedRoot)?,
367 )?;
368
369 let mut first_validation_error = None;
370 for cert in &trusted_anchors {
371 if !session.source_names_equal(cert.tbs().subject(), candidate_child.tbs().issuer())? {
372 continue;
373 }
374 match shared_certificate_signature_outcome(
375 &candidate_child,
376 cert,
377 provider,
378 options.certificate_signature_algorithms,
379 session,
380 )? {
381 structured_pkix::path::Verified::Yes | structured_pkix::path::Verified::Unsupported => {
382 }
383 _ => continue,
384 }
385 let mut candidate_path = session.memory().vector();
386 candidate_path
387 .try_extend_from_slice(candidate_base)
388 .map_err(storage_error)?;
389 candidate_path
390 .try_push(cert.as_bytes())
391 .map_err(storage_error)?;
392 match validate_path(
393 &candidate_path,
394 info,
395 options,
396 (provider, session),
397 additional_crls,
398 ) {
399 Ok(key) => return Ok(key),
400 Err(error) if error.is_execution_failure() => return Err(error),
401 Err(error) => first_validation_error.get_or_insert(error),
402 };
403 }
404
405 Err(first_validation_error.unwrap_or(X509ChainError::UntrustedRoot))
406}
407
408fn validate_path(
409 path_der: &[&[u8]],
410 info: &impl X509DataSource,
411 options: &X509ChainOptions<'_>,
412 execution: (
413 &dyn crate::provider::CryptoProvider,
414 &super::pkix::CertificateSession,
415 ),
416 additional_crls: &[Vec<u8>],
417) -> Result<super::pkix::ValidatedX509Key, X509ChainError> {
418 let (provider, session) = execution;
419 if path_der.len() > options.max_chain_depth {
420 return Err(X509ChainError::DepthExceeded(options.max_chain_depth));
421 }
422
423 let mut decoded = session.memory().vector();
424 for der in path_der {
425 let certificate = session.read(der)?;
426 if decoded.is_empty() {
427 let at = super::pkix::verification_instant(options.verification_time)?;
430 let validity = certificate.tbs().validity();
431 if at < validity.not_before().instant() || at > validity.not_after().instant() {
432 return Err(X509ChainError::CertificateNotValid(0));
433 }
434 }
435 decoded.try_push(certificate).map_err(|reason| {
436 X509ChainError::Processing(structured_pkix::Error::resource(
437 reason,
438 "certificate.path",
439 0,
440 ))
441 })?;
442 }
443
444 let mut effective_extended_key_usages = session.memory().vector();
445 if let Some(purposes) = options.allowed_extended_key_usages {
446 for purpose in purposes {
447 effective_extended_key_usages
448 .try_push(purpose)
449 .map_err(|reason| {
450 session.error(structured_pkix::Error::resource(
451 reason,
452 "certificate.extended_key_usage",
453 0,
454 ))
455 })?;
456 }
457 }
458 let mut anchor_limit = None;
459 let at = super::pkix::verification_instant(options.verification_time)?;
460 for (position, cert) in decoded.iter().enumerate() {
461 validate_positive_serial_bytes(
462 cert.tbs().serial().as_bytes(),
463 "certificate serial number",
464 )?;
465 if position == decoded.len() - 1
466 && (at < cert.tbs().validity().not_before().instant()
467 || at > cert.tbs().validity().not_after().instant())
468 {
469 return Err(X509ChainError::CertificateNotValid(position));
470 }
471 let extensions = cert.tbs().extensions();
472 if position == 0 {
473 if let Some(usage) = shared_key_usage(cert, position, session)?
474 && !usage.digital_signature()
475 && !usage.content_commitment()
476 {
477 return Err(X509ChainError::InvalidKeyUsage {
478 position,
479 required: "digitalSignature or nonRepudiation",
480 });
481 }
482 } else if position == decoded.len() - 1 {
483 let basic = extensions
489 .basic_constraints(session.memory())
490 .map_err(|error| session.error(error))?
491 .filter(|basic| basic.is_authority())
492 .ok_or(X509ChainError::IssuerNotCa(position))?;
493 anchor_limit = basic.path_length().and_then(|limit| limit.certificates());
494 if let Some(usage) = shared_key_usage(cert, position, session)?
495 && !usage.key_cert_sign()
496 {
497 return Err(X509ChainError::InvalidKeyUsage {
498 position,
499 required: "keyCertSign",
500 });
501 }
502 }
503 validate_shared_extended_key_usage(
504 cert,
505 position,
506 &mut effective_extended_key_usages,
507 session,
508 )?;
509 validate_shared_subject_identity(cert, session)?;
510 if position == decoded.len() - 1 {
511 validate_shared_anchor_extensions(cert, position, session)?;
512 }
513 if let Some(extension) = extensions
517 .get(structured_pkix::oid::NAME_CONSTRAINTS, session.memory())
518 .map_err(|error| session.error(error))?
519 && (position == 0 || !extension.is_critical())
520 {
521 return Err(X509ChainError::InvalidNameConstraints { position });
522 }
523 }
524 let mut effective_key = super::pkix::ValidatedX509Key {
525 effective_spki: None,
526 };
527 if path_der.len() > 1 {
528 let anchor = decoded.last().expect("nonempty path");
529 let mut chain = session.memory().vector();
530 for certificate in &decoded[..decoded.len() - 1] {
531 chain.try_push(certificate).map_err(|reason| {
532 X509ChainError::Processing(structured_pkix::Error::resource(
533 reason,
534 "certificate.path",
535 0,
536 ))
537 })?;
538 }
539 let facts =
540 super::pkix::validate_supplied_path(&chain, anchor, options, provider, session)?;
541 if let Some(limit) = anchor_limit
545 && facts.non_self_issued_intermediates > limit
546 {
547 return Err(X509ChainError::PathLengthExceeded {
548 position: decoded.len() - 1,
549 limit,
550 });
551 }
552 effective_key = super::pkix::effective_target_spki(facts.key, session.memory())?;
556 }
557
558 if options.check_crls {
559 super::pkix::verify_crls(
560 &decoded,
561 info.crls(),
562 additional_crls,
563 options,
564 provider,
565 session,
566 )?;
567 }
568 Ok(effective_key)
569}
570
571fn shared_key_usage<'a>(
572 certificate: &structured_pkix::Certificate<'a>,
573 position: usize,
574 session: &super::pkix::CertificateSession,
575) -> Result<Option<structured_pkix::usage::KeyUsage<'a>>, X509ChainError> {
576 let Some(extension) = certificate
577 .tbs()
578 .extensions()
579 .get(structured_pkix::oid::KEY_USAGE, session.memory())
580 .map_err(|error| session.error(error))?
581 else {
582 return Ok(None);
583 };
584 structured_pkix::usage::KeyUsage::read(extension, session.memory())
585 .map(Some)
586 .map_err(|error| {
587 if error.is_resource() {
588 session.error(error)
589 } else if extension.is_critical() {
590 X509ChainError::UnsupportedCriticalExtension {
591 position,
592 oid: "2.5.29.15".into(),
593 }
594 } else {
595 session.error(error)
596 }
597 })
598}
599
600fn validate_shared_extended_key_usage(
601 certificate: &structured_pkix::Certificate<'_>,
602 position: usize,
603 effective: &mut structured_asn1::memory::OwnedVec<&ExtendedKeyPurpose>,
604 session: &super::pkix::CertificateSession,
605) -> Result<(), X509ChainError> {
606 let Some(usage) = certificate
607 .tbs()
608 .extensions()
609 .extended_key_usage(session.memory())
610 .map_err(|error| session.error(error))?
611 else {
612 return Ok(());
613 };
614 if usage
615 .has(
616 structured_pkix::oid::ANY_EXTENDED_KEY_USAGE,
617 session.memory(),
618 )
619 .map_err(|error| session.error(error))?
620 {
621 return Ok(());
622 }
623 let mut kept = 0;
624 for index in 0..effective.len() {
625 if shared_usage_contains(&usage, effective[index], session)? {
626 effective[kept] = effective[index];
627 kept += 1;
628 }
629 }
630 while effective.len() > kept {
631 effective.pop();
632 }
633 if kept == 0 {
634 return Err(X509ChainError::InvalidKeyUsage {
635 position,
636 required: "an approved extended key usage",
637 });
638 }
639 Ok(())
640}
641
642fn shared_usage_contains(
643 usage: &structured_pkix::usage::ExtendedKeyUsage<'_>,
644 purpose: &ExtendedKeyPurpose,
645 session: &super::pkix::CertificateSession,
646) -> Result<bool, X509ChainError> {
647 use structured_pkix::oid;
648 let kind = match purpose {
649 ExtendedKeyPurpose::ServerAuth => oid::SERVER_AUTH,
650 ExtendedKeyPurpose::ClientAuth => oid::CLIENT_AUTH,
651 ExtendedKeyPurpose::CodeSigning => oid::CODE_SIGNING,
652 ExtendedKeyPurpose::EmailProtection => oid::EMAIL_PROTECTION,
653 ExtendedKeyPurpose::TimeStamping => oid::TIME_STAMPING,
654 ExtendedKeyPurpose::OcspSigning => oid::OCSP_SIGNING,
655 ExtendedKeyPurpose::Other(expected) => {
656 for kind in usage.purposes() {
657 session
658 .memory()
659 .charge_work(kind.as_bytes().len() as u64 + expected.len() as u64 + 1)
660 .map_err(|reason| {
661 session.error(structured_pkix::Error::resource(
662 reason,
663 "certificate.extended_key_usage",
664 0,
665 ))
666 })?;
667 let mut arcs = kind.arcs();
668 let mut matches = true;
669 for &expected in expected {
670 if arcs.next().and_then(|arc| arc.as_u64()) != Some(expected) {
671 matches = false;
672 break;
673 }
674 }
675 if matches && arcs.next().is_none() {
676 return Ok(true);
677 }
678 }
679 return Ok(false);
680 }
681 };
682 usage
683 .has(kind, session.memory())
684 .map_err(|error| session.error(error))
685}
686
687fn validate_shared_subject_identity(
688 certificate: &structured_pkix::Certificate<'_>,
689 session: &super::pkix::CertificateSession,
690) -> Result<(), X509ChainError> {
691 let subject = certificate.tbs().subject();
692 for attribute in subject.attributes_of(structured_pkix::oid::EMAIL_ADDRESS, session.memory()) {
693 let attribute = attribute.map_err(|error| session.error(error))?;
694 let text = attribute
695 .text(session.memory())
696 .map_err(|error| session.error(error))?
697 .ok_or_else(|| invalid_subject_identity("subject emailAddress is not text"))?;
698 let text = text.as_str(session.memory()).map_err(|reason| {
699 session.error(structured_pkix::Error::resource(
700 reason,
701 "certificate.subject",
702 0,
703 ))
704 })?;
705 session
706 .memory()
707 .charge_work(text.len() as u64)
708 .map_err(|reason| {
709 session.error(structured_pkix::Error::resource(
710 reason,
711 "certificate.subject",
712 0,
713 ))
714 })?;
715 if !mailbox_has_valid_syntax(text) {
716 return Err(invalid_subject_identity(
717 "invalid subject emailAddress syntax",
718 ));
719 }
720 }
721 let extensions = certificate.tbs().extensions();
722 let extension = extensions
723 .get(structured_pkix::oid::SUBJECT_ALT_NAME, session.memory())
724 .map_err(|error| session.error(error))?;
725 let Some(extension) = extension else {
726 return if subject.is_empty() {
727 Err(invalid_subject_identity(
728 "an empty subject requires a critical SubjectAlternativeName",
729 ))
730 } else {
731 Ok(())
732 };
733 };
734 if let Err(error) = structured_pkix::general_name::read_alternative(extension, session.memory())
738 {
739 return Err(shared_san_error(extension, error, session)?);
740 }
741 if subject.is_empty() && !extension.is_critical() {
742 return Err(invalid_subject_identity(
743 "an empty subject requires a critical SubjectAlternativeName",
744 ));
745 }
746 Ok(())
747}
748
749fn shared_san_error(
750 extension: &structured_pkix::extension::Extension<'_>,
751 error: structured_pkix::Error,
752 session: &super::pkix::CertificateSession,
753) -> Result<X509ChainError, X509ChainError> {
754 if error.is_resource() {
755 return Ok(session.error(error));
756 }
757 let decode_error = |error: structured_asn1::Error| {
758 let error = structured_pkix::Error::new(
759 structured_pkix::ErrorCode::Encoding(error.code()),
760 "certificate.subjectAltName",
761 error.offset(),
762 );
763 if error.is_resource() {
764 session.error(error)
765 } else {
766 invalid_subject_identity("invalid SubjectAlternativeName")
767 }
768 };
769 let mut reader = extension.reader(session.memory()).map_err(decode_error)?;
773 let sequence = reader
774 .expect(structured_asn1::Tag::constructed(
775 structured_asn1::universal::SEQUENCE,
776 ))
777 .map_err(decode_error)?;
778 let mut inside = reader.enter(&sequence).map_err(decode_error)?;
779 while let Some(element) = inside.next_element().map_err(decode_error)? {
780 if element.span().start != error.offset() {
781 continue;
782 }
783 if element.tag() == structured_asn1::Tag::context(false, 2) {
784 inside
785 .charge(element.contents().len() as u64)
786 .map_err(|error| {
787 session.error(structured_pkix::Error::new(
788 structured_pkix::ErrorCode::Encoding(error.code()),
789 "certificate.subjectAltName",
790 error.offset(),
791 ))
792 })?;
793 if element.contents().is_ascii() {
794 return Ok(X509ChainError::InvalidDer {
795 kind: "certificate DNS name",
796 message: "invalid SubjectAlternativeName dNSName".into(),
797 });
798 }
799 }
800 break;
801 }
802 Ok(invalid_subject_identity("invalid SubjectAlternativeName"))
803}
804
805fn validate_shared_anchor_extensions(
806 certificate: &structured_pkix::Certificate<'_>,
807 position: usize,
808 session: &super::pkix::CertificateSession,
809) -> Result<(), X509ChainError> {
810 use structured_pkix::{error::CallbackError, oid};
811 let extensions = certificate.tbs().extensions();
812 for extension in extensions.criticals(session.memory()) {
813 let extension = extension.map_err(|error| session.error(error))?;
814 let kind = extension.kind();
815 let accepted = matches!(
816 kind,
817 oid::KEY_USAGE
818 | oid::SUBJECT_ALT_NAME
819 | oid::BASIC_CONSTRAINTS
820 | oid::NAME_CONSTRAINTS
821 | oid::EXT_KEY_USAGE
822 );
823 let understood = if accepted {
824 match extensions.understands(extension, &(), session.memory()) {
825 Ok(value) => value,
826 Err(CallbackError::Processing(error)) => return Err(session.error(error)),
827 Err(CallbackError::Callback(error)) => match error {},
828 }
829 } else {
830 false
831 };
832 if !understood {
833 if kind == oid::NAME_CONSTRAINTS {
834 return Err(X509ChainError::InvalidNameConstraints { position });
835 }
836 let oid = kind.text(session.memory()).map_err(|reason| {
837 session.error(structured_pkix::Error::resource(
838 reason,
839 "certificate.anchor",
840 extension.span().start,
841 ))
842 })?;
843 let oid = oid.as_str(session.memory()).map_err(|reason| {
844 session.error(structured_pkix::Error::resource(
845 reason,
846 "certificate.anchor",
847 extension.span().start,
848 ))
849 })?;
850 return Err(X509ChainError::UnsupportedCriticalExtension {
851 position,
852 oid: oid.into(),
853 });
854 }
855 }
856 Ok(())
857}
858
859pub(crate) fn validate_issuer_spki_policy(
860 encoded: &[u8],
861 position: usize,
862 rsa_keys: RsaKeyPolicy,
863 dsa_keys: DsaKeyPolicy,
864) -> Result<(), X509ChainError> {
865 use der::Decode as _;
866 let key = pkcs8::SubjectPublicKeyInfoRef::from_der(encoded).map_err(|error| {
867 X509ChainError::InvalidDer {
868 kind: "issuer SubjectPublicKeyInfo",
869 message: error.to_string(),
870 }
871 })?;
872 match key.algorithm.oid {
876 oid if oid == pkcs1::ALGORITHM_OID
880 || oid.as_bytes() == structured_pkix::oid::RSASSA_PSS.as_bytes() =>
881 {
882 let octets =
883 key.subject_public_key
884 .as_bytes()
885 .ok_or_else(|| X509ChainError::InvalidDer {
886 kind: "issuer SubjectPublicKeyInfo",
887 message: "RSA public key is not octet-aligned".into(),
888 })?;
889 let key = pkcs1::RsaPublicKeyRef::from_der(octets).map_err(|error| {
890 X509ChainError::InvalidDer {
891 kind: "RSA issuer SubjectPublicKeyInfo",
892 message: error.to_string(),
893 }
894 })?;
895 rsa_keys
896 .validate_components(
897 "X.509 issuer verification",
898 key.modulus.as_bytes(),
899 key.public_exponent.as_bytes(),
900 )
901 .map(|_| ())
902 .map_err(|source| X509ChainError::KeyPolicy { position, source })
903 }
904 dsa::OID => {
905 match super::signature::validate_dsa_signature_spki_with_minimum(
906 encoded,
907 dsa_keys.minimum_modulus_bits,
908 ) {
909 Ok(()) => Ok(()),
910 Err(super::SignatureVerificationError::KeyPolicy(source)) => {
911 Err(X509ChainError::KeyPolicy { position, source })
912 }
913 Err(_) => Err(X509ChainError::InvalidDer {
914 kind: "DSA issuer SubjectPublicKeyInfo",
915 message: "invalid DSA key parameters".into(),
916 }),
917 }
918 }
919 _ => Ok(()),
920 }
921}
922
923pub(crate) fn validate_positive_serial_bytes(
924 serial: &[u8],
925 kind: &'static str,
926) -> Result<(), X509ChainError> {
927 let magnitude = serial.strip_prefix(&[0]).unwrap_or(serial);
928 if serial.is_empty()
929 || serial[0] & 0x80 != 0
930 || magnitude.is_empty()
931 || magnitude.len() > 20
932 || magnitude.iter().all(|byte| *byte == 0)
933 {
934 return Err(X509ChainError::InvalidDer {
935 kind,
936 message: "RFC 5280 requires a positive, non-zero value of at most 20 octets".into(),
937 });
938 }
939 Ok(())
940}
941
942fn mailbox_has_valid_syntax(value: &str) -> bool {
943 value.rsplit_once('@').is_some_and(|(local, domain)| {
944 mailbox_local_part_has_valid_syntax(local) && mailbox_domain_has_valid_syntax(domain)
945 })
946}
947
948fn mailbox_domain_has_valid_syntax(domain: &str) -> bool {
949 let Some(literal) = domain
950 .strip_prefix('[')
951 .and_then(|value| value.strip_suffix(']'))
952 else {
953 return dns_name_has_valid_syntax(domain, false);
954 };
955 if literal
956 .get(..5)
957 .is_some_and(|prefix| prefix.eq_ignore_ascii_case("IPv6:"))
958 {
959 literal[5..].parse::<std::net::Ipv6Addr>().is_ok()
960 } else {
961 literal.parse::<std::net::Ipv4Addr>().is_ok()
962 }
963}
964
965fn mailbox_local_part_has_valid_syntax(local: &str) -> bool {
966 if let Some(quoted) = local
967 .strip_prefix('"')
968 .and_then(|value| value.strip_suffix('"'))
969 {
970 if quoted.is_empty() {
971 return false;
972 }
973 let mut escaped = false;
974 for byte in quoted.bytes() {
975 if escaped {
976 if !(0x20..=0x7e).contains(&byte) {
977 return false;
978 }
979 escaped = false;
980 } else if byte == b'\\' {
981 escaped = true;
982 } else if byte == b'"' || !(0x20..=0x7e).contains(&byte) {
983 return false;
984 }
985 }
986 return !escaped;
987 }
988
989 local.split('.').all(|atom| {
990 !atom.is_empty()
991 && atom.bytes().all(|byte| {
992 byte.is_ascii_alphanumeric()
993 || matches!(
994 byte,
995 b'!' | b'#'
996 | b'$'
997 | b'%'
998 | b'&'
999 | b'\''
1000 | b'*'
1001 | b'+'
1002 | b'-'
1003 | b'/'
1004 | b'='
1005 | b'?'
1006 | b'^'
1007 | b'_'
1008 | b'`'
1009 | b'{'
1010 | b'|'
1011 | b'}'
1012 | b'~'
1013 )
1014 })
1015 })
1016}
1017
1018fn invalid_subject_identity(message: &str) -> X509ChainError {
1019 X509ChainError::InvalidDer {
1020 kind: "certificate subject identity",
1021 message: message.into(),
1022 }
1023}
1024
1025#[cfg(test)]
1026fn verify_certificate_signature(
1027 certificate: &X509Certificate<'_>,
1028 issuer: &X509Certificate<'_>,
1029) -> bool {
1030 verify_certificate_signature_with_provider(
1031 certificate,
1032 issuer,
1033 crate::provider::default_provider(),
1034 None,
1035 )
1036 .unwrap_or(false)
1037}
1038
1039#[cfg(test)]
1040fn verify_certificate_signature_with_provider(
1041 certificate: &X509Certificate<'_>,
1042 issuer: &X509Certificate<'_>,
1043 provider: &dyn crate::provider::CryptoProvider,
1044 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
1045) -> Result<bool, X509ChainError> {
1046 verify_x509_signed_object_with_provider(
1050 &certificate.signature_algorithm,
1051 &certificate.tbs_certificate.signature,
1052 &certificate.signature_value.data,
1053 certificate.tbs_certificate.as_ref(),
1054 issuer.public_key().raw,
1055 provider,
1056 allowed,
1057 )
1058}
1059
1060#[cfg(test)]
1065pub(crate) fn certificate_signature_matches(certificate_der: &[u8], issuer_der: &[u8]) -> bool {
1066 certificate_signature_matches_with_provider(
1067 certificate_der,
1068 issuer_der,
1069 crate::provider::default_provider(),
1070 None,
1071 &super::pkix::CertificateSession::new(&crate::policy::ResourcePolicy::default()),
1072 )
1073 .unwrap_or(false)
1074}
1075
1076pub(crate) fn certificate_signature_matches_with_provider(
1077 certificate_der: &[u8],
1078 issuer_der: &[u8],
1079 provider: &dyn crate::provider::CryptoProvider,
1080 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
1081 session: &super::pkix::CertificateSession,
1082) -> Result<bool, X509ChainError> {
1083 match certificate_signature_outcome_with_provider(
1084 certificate_der,
1085 issuer_der,
1086 provider,
1087 allowed,
1088 session,
1089 )? {
1090 structured_pkix::path::Verified::Yes => Ok(true),
1091 structured_pkix::path::Verified::No => Ok(false),
1092 _ => Err(X509ChainError::Provider(
1093 crate::provider::ProviderError::Unsupported {
1094 operation: crate::provider::ProviderOperation::VerifyCertificate,
1095 algorithm: None,
1096 },
1097 )),
1098 }
1099}
1100
1101pub(crate) fn certificate_signature_outcome_with_provider(
1102 certificate_der: &[u8],
1103 issuer_der: &[u8],
1104 provider: &dyn crate::provider::CryptoProvider,
1105 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
1106 session: &super::pkix::CertificateSession,
1107) -> Result<structured_pkix::path::Verified, X509ChainError> {
1108 let certificate = session.read(certificate_der)?;
1109 let issuer = session.read(issuer_der)?;
1110 shared_certificate_signature_outcome(&certificate, &issuer, provider, allowed, session)
1111}
1112
1113pub(crate) fn shared_certificate_signature_outcome(
1114 certificate: &structured_pkix::Certificate<'_>,
1115 issuer: &structured_pkix::Certificate<'_>,
1116 provider: &dyn crate::provider::CryptoProvider,
1117 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
1118 session: &super::pkix::CertificateSession,
1119) -> Result<structured_pkix::path::Verified, X509ChainError> {
1120 use structured_pkix::path::{Verify as _, WorkingKey};
1121 let verifier = super::pkix::ProviderVerifier { provider, allowed };
1122 verifier
1123 .verify(
1124 &certificate.tbs().signature(),
1125 WorkingKey::standing(issuer.tbs().public_key()),
1126 certificate.signed_bytes(),
1127 certificate.signature(),
1128 session.memory(),
1129 )
1130 .map_err(|error| match error {
1131 X509ChainError::Processing(error) => session.error(error),
1132 error => error,
1133 })
1134}
1135
1136#[cfg(test)]
1137fn verify_crl_signature(crl: &CertificateRevocationList<'_>, issuer: &X509Certificate<'_>) -> bool {
1138 verify_crl_signature_with_provider(crl, issuer, crate::provider::default_provider(), None)
1139 .unwrap_or(false)
1140}
1141
1142#[cfg(test)]
1143fn verify_crl_signature_with_provider(
1144 crl: &CertificateRevocationList<'_>,
1145 issuer: &X509Certificate<'_>,
1146 provider: &dyn crate::provider::CryptoProvider,
1147 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
1148) -> Result<bool, X509ChainError> {
1149 verify_x509_signed_object_with_provider(
1152 &crl.signature_algorithm,
1153 &crl.tbs_cert_list.signature,
1154 &crl.signature_value.data,
1155 crl.tbs_cert_list.as_ref(),
1156 issuer.public_key().raw,
1157 provider,
1158 allowed,
1159 )
1160}
1161
1162#[cfg(test)]
1163fn verify_x509_signed_object_with_provider(
1164 outer_algorithm: &AlgorithmIdentifier<'_>,
1165 signed_algorithm: &AlgorithmIdentifier<'_>,
1166 signature_der: &[u8],
1167 signed_data: &[u8],
1168 issuer_spki_der: &[u8],
1169 provider: &dyn crate::provider::CryptoProvider,
1170 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
1171) -> Result<bool, X509ChainError> {
1172 if outer_algorithm != signed_algorithm {
1173 return Ok(false);
1174 }
1175 verify_x509_signature_with_provider(
1176 outer_algorithm,
1177 signature_der,
1178 signed_data,
1179 issuer_spki_der,
1180 provider,
1181 allowed,
1182 )
1183}
1184
1185#[cfg(test)]
1186pub(crate) fn verify_x509_signature_with_provider(
1187 algorithm_identifier: &AlgorithmIdentifier<'_>,
1188 signature_der: &[u8],
1189 signed_data: &[u8],
1190 issuer_spki_der: &[u8],
1191 provider: &dyn crate::provider::CryptoProvider,
1192 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
1193) -> Result<bool, X509ChainError> {
1194 let algorithm = authorize_x509_signature_algorithm(algorithm_identifier, provider, allowed)?;
1195 provider
1196 .verify_x509_signature(algorithm, signed_data, signature_der, issuer_spki_der)
1197 .map_err(Into::into)
1198}
1199
1200pub(crate) fn authorize_x509_signature_algorithm(
1201 algorithm_identifier: &AlgorithmIdentifier<'_>,
1202 provider: &dyn crate::provider::CryptoProvider,
1203 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
1204) -> Result<X509SignatureAlgorithm, X509ChainError> {
1205 let algorithm = x509_signature_algorithm(algorithm_identifier)?;
1206 crate::policy::KeyTrustPolicy::check_certificate_signature_algorithm(algorithm, allowed)?;
1207 provider
1208 .require_capability(crate::provider::ProviderCapability::VerifyCertificate(
1209 algorithm,
1210 ))
1211 .map_err(X509ChainError::from)?;
1212 Ok(algorithm)
1213}
1214
1215fn x509_signature_algorithm(
1216 identifier: &AlgorithmIdentifier<'_>,
1217) -> Result<X509SignatureAlgorithm, X509ChainError> {
1218 let oid = identifier.algorithm.to_id_string();
1219 let algorithm = match oid.as_str() {
1220 "1.2.840.10040.4.3" => X509SignatureAlgorithm::Dsa(super::DigestAlgorithm::Sha1),
1221 "2.16.840.1.101.3.4.3.1" => X509SignatureAlgorithm::Dsa(super::DigestAlgorithm::Sha224),
1222 "2.16.840.1.101.3.4.3.2" => X509SignatureAlgorithm::Dsa(super::DigestAlgorithm::Sha256),
1223 "2.16.840.1.101.3.4.3.3" => X509SignatureAlgorithm::Dsa(super::DigestAlgorithm::Sha384),
1224 "2.16.840.1.101.3.4.3.4" => X509SignatureAlgorithm::Dsa(super::DigestAlgorithm::Sha512),
1225 "1.2.840.113549.1.1.5" | "1.3.14.3.2.29" => {
1226 X509SignatureAlgorithm::RsaPkcs1v15(super::DigestAlgorithm::Sha1)
1227 }
1228 "1.2.840.113549.1.1.14" => {
1229 X509SignatureAlgorithm::RsaPkcs1v15(super::DigestAlgorithm::Sha224)
1230 }
1231 "1.2.840.113549.1.1.11" => {
1232 X509SignatureAlgorithm::RsaPkcs1v15(super::DigestAlgorithm::Sha256)
1233 }
1234 "1.2.840.113549.1.1.12" => {
1235 X509SignatureAlgorithm::RsaPkcs1v15(super::DigestAlgorithm::Sha384)
1236 }
1237 "1.2.840.113549.1.1.13" => {
1238 X509SignatureAlgorithm::RsaPkcs1v15(super::DigestAlgorithm::Sha512)
1239 }
1240 "1.2.840.113549.1.1.10" => parse_rsa_pss_algorithm(identifier)?,
1241 "1.2.840.10045.4.1" => X509SignatureAlgorithm::Ecdsa(super::DigestAlgorithm::Sha1),
1242 "1.2.840.10045.4.3.1" => X509SignatureAlgorithm::Ecdsa(super::DigestAlgorithm::Sha224),
1243 "1.2.840.10045.4.3.2" => X509SignatureAlgorithm::Ecdsa(super::DigestAlgorithm::Sha256),
1244 "1.2.840.10045.4.3.3" => X509SignatureAlgorithm::Ecdsa(super::DigestAlgorithm::Sha384),
1245 "1.2.840.10045.4.3.4" => X509SignatureAlgorithm::Ecdsa(super::DigestAlgorithm::Sha512),
1246 "2.16.840.1.101.3.4.3.9" => X509SignatureAlgorithm::Ecdsa(super::DigestAlgorithm::Sha3_224),
1250 "2.16.840.1.101.3.4.3.10" => {
1251 X509SignatureAlgorithm::Ecdsa(super::DigestAlgorithm::Sha3_256)
1252 }
1253 "2.16.840.1.101.3.4.3.11" => {
1254 X509SignatureAlgorithm::Ecdsa(super::DigestAlgorithm::Sha3_384)
1255 }
1256 "2.16.840.1.101.3.4.3.12" => {
1257 X509SignatureAlgorithm::Ecdsa(super::DigestAlgorithm::Sha3_512)
1258 }
1259 "1.3.101.112" => X509SignatureAlgorithm::Ed25519,
1260 "1.3.101.113" => X509SignatureAlgorithm::Ed448,
1263 _ => {
1264 let Some(algorithm) = super::PqAlgorithm::ALL
1265 .into_iter()
1266 .find(|algorithm| algorithm.oid() == oid)
1267 else {
1268 return Err(X509ChainError::UnsupportedSignatureAlgorithm { oid });
1269 };
1270 X509SignatureAlgorithm::PostQuantum(algorithm)
1271 }
1272 };
1273 match &algorithm {
1274 X509SignatureAlgorithm::Dsa(_)
1275 | X509SignatureAlgorithm::Ecdsa(_)
1276 | X509SignatureAlgorithm::Ed25519
1277 | X509SignatureAlgorithm::Ed448 => require_absent_signature_parameters(identifier)?,
1278 X509SignatureAlgorithm::PostQuantum(_) => {
1279 require_absent_signature_parameters(identifier)?;
1283 }
1284 X509SignatureAlgorithm::RsaPkcs1v15(_) => {
1285 require_null_or_absent_signature_parameters(identifier)?;
1286 }
1287 X509SignatureAlgorithm::RsaPss { .. } => {}
1288 }
1289 Ok(algorithm)
1290}
1291
1292fn require_absent_signature_parameters(
1293 identifier: &AlgorithmIdentifier<'_>,
1294) -> Result<(), X509ChainError> {
1295 if identifier.parameters.is_some() {
1296 return Err(invalid_signature_parameters(
1297 identifier,
1298 "parameters must be absent",
1299 ));
1300 }
1301 Ok(())
1302}
1303
1304fn require_null_or_absent_signature_parameters(
1305 identifier: &AlgorithmIdentifier<'_>,
1306) -> Result<(), X509ChainError> {
1307 if identifier
1308 .parameters
1309 .as_ref()
1310 .is_some_and(|parameters| parameters.tag() != x509_parser::asn1_rs::Tag::Null)
1311 {
1312 return Err(invalid_signature_parameters(
1313 identifier,
1314 "parameters must be NULL or absent",
1315 ));
1316 }
1317 Ok(())
1318}
1319
1320fn invalid_signature_parameters(
1321 identifier: &AlgorithmIdentifier<'_>,
1322 requirement: &str,
1323) -> X509ChainError {
1324 X509ChainError::InvalidDer {
1325 kind: "X.509 signature AlgorithmIdentifier parameters",
1326 message: format!("{}: {requirement}", identifier.algorithm),
1327 }
1328}
1329
1330fn parse_rsa_pss_algorithm(
1331 identifier: &AlgorithmIdentifier<'_>,
1332) -> Result<X509SignatureAlgorithm, X509ChainError> {
1333 let parameters = identifier
1334 .parameters
1335 .as_ref()
1336 .ok_or_else(|| X509ChainError::InvalidDer {
1337 kind: "RSASSA-PSS parameters",
1338 message: "missing parameters".into(),
1339 })?;
1340 let parameters = x509_parser::signature_algorithm::RsaSsaPssParams::try_from(parameters)
1341 .map_err(|error| X509ChainError::InvalidDer {
1342 kind: "RSASSA-PSS parameters",
1343 message: error.to_string(),
1344 })?;
1345 if parameters.trailer_field() != 1 {
1346 return Err(X509ChainError::InvalidDer {
1347 kind: "RSASSA-PSS parameters",
1348 message: "trailerField must be 1".into(),
1349 });
1350 }
1351 let digest = x509_digest_algorithm(¶meters.hash_algorithm_oid().to_id_string())?;
1352 let mask = parameters
1353 .mask_gen_algorithm()
1354 .map_err(|error| X509ChainError::InvalidDer {
1355 kind: "RSASSA-PSS parameters",
1356 message: error.to_string(),
1357 })?;
1358 if mask.mgf.to_id_string() != "1.2.840.113549.1.1.8" {
1359 return Err(X509ChainError::UnsupportedSignatureAlgorithm {
1360 oid: mask.mgf.to_id_string(),
1361 });
1362 }
1363 let mgf_digest = x509_digest_algorithm(&mask.hash.to_id_string())?;
1364 let salt_len =
1365 usize::try_from(parameters.salt_length()).map_err(|_| X509ChainError::InvalidDer {
1366 kind: "RSASSA-PSS parameters",
1367 message: "saltLength does not fit this platform".into(),
1368 })?;
1369 Ok(X509SignatureAlgorithm::RsaPss {
1370 digest,
1371 mgf_digest,
1372 salt_len,
1373 })
1374}
1375
1376fn x509_digest_algorithm(oid: &str) -> Result<super::DigestAlgorithm, X509ChainError> {
1377 match oid {
1378 "1.3.14.3.2.26" => Ok(super::DigestAlgorithm::Sha1),
1379 "2.16.840.1.101.3.4.2.4" => Ok(super::DigestAlgorithm::Sha224),
1380 "2.16.840.1.101.3.4.2.1" => Ok(super::DigestAlgorithm::Sha256),
1381 "2.16.840.1.101.3.4.2.2" => Ok(super::DigestAlgorithm::Sha384),
1382 "2.16.840.1.101.3.4.2.3" => Ok(super::DigestAlgorithm::Sha512),
1383 _ => Err(X509ChainError::UnsupportedSignatureAlgorithm {
1384 oid: oid.to_owned(),
1385 }),
1386 }
1387}
1388
1389#[cfg(test)]
1390fn parse_certificate(der: &[u8]) -> Result<X509Certificate<'_>, X509ChainError> {
1391 let (rest, cert) =
1392 X509Certificate::from_der(der).map_err(|error| X509ChainError::InvalidDer {
1393 kind: "certificate",
1394 message: error.to_string(),
1395 })?;
1396 if !rest.is_empty() {
1397 return Err(X509ChainError::InvalidDer {
1398 kind: "certificate",
1399 message: "trailing data".into(),
1400 });
1401 }
1402 Ok(cert)
1403}
1404
1405#[cfg(test)]
1406fn system_time_to_asn1(time: SystemTime) -> Result<ASN1Time, X509ChainError> {
1407 let instant = super::pkix::verification_instant(time)?;
1408 let whole = ASN1Time::from_timestamp(instant.seconds()).map_err(|error| {
1412 X509ChainError::InvalidDer {
1413 kind: "verification time",
1414 message: error.to_string(),
1415 }
1416 })?;
1417 whole
1418 .to_datetime()
1419 .replace_nanosecond(instant.nanos())
1420 .map(ASN1Time::new)
1421 .map_err(|error| X509ChainError::InvalidDer {
1422 kind: "verification time",
1423 message: error.to_string(),
1424 })
1425}
1426
1427fn dns_name_has_valid_syntax(value: &str, allow_leading_dot: bool) -> bool {
1428 let domain = if allow_leading_dot {
1429 value.strip_prefix('.').unwrap_or(value)
1430 } else {
1431 value
1432 };
1433 if domain.is_empty()
1434 || domain.len() > 253
1435 || domain.split('.').any(|label| {
1436 label.is_empty()
1437 || label.len() > 63
1438 || !label
1439 .bytes()
1440 .all(|byte| byte.is_ascii_alphanumeric() || byte == b'-')
1441 || !label
1442 .as_bytes()
1443 .first()
1444 .is_some_and(u8::is_ascii_alphanumeric)
1445 || !label
1446 .as_bytes()
1447 .last()
1448 .is_some_and(u8::is_ascii_alphanumeric)
1449 })
1450 {
1451 return false;
1452 }
1453 true
1454}
1455
1456#[cfg(test)]
1457mod tests {
1458 use std::str::FromStr as _;
1459
1460 use super::*;
1461 use crate::xml::dom::Document;
1462 use crate::xmldsig::{KeyInfoSource, parse::XMLDSIG_NS, parse_key_info};
1463 use p256::pkcs8::EncodePublicKey;
1464 use sha2::{Digest, Sha256, Sha384};
1465 use signature::hazmat::PrehashSigner;
1466 use std::time::{Duration, UNIX_EPOCH};
1467 use x509_parser::oid_registry::{OID_SIG_ECDSA_WITH_SHA256, OID_SIG_ECDSA_WITH_SHA384, Oid};
1468
1469 #[test]
1470 fn issuer_spki_preflight_checks_borrowed_components_and_exact_der()
1471 -> Result<(), Box<dyn std::error::Error>> {
1472 use der::Encode as _;
1475 let modulus = [0xff; 32];
1476 let exponent = [1, 0, 1];
1477 let public = pkcs1::RsaPublicKeyRef {
1478 modulus: der::asn1::UintRef::new(&modulus)?,
1479 public_exponent: der::asn1::UintRef::new(&exponent)?,
1480 }
1481 .to_der()?;
1482 let encoded = pkcs8::SubjectPublicKeyInfoRef {
1483 algorithm: pkcs1::ALGORITHM_ID,
1484 subject_public_key: der::asn1::BitStringRef::from_bytes(&public)?,
1485 }
1486 .to_der()?;
1487 assert!(matches!(
1488 validate_issuer_spki_policy(
1489 &encoded,
1490 7,
1491 RsaKeyPolicy::default(),
1492 DsaKeyPolicy::default()
1493 ),
1494 Err(X509ChainError::KeyPolicy { position: 7, .. }),
1495 ));
1496 let pss = pkcs8::SubjectPublicKeyInfoRef {
1500 algorithm: pkcs8::AlgorithmIdentifierRef {
1501 oid: der::asn1::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.10"),
1502 parameters: None,
1503 },
1504 subject_public_key: der::asn1::BitStringRef::from_bytes(&public)?,
1505 }
1506 .to_der()?;
1507 assert!(matches!(
1508 validate_issuer_spki_policy(&pss, 7, RsaKeyPolicy::default(), DsaKeyPolicy::default()),
1509 Err(X509ChainError::KeyPolicy { position: 7, .. }),
1510 ));
1511 let mut trailing = encoded.clone();
1512 trailing.push(0);
1513 assert!(matches!(
1514 validate_issuer_spki_policy(
1515 &trailing,
1516 7,
1517 RsaKeyPolicy::default(),
1518 DsaKeyPolicy::default()
1519 ),
1520 Err(X509ChainError::InvalidDer {
1521 kind: "issuer SubjectPublicKeyInfo",
1522 ..
1523 }),
1524 ));
1525 let unaligned = pkcs8::SubjectPublicKeyInfoRef {
1526 algorithm: pkcs1::ALGORITHM_ID,
1527 subject_public_key: der::asn1::BitStringRef::new(1, &[0])?,
1528 }
1529 .to_der()?;
1530 assert!(matches!(
1531 validate_issuer_spki_policy(
1532 &unaligned,
1533 7,
1534 RsaKeyPolicy::default(),
1535 DsaKeyPolicy::default()
1536 ),
1537 Err(X509ChainError::InvalidDer {
1538 kind: "issuer SubjectPublicKeyInfo",
1539 ..
1540 }),
1541 ));
1542 let opaque = pkcs8::SubjectPublicKeyInfoRef {
1545 algorithm: pkcs8::AlgorithmIdentifierRef {
1546 oid: der::asn1::ObjectIdentifier::new_unwrap("1.2.3.4"),
1547 parameters: None,
1548 },
1549 subject_public_key: der::asn1::BitStringRef::new(1, &[0])?,
1550 }
1551 .to_der()?;
1552 validate_issuer_spki_policy(&opaque, 7, RsaKeyPolicy::default(), DsaKeyPolicy::default())?;
1553 Ok(())
1554 }
1555
1556 #[test]
1557 fn verification_time_retains_subseconds_on_both_sides_of_epoch() {
1558 for (input, seconds, nanos) in [
1561 (UNIX_EPOCH + Duration::new(1, 123), 1, 123),
1562 (UNIX_EPOCH - Duration::new(1, 123), -2, 999_999_877),
1563 ] {
1564 let actual = system_time_to_asn1(input).expect("representable validation time");
1565 assert_eq!(actual.timestamp(), seconds);
1566 assert_eq!(actual.to_datetime().nanosecond(), nanos);
1567 }
1568 }
1569
1570 #[test]
1571 fn exact_certificate_pin_does_not_bypass_execution_budget() {
1572 let certificate = generated_certificate_params("pinned signer", false)
1574 .self_signed(&rcgen::KeyPair::generate().expect("test key"))
1575 .expect("test certificate");
1576 let anchors = vec![certificate.der().to_vec()];
1577 let info = X509DataInfo {
1578 certificates: anchors.clone(),
1579 certificate_chain: vec![0],
1580 ..X509DataInfo::default()
1581 };
1582 let options = X509ChainOptions {
1583 trusted_certs: &anchors,
1584 verification_time: SystemTime::now(),
1585 max_chain_depth: 1,
1586 utc_facts: None,
1587 check_crls: false,
1588 allowed_extended_key_usages: None,
1589 rsa_keys: RsaKeyPolicy::default(),
1590 dsa_keys: DsaKeyPolicy::default(),
1591 certificate_signature_algorithms: None,
1592 };
1593 let session = super::super::pkix::CertificateSession::new(&crate::policy::ResourcePolicy {
1594 max_x509_work: 0,
1595 ..crate::policy::ResourcePolicy::default()
1596 });
1597 assert!(matches!(
1598 verify_x509_certificate_chain_in_session(
1599 &info,
1600 &options,
1601 crate::provider::default_provider(),
1602 &[],
1603 &session,
1604 ),
1605 Err(X509ChainError::Policy(
1606 crate::policy::PolicyViolation::ResourceLimitExceeded {
1607 resource: crate::policy::resource_name::X509_WORK,
1608 maximum: 0,
1609 }
1610 ))
1611 ));
1612 }
1613
1614 #[test]
1615 fn crl_key_usage_absence_depends_on_certificate_version() {
1616 use der::{Decode as _, Encode as _};
1619 let mut params = rcgen::CertificateParams::new(Vec::new()).expect("empty SANs are valid");
1620 params
1621 .distinguished_name
1622 .push(rcgen::DnType::CommonName, "CRL issuer");
1623 let certificate = params
1624 .self_signed(&rcgen::KeyPair::generate().expect("test key generation"))
1625 .expect("test certificate signing");
1626 let encoded =
1627 x509_cert::Certificate::from_der(certificate.der()).expect("generated issuer DER");
1628 let mut crl: x509_cert::crl::CertificateList =
1629 x509_cert::crl::CertificateList::from_der(&merlin_crl_der()).expect("Merlin CRL DER");
1630 crl.tbs_cert_list.issuer = encoded.tbs_certificate().subject().clone();
1631 let crl_bytes = crl.to_der().expect("modified CRL encoding");
1632 for version in [
1633 x509_cert::Version::V1,
1634 x509_cert::Version::V2,
1635 x509_cert::Version::V3,
1636 ] {
1637 use der::Tagged as _;
1638 let mut fields = Vec::<der::asn1::Any>::from_der(
1639 &encoded
1640 .tbs_certificate()
1641 .to_der()
1642 .expect("issuer TBS encoding"),
1643 )
1644 .expect("issuer TBS fields");
1645 fields.remove(0); fields.retain(|field| {
1647 field.tag()
1648 != der::Tag::ContextSpecific {
1649 constructed: true,
1650 number: der::TagNumber(3),
1651 }
1652 });
1653 if version != x509_cert::Version::V1 {
1654 fields.insert(
1655 0,
1656 der::asn1::Any::from_der(&[0xa0, 3, 2, 1, version as u8])
1657 .expect("version encoding"),
1658 );
1659 }
1660 let sequence = vec![
1661 der::asn1::Any::from_der(&fields.to_der().expect("modified TBS encoding"))
1662 .expect("modified TBS sequence"),
1663 der::asn1::Any::from_der(
1664 &encoded
1665 .signature_algorithm()
1666 .to_der()
1667 .expect("signature algorithm encoding"),
1668 )
1669 .expect("signature algorithm sequence"),
1670 der::asn1::Any::from_der(
1671 &encoded.signature().to_der().expect("signature encoding"),
1672 )
1673 .expect("signature BIT STRING"),
1674 ];
1675 let bytes = sequence.to_der().expect("modified issuer encoding");
1676 let session = super::super::pkix::CertificateSession::new(
1677 &crate::policy::ResourcePolicy::default(),
1678 );
1679 let issuer = session.read(&bytes).expect("modified issuer parses");
1680 let crl = session.read_crl(&crl_bytes).expect("modified CRL parses");
1681 let outcome = crl
1682 .authenticate(
1683 &issuer,
1684 structured_pkix::path::WorkingKey::standing(issuer.tbs().public_key()),
1685 &FormatOnly,
1686 session.memory(),
1687 )
1688 .expect("format-only authentication returns an outcome");
1689 if version == x509_cert::Version::V3 {
1690 assert!(matches!(
1691 outcome,
1692 structured_pkix::crl::Authentication::Rejected(
1693 structured_pkix::crl::AuthenticationFailure::KeyUsage
1694 )
1695 ));
1696 } else {
1697 assert!(matches!(
1698 outcome,
1699 structured_pkix::crl::Authentication::Authenticated(_)
1700 ));
1701 }
1702 }
1703 }
1704
1705 fn generated_certificate_params(common_name: &str, is_ca: bool) -> rcgen::CertificateParams {
1706 let mut params = rcgen::CertificateParams::new(Vec::new())
1707 .expect("empty SAN list should produce valid certificate parameters");
1708 params
1709 .distinguished_name
1710 .push(rcgen::DnType::CommonName, common_name);
1711 if is_ca {
1712 params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
1713 params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
1714 }
1715 params
1716 }
1717
1718 fn verify_generated_path(
1719 certificates: Vec<Vec<u8>>,
1720 trusted_anchor: Vec<u8>,
1721 ) -> Result<(), X509ChainError> {
1722 verify_generated_path_with_eku(certificates, trusted_anchor, None)
1723 }
1724
1725 fn verify_generated_path_with_eku(
1726 certificates: Vec<Vec<u8>>,
1727 trusted_anchor: Vec<u8>,
1728 allowed_extended_key_usages: Option<&HashSet<ExtendedKeyPurpose>>,
1729 ) -> Result<(), X509ChainError> {
1730 let info = X509DataInfo {
1731 certificate_chain: (0..certificates.len()).collect(),
1732 certificates,
1733 ..X509DataInfo::default()
1734 };
1735 let anchors = vec![trusted_anchor];
1736 verify_x509_certificate_chain(
1737 &info,
1738 &X509ChainOptions {
1739 trusted_certs: &anchors,
1740 utc_facts: None,
1741 verification_time: SystemTime::now(),
1742 max_chain_depth: info.certificate_chain.len(),
1743 check_crls: false,
1744 allowed_extended_key_usages,
1745 rsa_keys: RsaKeyPolicy::default(),
1746 dsa_keys: DsaKeyPolicy::default(),
1747 certificate_signature_algorithms: None,
1748 },
1749 )
1750 }
1751
1752 fn verify_generated_constraints(
1753 constraints: x509_cert::ext::pkix::NameConstraints,
1754 name: x509_cert::ext::pkix::name::GeneralName,
1755 ) -> Result<(), X509ChainError> {
1756 use der::Encode as _;
1757 let mut root_params = generated_certificate_params("constraint authority", true);
1758 let mut constraint_extension = rcgen::CustomExtension::from_oid_content(
1759 &[2, 5, 29, 30],
1760 constraints.to_der().expect("constraint fixture DER"),
1761 );
1762 constraint_extension.set_criticality(true);
1763 root_params.custom_extensions.push(constraint_extension);
1764 let root = rcgen::CertifiedIssuer::self_signed(
1765 root_params,
1766 rcgen::KeyPair::generate().expect("constraint root key"),
1767 )
1768 .expect("constraint root");
1769 let mut leaf_params = generated_certificate_params("constraint signer", false);
1770 let mut san = rcgen::CustomExtension::from_oid_content(
1771 &[2, 5, 29, 17],
1772 vec![name].to_der().expect("SAN fixture DER"),
1773 );
1774 san.set_criticality(true);
1775 leaf_params.custom_extensions.push(san);
1776 let leaf = leaf_params
1777 .signed_by(
1778 &rcgen::KeyPair::generate().expect("constraint leaf key"),
1779 &root,
1780 )
1781 .expect("constraint leaf");
1782 let session =
1783 super::super::pkix::CertificateSession::new(&crate::policy::ResourcePolicy::default());
1784 let leaf = session.read(leaf.der())?;
1785 let root = session.read(root.der())?;
1786 let options = X509ChainOptions {
1787 trusted_certs: &[],
1788 utc_facts: None,
1789 verification_time: SystemTime::now(),
1790 max_chain_depth: 2,
1791 check_crls: false,
1792 allowed_extended_key_usages: None,
1793 rsa_keys: RsaKeyPolicy::default(),
1794 dsa_keys: DsaKeyPolicy::default(),
1795 certificate_signature_algorithms: None,
1796 };
1797 super::super::pkix::validate_supplied_path(
1798 &[&leaf],
1799 &root,
1800 &options,
1801 crate::provider::default_provider(),
1802 &session,
1803 )
1804 .map(|_| ())
1805 }
1806
1807 #[test]
1808 fn noncritical_ca_basic_constraints_remain_path_compatible() {
1809 let mut params = generated_certificate_params("non-critical authority", false);
1812 params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
1813 params
1814 .custom_extensions
1815 .push(rcgen::CustomExtension::from_oid_content(
1816 &[2, 5, 29, 19],
1817 vec![0x30, 0x03, 0x01, 0x01, 0xff],
1818 ));
1819 let key = rcgen::KeyPair::generate().expect("CA key generation should succeed");
1820 let issuer = rcgen::Issuer::from_params(¶ms, &key);
1821 let certificate = params
1822 .clone()
1823 .self_signed(&key)
1824 .expect("test CA should be self-signable");
1825 let leaf = generated_certificate_params("noncritical CA target", false)
1826 .signed_by(&rcgen::KeyPair::generate().expect("target key"), &issuer)
1827 .expect("target certificate");
1828 verify_generated_path(
1829 vec![leaf.der().to_vec(), certificate.der().to_vec()],
1830 certificate.der().to_vec(),
1831 )
1832 .expect("the actual common path accepts noncritical cA");
1833 }
1834
1835 #[test]
1836 fn restricted_leaf_eku_requires_an_approved_purpose() {
1837 let root = rcgen::CertifiedIssuer::self_signed(
1840 generated_certificate_params("EKU authority", true),
1841 rcgen::KeyPair::generate().expect("root key generation should succeed"),
1842 )
1843 .expect("root should be self-signable");
1844 let mut leaf_params = generated_certificate_params("TLS-only signer", false);
1845 leaf_params.key_usages = vec![rcgen::KeyUsagePurpose::DigitalSignature];
1846 leaf_params.extended_key_usages = vec![rcgen::ExtendedKeyUsagePurpose::ServerAuth];
1847 let leaf = leaf_params
1848 .signed_by(
1849 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
1850 &root,
1851 )
1852 .expect("root should sign leaf certificate");
1853 let leaf_der = leaf.der().to_vec();
1854 let root_der = root.der().to_vec();
1855
1856 assert!(matches!(
1857 verify_generated_path(vec![leaf_der.clone(), root_der.clone()], root_der.clone(),),
1858 Err(X509ChainError::InvalidKeyUsage {
1859 position: 0,
1860 required: "an approved extended key usage",
1861 })
1862 ));
1863
1864 let allowed = HashSet::from([ExtendedKeyPurpose::ServerAuth]);
1865 verify_generated_path_with_eku(vec![leaf_der, root_der.clone()], root_der, Some(&allowed))
1866 .expect("an explicitly approved leaf purpose must be accepted");
1867 }
1868
1869 #[test]
1870 fn critical_leaf_eku_uses_the_same_purpose_policy() {
1871 let root = rcgen::CertifiedIssuer::self_signed(
1874 generated_certificate_params("critical EKU authority", true),
1875 rcgen::KeyPair::generate().expect("root key generation should succeed"),
1876 )
1877 .expect("root should be self-signable");
1878 let mut leaf_params = generated_certificate_params("critical TLS signer", false);
1879 leaf_params.key_usages = vec![rcgen::KeyUsagePurpose::DigitalSignature];
1880 let mut extension = rcgen::CustomExtension::from_oid_content(
1881 &[2, 5, 29, 37],
1882 vec![
1883 0x30, 0x0a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01,
1884 ],
1885 );
1886 extension.set_criticality(true);
1887 leaf_params.custom_extensions.push(extension);
1888 let leaf = leaf_params
1889 .signed_by(
1890 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
1891 &root,
1892 )
1893 .expect("root should sign leaf certificate");
1894 let allowed = HashSet::from([ExtendedKeyPurpose::ServerAuth]);
1895
1896 verify_generated_path_with_eku(
1897 vec![leaf.der().to_vec(), root.der().to_vec()],
1898 root.der().to_vec(),
1899 Some(&allowed),
1900 )
1901 .expect("approved critical EKU must be processed rather than rejected as unknown");
1902 }
1903
1904 #[test]
1905 fn issuer_eku_restricts_the_entire_certificate_path() {
1906 for (issuer_purpose_der, accepted) in [
1909 (
1910 vec![
1911 0x30, 0x0a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02,
1912 ],
1913 false,
1914 ),
1915 (
1916 vec![
1917 0x30, 0x0a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01,
1918 ],
1919 true,
1920 ),
1921 ] {
1922 let mut root_params =
1923 generated_certificate_params("purpose-constrained authority", true);
1924 let mut extension =
1925 rcgen::CustomExtension::from_oid_content(&[2, 5, 29, 37], issuer_purpose_der);
1926 extension.set_criticality(true);
1927 root_params.custom_extensions.push(extension);
1928 let root = rcgen::CertifiedIssuer::self_signed(
1929 root_params,
1930 rcgen::KeyPair::generate().expect("root key generation should succeed"),
1931 )
1932 .expect("root should be self-signable");
1933 let mut leaf_params = generated_certificate_params("TLS server signer", false);
1934 leaf_params.key_usages = vec![rcgen::KeyUsagePurpose::DigitalSignature];
1935 leaf_params.extended_key_usages = vec![rcgen::ExtendedKeyUsagePurpose::ServerAuth];
1936 let leaf = leaf_params
1937 .signed_by(
1938 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
1939 &root,
1940 )
1941 .expect("root should sign leaf certificate");
1942 let allowed = HashSet::from([
1943 ExtendedKeyPurpose::ServerAuth,
1944 ExtendedKeyPurpose::ClientAuth,
1945 ]);
1946 let result = verify_generated_path_with_eku(
1947 vec![leaf.der().to_vec(), root.der().to_vec()],
1948 root.der().to_vec(),
1949 Some(&allowed),
1950 );
1951
1952 if accepted {
1953 result.expect("a shared allowed purpose must satisfy the complete path");
1954 } else {
1955 assert!(matches!(
1956 result,
1957 Err(X509ChainError::InvalidKeyUsage {
1958 position: 1,
1959 required: "an approved extended key usage",
1960 })
1961 ));
1962 }
1963 }
1964 }
1965
1966 #[test]
1967 fn any_extended_key_usage_does_not_restrict_xml_signing() {
1968 let root = rcgen::CertifiedIssuer::self_signed(
1971 generated_certificate_params("any EKU authority", true),
1972 rcgen::KeyPair::generate().expect("root key generation should succeed"),
1973 )
1974 .expect("root should be self-signable");
1975 let mut leaf_params = generated_certificate_params("unrestricted signer", false);
1976 leaf_params.key_usages = vec![rcgen::KeyUsagePurpose::DigitalSignature];
1977 leaf_params.extended_key_usages = vec![rcgen::ExtendedKeyUsagePurpose::Any];
1978 let leaf = leaf_params
1979 .signed_by(
1980 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
1981 &root,
1982 )
1983 .expect("root should sign leaf certificate");
1984
1985 verify_generated_path(
1986 vec![leaf.der().to_vec(), root.der().to_vec()],
1987 root.der().to_vec(),
1988 )
1989 .expect("anyExtendedKeyUsage must remain unrestricted");
1990 }
1991
1992 #[test]
1993 #[cfg(feature = "experimental-pq")]
1994 fn pq_certificate_verification_requires_explicit_trust_permission() {
1995 use super::super::{
1996 PostQuantumSigningKey, PqAlgorithm, SignatureAlgorithm, SignatureContext, SigningKey,
1997 };
1998 let parameter = PqAlgorithm::MlDsa44;
2001 let key = PostQuantumSigningKey::from_pkcs8_der(
2002 parameter,
2003 include_bytes!("../../tests/fixtures/xmldsig/keys/ml-dsa/ml-dsa-44-key.der"),
2004 )
2005 .expect("donor ML-DSA private key must import");
2006 let data = b"PKIX signed data";
2007 let provider = crate::provider::default_provider();
2008 let signature = provider
2009 .sign_with_context(
2010 &key,
2011 SignatureAlgorithm::PostQuantum(parameter),
2012 &SignatureContext::default(),
2013 data,
2014 )
2015 .expect("ML-DSA key must sign PKIX data");
2016 let public = key.public_key_info().expect("public key must export");
2017 let spki = public.spki_der().expect("PQ public key must contain SPKI");
2018 let algorithm = X509SignatureAlgorithm::PostQuantum(parameter);
2019 assert!(
2020 provider
2021 .verify_x509_signature(algorithm, data, &signature, spki)
2022 .expect("provider must verify valid PQ signature")
2023 );
2024 let identifier = AlgorithmIdentifier::new(
2025 parameter
2026 .oid()
2027 .parse()
2028 .expect("registered PQ OID must parse"),
2029 None,
2030 );
2031 assert!(
2032 verify_x509_signature_with_provider(
2033 &identifier,
2034 &signature,
2035 data,
2036 spki,
2037 provider,
2038 None
2039 )
2040 .is_err(),
2041 "default certificate policy must reject PQ before provider verification"
2042 );
2043 let permitted =
2044 crate::policy::CertificateSignatureAlgorithms::Allowlist(HashSet::from([algorithm]));
2045 assert!(
2046 verify_x509_signed_object_with_provider(
2047 &identifier,
2048 &identifier,
2049 &signature,
2050 data,
2051 spki,
2052 provider,
2053 Some(&permitted)
2054 )
2055 .expect("explicit permission must enable valid PQ authentication")
2056 );
2057 assert!(matches!(
2058 verify_x509_signed_object_with_provider(
2059 &identifier,
2060 &identifier,
2061 &signature,
2062 data,
2063 spki,
2064 provider,
2065 Some(&crate::policy::CertificateSignatureAlgorithms::Allowlist(
2066 HashSet::new()
2067 ))
2068 ),
2069 Err(X509ChainError::Policy(_))
2070 ));
2071 }
2072
2073 #[test]
2074 fn x509_ecdsa_hash_oid_does_not_select_the_issuer_curve() {
2075 let data = b"certificate tbs bytes";
2080
2081 let p384_key = p384::ecdsa::SigningKey::from_slice(&[0x42; 48])
2082 .expect("fixed P-384 test key must be valid");
2083 let p384_signature: p384::ecdsa::Signature = p384_key
2084 .sign_prehash(&Sha256::digest(data))
2085 .expect("P-384 must sign a SHA-256 prehash");
2086 let p384_spki = p384_key
2087 .verifying_key()
2088 .to_public_key_der()
2089 .expect("P-384 SPKI must encode");
2090 assert!(
2091 verify_x509_signature_with_provider(
2092 &AlgorithmIdentifier::new(OID_SIG_ECDSA_WITH_SHA256, None),
2093 p384_signature.to_der().as_bytes(),
2094 data,
2095 p384_spki.as_bytes(),
2096 crate::provider::default_provider(),
2097 None,
2098 )
2099 .expect("P-384 with SHA-256 must be a supported X.509 pairing")
2100 );
2101
2102 let p256_key = p256::ecdsa::SigningKey::from_slice(&[0x24; 32])
2103 .expect("fixed P-256 test key must be valid");
2104 let p256_signature: p256::ecdsa::Signature = p256_key
2105 .sign_prehash(&Sha384::digest(data))
2106 .expect("P-256 must sign a SHA-384 prehash");
2107 let p256_spki = p256_key
2108 .verifying_key()
2109 .to_public_key_der()
2110 .expect("P-256 SPKI must encode");
2111 assert!(
2112 verify_x509_signature_with_provider(
2113 &AlgorithmIdentifier::new(OID_SIG_ECDSA_WITH_SHA384, None),
2114 p256_signature.to_der().as_bytes(),
2115 data,
2116 p256_spki.as_bytes(),
2117 crate::provider::default_provider(),
2118 None,
2119 )
2120 .expect("P-256 with SHA-384 must be a supported X.509 pairing")
2121 );
2122 }
2123
2124 #[test]
2125 fn path_edge_signature_check_does_not_repeat_name_matching() {
2126 let issuer_key = rcgen::KeyPair::generate().expect("issuer key generation should succeed");
2130 let issuer_key_pem = issuer_key.serialize_pem();
2131 let mut signing_params = rcgen::CertificateParams::new(Vec::new())
2132 .expect("empty issuer SAN list should be valid");
2133 signing_params
2134 .distinguished_name
2135 .push(rcgen::DnType::CommonName, "signing name");
2136 signing_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
2137 signing_params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
2138 let signing_issuer = rcgen::CertifiedIssuer::self_signed(signing_params, issuer_key)
2139 .expect("issuer certificate should be self-signable");
2140
2141 let mut alternate_params = rcgen::CertificateParams::new(Vec::new())
2142 .expect("empty alternate SAN list should be valid");
2143 alternate_params
2144 .distinguished_name
2145 .push(rcgen::DnType::CommonName, "name already matched by caller");
2146 alternate_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
2147 alternate_params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
2148 let alternate_issuer = rcgen::CertifiedIssuer::self_signed(
2149 alternate_params,
2150 rcgen::KeyPair::from_pem(&issuer_key_pem)
2151 .expect("serialized issuer key should parse again"),
2152 )
2153 .expect("alternate issuer certificate should be self-signable");
2154
2155 let leaf = rcgen::CertificateParams::new(Vec::new())
2156 .expect("empty leaf SAN list should be valid")
2157 .signed_by(
2158 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2159 &signing_issuer,
2160 )
2161 .expect("issuer should sign leaf certificate");
2162
2163 assert!(certificate_signature_matches(
2164 leaf.der(),
2165 alternate_issuer.der()
2166 ));
2167 }
2168
2169 #[test]
2170 fn certificate_path_edge_preserves_ed25519_verification() {
2171 let issuer_key = rcgen::KeyPair::generate_for(&rcgen::PKCS_ED25519)
2175 .expect("Ed25519 issuer key generation should succeed");
2176 let mut issuer_params = rcgen::CertificateParams::new(Vec::new())
2177 .expect("empty issuer SAN list should be valid");
2178 issuer_params
2179 .distinguished_name
2180 .push(rcgen::DnType::CommonName, "Ed25519 issuer");
2181 issuer_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
2182 issuer_params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
2183 let issuer = rcgen::CertifiedIssuer::self_signed(issuer_params, issuer_key)
2184 .expect("Ed25519 issuer certificate should be self-signable");
2185 let leaf_key = rcgen::KeyPair::generate_for(&rcgen::PKCS_ED25519)
2186 .expect("Ed25519 leaf key generation should succeed");
2187 let leaf = rcgen::CertificateParams::new(Vec::new())
2188 .expect("empty leaf SAN list should be valid")
2189 .signed_by(&leaf_key, &issuer)
2190 .expect("Ed25519 issuer should sign leaf certificate");
2191
2192 assert!(certificate_signature_matches(leaf.der(), issuer.der()));
2193 }
2194
2195 #[test]
2196 fn ed448_certificate_signature_identifier_requires_absent_parameters() {
2197 use x509_parser::asn1_rs::{Any, Tag};
2198
2199 let oid = Oid::from_str("1.3.101.113").expect("RFC 8410 Ed448 OID must parse");
2202 assert!(x509_signature_algorithm(&AlgorithmIdentifier::new(oid.clone(), None)).is_ok());
2203 assert!(matches!(
2204 x509_signature_algorithm(&AlgorithmIdentifier::new(
2205 oid,
2206 Some(Any::from_tag_and_data(Tag::Null, &[]))
2207 )),
2208 Err(X509ChainError::InvalidDer {
2209 kind: "X.509 signature AlgorithmIdentifier parameters",
2210 ..
2211 })
2212 ));
2213 }
2214
2215 #[test]
2216 fn every_modeled_non_parameterized_x509_algorithm_reaches_the_provider() {
2217 for (oid, expected) in [
2221 (
2222 "2.16.840.1.101.3.4.3.1",
2223 X509SignatureAlgorithm::Dsa(super::super::DigestAlgorithm::Sha224),
2224 ),
2225 (
2226 "2.16.840.1.101.3.4.3.2",
2227 X509SignatureAlgorithm::Dsa(super::super::DigestAlgorithm::Sha256),
2228 ),
2229 (
2230 "2.16.840.1.101.3.4.3.3",
2231 X509SignatureAlgorithm::Dsa(super::super::DigestAlgorithm::Sha384),
2232 ),
2233 (
2234 "2.16.840.1.101.3.4.3.4",
2235 X509SignatureAlgorithm::Dsa(super::super::DigestAlgorithm::Sha512),
2236 ),
2237 (
2238 "1.2.840.10045.4.1",
2239 X509SignatureAlgorithm::Ecdsa(super::super::DigestAlgorithm::Sha1),
2240 ),
2241 (
2242 "1.2.840.113549.1.1.14",
2243 X509SignatureAlgorithm::RsaPkcs1v15(super::super::DigestAlgorithm::Sha224),
2244 ),
2245 (
2246 "1.2.840.10045.4.3.1",
2247 X509SignatureAlgorithm::Ecdsa(super::super::DigestAlgorithm::Sha224),
2248 ),
2249 (
2250 "1.2.840.10045.4.3.4",
2251 X509SignatureAlgorithm::Ecdsa(super::super::DigestAlgorithm::Sha512),
2252 ),
2253 ] {
2254 let identifier = AlgorithmIdentifier::new(
2255 Oid::from_str(oid).expect("static signature OID must parse"),
2256 None,
2257 );
2258 assert_eq!(x509_signature_algorithm(&identifier), Ok(expected), "{oid}");
2259 }
2260 }
2261
2262 #[test]
2263 fn pq_certificate_identifiers_require_absent_parameters() {
2264 use x509_parser::asn1_rs::{Any, Tag};
2265
2266 for algorithm in super::super::PqAlgorithm::ALL {
2269 let oid = Oid::from_str(algorithm.oid()).expect("registered PQ signature OID");
2270 assert!(x509_signature_algorithm(&AlgorithmIdentifier::new(oid.clone(), None)).is_ok());
2271 assert!(matches!(
2272 x509_signature_algorithm(&AlgorithmIdentifier::new(
2273 oid,
2274 Some(Any::from_tag_and_data(Tag::Null, &[]))
2275 )),
2276 Err(X509ChainError::InvalidDer {
2277 kind: "X.509 signature AlgorithmIdentifier parameters",
2278 ..
2279 })
2280 ));
2281 }
2282 }
2283
2284 #[test]
2285 fn sha3_ecdsa_certificate_identifiers_require_absent_parameters() {
2286 use x509_parser::asn1_rs::{Any, Tag};
2287 for digest in [
2290 super::super::DigestAlgorithm::Sha3_224,
2291 super::super::DigestAlgorithm::Sha3_256,
2292 super::super::DigestAlgorithm::Sha3_384,
2293 super::super::DigestAlgorithm::Sha3_512,
2294 ] {
2295 let expected = X509SignatureAlgorithm::Ecdsa(digest);
2296 let oid = Oid::from_str(expected.oid().expect("registered signature OID"))
2297 .expect("registered SHA3 ECDSA OID must parse");
2298 assert_eq!(
2299 x509_signature_algorithm(&AlgorithmIdentifier::new(oid.clone(), None)),
2300 Ok(expected)
2301 );
2302 assert!(matches!(
2303 x509_signature_algorithm(&AlgorithmIdentifier::new(
2304 oid,
2305 Some(Any::from_tag_and_data(Tag::Null, &[]))
2306 )),
2307 Err(X509ChainError::InvalidDer { .. })
2308 ));
2309 }
2310 }
2311
2312 #[test]
2313 fn x509_signature_parameters_follow_each_algorithm_profile() {
2314 use x509_parser::asn1_rs::{Any, Tag};
2315
2316 for oid in [
2319 "1.2.840.10040.4.3",
2320 "2.16.840.1.101.3.4.3.1",
2321 "2.16.840.1.101.3.4.3.2",
2322 "1.2.840.10045.4.1",
2323 "1.2.840.10045.4.3.1",
2324 "1.2.840.10045.4.3.2",
2325 "1.3.101.112",
2326 ] {
2327 let identifier = AlgorithmIdentifier::new(
2328 Oid::from_str(oid).expect("static signature OID must parse"),
2329 Some(Any::from_tag_and_data(Tag::Null, &[])),
2330 );
2331 assert!(matches!(
2332 x509_signature_algorithm(&identifier),
2333 Err(X509ChainError::InvalidDer {
2334 kind: "X.509 signature AlgorithmIdentifier parameters",
2335 ..
2336 })
2337 ));
2338 }
2339
2340 for (oid, digest) in [
2343 (
2344 "1.2.840.113549.1.1.14",
2345 super::super::DigestAlgorithm::Sha224,
2346 ),
2347 (
2348 "1.2.840.113549.1.1.11",
2349 super::super::DigestAlgorithm::Sha256,
2350 ),
2351 ] {
2352 let rsa_oid = Oid::from_str(oid).expect("static RSA signature OID must parse");
2353 for parameters in [None, Some(Any::from_tag_and_data(Tag::Null, &[]))] {
2354 assert_eq!(
2355 x509_signature_algorithm(&AlgorithmIdentifier::new(
2356 rsa_oid.clone(),
2357 parameters,
2358 )),
2359 Ok(X509SignatureAlgorithm::RsaPkcs1v15(digest)),
2360 );
2361 }
2362 assert!(matches!(
2363 x509_signature_algorithm(&AlgorithmIdentifier::new(
2364 rsa_oid,
2365 Some(Any::from_tag_and_data(Tag::OctetString, &[])),
2366 )),
2367 Err(X509ChainError::InvalidDer {
2368 kind: "X.509 signature AlgorithmIdentifier parameters",
2369 ..
2370 })
2371 ));
2372 }
2373 }
2374
2375 #[test]
2376 fn unknown_x509_signature_algorithm_remains_diagnosable() {
2377 let oid = "1.2.3.4.5";
2378 let identifier = AlgorithmIdentifier::new(
2379 Oid::from_str(oid).expect("static unknown OID must parse"),
2380 None,
2381 );
2382
2383 assert_eq!(
2384 x509_signature_algorithm(&identifier),
2385 Err(X509ChainError::UnsupportedSignatureAlgorithm { oid: oid.into() })
2386 );
2387 }
2388
2389 #[test]
2390 fn parses_rsa_pss_certificate_parameters_without_xml_dsig_loss() {
2391 let der = [
2394 0x30, 0x41, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0a, 0x30,
2395 0x34, 0xa0, 0x0f, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04,
2396 0x02, 0x01, 0x05, 0x00, 0xa1, 0x1c, 0x30, 0x1a, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
2397 0xf7, 0x0d, 0x01, 0x01, 0x08, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65,
2398 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0xa2, 0x03, 0x02, 0x01, 0x20,
2399 ];
2400 let (rest, identifier) = AlgorithmIdentifier::from_der(&der)
2401 .expect("standard SHA-256 RSA-PSS AlgorithmIdentifier must parse");
2402 assert!(rest.is_empty());
2403
2404 assert_eq!(
2405 x509_signature_algorithm(&identifier),
2406 Ok(X509SignatureAlgorithm::RsaPss {
2407 digest: super::super::DigestAlgorithm::Sha256,
2408 mgf_digest: super::super::DigestAlgorithm::Sha256,
2409 salt_len: 32,
2410 })
2411 );
2412 }
2413
2414 #[test]
2415 fn parses_sha224_rsa_pss_certificate_parameters() {
2416 let der = [
2419 0x30, 0x41, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0a, 0x30,
2420 0x34, 0xa0, 0x0f, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04,
2421 0x02, 0x04, 0x05, 0x00, 0xa1, 0x1c, 0x30, 0x1a, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
2422 0xf7, 0x0d, 0x01, 0x01, 0x08, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65,
2423 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0xa2, 0x03, 0x02, 0x01, 0x1c,
2424 ];
2425 let (rest, identifier) = AlgorithmIdentifier::from_der(&der)
2426 .expect("standard SHA-224 RSA-PSS AlgorithmIdentifier must parse");
2427 assert!(rest.is_empty());
2428
2429 assert_eq!(
2430 x509_signature_algorithm(&identifier),
2431 Ok(X509SignatureAlgorithm::RsaPss {
2432 digest: super::super::DigestAlgorithm::Sha224,
2433 mgf_digest: super::super::DigestAlgorithm::Sha224,
2434 salt_len: 28,
2435 })
2436 );
2437 }
2438
2439 #[test]
2440 fn dsa_rollover_replaces_embedded_root_before_depth_validation() {
2441 let leaf = include_bytes!(
2442 "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/balor.der"
2443 )
2444 .to_vec();
2445 let embedded_root =
2446 include_bytes!("../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/ca.der")
2447 .to_vec();
2448
2449 let mut rollover_anchor = embedded_root.clone();
2453 *rollover_anchor
2454 .last_mut()
2455 .expect("certificate is non-empty") ^= 1;
2456 parse_certificate(&rollover_anchor).expect("modified trust anchor remains valid DER");
2457 let anchors = vec![rollover_anchor];
2458 let info = X509DataInfo {
2459 certificates: vec![leaf, embedded_root],
2460 certificate_chain: vec![0, 1],
2461 ..X509DataInfo::default()
2462 };
2463 let options = X509ChainOptions {
2464 trusted_certs: &anchors,
2465 verification_time: UNIX_EPOCH + Duration::from_secs(1_104_580_800),
2466 max_chain_depth: 2,
2467 check_crls: false,
2468 allowed_extended_key_usages: None,
2469 rsa_keys: RsaKeyPolicy::default(),
2470 dsa_keys: DsaKeyPolicy {
2471 minimum_modulus_bits: 1024,
2472 },
2473 utc_facts: None,
2474 certificate_signature_algorithms: None,
2475 };
2476
2477 verify_x509_certificate_chain(&info, &options)
2478 .expect("the stale DSA root must be replaced by the configured anchor");
2479 }
2480
2481 #[test]
2482 fn dsa_issuer_key_uses_the_configured_strength_policy() {
2483 let leaf = include_bytes!(
2484 "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/balor.der"
2485 )
2486 .to_vec();
2487 let anchor =
2488 include_bytes!("../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/ca.der")
2489 .to_vec();
2490 let anchors = vec![anchor.clone()];
2491 let info = X509DataInfo {
2492 certificates: vec![leaf, anchor],
2493 certificate_chain: vec![0, 1],
2494 ..X509DataInfo::default()
2495 };
2496 let options = X509ChainOptions {
2497 trusted_certs: &anchors,
2498 verification_time: UNIX_EPOCH + Duration::from_secs(1_104_580_800),
2499 max_chain_depth: 2,
2500 check_crls: false,
2501 allowed_extended_key_usages: None,
2502 rsa_keys: RsaKeyPolicy::default(),
2503 dsa_keys: DsaKeyPolicy::default(),
2504 utc_facts: None,
2505 certificate_signature_algorithms: None,
2506 };
2507
2508 assert!(matches!(
2509 verify_x509_certificate_chain(&info, &options),
2510 Err(X509ChainError::KeyPolicy {
2511 position: 1,
2512 source: crate::policy::PolicyViolation::KeySize {
2513 key_type: "DSA",
2514 minimum_bits: 2048,
2515 actual_bits: 1024,
2516 ..
2517 }
2518 })
2519 ));
2520 }
2521
2522 #[test]
2523 fn path_length_excludes_self_issued_rollover_certificates() {
2524 let mut root_params = generated_certificate_params("rollover path authority", true);
2527 root_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Constrained(0));
2528 let root = rcgen::CertifiedIssuer::self_signed(
2529 root_params,
2530 rcgen::KeyPair::generate().expect("root key generation should succeed"),
2531 )
2532 .expect("root should be self-signable");
2533 let rollover_params = generated_certificate_params("rollover path authority", true);
2534 let rollover_key =
2535 rcgen::KeyPair::generate().expect("rollover key generation should succeed");
2536 let rollover_certificate = rollover_params
2537 .signed_by(&rollover_key, &root)
2538 .expect("root should sign same-name rollover certificate");
2539 let rollover_issuer = rcgen::Issuer::from_params(&rollover_params, &rollover_key);
2540 let leaf = generated_certificate_params("rollover path leaf", false)
2541 .signed_by(
2542 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2543 &rollover_issuer,
2544 )
2545 .expect("rollover key should sign leaf certificate");
2546
2547 verify_generated_path(
2548 vec![
2549 leaf.der().to_vec(),
2550 rollover_certificate.der().to_vec(),
2551 root.der().to_vec(),
2552 ],
2553 root.der().to_vec(),
2554 )
2555 .expect("self-issued rollover must not consume a zero path-length allowance");
2556 }
2557
2558 #[test]
2559 fn ca_name_constraints_reject_disallowed_dns_names() {
2560 let mut root_params = generated_certificate_params("constrained authority", true);
2561 root_params.name_constraints = Some(rcgen::NameConstraints {
2562 permitted_subtrees: vec![rcgen::GeneralSubtree::DnsName("example.com".into())],
2563 excluded_subtrees: vec![rcgen::GeneralSubtree::DnsName("blocked.example.com".into())],
2564 });
2565 let root = rcgen::CertifiedIssuer::self_signed(
2566 root_params,
2567 rcgen::KeyPair::generate().expect("root key generation should succeed"),
2568 )
2569 .expect("constrained root should be self-signable");
2570
2571 for (dns_name, accepted) in [
2572 ("www.example.com", true),
2573 ("blocked.example.com", false),
2574 ("www.example.net", false),
2575 ] {
2576 let leaf = rcgen::CertificateParams::new(vec![dns_name.into()])
2577 .expect("DNS SAN should be valid")
2578 .signed_by(
2579 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2580 &root,
2581 )
2582 .expect("root should sign leaf certificate");
2583 assert_eq!(
2584 verify_generated_path(
2585 vec![leaf.der().to_vec(), root.der().to_vec()],
2586 root.der().to_vec(),
2587 )
2588 .is_ok(),
2589 accepted,
2590 "unexpected name-constraint result for {dns_name}"
2591 );
2592 }
2593 }
2594
2595 #[test]
2596 fn rfc5280_dns_names_require_preferred_name_syntax() {
2597 let mut root_params = generated_certificate_params("DNS syntax authority", true);
2598 root_params.name_constraints = Some(rcgen::NameConstraints {
2599 permitted_subtrees: vec![rcgen::GeneralSubtree::DnsName("example.com".into())],
2600 excluded_subtrees: Vec::new(),
2601 });
2602 let root = rcgen::CertifiedIssuer::self_signed(
2603 root_params,
2604 rcgen::KeyPair::generate().expect("root key generation should succeed"),
2605 )
2606 .expect("constrained root should be self-signable");
2607
2608 let mut leaf_params = generated_certificate_params("malformed DNS leaf", false);
2609 let dns_name = b"bad..example.com";
2610 let mut san_der = vec![
2611 0x30,
2612 u8::try_from(dns_name.len() + 2).expect("test SAN must fit short-form DER"),
2613 0x82,
2614 ];
2615 san_der.push(u8::try_from(dns_name.len()).expect("test DNS name must fit short-form DER"));
2616 san_der.extend_from_slice(dns_name);
2617 leaf_params
2618 .custom_extensions
2619 .push(rcgen::CustomExtension::from_oid_content(
2620 &[2, 5, 29, 17],
2621 san_der,
2622 ));
2623 let leaf = leaf_params
2624 .signed_by(
2625 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2626 &root,
2627 )
2628 .expect("root should sign malformed-DNS leaf");
2629
2630 let outcome = verify_generated_path(
2631 vec![leaf.der().to_vec(), root.der().to_vec()],
2632 root.der().to_vec(),
2633 );
2634 assert!(
2635 matches!(
2636 outcome,
2637 Err(X509ChainError::InvalidDer {
2638 kind: "certificate DNS name",
2639 ..
2640 })
2641 ),
2642 "{outcome:?}"
2643 );
2644
2645 for dns_name in ["*.example.com", "_signing.example.com"] {
2646 let mut params = generated_certificate_params("invalid DNS target", false);
2647 let mut san = vec![
2648 0x30,
2649 u8::try_from(dns_name.len() + 2).expect("SAN length"),
2650 0x82,
2651 u8::try_from(dns_name.len()).expect("DNS length"),
2652 ];
2653 san.extend_from_slice(dns_name.as_bytes());
2654 params
2655 .custom_extensions
2656 .push(rcgen::CustomExtension::from_oid_content(
2657 &[2, 5, 29, 17],
2658 san,
2659 ));
2660 let leaf = params
2661 .signed_by(&rcgen::KeyPair::generate().expect("target key"), &root)
2662 .expect("target certificate");
2663 assert!(
2664 matches!(
2665 verify_generated_path(
2666 vec![leaf.der().to_vec(), root.der().to_vec()],
2667 root.der().to_vec()
2668 ),
2669 Err(X509ChainError::InvalidDer {
2670 kind: "certificate DNS name",
2671 ..
2672 })
2673 ),
2674 "{dns_name}"
2675 );
2676 }
2677 }
2678
2679 #[test]
2680 fn name_constraint_matchers_cover_email_uri_and_ip_forms() {
2681 use der::asn1::{Ia5String, OctetString};
2682 use x509_cert::ext::pkix::{
2683 NameConstraints as Constraints, constraints::name::GeneralSubtree,
2684 name::GeneralName as Name,
2685 };
2686 let email = |value: &str| Name::Rfc822Name(Ia5String::new(value).expect("email fixture"));
2689 let uri = |value: &str| {
2690 Name::UniformResourceIdentifier(Ia5String::new(value).expect("URI fixture"))
2691 };
2692 let ip = |value: &[u8]| Name::IpAddress(OctetString::new(value).expect("IP fixture"));
2693 for (name, base, accepted) in [
2694 (email("ops@example.com"), email("example.com"), true),
2695 (email("ops@example.com"), email("ops@example.com"), true),
2696 (email("other@example.com"), email("ops@example.com"), false),
2697 (
2698 uri("https://user@api.example.com:8443/path"),
2699 uri(".example.com"),
2700 true,
2701 ),
2702 (
2703 uri("https://api.example.com/path"),
2704 uri(".example.com"),
2705 true,
2706 ),
2707 (
2708 ip(&[192, 0, 2, 42]),
2709 ip(&[192, 0, 2, 0, 255, 255, 255, 0]),
2710 true,
2711 ),
2712 (
2713 ip(&[192, 0, 3, 42]),
2714 ip(&[192, 0, 2, 0, 255, 255, 255, 0]),
2715 false,
2716 ),
2717 ] {
2718 let result = verify_generated_constraints(
2719 Constraints {
2720 permitted_subtrees: Some(vec![GeneralSubtree {
2721 base,
2722 minimum: 0,
2723 maximum: None,
2724 }]),
2725 excluded_subtrees: None,
2726 },
2727 name,
2728 );
2729 if accepted {
2730 result.expect("matching name must be accepted");
2731 } else {
2732 assert_eq!(
2733 result,
2734 Err(X509ChainError::NameConstraintViolation {
2735 position: 0,
2736 constraining_position: 1,
2737 })
2738 );
2739 }
2740 }
2741 let result = verify_generated_constraints(
2742 Constraints {
2743 permitted_subtrees: Some(vec![GeneralSubtree {
2744 base: uri(".example.com"),
2745 minimum: 0,
2746 maximum: None,
2747 }]),
2748 excluded_subtrees: None,
2749 },
2750 uri("https://user@other@example.com/path"),
2751 );
2752 assert_eq!(
2753 result,
2754 Err(X509ChainError::UnsupportedCriticalExtension {
2755 position: 0,
2756 oid: "2.5.29.17".into(),
2757 }),
2758 "a second userinfo delimiter must not expose a constraint-matchable host"
2759 );
2760 let result = verify_generated_constraints(
2761 Constraints {
2762 permitted_subtrees: Some(vec![GeneralSubtree {
2763 base: ip(&[192, 0, 2, 0, 255, 0, 255, 0]),
2764 minimum: 0,
2765 maximum: None,
2766 }]),
2767 excluded_subtrees: None,
2768 },
2769 ip(&[192, 0, 2, 42]),
2770 );
2771 assert_eq!(
2772 result,
2773 Err(X509ChainError::InvalidNameConstraints { position: 1 })
2774 );
2775 }
2776
2777 #[test]
2778 fn malformed_ip_name_constraints_fail_before_matching() {
2779 use der::asn1::OctetString;
2780 use x509_cert::ext::pkix::{
2781 NameConstraints as Constraints, constraints::name::GeneralSubtree,
2782 name::GeneralName as Name,
2783 };
2784 for malformed in [
2787 &[192, 0, 2, 0, 255, 255, 255][..],
2788 &[192, 0, 2, 0, 255, 0, 255, 0][..],
2789 ] {
2790 for permitted in [true, false] {
2791 let subtree = GeneralSubtree {
2792 base: Name::IpAddress(
2793 OctetString::new(malformed).expect("IP constraint fixture"),
2794 ),
2795 minimum: 0,
2796 maximum: None,
2797 };
2798 let constraints = Constraints {
2799 permitted_subtrees: permitted.then(|| vec![subtree.clone()]),
2800 excluded_subtrees: (!permitted).then(|| vec![subtree]),
2801 };
2802 assert_eq!(
2803 verify_generated_constraints(
2804 constraints,
2805 Name::IpAddress(OctetString::new([192, 0, 2, 42]).expect("IP SAN fixture")),
2806 ),
2807 Err(X509ChainError::InvalidNameConstraints { position: 1 })
2808 );
2809 }
2810 }
2811 }
2812
2813 #[test]
2814 fn malformed_string_name_constraints_fail_before_matching() {
2815 use der::asn1::Ia5String;
2816 use x509_cert::ext::pkix::{
2817 NameConstraints as Constraints, constraints::name::GeneralSubtree,
2818 name::GeneralName as Name,
2819 };
2820 let dns =
2821 |value: &str| Name::DnsName(Ia5String::new(value).expect("DNS constraint fixture"));
2822 let email = |value: &str| {
2823 Name::Rfc822Name(Ia5String::new(value).expect("email constraint fixture"))
2824 };
2825 let uri = |value: &str| {
2826 Name::UniformResourceIdentifier(Ia5String::new(value).expect("URI constraint fixture"))
2827 };
2828
2829 for malformed in [
2832 dns(""),
2833 dns("example..com"),
2834 email("@example.com"),
2835 email("bad..local@example.com"),
2836 uri("https://example.com"),
2837 ] {
2838 let constraints = Constraints {
2839 permitted_subtrees: None,
2840 excluded_subtrees: Some(vec![GeneralSubtree {
2841 base: malformed,
2842 minimum: 0,
2843 maximum: None,
2844 }]),
2845 };
2846 assert_eq!(
2847 verify_generated_constraints(constraints, dns("www.example.com")),
2848 Err(X509ChainError::InvalidNameConstraints { position: 1 })
2849 );
2850 }
2851
2852 for valid in [
2853 dns("example.com"),
2854 dns(".example.com"),
2855 email("ops@example.com"),
2856 email("example.com"),
2857 uri(".example.com"),
2858 ] {
2859 let constraints = Constraints {
2860 permitted_subtrees: Some(vec![GeneralSubtree {
2861 base: valid,
2862 minimum: 0,
2863 maximum: None,
2864 }]),
2865 excluded_subtrees: None,
2866 };
2867 verify_generated_constraints(constraints, dns("www.example.com"))
2868 .expect("valid string constraints must remain supported");
2869 }
2870 }
2871
2872 #[test]
2873 fn empty_name_constraint_collections_are_rejected() {
2874 use der::asn1::Ia5String;
2875 use x509_cert::ext::pkix::NameConstraints as EncodedNameConstraints;
2876
2877 for constraints in [
2880 EncodedNameConstraints {
2881 permitted_subtrees: None,
2882 excluded_subtrees: None,
2883 },
2884 EncodedNameConstraints {
2885 permitted_subtrees: Some(Vec::new()),
2886 excluded_subtrees: None,
2887 },
2888 EncodedNameConstraints {
2889 permitted_subtrees: None,
2890 excluded_subtrees: Some(Vec::new()),
2891 },
2892 ] {
2893 assert_eq!(
2894 verify_generated_constraints(
2895 constraints,
2896 x509_cert::ext::pkix::name::GeneralName::DnsName(
2897 Ia5String::new("www.example.com").expect("DNS SAN fixture")
2898 ),
2899 ),
2900 Err(X509ChainError::InvalidNameConstraints { position: 1 })
2901 );
2902 }
2903 }
2904
2905 #[test]
2906 fn unsupported_name_constraint_distances_fail_path_validation() {
2907 use der::{Encode as _, asn1::Ia5String};
2908 use x509_cert::ext::pkix::{
2909 NameConstraints as EncodedNameConstraints,
2910 constraints::name::GeneralSubtree as EncodedGeneralSubtree,
2911 name::GeneralName as EncodedGeneralName,
2912 };
2913
2914 for (permitted, minimum, maximum) in [
2918 (true, 1, None),
2919 (false, 1, None),
2920 (true, 0, Some(1)),
2921 (false, 0, Some(1)),
2922 ] {
2923 let dns_name = if permitted {
2924 "example.com"
2925 } else {
2926 "blocked.example.com"
2927 };
2928 let subtree = EncodedGeneralSubtree {
2929 base: EncodedGeneralName::DnsName(
2930 Ia5String::new(dns_name.as_bytes()).expect("valid DNS IA5String"),
2931 ),
2932 minimum,
2933 maximum,
2934 };
2935 let constraints = EncodedNameConstraints {
2936 permitted_subtrees: permitted.then(|| vec![subtree.clone()]),
2937 excluded_subtrees: (!permitted).then(|| vec![subtree]),
2938 };
2939 let mut extension = rcgen::CustomExtension::from_oid_content(
2940 &[2, 5, 29, 30],
2941 constraints
2942 .to_der()
2943 .expect("NameConstraints must encode as DER"),
2944 );
2945 extension.set_criticality(true);
2946
2947 let mut root_params = generated_certificate_params("distance authority", true);
2948 root_params.custom_extensions.push(extension);
2949 let root = rcgen::CertifiedIssuer::self_signed(
2950 root_params,
2951 rcgen::KeyPair::generate().expect("root key generation should succeed"),
2952 )
2953 .expect("constrained root should be self-signable");
2954 let leaf = rcgen::CertificateParams::new(vec!["www.example.com".into()])
2955 .expect("leaf DNS SAN should be valid")
2956 .signed_by(
2957 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2958 &root,
2959 )
2960 .expect("root should sign leaf certificate");
2961
2962 let outcome = verify_generated_path(
2963 vec![leaf.der().to_vec(), root.der().to_vec()],
2964 root.der().to_vec(),
2965 );
2966 assert!(
2967 matches!(
2968 outcome,
2969 Err(X509ChainError::InvalidNameConstraints { position: 1 })
2970 ),
2971 "{outcome:?}"
2972 );
2973 }
2974 }
2975
2976 #[test]
2977 fn name_constraints_cover_subject_email_and_directory_name() {
2978 let mut permitted_directory = rcgen::DistinguishedName::new();
2981 permitted_directory.push(rcgen::DnType::OrganizationName, "Example Corp");
2982 let mut root_params = generated_certificate_params("name authority", true);
2983 root_params.name_constraints = Some(rcgen::NameConstraints {
2984 permitted_subtrees: vec![
2985 rcgen::GeneralSubtree::Rfc822Name("example.com".into()),
2986 rcgen::GeneralSubtree::DirectoryName(permitted_directory),
2987 ],
2988 excluded_subtrees: Vec::new(),
2989 });
2990 let root = rcgen::CertifiedIssuer::self_signed(
2991 root_params,
2992 rcgen::KeyPair::generate().expect("root key generation should succeed"),
2993 )
2994 .expect("constrained root should be self-signable");
2995
2996 for (organization, email, accepted) in [
2997 ("Example Corp", "ops@example.com", true),
2998 ("Other Corp", "ops@example.com", false),
2999 ("Example Corp", "ops@example.net", false),
3000 ("Example Corp", "bad..local@example.com", false),
3001 ] {
3002 let mut leaf_params = generated_certificate_params("name-constrained leaf", false);
3003 leaf_params.distinguished_name = rcgen::DistinguishedName::new();
3004 leaf_params
3005 .distinguished_name
3006 .push(rcgen::DnType::OrganizationName, organization);
3007 leaf_params
3008 .distinguished_name
3009 .push(rcgen::DnType::CommonName, "name-constrained leaf");
3010 leaf_params.distinguished_name.push(
3011 rcgen::DnType::CustomDnType(vec![1, 2, 840, 113549, 1, 9, 1]),
3012 rcgen::DnValue::Ia5String(
3013 email
3014 .try_into()
3015 .expect("test email must be a valid IA5String"),
3016 ),
3017 );
3018 let leaf = leaf_params
3019 .signed_by(
3020 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3021 &root,
3022 )
3023 .expect("root should sign leaf certificate");
3024 let result = verify_generated_path(
3025 vec![leaf.der().to_vec(), root.der().to_vec()],
3026 root.der().to_vec(),
3027 );
3028 assert_eq!(
3029 result.is_ok(),
3030 accepted,
3031 "unexpected subject constraint result for {organization} / {email}: {result:?}",
3032 );
3033 }
3034 }
3035
3036 #[test]
3037 fn empty_subject_requires_a_critical_nonempty_san() {
3038 let root = rcgen::CertifiedIssuer::self_signed(
3039 generated_certificate_params("subject identity authority", true),
3040 rcgen::KeyPair::generate().expect("root key generation should succeed"),
3041 )
3042 .expect("root should be self-signable");
3043
3044 let mut missing_san = rcgen::CertificateParams::new(Vec::new())
3045 .expect("empty SAN list should produce certificate parameters");
3046 missing_san.distinguished_name = rcgen::DistinguishedName::new();
3047 let missing_san = missing_san
3048 .signed_by(
3049 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3050 &root,
3051 )
3052 .expect("test issuer should sign an empty-subject certificate");
3053
3054 let mut noncritical_san = rcgen::CertificateParams::new(Vec::new())
3055 .expect("empty SAN list should produce certificate parameters");
3056 noncritical_san.distinguished_name = rcgen::DistinguishedName::new();
3057 noncritical_san
3061 .custom_extensions
3062 .push(rcgen::CustomExtension::from_oid_content(
3063 &[2, 5, 29, 17],
3064 vec![0x30, 0x03, 0x82, 0x01, b'a'],
3065 ));
3066 let noncritical_san = noncritical_san
3067 .signed_by(
3068 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3069 &root,
3070 )
3071 .expect("test issuer should sign a non-critical-SAN certificate");
3072
3073 for leaf in [missing_san, noncritical_san] {
3074 let error = verify_generated_path(
3077 vec![leaf.der().to_vec(), root.der().to_vec()],
3078 root.der().to_vec(),
3079 )
3080 .expect_err("an empty subject without critical SAN must fail");
3081 let X509ChainError::Processing(error) = error else {
3082 panic!("expected located subject rejection: {error:?}");
3083 };
3084 assert_eq!(error.code(), structured_pkix::ErrorCode::MissingField);
3085 assert_eq!(error.field(), "certificate.tbsCertificate.subject");
3086 assert!(error.offset() > 0 && error.offset() < leaf.der().len() as u64);
3087 }
3088 }
3089
3090 #[test]
3091 fn empty_subject_with_critical_san_skips_directory_name_constraints() {
3092 let mut permitted_directory = rcgen::DistinguishedName::new();
3095 permitted_directory.push(rcgen::DnType::OrganizationName, "Example Corp");
3096 let mut root_params = generated_certificate_params("empty-subject authority", true);
3097 root_params.name_constraints = Some(rcgen::NameConstraints {
3098 permitted_subtrees: vec![rcgen::GeneralSubtree::DirectoryName(permitted_directory)],
3099 excluded_subtrees: Vec::new(),
3100 });
3101 let root = rcgen::CertifiedIssuer::self_signed(
3102 root_params,
3103 rcgen::KeyPair::generate().expect("root key generation should succeed"),
3104 )
3105 .expect("constrained root should be self-signable");
3106 let mut leaf_params = rcgen::CertificateParams::new(vec!["allowed.example".into()])
3107 .expect("DNS SAN should be valid");
3108 leaf_params.distinguished_name = rcgen::DistinguishedName::new();
3109 let leaf = leaf_params
3110 .signed_by(
3111 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3112 &root,
3113 )
3114 .expect("root should sign empty-subject leaf");
3115
3116 verify_generated_path(
3117 vec![leaf.der().to_vec(), root.der().to_vec()],
3118 root.der().to_vec(),
3119 )
3120 .expect("only present name forms should be constrained");
3121 }
3122
3123 #[test]
3124 fn malformed_general_names_in_san_fail_path_validation() {
3125 let root = rcgen::CertifiedIssuer::self_signed(
3126 generated_certificate_params("malformed-SAN root", true),
3127 rcgen::KeyPair::generate().expect("root key generation should succeed"),
3128 )
3129 .expect("root should be self-signable");
3130 for empty_subject in [true, false] {
3131 let mut leaf_params = generated_certificate_params("malformed-SAN leaf", false);
3132 if empty_subject {
3133 leaf_params.distinguished_name = rcgen::DistinguishedName::new();
3134 }
3135 let mut malformed_san = rcgen::CustomExtension::from_oid_content(
3137 &[2, 5, 29, 17],
3138 vec![0x30, 0x03, 0x82, 0x01, 0xff],
3139 );
3140 malformed_san.set_criticality(true);
3141 leaf_params.custom_extensions.push(malformed_san);
3142 let leaf = leaf_params
3143 .signed_by(
3144 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3145 &root,
3146 )
3147 .expect("root should sign malformed-SAN leaf");
3148
3149 assert!(matches!(
3150 verify_generated_path(
3151 vec![leaf.der().to_vec(), root.der().to_vec()],
3152 root.der().to_vec(),
3153 ),
3154 Err(X509ChainError::InvalidDer {
3155 kind: "certificate subject identity",
3156 ..
3157 })
3158 ));
3159 }
3160 }
3161
3162 #[test]
3163 fn typed_subject_alternative_names_require_rfc5280_syntax() {
3164 let root = rcgen::CertifiedIssuer::self_signed(
3165 generated_certificate_params("typed-SAN root", true),
3166 rcgen::KeyPair::generate().expect("root key generation should succeed"),
3167 )
3168 .expect("root should be self-signable");
3169
3170 for (tag, value) in [
3171 (0x81, b"operator@".as_slice()),
3172 (0x81, b"first..last@example.com".as_slice()),
3173 (0x86, b"relative/path".as_slice()),
3174 (0x86, b"https://example.com/%zz".as_slice()),
3175 (0x86, b"https://user@other@example.com/path".as_slice()),
3176 (0x86, b"file:///path".as_slice()),
3177 (0x87, &[192, 0, 2][..]),
3178 ] {
3179 for empty_subject in [false, true] {
3180 let mut leaf_params = generated_certificate_params("typed-SAN leaf", false);
3181 if empty_subject {
3182 leaf_params.distinguished_name = rcgen::DistinguishedName::new();
3183 }
3184 let mut san_der = vec![
3185 0x30,
3186 u8::try_from(value.len() + 2).expect("test SAN must fit short-form DER"),
3187 tag,
3188 u8::try_from(value.len()).expect("test GeneralName must fit short-form DER"),
3189 ];
3190 san_der.extend_from_slice(value);
3191 let mut san = rcgen::CustomExtension::from_oid_content(&[2, 5, 29, 17], san_der);
3192 san.set_criticality(true);
3193 leaf_params.custom_extensions.push(san);
3194 let leaf = leaf_params
3195 .signed_by(
3196 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3197 &root,
3198 )
3199 .expect("root should sign typed-SAN leaf");
3200
3201 assert!(matches!(
3202 verify_generated_path(
3203 vec![leaf.der().to_vec(), root.der().to_vec()],
3204 root.der().to_vec(),
3205 ),
3206 Err(X509ChainError::InvalidDer {
3207 kind: "certificate subject identity",
3208 ..
3209 })
3210 ));
3211 }
3212 }
3213
3214 for (tag, value) in [
3215 (0x81, b"operator@example.com".as_slice()),
3216 (0x81, b"operator@[192.0.2.1]".as_slice()),
3217 (0x81, b"operator@[IPv6:2001:db8::1]".as_slice()),
3218 (0x81, br#""operator desk"@example.com"#.as_slice()),
3219 (0x86, b"urn:example:operator".as_slice()),
3220 (
3221 0x86,
3222 b"https://operator@example.com:8443/path?q=1#id".as_slice(),
3223 ),
3224 (0x87, &[192, 0, 2, 1][..]),
3225 (
3226 0x87,
3227 &[0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1][..],
3228 ),
3229 ] {
3230 let mut leaf_params = rcgen::CertificateParams::new(Vec::new())
3231 .expect("empty SAN list should produce certificate parameters");
3232 leaf_params.distinguished_name = rcgen::DistinguishedName::new();
3233 let mut san_der = vec![
3234 0x30,
3235 u8::try_from(value.len() + 2).expect("test SAN must fit short-form DER"),
3236 tag,
3237 u8::try_from(value.len()).expect("test GeneralName must fit short-form DER"),
3238 ];
3239 san_der.extend_from_slice(value);
3240 let mut san = rcgen::CustomExtension::from_oid_content(&[2, 5, 29, 17], san_der);
3241 san.set_criticality(true);
3242 leaf_params.custom_extensions.push(san);
3243 let leaf = leaf_params
3244 .signed_by(
3245 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3246 &root,
3247 )
3248 .expect("root should sign typed-SAN leaf");
3249
3250 verify_generated_path(
3251 vec![leaf.der().to_vec(), root.der().to_vec()],
3252 root.der().to_vec(),
3253 )
3254 .unwrap_or_else(|error| {
3255 panic!(
3256 "valid typed SAN {tag:#x} {value:?} must satisfy an empty subject: {error:?}"
3257 )
3258 });
3259 }
3260 }
3261
3262 fn parsed_merlin_crl(der: &[u8]) -> CertificateRevocationList<'_> {
3263 CertificateRevocationList::from_der(der)
3264 .expect("modified Merlin CRL must remain parseable")
3265 .1
3266 }
3267
3268 fn merlin_crl_fixture() -> X509DataInfo {
3269 let xml = include_str!(
3270 "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt-crl.xml"
3271 );
3272 let document = Document::parse(xml).expect("tracked Merlin document must parse");
3273 let key_info_node = document
3274 .descendants()
3275 .find(|node| node.has_tag_name((XMLDSIG_NS, "KeyInfo")))
3276 .expect("tracked Merlin document contains KeyInfo");
3277 let key_info = parse_key_info(key_info_node).expect("tracked Merlin KeyInfo must parse");
3278 let KeyInfoSource::X509Data(info) = &key_info.sources[0] else {
3279 panic!("expected X509Data")
3280 };
3281 info.clone()
3282 }
3283
3284 fn merlin_crl_der() -> Vec<u8> {
3285 merlin_crl_fixture().crls[0].clone()
3286 }
3287
3288 struct FormatOnly;
3289 impl structured_pkix::path::Verify for FormatOnly {
3290 type Error = std::convert::Infallible;
3291 fn verify(
3292 &self,
3293 _: &structured_pkix::algorithm::AlgorithmIdentifier<'_>,
3294 _: structured_pkix::path::WorkingKey<'_>,
3295 _: &[u8],
3296 _: structured_asn1::BitString<'_>,
3297 _: &structured_asn1::memory::Memory,
3298 ) -> Result<structured_pkix::path::Verified, Self::Error> {
3299 Ok(structured_pkix::path::Verified::Yes)
3300 }
3301 }
3302
3303 fn validate_crl_extensions(bytes: &[u8], index: usize) -> Result<(), X509ChainError> {
3304 let session =
3307 super::super::pkix::CertificateSession::new(&crate::policy::ResourcePolicy::default());
3308 let crl = session
3309 .read_crl(bytes)
3310 .map_err(|_| X509ChainError::InvalidCrl(index))?;
3311 let memory = session.memory();
3312 for entry in crl.tbs().revoked_certificates() {
3313 validate_positive_serial_bytes(
3314 entry.serial().as_bytes(),
3315 "CRL revoked certificate serial",
3316 )
3317 .map_err(|_| X509ChainError::InvalidCrl(index))?;
3318 }
3319 if crl
3320 .tbs()
3321 .extensions()
3322 .get(structured_pkix::oid::DELTA_CRL_INDICATOR, memory)
3323 .map_err(|_| X509ChainError::InvalidCrl(index))?
3324 .is_some()
3325 {
3326 return Err(X509ChainError::InvalidCrl(index));
3327 }
3328 let mut fixture = merlin_crl_fixture();
3329 fixture.certificates.push(
3330 pem::parse(include_bytes!(
3331 "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/ca.pem"
3332 ))
3333 .expect("public Merlin issuer PEM")
3334 .into_contents(),
3335 );
3336 for encoded in &fixture.certificates {
3337 use der::{Decode as _, Encode as _, Tagged as _};
3340 let mut envelope =
3341 Vec::<der::asn1::Any>::from_der(encoded).expect("fixture certificate fields");
3342 let mut fields = Vec::<der::asn1::Any>::from_der(
3343 &envelope[0].to_der().expect("fixture TBS encoding"),
3344 )
3345 .expect("fixture TBS fields");
3346 fields.retain(|field| {
3347 !matches!(
3348 field.tag(),
3349 der::Tag::ContextSpecific {
3350 number: der::TagNumber(0 | 3),
3351 ..
3352 }
3353 )
3354 });
3355 envelope[0] =
3356 der::asn1::Any::from_der(&fields.to_der().expect("versionless TBS encoding"))
3357 .expect("versionless TBS sequence");
3358 let bytes = envelope.to_der().expect("versionless issuer encoding");
3359 let issuer = session.read(&bytes)?;
3360 if !crl
3361 .tbs()
3362 .issuer()
3363 .matches(issuer.tbs().subject(), memory)
3364 .map_err(|_| X509ChainError::InvalidCrl(index))?
3365 {
3366 continue;
3367 }
3368 let proof = crl
3369 .authenticate(
3370 &issuer,
3371 structured_pkix::path::WorkingKey::standing(issuer.tbs().public_key()),
3372 &FormatOnly,
3373 memory,
3374 )
3375 .map_err(|_| X509ChainError::InvalidCrl(index))?;
3376 let structured_pkix::crl::Authentication::Authenticated(proof) = proof else {
3377 return Err(X509ChainError::InvalidCrl(index));
3378 };
3379 return match proof
3380 .entry(&issuer, memory)
3381 .map_err(|_| X509ChainError::InvalidCrl(index))?
3382 {
3383 structured_pkix::crl::EntryLookup::Absent
3384 | structured_pkix::crl::EntryLookup::Found(_) => Ok(()),
3385 _ => Err(X509ChainError::InvalidCrl(index)),
3386 };
3387 }
3388 Err(X509ChainError::InvalidCrl(index))
3389 }
3390
3391 #[test]
3392 fn duplicate_crl_and_entry_extension_oids_fail_closed() {
3393 use der::{Decode as _, Encode as _};
3394 use x509_cert::crl::CertificateList;
3395
3396 let original = merlin_crl_der();
3397 let mut duplicate_crl: CertificateList =
3398 CertificateList::from_der(&original).expect("tracked Merlin CRL must decode");
3399 let extensions = duplicate_crl
3400 .tbs_cert_list
3401 .crl_extensions
3402 .as_mut()
3403 .expect("tracked Merlin CRL must contain extensions");
3404 extensions.push(extensions[0].clone());
3405 let duplicate_crl = duplicate_crl
3406 .to_der()
3407 .expect("duplicate CRL extension test vector must encode");
3408 assert_eq!(
3409 validate_crl_extensions(&duplicate_crl, 0),
3410 Err(X509ChainError::InvalidCrl(0))
3411 );
3412
3413 let mut duplicate_entry: CertificateList =
3414 CertificateList::from_der(&original).expect("tracked Merlin CRL must decode");
3415 let duplicate = duplicate_entry
3416 .tbs_cert_list
3417 .crl_extensions
3418 .as_ref()
3419 .and_then(|extensions| extensions.first())
3420 .expect("tracked Merlin CRL must contain an extension")
3421 .clone();
3422 let revoked = duplicate_entry
3423 .tbs_cert_list
3424 .revoked_certificates
3425 .as_mut()
3426 .and_then(|entries| entries.first_mut())
3427 .expect("tracked Merlin CRL must contain a revoked entry");
3428 revoked.crl_entry_extensions = Some(vec![duplicate.clone(), duplicate]);
3429 let duplicate_entry = duplicate_entry
3430 .to_der()
3431 .expect("duplicate entry extension test vector must encode");
3432 assert_eq!(
3433 validate_crl_extensions(&duplicate_entry, 0),
3434 Err(X509ChainError::InvalidCrl(0))
3435 );
3436 }
3437
3438 #[test]
3439 fn malformed_revoked_certificate_serials_fail_closed() {
3440 let original = merlin_crl_der();
3443 let serial = parsed_merlin_crl(&original)
3444 .iter_revoked_certificates()
3445 .next()
3446 .expect("tracked Merlin CRL must contain a revoked entry")
3447 .raw_serial()
3448 .to_vec();
3449 let offsets = original
3450 .windows(serial.len())
3451 .enumerate()
3452 .filter_map(|(offset, bytes)| (bytes == serial).then_some(offset))
3453 .collect::<Vec<_>>();
3454 assert_eq!(
3455 offsets.len(),
3456 1,
3457 "revoked serial fixture must be unambiguous"
3458 );
3459
3460 for replacement in [vec![0; serial.len()], {
3461 let mut negative = serial.clone();
3462 negative[0] = 0x80;
3463 negative
3464 }] {
3465 let mut malformed = original.clone();
3466 malformed[offsets[0]..offsets[0] + serial.len()].copy_from_slice(&replacement);
3467 assert_eq!(
3468 validate_crl_extensions(&malformed, 0),
3469 Err(X509ChainError::InvalidCrl(0))
3470 );
3471 }
3472 }
3473
3474 #[test]
3475 fn delta_crl_indicator_is_rejected_regardless_of_criticality() {
3476 use der::{Decode as _, Encode as _, asn1::OctetString};
3477 use x509_cert::{crl::CertificateList, ext::Extension};
3478
3479 let original = merlin_crl_der();
3480 for critical in [false, true] {
3481 let mut encoded: CertificateList =
3482 CertificateList::from_der(&original).expect("tracked Merlin CRL must decode");
3483 encoded
3484 .tbs_cert_list
3485 .crl_extensions
3486 .get_or_insert_default()
3487 .push(Extension {
3488 extn_id: der::asn1::ObjectIdentifier::new_unwrap("2.5.29.27"),
3489 critical,
3490 extn_value: OctetString::new([0x02, 0x01, 0x01])
3491 .expect("DER INTEGER extension payload must be valid"),
3492 });
3493 let encoded = encoded
3494 .to_der()
3495 .expect("delta CRL indicator test vector must encode");
3496 assert_eq!(
3497 validate_crl_extensions(&encoded, 0),
3498 Err(X509ChainError::InvalidCrl(0)),
3499 "delta CRL indicator criticality must not change unsupported semantics"
3500 );
3501 }
3502 }
3503
3504 #[test]
3505 fn remove_from_crl_is_rejected_in_a_complete_crl() {
3506 use der::{Decode as _, Encode as _, asn1::OctetString};
3507 use x509_cert::{crl::CertificateList, ext::Extension};
3508
3509 let original = merlin_crl_der();
3510 for (reason, accepted) in [(1_u8, true), (8_u8, false)] {
3511 let mut encoded: CertificateList =
3512 CertificateList::from_der(&original).expect("tracked Merlin CRL must decode");
3513 encoded.tbs_cert_list.crl_extensions = None;
3516 let revoked = encoded
3517 .tbs_cert_list
3518 .revoked_certificates
3519 .as_mut()
3520 .and_then(|entries| entries.first_mut())
3521 .expect("tracked Merlin CRL must contain a revoked entry");
3522 revoked
3523 .crl_entry_extensions
3524 .get_or_insert_default()
3525 .push(Extension {
3526 extn_id: der::asn1::ObjectIdentifier::new_unwrap("2.5.29.21"),
3527 critical: false,
3528 extn_value: OctetString::new([0x0a, 0x01, reason])
3529 .expect("DER ENUMERATED extension payload must be valid"),
3530 });
3531 let encoded = encoded
3532 .to_der()
3533 .expect("reason-code CRL test vector must encode");
3534 let result = validate_crl_extensions(&encoded, 0);
3535 if accepted {
3536 assert_eq!(result, Ok(()), "ordinary revocation reasons remain valid");
3537 } else {
3538 assert_eq!(result, Err(X509ChainError::InvalidCrl(0)));
3539 }
3540 }
3541 }
3542
3543 #[test]
3544 fn unevaluable_uri_names_fail_closed_for_both_constraint_forms() {
3545 use der::asn1::Ia5String;
3546 use x509_cert::ext::pkix::{
3547 NameConstraints as Constraints, constraints::name::GeneralSubtree,
3548 name::GeneralName as Name,
3549 };
3550
3551 let uri = || {
3554 Name::UniformResourceIdentifier(
3555 Ia5String::new("urn:example:opaque").expect("URI SAN fixture"),
3556 )
3557 };
3558 let base = || {
3559 Name::UniformResourceIdentifier(
3560 Ia5String::new(".example.com").expect("URI constraint fixture"),
3561 )
3562 };
3563 for constraints in [
3564 Constraints {
3565 permitted_subtrees: Some(vec![GeneralSubtree {
3566 base: base(),
3567 minimum: 0,
3568 maximum: None,
3569 }]),
3570 excluded_subtrees: None,
3571 },
3572 Constraints {
3573 permitted_subtrees: None,
3574 excluded_subtrees: Some(vec![GeneralSubtree {
3575 base: base(),
3576 minimum: 0,
3577 maximum: None,
3578 }]),
3579 },
3580 ] {
3581 assert_eq!(
3582 verify_generated_constraints(constraints, uri()),
3583 Err(X509ChainError::NameConstraintViolation {
3584 position: 0,
3585 constraining_position: 1,
3586 })
3587 );
3588 }
3589 }
3590
3591 #[test]
3592 fn unknown_critical_certificate_extension_fails_closed() {
3593 let root = rcgen::CertifiedIssuer::self_signed(
3594 generated_certificate_params("critical-extension root", true),
3595 rcgen::KeyPair::generate().expect("root key generation should succeed"),
3596 )
3597 .expect("root should be self-signable");
3598 let mut leaf_params = generated_certificate_params("critical-extension leaf", false);
3599 let mut extension =
3600 rcgen::CustomExtension::from_oid_content(&[1, 2, 3, 4], vec![0x05, 0x00]);
3601 extension.set_criticality(true);
3602 leaf_params.custom_extensions.push(extension);
3603 let leaf = leaf_params
3604 .signed_by(
3605 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3606 &root,
3607 )
3608 .expect("root should sign leaf certificate");
3609
3610 assert_eq!(
3611 verify_generated_path(
3612 vec![leaf.der().to_vec(), root.der().to_vec()],
3613 root.der().to_vec(),
3614 ),
3615 Err(X509ChainError::UnsupportedCriticalExtension {
3616 position: 0,
3617 oid: "1.2.3.4".into(),
3618 })
3619 );
3620 }
3621
3622 #[test]
3623 fn duplicate_certificate_extension_oids_fail_closed() {
3624 let root = rcgen::CertifiedIssuer::self_signed(
3627 generated_certificate_params("duplicate-extension root", true),
3628 rcgen::KeyPair::generate().expect("root key generation should succeed"),
3629 )
3630 .expect("root should be self-signable");
3631 let mut leaf_params = generated_certificate_params("duplicate-extension leaf", false);
3632 for _ in 0..2 {
3633 leaf_params
3634 .custom_extensions
3635 .push(rcgen::CustomExtension::from_oid_content(
3636 &[1, 2, 3, 4],
3637 vec![0x05, 0x00],
3638 ));
3639 }
3640 let leaf = leaf_params
3641 .signed_by(
3642 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3643 &root,
3644 )
3645 .expect("root should sign leaf certificate");
3646
3647 let error = verify_generated_path(
3650 vec![leaf.der().to_vec(), root.der().to_vec()],
3651 root.der().to_vec(),
3652 )
3653 .expect_err("duplicate extension must fail");
3654 let X509ChainError::Processing(error) = error else {
3655 panic!("expected located duplicate-extension rejection: {error:?}");
3656 };
3657 assert_eq!(error.code(), structured_pkix::ErrorCode::DuplicateExtension);
3658 assert_eq!(error.field(), "certificate.tbsCertificate.extensions");
3659 assert!(error.offset() > 0 && error.offset() < leaf.der().len() as u64);
3660 }
3661
3662 #[test]
3663 fn invalid_certificate_serial_numbers_fail_path_validation() {
3664 for serial in [vec![0], vec![1; 21]] {
3665 let root = rcgen::CertifiedIssuer::self_signed(
3666 generated_certificate_params("serial root", true),
3667 rcgen::KeyPair::generate().expect("root key generation should succeed"),
3668 )
3669 .expect("root should be self-signable");
3670 let mut leaf_params = generated_certificate_params("invalid serial leaf", false);
3671 leaf_params.serial_number = Some(rcgen::SerialNumber::from_slice(&serial));
3672 let leaf = leaf_params
3673 .signed_by(
3674 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3675 &root,
3676 )
3677 .expect("root should sign leaf certificate");
3678
3679 assert!(matches!(
3680 verify_generated_path(
3681 vec![leaf.der().to_vec(), root.der().to_vec()],
3682 root.der().to_vec(),
3683 ),
3684 Err(X509ChainError::InvalidDer {
3685 kind: "certificate serial number",
3686 ..
3687 })
3688 ));
3689 }
3690
3691 assert!(validate_positive_serial_bytes(&[0x80], "certificate serial number").is_err());
3692 assert!(validate_positive_serial_bytes(&[1; 20], "certificate serial number").is_ok());
3693
3694 let root = rcgen::CertifiedIssuer::self_signed(
3695 generated_certificate_params("serial-padding root", true),
3696 rcgen::KeyPair::generate().expect("root key generation should succeed"),
3697 )
3698 .expect("root should be self-signable");
3699 let mut leaf_params = generated_certificate_params("serial-padding leaf", false);
3700 leaf_params.serial_number = Some(rcgen::SerialNumber::from_slice(&[0x80; 20]));
3701 let leaf = leaf_params
3702 .signed_by(
3703 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3704 &root,
3705 )
3706 .expect("root should sign a maximum-magnitude serial");
3707
3708 verify_generated_path(
3709 vec![leaf.der().to_vec(), root.der().to_vec()],
3710 root.der().to_vec(),
3711 )
3712 .expect("a 20-octet magnitude may require a DER sign-padding octet");
3713 }
3714
3715 #[test]
3716 fn name_constraints_are_rejected_on_end_entity_certificates() {
3717 let root = rcgen::CertifiedIssuer::self_signed(
3720 generated_certificate_params("name-placement root", true),
3721 rcgen::KeyPair::generate().expect("root key generation should succeed"),
3722 )
3723 .expect("root should be self-signable");
3724 let mut leaf_params = generated_certificate_params("name-placement leaf", false);
3725 leaf_params.name_constraints = Some(rcgen::NameConstraints {
3726 permitted_subtrees: vec![rcgen::GeneralSubtree::DnsName("example.com".into())],
3727 excluded_subtrees: Vec::new(),
3728 });
3729 let leaf = leaf_params
3730 .signed_by(
3731 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3732 &root,
3733 )
3734 .expect("root should sign leaf certificate");
3735
3736 assert!(matches!(
3737 verify_generated_path(
3738 vec![leaf.der().to_vec(), root.der().to_vec()],
3739 root.der().to_vec(),
3740 ),
3741 Err(X509ChainError::InvalidNameConstraints { position: 0 })
3742 ));
3743 }
3744
3745 #[test]
3746 fn dsa_certificate_rejects_mismatched_inner_signature_algorithm() {
3747 let (_, mut certificate) = X509Certificate::from_der(include_bytes!(
3751 "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/balor.der"
3752 ))
3753 .expect("the tracked Merlin certificate is valid DER");
3754 let (_, issuer) = X509Certificate::from_der(include_bytes!(
3755 "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/ca.der"
3756 ))
3757 .expect("the tracked Merlin issuer is a DER certificate");
3758 assert!(verify_certificate_signature(&certificate, &issuer));
3759
3760 certificate.tbs_certificate.signature = issuer.public_key().algorithm.clone();
3761
3762 assert_ne!(
3763 certificate.tbs_certificate.signature,
3764 certificate.signature_algorithm
3765 );
3766 assert!(!verify_certificate_signature(&certificate, &issuer));
3767 }
3768
3769 #[test]
3770 fn dsa_sha1_crl_signature_uses_the_same_fallback_as_certificates() {
3771 let xml = include_str!(
3772 "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt-crl.xml"
3773 );
3774 let document = Document::parse(xml).expect("the tracked Merlin document is valid XML");
3775 let key_info_node = document
3776 .descendants()
3777 .find(|node| node.has_tag_name((XMLDSIG_NS, "KeyInfo")))
3778 .expect("the Merlin document contains KeyInfo");
3779 let key_info = parse_key_info(key_info_node).expect("the Merlin KeyInfo is valid");
3780 let KeyInfoSource::X509Data(info) = &key_info.sources[0] else {
3781 panic!("expected X509Data")
3782 };
3783 let (_, issuer) = X509Certificate::from_der(include_bytes!(
3784 "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/ca.der"
3785 ))
3786 .expect("the tracked Merlin issuer is a DER certificate");
3787 let (_, crl) = CertificateRevocationList::from_der(&info.crls[0])
3788 .expect("the tracked Merlin CRL is valid DER");
3789
3790 assert!(verify_crl_signature(&crl, &issuer));
3791 }
3792
3793 #[test]
3794 fn dsa_crl_rejects_mismatched_inner_signature_algorithm() {
3795 let xml = include_str!(
3796 "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt-crl.xml"
3797 );
3798 let document = Document::parse(xml).expect("the tracked Merlin document is valid XML");
3799 let key_info_node = document
3800 .descendants()
3801 .find(|node| node.has_tag_name((XMLDSIG_NS, "KeyInfo")))
3802 .expect("the Merlin document contains KeyInfo");
3803 let key_info = parse_key_info(key_info_node).expect("the Merlin KeyInfo is valid");
3804 let KeyInfoSource::X509Data(info) = &key_info.sources[0] else {
3805 panic!("expected X509Data")
3806 };
3807 let (_, issuer) = X509Certificate::from_der(include_bytes!(
3808 "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/ca.der"
3809 ))
3810 .expect("the tracked Merlin issuer is a DER certificate");
3811 let (_, mut crl) = CertificateRevocationList::from_der(&info.crls[0])
3812 .expect("the tracked Merlin CRL is valid DER");
3813 assert!(verify_crl_signature(&crl, &issuer));
3814
3815 crl.tbs_cert_list.signature = issuer.public_key().algorithm.clone();
3816
3817 assert_ne!(crl.tbs_cert_list.signature, crl.signature_algorithm);
3818 assert!(!verify_crl_signature(&crl, &issuer));
3819 }
3820}