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