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