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 .verify_x509_signature(algorithm, signed_data, signature_der, issuer_spki_der)
806 .map_err(Into::into)
807}
808
809fn x509_signature_algorithm(
810 identifier: &AlgorithmIdentifier<'_>,
811) -> Result<X509SignatureAlgorithm, X509ChainError> {
812 let oid = identifier.algorithm.to_id_string();
813 let algorithm = match oid.as_str() {
814 "1.2.840.10040.4.3" => X509SignatureAlgorithm::Dsa(super::DigestAlgorithm::Sha1),
815 "2.16.840.1.101.3.4.3.2" => X509SignatureAlgorithm::Dsa(super::DigestAlgorithm::Sha256),
816 "2.16.840.1.101.3.4.3.3" => X509SignatureAlgorithm::Dsa(super::DigestAlgorithm::Sha384),
817 "2.16.840.1.101.3.4.3.4" => X509SignatureAlgorithm::Dsa(super::DigestAlgorithm::Sha512),
818 "1.2.840.113549.1.1.5" | "1.3.14.3.2.29" => {
819 X509SignatureAlgorithm::RsaPkcs1v15(super::DigestAlgorithm::Sha1)
820 }
821 "1.2.840.113549.1.1.11" => {
822 X509SignatureAlgorithm::RsaPkcs1v15(super::DigestAlgorithm::Sha256)
823 }
824 "1.2.840.113549.1.1.12" => {
825 X509SignatureAlgorithm::RsaPkcs1v15(super::DigestAlgorithm::Sha384)
826 }
827 "1.2.840.113549.1.1.13" => {
828 X509SignatureAlgorithm::RsaPkcs1v15(super::DigestAlgorithm::Sha512)
829 }
830 "1.2.840.113549.1.1.10" => parse_rsa_pss_algorithm(identifier)?,
831 "1.2.840.10045.4.1" => X509SignatureAlgorithm::Ecdsa(super::DigestAlgorithm::Sha1),
832 "1.2.840.10045.4.3.2" => X509SignatureAlgorithm::Ecdsa(super::DigestAlgorithm::Sha256),
833 "1.2.840.10045.4.3.3" => X509SignatureAlgorithm::Ecdsa(super::DigestAlgorithm::Sha384),
834 "1.2.840.10045.4.3.4" => X509SignatureAlgorithm::Ecdsa(super::DigestAlgorithm::Sha512),
835 "1.3.101.112" => X509SignatureAlgorithm::Ed25519,
836 _ => return Err(X509ChainError::UnsupportedSignatureAlgorithm { oid }),
837 };
838 match &algorithm {
839 X509SignatureAlgorithm::Dsa(_)
840 | X509SignatureAlgorithm::Ecdsa(_)
841 | X509SignatureAlgorithm::Ed25519 => require_absent_signature_parameters(identifier)?,
842 X509SignatureAlgorithm::RsaPkcs1v15(_) => {
843 require_null_or_absent_signature_parameters(identifier)?;
844 }
845 X509SignatureAlgorithm::RsaPss { .. } => {}
846 }
847 Ok(algorithm)
848}
849
850fn require_absent_signature_parameters(
851 identifier: &AlgorithmIdentifier<'_>,
852) -> Result<(), X509ChainError> {
853 if identifier.parameters.is_some() {
854 return Err(invalid_signature_parameters(
855 identifier,
856 "parameters must be absent",
857 ));
858 }
859 Ok(())
860}
861
862fn require_null_or_absent_signature_parameters(
863 identifier: &AlgorithmIdentifier<'_>,
864) -> Result<(), X509ChainError> {
865 if identifier
866 .parameters
867 .as_ref()
868 .is_some_and(|parameters| parameters.tag() != x509_parser::asn1_rs::Tag::Null)
869 {
870 return Err(invalid_signature_parameters(
871 identifier,
872 "parameters must be NULL or absent",
873 ));
874 }
875 Ok(())
876}
877
878fn invalid_signature_parameters(
879 identifier: &AlgorithmIdentifier<'_>,
880 requirement: &str,
881) -> X509ChainError {
882 X509ChainError::InvalidDer {
883 kind: "X.509 signature AlgorithmIdentifier parameters",
884 message: format!("{}: {requirement}", identifier.algorithm),
885 }
886}
887
888fn parse_rsa_pss_algorithm(
889 identifier: &AlgorithmIdentifier<'_>,
890) -> Result<X509SignatureAlgorithm, X509ChainError> {
891 let parameters = identifier
892 .parameters
893 .as_ref()
894 .ok_or_else(|| X509ChainError::InvalidDer {
895 kind: "RSASSA-PSS parameters",
896 message: "missing parameters".into(),
897 })?;
898 let parameters = x509_parser::signature_algorithm::RsaSsaPssParams::try_from(parameters)
899 .map_err(|error| X509ChainError::InvalidDer {
900 kind: "RSASSA-PSS parameters",
901 message: error.to_string(),
902 })?;
903 if parameters.trailer_field() != 1 {
904 return Err(X509ChainError::InvalidDer {
905 kind: "RSASSA-PSS parameters",
906 message: "trailerField must be 1".into(),
907 });
908 }
909 let digest = x509_digest_algorithm(¶meters.hash_algorithm_oid().to_id_string())?;
910 let mask = parameters
911 .mask_gen_algorithm()
912 .map_err(|error| X509ChainError::InvalidDer {
913 kind: "RSASSA-PSS parameters",
914 message: error.to_string(),
915 })?;
916 if mask.mgf.to_id_string() != "1.2.840.113549.1.1.8" {
917 return Err(X509ChainError::UnsupportedSignatureAlgorithm {
918 oid: mask.mgf.to_id_string(),
919 });
920 }
921 let mgf_digest = x509_digest_algorithm(&mask.hash.to_id_string())?;
922 let salt_len =
923 usize::try_from(parameters.salt_length()).map_err(|_| X509ChainError::InvalidDer {
924 kind: "RSASSA-PSS parameters",
925 message: "saltLength does not fit this platform".into(),
926 })?;
927 Ok(X509SignatureAlgorithm::RsaPss {
928 digest,
929 mgf_digest,
930 salt_len,
931 })
932}
933
934fn x509_digest_algorithm(oid: &str) -> Result<super::DigestAlgorithm, X509ChainError> {
935 match oid {
936 "1.3.14.3.2.26" => Ok(super::DigestAlgorithm::Sha1),
937 "2.16.840.1.101.3.4.2.1" => Ok(super::DigestAlgorithm::Sha256),
938 "2.16.840.1.101.3.4.2.2" => Ok(super::DigestAlgorithm::Sha384),
939 "2.16.840.1.101.3.4.2.3" => Ok(super::DigestAlgorithm::Sha512),
940 _ => Err(X509ChainError::UnsupportedSignatureAlgorithm {
941 oid: oid.to_owned(),
942 }),
943 }
944}
945
946fn validate_leaf_key_usage(cert: &X509Certificate<'_>) -> Result<(), X509ChainError> {
947 if cert
949 .key_usage()
950 .map_err(|error| X509ChainError::InvalidDer {
951 kind: "certificate KeyUsage",
952 message: error.to_string(),
953 })?
954 .is_some_and(|usage| !usage.value.digital_signature() && !usage.value.non_repudiation())
955 {
956 return Err(X509ChainError::InvalidKeyUsage {
957 position: 0,
958 required: "digitalSignature or nonRepudiation",
959 });
960 }
961 Ok(())
962}
963
964fn validate_extended_key_usage(
965 cert: &X509Certificate<'_>,
966 position: usize,
967 effective_extended_key_usages: &mut Option<HashSet<ExtendedKeyPurpose>>,
968) -> Result<(), X509ChainError> {
969 let Some(usage) = cert
970 .extended_key_usage()
971 .map_err(|error| X509ChainError::InvalidDer {
972 kind: "certificate ExtendedKeyUsage",
973 message: error.to_string(),
974 })?
975 else {
976 return Ok(());
977 };
978 if usage.value.any {
979 return Ok(());
980 }
981 if let Some(effective) = effective_extended_key_usages {
982 effective.retain(|purpose| extended_key_usage_contains(usage.value, purpose));
983 if !effective.is_empty() {
984 return Ok(());
985 }
986 }
987 Err(X509ChainError::InvalidKeyUsage {
988 position,
989 required: "an approved extended key usage",
990 })
991}
992
993fn extended_key_usage_contains(
994 usage: &x509_parser::extensions::ExtendedKeyUsage<'_>,
995 purpose: &ExtendedKeyPurpose,
996) -> bool {
997 match purpose {
998 ExtendedKeyPurpose::ServerAuth => usage.server_auth,
999 ExtendedKeyPurpose::ClientAuth => usage.client_auth,
1000 ExtendedKeyPurpose::CodeSigning => usage.code_signing,
1001 ExtendedKeyPurpose::EmailProtection => usage.email_protection,
1002 ExtendedKeyPurpose::TimeStamping => usage.time_stamping,
1003 ExtendedKeyPurpose::OcspSigning => usage.ocsp_signing,
1004 ExtendedKeyPurpose::Other(arcs) => usage.other.iter().any(|oid| {
1005 let oid = oid.to_id_string();
1006 arcs.iter()
1007 .map(u64::to_string)
1008 .collect::<Vec<_>>()
1009 .join(".")
1010 == oid
1011 }),
1012 }
1013}
1014
1015fn parse_certificate(der: &[u8]) -> Result<X509Certificate<'_>, X509ChainError> {
1016 let (rest, cert) =
1017 X509Certificate::from_der(der).map_err(|error| X509ChainError::InvalidDer {
1018 kind: "certificate",
1019 message: error.to_string(),
1020 })?;
1021 if !rest.is_empty() {
1022 return Err(X509ChainError::InvalidDer {
1023 kind: "certificate",
1024 message: "trailing data".into(),
1025 });
1026 }
1027 Ok(cert)
1028}
1029
1030fn system_time_to_asn1(time: SystemTime) -> Result<ASN1Time, X509ChainError> {
1031 let seconds = time
1032 .duration_since(UNIX_EPOCH)
1033 .map_err(|_| X509ChainError::CertificateNotValid(0))?
1034 .as_secs();
1035 let timestamp = i64::try_from(seconds).map_err(|_| X509ChainError::CertificateNotValid(0))?;
1036 ASN1Time::from_timestamp(timestamp).map_err(|error| X509ChainError::InvalidDer {
1037 kind: "verification time",
1038 message: error.to_string(),
1039 })
1040}
1041
1042fn validate_ca_constraints(
1043 cert: &X509Certificate<'_>,
1044 position: usize,
1045) -> Result<(), X509ChainError> {
1046 let extension = cert
1047 .extensions()
1048 .iter()
1049 .find(|extension| {
1050 matches!(
1051 extension.parsed_extension(),
1052 ParsedExtension::BasicConstraints(_)
1053 )
1054 })
1055 .ok_or(X509ChainError::IssuerNotCa(position))?;
1056 let ParsedExtension::BasicConstraints(constraints) = extension.parsed_extension() else {
1057 unreachable!("extension was selected by parsed type")
1058 };
1059 if !constraints.ca {
1060 return Err(X509ChainError::IssuerNotCa(position));
1061 }
1062 if cert
1068 .key_usage()
1069 .map_err(|error| X509ChainError::InvalidDer {
1070 kind: "certificate KeyUsage",
1071 message: error.to_string(),
1072 })?
1073 .is_some_and(|usage| !usage.value.key_cert_sign())
1074 {
1075 return Err(X509ChainError::InvalidKeyUsage {
1076 position,
1077 required: "keyCertSign",
1078 });
1079 }
1080
1081 Ok(())
1082}
1083
1084fn basic_constraints(
1085 cert: &X509Certificate<'_>,
1086) -> Option<x509_parser::extensions::BasicConstraints> {
1087 cert.extensions()
1088 .iter()
1089 .find_map(|extension| match extension.parsed_extension() {
1090 ParsedExtension::BasicConstraints(value) => Some(value.clone()),
1091 _ => None,
1092 })
1093}
1094
1095fn validate_path_length_constraints(path: &[X509Certificate<'_>]) -> Result<(), X509ChainError> {
1096 for (position, cert) in path.iter().enumerate().skip(1) {
1097 let Some(limit) = basic_constraints(cert).and_then(|value| value.path_len_constraint)
1098 else {
1099 continue;
1100 };
1101 let subordinate_ca_count = path[1..position]
1102 .iter()
1103 .filter(|subordinate| {
1104 basic_constraints(subordinate).is_some_and(|value| value.ca)
1105 && !certificate_names_equal(subordinate.subject(), subordinate.issuer())
1106 })
1107 .count();
1108 if subordinate_ca_count > limit as usize {
1109 return Err(X509ChainError::PathLengthExceeded { position, limit });
1110 }
1111 }
1112 Ok(())
1113}
1114
1115fn validate_critical_extensions(
1116 cert: &X509Certificate<'_>,
1117 position: usize,
1118) -> Result<(), X509ChainError> {
1119 for extension in cert
1120 .extensions()
1121 .iter()
1122 .filter(|extension| extension.critical)
1123 {
1124 let oid = extension.oid.to_id_string();
1125 if !matches!(
1126 oid.as_str(),
1127 "2.5.29.15" | "2.5.29.17" | "2.5.29.19" | "2.5.29.30" | "2.5.29.37"
1128 ) {
1129 return Err(X509ChainError::UnsupportedCriticalExtension { position, oid });
1130 }
1131 if matches!(
1132 extension.parsed_extension(),
1133 ParsedExtension::UnsupportedExtension { .. }
1134 | ParsedExtension::ParseError { .. }
1135 | ParsedExtension::Unparsed
1136 ) {
1137 return Err(X509ChainError::UnsupportedCriticalExtension { position, oid });
1138 }
1139 }
1140 Ok(())
1141}
1142
1143fn validate_name_constraints(path: &[X509Certificate<'_>]) -> Result<(), X509ChainError> {
1144 for (position, certificate) in path.iter().enumerate() {
1145 if let Some(extension) = certificate
1146 .extensions()
1147 .iter()
1148 .find(|extension| extension.oid.to_id_string() == "2.5.29.30")
1149 && (position == 0 || !extension.critical)
1150 {
1151 return Err(X509ChainError::InvalidNameConstraints { position });
1152 }
1153 }
1154 for (constraining_position, issuer) in path.iter().enumerate().skip(1) {
1155 let Some(extension) = issuer
1156 .extensions()
1157 .iter()
1158 .find(|extension| extension.oid.to_id_string() == "2.5.29.30")
1159 else {
1160 continue;
1161 };
1162 let ParsedExtension::NameConstraints(constraints) = extension.parsed_extension() else {
1163 continue;
1164 };
1165 validate_name_constraints_der(extension.value, constraining_position)?;
1166 ensure_supported_name_constraints(constraints, constraining_position)?;
1167 for (position, subordinate) in path[..constraining_position].iter().enumerate() {
1168 if position != 0 && certificate_names_equal(subordinate.subject(), subordinate.issuer())
1171 {
1172 continue;
1173 }
1174 validate_certificate_names(subordinate, constraints, position, constraining_position)?;
1175 }
1176 }
1177 Ok(())
1178}
1179
1180fn validate_name_constraints_der(
1181 extension_der: &[u8],
1182 position: usize,
1183) -> Result<(), X509ChainError> {
1184 use der::Decode as _;
1185
1186 let constraints =
1190 x509_cert::ext::pkix::NameConstraints::from_der(extension_der).map_err(|error| {
1191 X509ChainError::InvalidDer {
1192 kind: "NameConstraints",
1193 message: error.to_string(),
1194 }
1195 })?;
1196 if constraints.permitted_subtrees.is_none() && constraints.excluded_subtrees.is_none()
1197 || constraints
1198 .permitted_subtrees
1199 .as_ref()
1200 .is_some_and(Vec::is_empty)
1201 || constraints
1202 .excluded_subtrees
1203 .as_ref()
1204 .is_some_and(Vec::is_empty)
1205 {
1206 return Err(X509ChainError::InvalidNameConstraints { position });
1207 }
1208 let unsupported = constraints
1209 .permitted_subtrees
1210 .iter()
1211 .flatten()
1212 .chain(constraints.excluded_subtrees.iter().flatten())
1213 .any(|subtree| subtree.minimum != 0 || subtree.maximum.is_some());
1214 if unsupported {
1215 return Err(X509ChainError::InvalidNameConstraints { position });
1216 }
1217 Ok(())
1218}
1219
1220fn ensure_supported_name_constraints(
1221 constraints: &NameConstraints<'_>,
1222 position: usize,
1223) -> Result<(), X509ChainError> {
1224 for subtree in constraints
1225 .permitted_subtrees
1226 .iter()
1227 .flatten()
1228 .chain(constraints.excluded_subtrees.iter().flatten())
1229 {
1230 match &subtree.base {
1231 GeneralName::DNSName(value) | GeneralName::URI(value) => {
1232 validate_dns_name_constraint(value)?;
1233 }
1234 GeneralName::RFC822Name(value) => validate_email_name_constraint(value)?,
1235 GeneralName::IPAddress(bytes) => {
1236 validate_ip_name_constraint(bytes)?;
1237 }
1238 _ => {}
1239 }
1240 if matches!(
1241 subtree.base,
1242 GeneralName::OtherName(..)
1243 | GeneralName::X400Address(..)
1244 | GeneralName::EDIPartyName(..)
1245 | GeneralName::RegisteredID(..)
1246 | GeneralName::Invalid(..)
1247 ) {
1248 return Err(X509ChainError::UnsupportedCriticalExtension {
1249 position,
1250 oid: "2.5.29.30".into(),
1251 });
1252 }
1253 }
1254 Ok(())
1255}
1256
1257fn validate_email_name_constraint(value: &str) -> Result<(), X509ChainError> {
1258 if value.contains('@') {
1259 if !mailbox_has_valid_syntax(value) {
1260 return Err(invalid_string_name_constraint(value));
1261 }
1262 Ok(())
1263 } else {
1264 validate_dns_name_constraint(value)
1265 }
1266}
1267
1268fn validate_dns_name_constraint(value: &str) -> Result<(), X509ChainError> {
1269 if !dns_name_has_valid_syntax(value, true) {
1270 return Err(invalid_string_name_constraint(value));
1271 }
1272 Ok(())
1273}
1274
1275fn validate_rfc5280_dns_name(value: &str) -> Result<(), X509ChainError> {
1276 if !dns_name_has_valid_syntax(value, false) {
1277 return Err(X509ChainError::InvalidDer {
1278 kind: "certificate DNS name",
1279 message: format!("invalid RFC 5280 dNSName: {value:?}"),
1280 });
1281 }
1282 Ok(())
1283}
1284
1285fn dns_name_has_valid_syntax(value: &str, allow_leading_dot: bool) -> bool {
1286 let domain = if allow_leading_dot {
1287 value.strip_prefix('.').unwrap_or(value)
1288 } else {
1289 value
1290 };
1291 if domain.is_empty()
1292 || domain.len() > 253
1293 || domain.split('.').any(|label| {
1294 label.is_empty()
1295 || label.len() > 63
1296 || !label
1297 .bytes()
1298 .all(|byte| byte.is_ascii_alphanumeric() || byte == b'-')
1299 || !label
1300 .as_bytes()
1301 .first()
1302 .is_some_and(u8::is_ascii_alphanumeric)
1303 || !label
1304 .as_bytes()
1305 .last()
1306 .is_some_and(u8::is_ascii_alphanumeric)
1307 })
1308 {
1309 return false;
1310 }
1311 true
1312}
1313
1314fn invalid_string_name_constraint(value: &str) -> X509ChainError {
1315 X509ChainError::InvalidDer {
1316 kind: "string name constraint",
1317 message: format!("invalid RFC 5280 string name constraint: {value:?}"),
1318 }
1319}
1320
1321fn validate_certificate_names(
1322 certificate: &X509Certificate<'_>,
1323 constraints: &NameConstraints<'_>,
1324 position: usize,
1325 constraining_position: usize,
1326) -> Result<(), X509ChainError> {
1327 if certificate.subject().iter().next().is_some() {
1328 let subject = GeneralName::DirectoryName(certificate.subject().clone());
1329 validate_general_name(&subject, constraints, position, constraining_position)?;
1330 }
1331 for attribute in certificate.subject().iter_email() {
1332 let email = attribute
1333 .as_str()
1334 .map_err(|error| X509ChainError::InvalidDer {
1335 kind: "certificate subject emailAddress",
1336 message: error.to_string(),
1337 })?;
1338 validate_general_name(
1339 &GeneralName::RFC822Name(email),
1340 constraints,
1341 position,
1342 constraining_position,
1343 )?;
1344 }
1345 if let Some(names) =
1346 certificate
1347 .extensions()
1348 .iter()
1349 .find_map(|extension| match extension.parsed_extension() {
1350 ParsedExtension::SubjectAlternativeName(value) => Some(&value.general_names),
1351 _ => None,
1352 })
1353 {
1354 for name in names {
1355 validate_general_name(name, constraints, position, constraining_position)?;
1356 }
1357 }
1358 Ok(())
1359}
1360
1361fn validate_general_name(
1362 name: &GeneralName<'_>,
1363 constraints: &NameConstraints<'_>,
1364 position: usize,
1365 constraining_position: usize,
1366) -> Result<(), X509ChainError> {
1367 let permitted = constraints
1368 .permitted_subtrees
1369 .iter()
1370 .flatten()
1371 .filter(|subtree| general_names_have_same_form(name, &subtree.base));
1372 let mut has_permitted_form = false;
1373 let mut matches_permitted = false;
1374 for subtree in permitted {
1375 has_permitted_form = true;
1376 matches_permitted |=
1377 general_name_within_subtree(name, &subtree.base)? == NameConstraintMatch::Match;
1378 }
1379 let excluded = constraints
1380 .excluded_subtrees
1381 .iter()
1382 .flatten()
1383 .filter(|subtree| general_names_have_same_form(name, &subtree.base))
1384 .try_fold(false, |rejected, subtree| {
1385 general_name_within_subtree(name, &subtree.base)
1386 .map(|current| rejected || current != NameConstraintMatch::NoMatch)
1387 })?;
1388 if excluded || (has_permitted_form && !matches_permitted) {
1389 return Err(X509ChainError::NameConstraintViolation {
1390 position,
1391 constraining_position,
1392 });
1393 }
1394 Ok(())
1395}
1396
1397fn general_names_have_same_form(left: &GeneralName<'_>, right: &GeneralName<'_>) -> bool {
1398 matches!(
1399 (left, right),
1400 (GeneralName::RFC822Name(_), GeneralName::RFC822Name(_))
1401 | (GeneralName::DNSName(_), GeneralName::DNSName(_))
1402 | (GeneralName::DirectoryName(_), GeneralName::DirectoryName(_))
1403 | (GeneralName::URI(_), GeneralName::URI(_))
1404 | (GeneralName::IPAddress(_), GeneralName::IPAddress(_))
1405 )
1406}
1407
1408#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1409enum NameConstraintMatch {
1410 Match,
1411 NoMatch,
1412 Unevaluable,
1413}
1414
1415impl From<bool> for NameConstraintMatch {
1416 fn from(matched: bool) -> Self {
1417 if matched { Self::Match } else { Self::NoMatch }
1418 }
1419}
1420
1421fn general_name_within_subtree(
1422 name: &GeneralName<'_>,
1423 subtree: &GeneralName<'_>,
1424) -> Result<NameConstraintMatch, X509ChainError> {
1425 Ok(match (name, subtree) {
1426 (GeneralName::DNSName(name), GeneralName::DNSName(subtree)) => {
1427 dns_name_within_subtree(name, subtree, true).into()
1428 }
1429 (GeneralName::RFC822Name(name), GeneralName::RFC822Name(subtree)) => {
1430 email_within_subtree(name, subtree).into()
1431 }
1432 (GeneralName::DirectoryName(name), GeneralName::DirectoryName(subtree)) => {
1433 let name = x509_name_to_rfc4514(name).map_err(|error| X509ChainError::InvalidDer {
1434 kind: "certificate name constraint",
1435 message: error.to_string(),
1436 })?;
1437 let subtree =
1438 x509_name_to_rfc4514(subtree).map_err(|error| X509ChainError::InvalidDer {
1439 kind: "certificate name constraint",
1440 message: error.to_string(),
1441 })?;
1442 distinguished_name_within_subtree(&name, &subtree).into()
1443 }
1444 (GeneralName::URI(name), GeneralName::URI(subtree)) => uri_host(name)
1445 .map_or(NameConstraintMatch::Unevaluable, |host| {
1446 dns_name_within_subtree(host, subtree, false).into()
1447 }),
1448 (GeneralName::IPAddress(name), GeneralName::IPAddress(subtree)) => {
1449 ip_address_within_subtree(name, subtree)?.into()
1450 }
1451 _ => NameConstraintMatch::NoMatch,
1452 })
1453}
1454
1455fn dns_name_within_subtree(name: &str, subtree: &str, include_subdomains: bool) -> bool {
1456 let name = name.trim_end_matches('.');
1457 let subtree = subtree.trim_end_matches('.');
1458 if let Some(domain) = subtree.strip_prefix('.') {
1459 return name.len() > domain.len()
1460 && name.as_bytes()[name.len() - domain.len() - 1] == b'.'
1461 && name[name.len() - domain.len()..].eq_ignore_ascii_case(domain);
1462 }
1463 name.eq_ignore_ascii_case(subtree)
1464 || (include_subdomains
1465 && name.len() > subtree.len()
1466 && name.as_bytes()[name.len() - subtree.len() - 1] == b'.'
1467 && name[name.len() - subtree.len()..].eq_ignore_ascii_case(subtree))
1468}
1469
1470fn email_within_subtree(name: &str, subtree: &str) -> bool {
1471 let Some((local, domain)) = name.rsplit_once('@') else {
1472 return false;
1473 };
1474 if let Some((expected_local, expected_domain)) = subtree.rsplit_once('@') {
1475 return local == expected_local && domain.eq_ignore_ascii_case(expected_domain);
1476 }
1477 dns_name_within_subtree(domain, subtree, false)
1478}
1479
1480fn uri_host(uri: &str) -> Option<&str> {
1481 let authority = uri.split_once("://")?.1;
1482 let authority = authority.split(['/', '?', '#']).next()?;
1483 match parse_uri_authority_host(authority)? {
1484 UriAuthorityHost::Dns(host) => Some(host),
1485 UriAuthorityHost::Ip => None,
1486 }
1487}
1488
1489fn ip_address_within_subtree(address: &[u8], subtree: &[u8]) -> Result<bool, X509ChainError> {
1490 if !matches!(address.len(), 4 | 16) {
1491 return Err(X509ChainError::InvalidDer {
1492 kind: "IP subject alternative name",
1493 message: format!("expected 4 or 16 octets, got {}", address.len()),
1494 });
1495 }
1496 let (network, mask) = validate_ip_name_constraint(subtree)?;
1497 if network.len() != address.len() {
1498 return Ok(false);
1499 }
1500 Ok(address
1501 .iter()
1502 .zip(network)
1503 .zip(mask)
1504 .all(|((address, network), mask)| address & mask == network & mask))
1505}
1506
1507fn validate_ip_name_constraint(subtree: &[u8]) -> Result<(&[u8], &[u8]), X509ChainError> {
1508 if !matches!(subtree.len(), 8 | 32) {
1509 return Err(X509ChainError::InvalidDer {
1510 kind: "IP name constraint",
1511 message: format!("expected 8 or 32 octets, got {}", subtree.len()),
1512 });
1513 }
1514 let (network, mask) = subtree.split_at(subtree.len() / 2);
1515 if !ip_mask_is_contiguous(mask) {
1516 return Err(X509ChainError::InvalidDer {
1517 kind: "IP name constraint",
1518 message: "network mask is not contiguous".into(),
1519 });
1520 }
1521 Ok((network, mask))
1522}
1523
1524fn ip_mask_is_contiguous(mask: &[u8]) -> bool {
1525 let mut zero_seen = false;
1526 for byte in mask {
1527 for bit in (0..8).rev() {
1528 let set = byte & (1 << bit) != 0;
1529 if zero_seen && set {
1530 return false;
1531 }
1532 zero_seen |= !set;
1533 }
1534 }
1535 true
1536}
1537
1538fn certificate_subject_key_identifier<'a>(
1539 certificate: &'a X509Certificate<'a>,
1540) -> Option<&'a [u8]> {
1541 certificate
1542 .extensions()
1543 .iter()
1544 .find_map(|extension| match extension.parsed_extension() {
1545 ParsedExtension::SubjectKeyIdentifier(identifier) => Some(identifier.0),
1546 _ => None,
1547 })
1548}
1549
1550fn crl_authority_key_matches(
1551 crl: &CertificateRevocationList<'_>,
1552 issuer: &X509Certificate<'_>,
1553) -> Result<Option<bool>, X509ChainError> {
1554 let authority_key = crl
1555 .extensions()
1556 .iter()
1557 .find(|extension| extension.oid.to_id_string() == "2.5.29.35")
1558 .map(|extension| match extension.parsed_extension() {
1559 ParsedExtension::AuthorityKeyIdentifier(identifier) => {
1560 Ok(identifier.key_identifier.as_ref().map(|key| key.0))
1561 }
1562 _ => Err(X509ChainError::InvalidDer {
1563 kind: "CRL AuthorityKeyIdentifier",
1564 message: "extension could not be decoded".into(),
1565 }),
1566 })
1567 .transpose()?
1568 .flatten();
1569 Ok(authority_key
1570 .zip(certificate_subject_key_identifier(issuer))
1571 .map(|(authority, subject)| authority == subject))
1572}
1573
1574fn validate_crl_extensions(
1575 crl: &CertificateRevocationList<'_>,
1576 crl_index: usize,
1577) -> Result<(), X509ChainError> {
1578 validate_crl_extension_uniqueness(crl, crl_index)?;
1579 validate_crl_extension_semantics(crl, crl_index)
1580}
1581
1582fn validate_crl_extension_uniqueness(
1583 crl: &CertificateRevocationList<'_>,
1584 crl_index: usize,
1585) -> Result<(), X509ChainError> {
1586 crl.tbs_cert_list
1587 .extensions_map()
1588 .map_err(|_| X509ChainError::InvalidCrl(crl_index))?;
1589 for revoked in crl.iter_revoked_certificates() {
1590 validate_positive_serial_bytes(revoked.raw_serial(), "CRL revoked certificate serial")
1591 .map_err(|_| X509ChainError::InvalidCrl(crl_index))?;
1592 revoked
1593 .extensions_map()
1594 .map_err(|_| X509ChainError::InvalidCrl(crl_index))?;
1595 }
1596 Ok(())
1597}
1598
1599fn validate_crl_extension_semantics(
1600 crl: &CertificateRevocationList<'_>,
1601 crl_index: usize,
1602) -> Result<(), X509ChainError> {
1603 for extension in crl.extensions() {
1604 let oid = extension.oid.to_id_string();
1605 if matches!(oid.as_str(), "2.5.29.27" | "2.5.29.28")
1609 || (extension.critical && oid != "2.5.29.35")
1610 {
1611 return Err(X509ChainError::InvalidCrl(crl_index));
1612 }
1613 if oid == "2.5.29.35"
1614 && !matches!(
1615 extension.parsed_extension(),
1616 ParsedExtension::AuthorityKeyIdentifier(_)
1617 )
1618 {
1619 return Err(X509ChainError::InvalidCrl(crl_index));
1620 }
1621 }
1622 for revoked in crl.iter_revoked_certificates() {
1623 for extension in revoked.extensions() {
1624 let oid = extension.oid.to_id_string();
1625 let invalid_reason = oid == "2.5.29.21"
1629 && !matches!(
1630 extension.parsed_extension(),
1631 ParsedExtension::ReasonCode(code)
1632 if *code != x509_parser::x509::ReasonCode::RemoveFromCRL
1633 );
1634 if oid == "2.5.29.29" || extension.critical || invalid_reason {
1635 return Err(X509ChainError::InvalidCrl(crl_index));
1636 }
1637 }
1638 }
1639 Ok(())
1640}
1641
1642fn verify_crls(
1643 path: &[X509Certificate<'_>],
1644 crl_der: &[Vec<u8>],
1645 verification_time: ASN1Time,
1646 provider: &dyn crate::provider::CryptoProvider,
1647) -> Result<(), X509ChainError> {
1648 let crls = crl_der
1649 .iter()
1650 .enumerate()
1651 .map(|(idx, der)| {
1652 let (rest, crl) = CertificateRevocationList::from_der(der).map_err(|error| {
1653 X509ChainError::InvalidDer {
1654 kind: "CRL",
1655 message: error.to_string(),
1656 }
1657 })?;
1658 if !rest.is_empty() {
1659 return Err(X509ChainError::InvalidDer {
1660 kind: "CRL",
1661 message: "trailing data".into(),
1662 });
1663 }
1664 Ok((idx, crl))
1665 })
1666 .collect::<Result<Vec<_>, _>>()?;
1667
1668 for (position, cert) in path.iter().enumerate().take(path.len().saturating_sub(1)) {
1669 let issuer = &path[position + 1];
1670 for (crl_index, crl) in crls
1671 .iter()
1672 .filter(|(_, crl)| certificate_names_equal(crl.issuer(), cert.issuer()))
1673 {
1674 validate_crl_extension_uniqueness(crl, *crl_index)?;
1677 let authority_key_match = crl_authority_key_matches(crl, issuer)?;
1678 if authority_key_match == Some(false) {
1679 continue;
1680 }
1681 if !verify_crl_signature_with_provider(crl, issuer, provider)? {
1682 if authority_key_match == Some(true) {
1683 return Err(X509ChainError::InvalidCrl(*crl_index));
1684 }
1685 continue;
1686 }
1687 validate_crl_extensions(crl, *crl_index)?;
1690 if issuer
1691 .key_usage()
1692 .map_err(|error| X509ChainError::InvalidDer {
1693 kind: "certificate KeyUsage",
1694 message: error.to_string(),
1695 })?
1696 .is_some_and(|usage| !usage.value.crl_sign())
1697 {
1698 return Err(X509ChainError::InvalidKeyUsage {
1699 position: position + 1,
1700 required: "cRLSign",
1701 });
1702 }
1703 let time_valid = crl.next_update().is_some_and(|next| {
1706 crl.last_update() <= verification_time && verification_time <= next
1707 });
1708 if !time_valid {
1709 return Err(X509ChainError::InvalidCrl(*crl_index));
1710 }
1711 if crl.iter_revoked_certificates().any(|revoked| {
1712 revoked.raw_serial() == cert.raw_serial()
1713 && revoked.revocation_date <= verification_time
1714 }) {
1715 return Err(X509ChainError::Revoked(position));
1716 }
1717 }
1718 }
1719 Ok(())
1720}
1721
1722#[cfg(test)]
1723mod tests {
1724 use std::str::FromStr as _;
1725
1726 use super::*;
1727 use crate::xmldsig::{KeyInfoSource, parse::XMLDSIG_NS, parse_key_info};
1728 use p256::pkcs8::EncodePublicKey;
1729 use roxmltree::Document;
1730 use sha2::{Digest, Sha256, Sha384};
1731 use signature::hazmat::PrehashSigner;
1732 use std::time::Duration;
1733 use x509_parser::oid_registry::{OID_SIG_ECDSA_WITH_SHA256, OID_SIG_ECDSA_WITH_SHA384, Oid};
1734
1735 fn generated_certificate_params(common_name: &str, is_ca: bool) -> rcgen::CertificateParams {
1736 let mut params = rcgen::CertificateParams::new(Vec::new())
1737 .expect("empty SAN list should produce valid certificate parameters");
1738 params
1739 .distinguished_name
1740 .push(rcgen::DnType::CommonName, common_name);
1741 if is_ca {
1742 params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
1743 params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
1744 }
1745 params
1746 }
1747
1748 fn verify_generated_path(
1749 certificates: Vec<Vec<u8>>,
1750 trusted_anchor: Vec<u8>,
1751 ) -> Result<(), X509ChainError> {
1752 verify_generated_path_with_eku(certificates, trusted_anchor, None)
1753 }
1754
1755 fn verify_generated_path_with_eku(
1756 certificates: Vec<Vec<u8>>,
1757 trusted_anchor: Vec<u8>,
1758 allowed_extended_key_usages: Option<&HashSet<ExtendedKeyPurpose>>,
1759 ) -> Result<(), X509ChainError> {
1760 let info = X509DataInfo {
1761 certificate_chain: (0..certificates.len()).collect(),
1762 certificates,
1763 ..X509DataInfo::default()
1764 };
1765 let anchors = vec![trusted_anchor];
1766 verify_x509_certificate_chain(
1767 &info,
1768 &X509ChainOptions {
1769 trusted_certs: &anchors,
1770 verification_time: SystemTime::now(),
1771 max_chain_depth: info.certificate_chain.len(),
1772 check_crls: false,
1773 allowed_extended_key_usages,
1774 rsa_keys: RsaKeyPolicy::default(),
1775 dsa_keys: DsaKeyPolicy::default(),
1776 },
1777 )
1778 }
1779
1780 #[test]
1781 fn noncritical_ca_basic_constraints_remain_path_compatible() {
1782 let mut params = generated_certificate_params("non-critical authority", false);
1785 params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
1786 params
1787 .custom_extensions
1788 .push(rcgen::CustomExtension::from_oid_content(
1789 &[2, 5, 29, 19],
1790 vec![0x30, 0x03, 0x01, 0x01, 0xff],
1791 ));
1792 let certificate = params
1793 .self_signed(&rcgen::KeyPair::generate().expect("CA key generation should succeed"))
1794 .expect("test CA should be self-signable");
1795 let parsed = parse_certificate(certificate.der()).expect("test CA DER should parse");
1796
1797 assert_eq!(validate_ca_constraints(&parsed, 1), Ok(()));
1798 }
1799
1800 #[test]
1801 fn restricted_leaf_eku_requires_an_approved_purpose() {
1802 let root = rcgen::CertifiedIssuer::self_signed(
1805 generated_certificate_params("EKU authority", true),
1806 rcgen::KeyPair::generate().expect("root key generation should succeed"),
1807 )
1808 .expect("root should be self-signable");
1809 let mut leaf_params = generated_certificate_params("TLS-only signer", false);
1810 leaf_params.key_usages = vec![rcgen::KeyUsagePurpose::DigitalSignature];
1811 leaf_params.extended_key_usages = vec![rcgen::ExtendedKeyUsagePurpose::ServerAuth];
1812 let leaf = leaf_params
1813 .signed_by(
1814 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
1815 &root,
1816 )
1817 .expect("root should sign leaf certificate");
1818 let leaf_der = leaf.der().to_vec();
1819 let root_der = root.der().to_vec();
1820
1821 assert!(matches!(
1822 verify_generated_path(vec![leaf_der.clone(), root_der.clone()], root_der.clone(),),
1823 Err(X509ChainError::InvalidKeyUsage {
1824 position: 0,
1825 required: "an approved extended key usage",
1826 })
1827 ));
1828
1829 let allowed = HashSet::from([ExtendedKeyPurpose::ServerAuth]);
1830 verify_generated_path_with_eku(vec![leaf_der, root_der.clone()], root_der, Some(&allowed))
1831 .expect("an explicitly approved leaf purpose must be accepted");
1832 }
1833
1834 #[test]
1835 fn critical_leaf_eku_uses_the_same_purpose_policy() {
1836 let root = rcgen::CertifiedIssuer::self_signed(
1839 generated_certificate_params("critical EKU authority", true),
1840 rcgen::KeyPair::generate().expect("root key generation should succeed"),
1841 )
1842 .expect("root should be self-signable");
1843 let mut leaf_params = generated_certificate_params("critical TLS signer", false);
1844 leaf_params.key_usages = vec![rcgen::KeyUsagePurpose::DigitalSignature];
1845 let mut extension = rcgen::CustomExtension::from_oid_content(
1846 &[2, 5, 29, 37],
1847 vec![
1848 0x30, 0x0a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01,
1849 ],
1850 );
1851 extension.set_criticality(true);
1852 leaf_params.custom_extensions.push(extension);
1853 let leaf = leaf_params
1854 .signed_by(
1855 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
1856 &root,
1857 )
1858 .expect("root should sign leaf certificate");
1859 let allowed = HashSet::from([ExtendedKeyPurpose::ServerAuth]);
1860
1861 verify_generated_path_with_eku(
1862 vec![leaf.der().to_vec(), root.der().to_vec()],
1863 root.der().to_vec(),
1864 Some(&allowed),
1865 )
1866 .expect("approved critical EKU must be processed rather than rejected as unknown");
1867 }
1868
1869 #[test]
1870 fn issuer_eku_restricts_the_entire_certificate_path() {
1871 for (issuer_purpose_der, accepted) in [
1874 (
1875 vec![
1876 0x30, 0x0a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02,
1877 ],
1878 false,
1879 ),
1880 (
1881 vec![
1882 0x30, 0x0a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01,
1883 ],
1884 true,
1885 ),
1886 ] {
1887 let mut root_params =
1888 generated_certificate_params("purpose-constrained authority", true);
1889 let mut extension =
1890 rcgen::CustomExtension::from_oid_content(&[2, 5, 29, 37], issuer_purpose_der);
1891 extension.set_criticality(true);
1892 root_params.custom_extensions.push(extension);
1893 let root = rcgen::CertifiedIssuer::self_signed(
1894 root_params,
1895 rcgen::KeyPair::generate().expect("root key generation should succeed"),
1896 )
1897 .expect("root should be self-signable");
1898 let mut leaf_params = generated_certificate_params("TLS server signer", false);
1899 leaf_params.key_usages = vec![rcgen::KeyUsagePurpose::DigitalSignature];
1900 leaf_params.extended_key_usages = vec![rcgen::ExtendedKeyUsagePurpose::ServerAuth];
1901 let leaf = leaf_params
1902 .signed_by(
1903 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
1904 &root,
1905 )
1906 .expect("root should sign leaf certificate");
1907 let allowed = HashSet::from([
1908 ExtendedKeyPurpose::ServerAuth,
1909 ExtendedKeyPurpose::ClientAuth,
1910 ]);
1911 let result = verify_generated_path_with_eku(
1912 vec![leaf.der().to_vec(), root.der().to_vec()],
1913 root.der().to_vec(),
1914 Some(&allowed),
1915 );
1916
1917 if accepted {
1918 result.expect("a shared allowed purpose must satisfy the complete path");
1919 } else {
1920 assert!(matches!(
1921 result,
1922 Err(X509ChainError::InvalidKeyUsage {
1923 position: 1,
1924 required: "an approved extended key usage",
1925 })
1926 ));
1927 }
1928 }
1929 }
1930
1931 #[test]
1932 fn any_extended_key_usage_does_not_restrict_xml_signing() {
1933 let root = rcgen::CertifiedIssuer::self_signed(
1936 generated_certificate_params("any EKU authority", true),
1937 rcgen::KeyPair::generate().expect("root key generation should succeed"),
1938 )
1939 .expect("root should be self-signable");
1940 let mut leaf_params = generated_certificate_params("unrestricted signer", false);
1941 leaf_params.key_usages = vec![rcgen::KeyUsagePurpose::DigitalSignature];
1942 leaf_params.extended_key_usages = vec![rcgen::ExtendedKeyUsagePurpose::Any];
1943 let leaf = leaf_params
1944 .signed_by(
1945 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
1946 &root,
1947 )
1948 .expect("root should sign leaf certificate");
1949
1950 verify_generated_path(
1951 vec![leaf.der().to_vec(), root.der().to_vec()],
1952 root.der().to_vec(),
1953 )
1954 .expect("anyExtendedKeyUsage must remain unrestricted");
1955 }
1956
1957 #[test]
1958 fn x509_ecdsa_hash_oid_does_not_select_the_issuer_curve() {
1959 let data = b"certificate tbs bytes";
1964
1965 let p384_key = p384::ecdsa::SigningKey::from_slice(&[0x42; 48])
1966 .expect("fixed P-384 test key must be valid");
1967 let p384_signature: p384::ecdsa::Signature = p384_key
1968 .sign_prehash(&Sha256::digest(data))
1969 .expect("P-384 must sign a SHA-256 prehash");
1970 let p384_spki = p384_key
1971 .verifying_key()
1972 .to_public_key_der()
1973 .expect("P-384 SPKI must encode");
1974 assert!(
1975 verify_x509_signature_with_provider(
1976 &AlgorithmIdentifier::new(OID_SIG_ECDSA_WITH_SHA256, None),
1977 p384_signature.to_der().as_bytes(),
1978 data,
1979 p384_spki.as_bytes(),
1980 crate::provider::default_provider(),
1981 )
1982 .expect("P-384 with SHA-256 must be a supported X.509 pairing")
1983 );
1984
1985 let p256_key = p256::ecdsa::SigningKey::from_slice(&[0x24; 32])
1986 .expect("fixed P-256 test key must be valid");
1987 let p256_signature: p256::ecdsa::Signature = p256_key
1988 .sign_prehash(&Sha384::digest(data))
1989 .expect("P-256 must sign a SHA-384 prehash");
1990 let p256_spki = p256_key
1991 .verifying_key()
1992 .to_public_key_der()
1993 .expect("P-256 SPKI must encode");
1994 assert!(
1995 verify_x509_signature_with_provider(
1996 &AlgorithmIdentifier::new(OID_SIG_ECDSA_WITH_SHA384, None),
1997 p256_signature.to_der().as_bytes(),
1998 data,
1999 p256_spki.as_bytes(),
2000 crate::provider::default_provider(),
2001 )
2002 .expect("P-256 with SHA-384 must be a supported X.509 pairing")
2003 );
2004 }
2005
2006 #[test]
2007 fn path_edge_signature_check_does_not_repeat_name_matching() {
2008 let issuer_key = rcgen::KeyPair::generate().expect("issuer key generation should succeed");
2012 let issuer_key_pem = issuer_key.serialize_pem();
2013 let mut signing_params = rcgen::CertificateParams::new(Vec::new())
2014 .expect("empty issuer SAN list should be valid");
2015 signing_params
2016 .distinguished_name
2017 .push(rcgen::DnType::CommonName, "signing name");
2018 signing_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
2019 signing_params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
2020 let signing_issuer = rcgen::CertifiedIssuer::self_signed(signing_params, issuer_key)
2021 .expect("issuer certificate should be self-signable");
2022
2023 let mut alternate_params = rcgen::CertificateParams::new(Vec::new())
2024 .expect("empty alternate SAN list should be valid");
2025 alternate_params
2026 .distinguished_name
2027 .push(rcgen::DnType::CommonName, "name already matched by caller");
2028 alternate_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
2029 alternate_params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
2030 let alternate_issuer = rcgen::CertifiedIssuer::self_signed(
2031 alternate_params,
2032 rcgen::KeyPair::from_pem(&issuer_key_pem)
2033 .expect("serialized issuer key should parse again"),
2034 )
2035 .expect("alternate issuer certificate should be self-signable");
2036
2037 let leaf = rcgen::CertificateParams::new(Vec::new())
2038 .expect("empty leaf SAN list should be valid")
2039 .signed_by(
2040 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2041 &signing_issuer,
2042 )
2043 .expect("issuer should sign leaf certificate");
2044
2045 assert!(certificate_signature_matches(
2046 leaf.der(),
2047 alternate_issuer.der()
2048 ));
2049 }
2050
2051 #[test]
2052 fn certificate_path_edge_preserves_ed25519_verification() {
2053 let issuer_key = rcgen::KeyPair::generate_for(&rcgen::PKCS_ED25519)
2057 .expect("Ed25519 issuer key generation should succeed");
2058 let mut issuer_params = rcgen::CertificateParams::new(Vec::new())
2059 .expect("empty issuer SAN list should be valid");
2060 issuer_params
2061 .distinguished_name
2062 .push(rcgen::DnType::CommonName, "Ed25519 issuer");
2063 issuer_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
2064 issuer_params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
2065 let issuer = rcgen::CertifiedIssuer::self_signed(issuer_params, issuer_key)
2066 .expect("Ed25519 issuer certificate should be self-signable");
2067 let leaf_key = rcgen::KeyPair::generate_for(&rcgen::PKCS_ED25519)
2068 .expect("Ed25519 leaf key generation should succeed");
2069 let leaf = rcgen::CertificateParams::new(Vec::new())
2070 .expect("empty leaf SAN list should be valid")
2071 .signed_by(&leaf_key, &issuer)
2072 .expect("Ed25519 issuer should sign leaf certificate");
2073
2074 assert!(certificate_signature_matches(leaf.der(), issuer.der()));
2075 }
2076
2077 #[test]
2078 fn every_modeled_non_parameterized_x509_algorithm_reaches_the_provider() {
2079 for (oid, expected) in [
2083 (
2084 "2.16.840.1.101.3.4.3.2",
2085 X509SignatureAlgorithm::Dsa(super::super::DigestAlgorithm::Sha256),
2086 ),
2087 (
2088 "2.16.840.1.101.3.4.3.3",
2089 X509SignatureAlgorithm::Dsa(super::super::DigestAlgorithm::Sha384),
2090 ),
2091 (
2092 "2.16.840.1.101.3.4.3.4",
2093 X509SignatureAlgorithm::Dsa(super::super::DigestAlgorithm::Sha512),
2094 ),
2095 (
2096 "1.2.840.10045.4.1",
2097 X509SignatureAlgorithm::Ecdsa(super::super::DigestAlgorithm::Sha1),
2098 ),
2099 (
2100 "1.2.840.10045.4.3.4",
2101 X509SignatureAlgorithm::Ecdsa(super::super::DigestAlgorithm::Sha512),
2102 ),
2103 ] {
2104 let identifier = AlgorithmIdentifier::new(
2105 Oid::from_str(oid).expect("static signature OID must parse"),
2106 None,
2107 );
2108 assert_eq!(x509_signature_algorithm(&identifier), Ok(expected), "{oid}");
2109 }
2110 }
2111
2112 #[test]
2113 fn x509_signature_parameters_follow_each_algorithm_profile() {
2114 use x509_parser::asn1_rs::{Any, Tag};
2115
2116 for oid in [
2119 "1.2.840.10040.4.3",
2120 "2.16.840.1.101.3.4.3.2",
2121 "1.2.840.10045.4.1",
2122 "1.2.840.10045.4.3.2",
2123 "1.3.101.112",
2124 ] {
2125 let identifier = AlgorithmIdentifier::new(
2126 Oid::from_str(oid).expect("static signature OID must parse"),
2127 Some(Any::from_tag_and_data(Tag::Null, &[])),
2128 );
2129 assert!(matches!(
2130 x509_signature_algorithm(&identifier),
2131 Err(X509ChainError::InvalidDer {
2132 kind: "X.509 signature AlgorithmIdentifier parameters",
2133 ..
2134 })
2135 ));
2136 }
2137
2138 let rsa_oid =
2141 Oid::from_str("1.2.840.113549.1.1.11").expect("static RSA signature OID must parse");
2142 for parameters in [None, Some(Any::from_tag_and_data(Tag::Null, &[]))] {
2143 assert!(matches!(
2144 x509_signature_algorithm(&AlgorithmIdentifier::new(rsa_oid.clone(), parameters)),
2145 Ok(X509SignatureAlgorithm::RsaPkcs1v15(
2146 super::super::DigestAlgorithm::Sha256
2147 ))
2148 ));
2149 }
2150 assert!(matches!(
2151 x509_signature_algorithm(&AlgorithmIdentifier::new(
2152 rsa_oid,
2153 Some(Any::from_tag_and_data(Tag::OctetString, &[])),
2154 )),
2155 Err(X509ChainError::InvalidDer {
2156 kind: "X.509 signature AlgorithmIdentifier parameters",
2157 ..
2158 })
2159 ));
2160 }
2161
2162 #[test]
2163 fn unknown_x509_signature_algorithm_remains_diagnosable() {
2164 let oid = "1.2.3.4.5";
2165 let identifier = AlgorithmIdentifier::new(
2166 Oid::from_str(oid).expect("static unknown OID must parse"),
2167 None,
2168 );
2169
2170 assert_eq!(
2171 x509_signature_algorithm(&identifier),
2172 Err(X509ChainError::UnsupportedSignatureAlgorithm { oid: oid.into() })
2173 );
2174 }
2175
2176 #[test]
2177 fn parses_rsa_pss_certificate_parameters_without_xml_dsig_loss() {
2178 let der = [
2181 0x30, 0x41, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0a, 0x30,
2182 0x34, 0xa0, 0x0f, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04,
2183 0x02, 0x01, 0x05, 0x00, 0xa1, 0x1c, 0x30, 0x1a, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
2184 0xf7, 0x0d, 0x01, 0x01, 0x08, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65,
2185 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0xa2, 0x03, 0x02, 0x01, 0x20,
2186 ];
2187 let (rest, identifier) = AlgorithmIdentifier::from_der(&der)
2188 .expect("standard SHA-256 RSA-PSS AlgorithmIdentifier must parse");
2189 assert!(rest.is_empty());
2190
2191 assert_eq!(
2192 x509_signature_algorithm(&identifier),
2193 Ok(X509SignatureAlgorithm::RsaPss {
2194 digest: super::super::DigestAlgorithm::Sha256,
2195 mgf_digest: super::super::DigestAlgorithm::Sha256,
2196 salt_len: 32,
2197 })
2198 );
2199 }
2200
2201 #[test]
2202 fn dsa_rollover_replaces_embedded_root_before_depth_validation() {
2203 let leaf = include_bytes!(
2204 "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/balor.der"
2205 )
2206 .to_vec();
2207 let embedded_root =
2208 include_bytes!("../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/ca.der")
2209 .to_vec();
2210
2211 let mut rollover_anchor = embedded_root.clone();
2215 *rollover_anchor
2216 .last_mut()
2217 .expect("certificate is non-empty") ^= 1;
2218 parse_certificate(&rollover_anchor).expect("modified trust anchor remains valid DER");
2219 let anchors = vec![rollover_anchor];
2220 let info = X509DataInfo {
2221 certificates: vec![leaf, embedded_root],
2222 certificate_chain: vec![0, 1],
2223 ..X509DataInfo::default()
2224 };
2225 let options = X509ChainOptions {
2226 trusted_certs: &anchors,
2227 verification_time: UNIX_EPOCH + Duration::from_secs(1_104_580_800),
2228 max_chain_depth: 2,
2229 check_crls: false,
2230 allowed_extended_key_usages: None,
2231 rsa_keys: RsaKeyPolicy::default(),
2232 dsa_keys: DsaKeyPolicy {
2233 minimum_modulus_bits: 1024,
2234 },
2235 };
2236
2237 verify_x509_certificate_chain(&info, &options)
2238 .expect("the stale DSA root must be replaced by the configured anchor");
2239 }
2240
2241 #[test]
2242 fn dsa_issuer_key_uses_the_configured_strength_policy() {
2243 let leaf = include_bytes!(
2244 "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/balor.der"
2245 )
2246 .to_vec();
2247 let anchor =
2248 include_bytes!("../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/ca.der")
2249 .to_vec();
2250 let anchors = vec![anchor.clone()];
2251 let info = X509DataInfo {
2252 certificates: vec![leaf, anchor],
2253 certificate_chain: vec![0, 1],
2254 ..X509DataInfo::default()
2255 };
2256 let options = X509ChainOptions {
2257 trusted_certs: &anchors,
2258 verification_time: UNIX_EPOCH + Duration::from_secs(1_104_580_800),
2259 max_chain_depth: 2,
2260 check_crls: false,
2261 allowed_extended_key_usages: None,
2262 rsa_keys: RsaKeyPolicy::default(),
2263 dsa_keys: DsaKeyPolicy::default(),
2264 };
2265
2266 assert!(matches!(
2267 verify_x509_certificate_chain(&info, &options),
2268 Err(X509ChainError::KeyPolicy {
2269 position: 1,
2270 source: crate::policy::PolicyViolation::KeySize {
2271 key_type: "DSA",
2272 minimum_bits: 2048,
2273 actual_bits: 1024,
2274 ..
2275 }
2276 })
2277 ));
2278 }
2279
2280 #[test]
2281 fn path_length_excludes_self_issued_rollover_certificates() {
2282 let mut root_params = generated_certificate_params("rollover path authority", true);
2285 root_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Constrained(0));
2286 let root = rcgen::CertifiedIssuer::self_signed(
2287 root_params,
2288 rcgen::KeyPair::generate().expect("root key generation should succeed"),
2289 )
2290 .expect("root should be self-signable");
2291 let rollover_params = generated_certificate_params("rollover path authority", true);
2292 let rollover_key =
2293 rcgen::KeyPair::generate().expect("rollover key generation should succeed");
2294 let rollover_certificate = rollover_params
2295 .signed_by(&rollover_key, &root)
2296 .expect("root should sign same-name rollover certificate");
2297 let rollover_issuer = rcgen::Issuer::from_params(&rollover_params, &rollover_key);
2298 let leaf = generated_certificate_params("rollover path leaf", false)
2299 .signed_by(
2300 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2301 &rollover_issuer,
2302 )
2303 .expect("rollover key should sign leaf certificate");
2304
2305 verify_generated_path(
2306 vec![
2307 leaf.der().to_vec(),
2308 rollover_certificate.der().to_vec(),
2309 root.der().to_vec(),
2310 ],
2311 root.der().to_vec(),
2312 )
2313 .expect("self-issued rollover must not consume a zero path-length allowance");
2314 }
2315
2316 #[test]
2317 fn ca_name_constraints_reject_disallowed_dns_names() {
2318 let mut root_params = generated_certificate_params("constrained authority", true);
2319 root_params.name_constraints = Some(rcgen::NameConstraints {
2320 permitted_subtrees: vec![rcgen::GeneralSubtree::DnsName("example.com".into())],
2321 excluded_subtrees: vec![rcgen::GeneralSubtree::DnsName("blocked.example.com".into())],
2322 });
2323 let root = rcgen::CertifiedIssuer::self_signed(
2324 root_params,
2325 rcgen::KeyPair::generate().expect("root key generation should succeed"),
2326 )
2327 .expect("constrained root should be self-signable");
2328
2329 for (dns_name, accepted) in [
2330 ("www.example.com", true),
2331 ("blocked.example.com", false),
2332 ("www.example.net", false),
2333 ] {
2334 let leaf = rcgen::CertificateParams::new(vec![dns_name.into()])
2335 .expect("DNS SAN should be valid")
2336 .signed_by(
2337 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2338 &root,
2339 )
2340 .expect("root should sign leaf certificate");
2341 assert_eq!(
2342 verify_generated_path(
2343 vec![leaf.der().to_vec(), root.der().to_vec()],
2344 root.der().to_vec(),
2345 )
2346 .is_ok(),
2347 accepted,
2348 "unexpected name-constraint result for {dns_name}"
2349 );
2350 }
2351 }
2352
2353 #[test]
2354 fn rfc5280_dns_names_require_preferred_name_syntax() {
2355 let mut root_params = generated_certificate_params("DNS syntax authority", true);
2356 root_params.name_constraints = Some(rcgen::NameConstraints {
2357 permitted_subtrees: vec![rcgen::GeneralSubtree::DnsName("example.com".into())],
2358 excluded_subtrees: Vec::new(),
2359 });
2360 let root = rcgen::CertifiedIssuer::self_signed(
2361 root_params,
2362 rcgen::KeyPair::generate().expect("root key generation should succeed"),
2363 )
2364 .expect("constrained root should be self-signable");
2365
2366 let mut leaf_params = generated_certificate_params("malformed DNS leaf", false);
2367 let dns_name = b"bad..example.com";
2368 let mut san_der = vec![
2369 0x30,
2370 u8::try_from(dns_name.len() + 2).expect("test SAN must fit short-form DER"),
2371 0x82,
2372 ];
2373 san_der.push(u8::try_from(dns_name.len()).expect("test DNS name must fit short-form DER"));
2374 san_der.extend_from_slice(dns_name);
2375 leaf_params
2376 .custom_extensions
2377 .push(rcgen::CustomExtension::from_oid_content(
2378 &[2, 5, 29, 17],
2379 san_der,
2380 ));
2381 let leaf = leaf_params
2382 .signed_by(
2383 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2384 &root,
2385 )
2386 .expect("root should sign malformed-DNS leaf");
2387
2388 assert!(matches!(
2389 verify_generated_path(
2390 vec![leaf.der().to_vec(), root.der().to_vec()],
2391 root.der().to_vec(),
2392 ),
2393 Err(X509ChainError::InvalidDer {
2394 kind: "certificate DNS name",
2395 ..
2396 })
2397 ));
2398
2399 for dns_name in ["*.example.com", "_signing.example.com"] {
2400 assert!(validate_rfc5280_dns_name(dns_name).is_err(), "{dns_name}");
2401 }
2402 }
2403
2404 #[test]
2405 fn name_constraint_matchers_cover_email_uri_and_ip_forms() {
2406 assert!(email_within_subtree("ops@example.com", "example.com"));
2409 assert!(email_within_subtree("ops@example.com", "ops@example.com"));
2410 assert!(!email_within_subtree(
2411 "other@example.com",
2412 "ops@example.com"
2413 ));
2414 assert_eq!(
2415 uri_host("https://user@api.example.com:8443/path"),
2416 Some("api.example.com")
2417 );
2418 assert_eq!(
2419 uri_host("https://user@other@example.com/path"),
2420 None,
2421 "a second userinfo delimiter must not expose a constraint-matchable host"
2422 );
2423 assert!(dns_name_within_subtree(
2424 uri_host("https://api.example.com/path").expect("URI must expose a DNS host"),
2425 ".example.com",
2426 false,
2427 ));
2428 assert!(
2429 ip_address_within_subtree(&[192, 0, 2, 42], &[192, 0, 2, 0, 255, 255, 255, 0],)
2430 .expect("valid IPv4 constraint must evaluate")
2431 );
2432 assert!(
2433 !ip_address_within_subtree(&[192, 0, 3, 42], &[192, 0, 2, 0, 255, 255, 255, 0],)
2434 .expect("valid non-matching IPv4 constraint must evaluate")
2435 );
2436 assert!(matches!(
2437 ip_address_within_subtree(&[192, 0, 2, 42], &[192, 0, 2, 0, 255, 0, 255, 0],),
2438 Err(X509ChainError::InvalidDer {
2439 kind: "IP name constraint",
2440 ..
2441 })
2442 ));
2443 }
2444
2445 #[test]
2446 fn malformed_ip_name_constraints_fail_before_matching() {
2447 use x509_parser::extensions::GeneralSubtree;
2448
2449 let name = GeneralName::IPAddress(&[192, 0, 2, 42]);
2450 for malformed in [
2451 &[192, 0, 2, 0, 255, 255, 255][..],
2452 &[192, 0, 2, 0, 255, 0, 255, 0][..],
2453 ] {
2454 for permitted in [true, false] {
2455 let subtree = GeneralSubtree {
2456 base: GeneralName::IPAddress(malformed),
2457 };
2458 let constraints = NameConstraints {
2459 permitted_subtrees: permitted.then(|| vec![subtree.clone()]),
2460 excluded_subtrees: (!permitted).then(|| vec![subtree]),
2461 };
2462 assert!(matches!(
2463 validate_general_name(&name, &constraints, 0, 1),
2464 Err(X509ChainError::InvalidDer {
2465 kind: "IP name constraint",
2466 ..
2467 })
2468 ));
2469 }
2470 }
2471 }
2472
2473 #[test]
2474 fn malformed_string_name_constraints_fail_before_matching() {
2475 use x509_parser::extensions::GeneralSubtree;
2476
2477 for malformed in [
2480 GeneralName::DNSName(""),
2481 GeneralName::DNSName("example..com"),
2482 GeneralName::RFC822Name("@example.com"),
2483 GeneralName::RFC822Name("bad..local@example.com"),
2484 GeneralName::URI("https://example.com"),
2485 ] {
2486 let constraints = NameConstraints {
2487 permitted_subtrees: None,
2488 excluded_subtrees: Some(vec![GeneralSubtree { base: malformed }]),
2489 };
2490 assert!(matches!(
2491 ensure_supported_name_constraints(&constraints, 1),
2492 Err(X509ChainError::InvalidDer {
2493 kind: "string name constraint",
2494 ..
2495 })
2496 ));
2497 }
2498
2499 for valid in [
2500 GeneralName::DNSName("example.com"),
2501 GeneralName::DNSName(".example.com"),
2502 GeneralName::RFC822Name("ops@example.com"),
2503 GeneralName::RFC822Name("example.com"),
2504 GeneralName::URI(".example.com"),
2505 ] {
2506 let constraints = NameConstraints {
2507 permitted_subtrees: Some(vec![GeneralSubtree { base: valid }]),
2508 excluded_subtrees: None,
2509 };
2510 ensure_supported_name_constraints(&constraints, 1)
2511 .expect("valid string constraints must remain supported");
2512 }
2513 }
2514
2515 #[test]
2516 fn empty_name_constraint_collections_are_rejected() {
2517 use der::Encode as _;
2518 use x509_cert::ext::pkix::NameConstraints as EncodedNameConstraints;
2519
2520 for constraints in [
2523 EncodedNameConstraints {
2524 permitted_subtrees: None,
2525 excluded_subtrees: None,
2526 },
2527 EncodedNameConstraints {
2528 permitted_subtrees: Some(Vec::new()),
2529 excluded_subtrees: None,
2530 },
2531 EncodedNameConstraints {
2532 permitted_subtrees: None,
2533 excluded_subtrees: Some(Vec::new()),
2534 },
2535 ] {
2536 let der = constraints
2537 .to_der()
2538 .expect("malformed NameConstraints test input must encode");
2539 assert!(matches!(
2540 validate_name_constraints_der(&der, 1),
2541 Err(X509ChainError::InvalidNameConstraints { position: 1 })
2542 ));
2543 }
2544 }
2545
2546 #[test]
2547 fn unsupported_name_constraint_distances_fail_path_validation() {
2548 use der::{Encode as _, asn1::Ia5String};
2549 use x509_cert::ext::pkix::{
2550 NameConstraints as EncodedNameConstraints,
2551 constraints::name::GeneralSubtree as EncodedGeneralSubtree,
2552 name::GeneralName as EncodedGeneralName,
2553 };
2554
2555 for (permitted, minimum, maximum) in [
2559 (true, 1, None),
2560 (false, 1, None),
2561 (true, 0, Some(1)),
2562 (false, 0, Some(1)),
2563 ] {
2564 let dns_name = if permitted {
2565 "example.com"
2566 } else {
2567 "blocked.example.com"
2568 };
2569 let subtree = EncodedGeneralSubtree {
2570 base: EncodedGeneralName::DnsName(
2571 Ia5String::new(dns_name.as_bytes()).expect("valid DNS IA5String"),
2572 ),
2573 minimum,
2574 maximum,
2575 };
2576 let constraints = EncodedNameConstraints {
2577 permitted_subtrees: permitted.then(|| vec![subtree.clone()]),
2578 excluded_subtrees: (!permitted).then(|| vec![subtree]),
2579 };
2580 let mut extension = rcgen::CustomExtension::from_oid_content(
2581 &[2, 5, 29, 30],
2582 constraints
2583 .to_der()
2584 .expect("NameConstraints must encode as DER"),
2585 );
2586 extension.set_criticality(true);
2587
2588 let mut root_params = generated_certificate_params("distance authority", true);
2589 root_params.custom_extensions.push(extension);
2590 let root = rcgen::CertifiedIssuer::self_signed(
2591 root_params,
2592 rcgen::KeyPair::generate().expect("root key generation should succeed"),
2593 )
2594 .expect("constrained root should be self-signable");
2595 let leaf = rcgen::CertificateParams::new(vec!["www.example.com".into()])
2596 .expect("leaf DNS SAN should be valid")
2597 .signed_by(
2598 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2599 &root,
2600 )
2601 .expect("root should sign leaf certificate");
2602
2603 assert!(matches!(
2604 verify_generated_path(
2605 vec![leaf.der().to_vec(), root.der().to_vec()],
2606 root.der().to_vec(),
2607 ),
2608 Err(X509ChainError::InvalidNameConstraints { position: 1 })
2609 ));
2610 }
2611 }
2612
2613 #[test]
2614 fn name_constraints_cover_subject_email_and_directory_name() {
2615 let mut permitted_directory = rcgen::DistinguishedName::new();
2618 permitted_directory.push(rcgen::DnType::OrganizationName, "Example Corp");
2619 let mut root_params = generated_certificate_params("name authority", true);
2620 root_params.name_constraints = Some(rcgen::NameConstraints {
2621 permitted_subtrees: vec![
2622 rcgen::GeneralSubtree::Rfc822Name("example.com".into()),
2623 rcgen::GeneralSubtree::DirectoryName(permitted_directory),
2624 ],
2625 excluded_subtrees: Vec::new(),
2626 });
2627 let root = rcgen::CertifiedIssuer::self_signed(
2628 root_params,
2629 rcgen::KeyPair::generate().expect("root key generation should succeed"),
2630 )
2631 .expect("constrained root should be self-signable");
2632
2633 for (organization, email, accepted) in [
2634 ("Example Corp", "ops@example.com", true),
2635 ("Other Corp", "ops@example.com", false),
2636 ("Example Corp", "ops@example.net", false),
2637 ("Example Corp", "bad..local@example.com", false),
2638 ] {
2639 let mut leaf_params = generated_certificate_params("name-constrained leaf", false);
2640 leaf_params.distinguished_name = rcgen::DistinguishedName::new();
2641 leaf_params
2642 .distinguished_name
2643 .push(rcgen::DnType::OrganizationName, organization);
2644 leaf_params
2645 .distinguished_name
2646 .push(rcgen::DnType::CommonName, "name-constrained leaf");
2647 leaf_params.distinguished_name.push(
2648 rcgen::DnType::CustomDnType(vec![1, 2, 840, 113549, 1, 9, 1]),
2649 rcgen::DnValue::Ia5String(
2650 email
2651 .try_into()
2652 .expect("test email must be a valid IA5String"),
2653 ),
2654 );
2655 let leaf = leaf_params
2656 .signed_by(
2657 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2658 &root,
2659 )
2660 .expect("root should sign leaf certificate");
2661 let result = verify_generated_path(
2662 vec![leaf.der().to_vec(), root.der().to_vec()],
2663 root.der().to_vec(),
2664 );
2665 assert_eq!(
2666 result.is_ok(),
2667 accepted,
2668 "unexpected subject constraint result for {organization} / {email}: {result:?}",
2669 );
2670 }
2671 }
2672
2673 #[test]
2674 fn empty_subject_requires_a_critical_nonempty_san() {
2675 let root = rcgen::CertifiedIssuer::self_signed(
2676 generated_certificate_params("subject identity authority", true),
2677 rcgen::KeyPair::generate().expect("root key generation should succeed"),
2678 )
2679 .expect("root should be self-signable");
2680
2681 let mut missing_san = rcgen::CertificateParams::new(Vec::new())
2682 .expect("empty SAN list should produce certificate parameters");
2683 missing_san.distinguished_name = rcgen::DistinguishedName::new();
2684 let missing_san = missing_san
2685 .signed_by(
2686 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2687 &root,
2688 )
2689 .expect("test issuer should sign an empty-subject certificate");
2690
2691 let mut noncritical_san = rcgen::CertificateParams::new(Vec::new())
2692 .expect("empty SAN list should produce certificate parameters");
2693 noncritical_san.distinguished_name = rcgen::DistinguishedName::new();
2694 noncritical_san
2698 .custom_extensions
2699 .push(rcgen::CustomExtension::from_oid_content(
2700 &[2, 5, 29, 17],
2701 vec![0x30, 0x03, 0x82, 0x01, b'a'],
2702 ));
2703 let noncritical_san = noncritical_san
2704 .signed_by(
2705 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2706 &root,
2707 )
2708 .expect("test issuer should sign a non-critical-SAN certificate");
2709
2710 for leaf in [missing_san, noncritical_san] {
2711 assert!(matches!(
2712 verify_generated_path(
2713 vec![leaf.der().to_vec(), root.der().to_vec()],
2714 root.der().to_vec(),
2715 ),
2716 Err(X509ChainError::InvalidDer {
2717 kind: "certificate subject identity",
2718 ..
2719 })
2720 ));
2721 }
2722 }
2723
2724 #[test]
2725 fn empty_subject_with_critical_san_skips_directory_name_constraints() {
2726 let mut permitted_directory = rcgen::DistinguishedName::new();
2729 permitted_directory.push(rcgen::DnType::OrganizationName, "Example Corp");
2730 let mut root_params = generated_certificate_params("empty-subject authority", true);
2731 root_params.name_constraints = Some(rcgen::NameConstraints {
2732 permitted_subtrees: vec![rcgen::GeneralSubtree::DirectoryName(permitted_directory)],
2733 excluded_subtrees: Vec::new(),
2734 });
2735 let root = rcgen::CertifiedIssuer::self_signed(
2736 root_params,
2737 rcgen::KeyPair::generate().expect("root key generation should succeed"),
2738 )
2739 .expect("constrained root should be self-signable");
2740 let mut leaf_params = rcgen::CertificateParams::new(vec!["allowed.example".into()])
2741 .expect("DNS SAN should be valid");
2742 leaf_params.distinguished_name = rcgen::DistinguishedName::new();
2743 let leaf = leaf_params
2744 .signed_by(
2745 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2746 &root,
2747 )
2748 .expect("root should sign empty-subject leaf");
2749
2750 verify_generated_path(
2751 vec![leaf.der().to_vec(), root.der().to_vec()],
2752 root.der().to_vec(),
2753 )
2754 .expect("only present name forms should be constrained");
2755 }
2756
2757 #[test]
2758 fn malformed_general_names_in_san_fail_path_validation() {
2759 let root = rcgen::CertifiedIssuer::self_signed(
2760 generated_certificate_params("malformed-SAN root", true),
2761 rcgen::KeyPair::generate().expect("root key generation should succeed"),
2762 )
2763 .expect("root should be self-signable");
2764 for empty_subject in [true, false] {
2765 let mut leaf_params = generated_certificate_params("malformed-SAN leaf", false);
2766 if empty_subject {
2767 leaf_params.distinguished_name = rcgen::DistinguishedName::new();
2768 }
2769 let mut malformed_san = rcgen::CustomExtension::from_oid_content(
2771 &[2, 5, 29, 17],
2772 vec![0x30, 0x03, 0x82, 0x01, 0xff],
2773 );
2774 malformed_san.set_criticality(true);
2775 leaf_params.custom_extensions.push(malformed_san);
2776 let leaf = leaf_params
2777 .signed_by(
2778 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2779 &root,
2780 )
2781 .expect("root should sign malformed-SAN leaf");
2782
2783 assert!(matches!(
2784 verify_generated_path(
2785 vec![leaf.der().to_vec(), root.der().to_vec()],
2786 root.der().to_vec(),
2787 ),
2788 Err(X509ChainError::InvalidDer {
2789 kind: "certificate subject identity",
2790 ..
2791 })
2792 ));
2793 }
2794 }
2795
2796 #[test]
2797 fn typed_subject_alternative_names_require_rfc5280_syntax() {
2798 let root = rcgen::CertifiedIssuer::self_signed(
2799 generated_certificate_params("typed-SAN root", true),
2800 rcgen::KeyPair::generate().expect("root key generation should succeed"),
2801 )
2802 .expect("root should be self-signable");
2803
2804 for (tag, value) in [
2805 (0x81, b"operator@".as_slice()),
2806 (0x81, b"first..last@example.com".as_slice()),
2807 (0x86, b"relative/path".as_slice()),
2808 (0x86, b"https://example.com/%zz".as_slice()),
2809 (0x86, b"https://user@other@example.com/path".as_slice()),
2810 (0x86, b"file:///path".as_slice()),
2811 (0x87, &[192, 0, 2][..]),
2812 ] {
2813 for empty_subject in [false, true] {
2814 let mut leaf_params = generated_certificate_params("typed-SAN leaf", false);
2815 if empty_subject {
2816 leaf_params.distinguished_name = rcgen::DistinguishedName::new();
2817 }
2818 let mut san_der = vec![
2819 0x30,
2820 u8::try_from(value.len() + 2).expect("test SAN must fit short-form DER"),
2821 tag,
2822 u8::try_from(value.len()).expect("test GeneralName must fit short-form DER"),
2823 ];
2824 san_der.extend_from_slice(value);
2825 let mut san = rcgen::CustomExtension::from_oid_content(&[2, 5, 29, 17], san_der);
2826 san.set_criticality(true);
2827 leaf_params.custom_extensions.push(san);
2828 let leaf = leaf_params
2829 .signed_by(
2830 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2831 &root,
2832 )
2833 .expect("root should sign typed-SAN leaf");
2834
2835 assert!(matches!(
2836 verify_generated_path(
2837 vec![leaf.der().to_vec(), root.der().to_vec()],
2838 root.der().to_vec(),
2839 ),
2840 Err(X509ChainError::InvalidDer {
2841 kind: "certificate subject identity",
2842 ..
2843 })
2844 ));
2845 }
2846 }
2847
2848 for (tag, value) in [
2849 (0x81, b"operator@example.com".as_slice()),
2850 (0x81, b"operator@[192.0.2.1]".as_slice()),
2851 (0x81, b"operator@[IPv6:2001:db8::1]".as_slice()),
2852 (0x81, br#""operator desk"@example.com"#.as_slice()),
2853 (0x86, b"urn:example:operator".as_slice()),
2854 (
2855 0x86,
2856 b"https://operator@example.com:8443/path?q=1#id".as_slice(),
2857 ),
2858 (0x87, &[192, 0, 2, 1][..]),
2859 (
2860 0x87,
2861 &[0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1][..],
2862 ),
2863 ] {
2864 let mut leaf_params = rcgen::CertificateParams::new(Vec::new())
2865 .expect("empty SAN list should produce certificate parameters");
2866 leaf_params.distinguished_name = rcgen::DistinguishedName::new();
2867 let mut san_der = vec![
2868 0x30,
2869 u8::try_from(value.len() + 2).expect("test SAN must fit short-form DER"),
2870 tag,
2871 u8::try_from(value.len()).expect("test GeneralName must fit short-form DER"),
2872 ];
2873 san_der.extend_from_slice(value);
2874 let mut san = rcgen::CustomExtension::from_oid_content(&[2, 5, 29, 17], san_der);
2875 san.set_criticality(true);
2876 leaf_params.custom_extensions.push(san);
2877 let leaf = leaf_params
2878 .signed_by(
2879 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2880 &root,
2881 )
2882 .expect("root should sign typed-SAN leaf");
2883
2884 verify_generated_path(
2885 vec![leaf.der().to_vec(), root.der().to_vec()],
2886 root.der().to_vec(),
2887 )
2888 .expect("valid typed SAN identity must satisfy an empty subject");
2889 }
2890 }
2891
2892 fn parsed_merlin_crl(der: &[u8]) -> CertificateRevocationList<'_> {
2893 CertificateRevocationList::from_der(der)
2894 .expect("modified Merlin CRL must remain parseable")
2895 .1
2896 }
2897
2898 fn merlin_crl_der() -> Vec<u8> {
2899 let xml = include_str!(
2900 "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt-crl.xml"
2901 );
2902 let document = Document::parse(xml).expect("tracked Merlin document must parse");
2903 let key_info_node = document
2904 .descendants()
2905 .find(|node| node.has_tag_name((XMLDSIG_NS, "KeyInfo")))
2906 .expect("tracked Merlin document contains KeyInfo");
2907 let key_info = parse_key_info(key_info_node).expect("tracked Merlin KeyInfo must parse");
2908 let KeyInfoSource::X509Data(info) = &key_info.sources[0] else {
2909 panic!("expected X509Data")
2910 };
2911 info.crls[0].clone()
2912 }
2913
2914 #[test]
2915 fn duplicate_crl_and_entry_extension_oids_fail_closed() {
2916 use der::{Decode as _, Encode as _};
2917 use x509_cert::crl::CertificateList;
2918
2919 let original = merlin_crl_der();
2920 let mut duplicate_crl: CertificateList =
2921 CertificateList::from_der(&original).expect("tracked Merlin CRL must decode");
2922 let extensions = duplicate_crl
2923 .tbs_cert_list
2924 .crl_extensions
2925 .as_mut()
2926 .expect("tracked Merlin CRL must contain extensions");
2927 extensions.push(extensions[0].clone());
2928 let duplicate_crl = duplicate_crl
2929 .to_der()
2930 .expect("duplicate CRL extension test vector must encode");
2931 assert_eq!(
2932 validate_crl_extensions(&parsed_merlin_crl(&duplicate_crl), 0),
2933 Err(X509ChainError::InvalidCrl(0))
2934 );
2935
2936 let mut duplicate_entry: CertificateList =
2937 CertificateList::from_der(&original).expect("tracked Merlin CRL must decode");
2938 let duplicate = duplicate_entry
2939 .tbs_cert_list
2940 .crl_extensions
2941 .as_ref()
2942 .and_then(|extensions| extensions.first())
2943 .expect("tracked Merlin CRL must contain an extension")
2944 .clone();
2945 let revoked = duplicate_entry
2946 .tbs_cert_list
2947 .revoked_certificates
2948 .as_mut()
2949 .and_then(|entries| entries.first_mut())
2950 .expect("tracked Merlin CRL must contain a revoked entry");
2951 revoked.crl_entry_extensions = Some(vec![duplicate.clone(), duplicate]);
2952 let duplicate_entry = duplicate_entry
2953 .to_der()
2954 .expect("duplicate entry extension test vector must encode");
2955 assert_eq!(
2956 validate_crl_extensions(&parsed_merlin_crl(&duplicate_entry), 0),
2957 Err(X509ChainError::InvalidCrl(0))
2958 );
2959 }
2960
2961 #[test]
2962 fn malformed_revoked_certificate_serials_fail_closed() {
2963 let original = merlin_crl_der();
2966 let serial = parsed_merlin_crl(&original)
2967 .iter_revoked_certificates()
2968 .next()
2969 .expect("tracked Merlin CRL must contain a revoked entry")
2970 .raw_serial()
2971 .to_vec();
2972 let offsets = original
2973 .windows(serial.len())
2974 .enumerate()
2975 .filter_map(|(offset, bytes)| (bytes == serial).then_some(offset))
2976 .collect::<Vec<_>>();
2977 assert_eq!(
2978 offsets.len(),
2979 1,
2980 "revoked serial fixture must be unambiguous"
2981 );
2982
2983 for replacement in [vec![0; serial.len()], {
2984 let mut negative = serial.clone();
2985 negative[0] = 0x80;
2986 negative
2987 }] {
2988 let mut malformed = original.clone();
2989 malformed[offsets[0]..offsets[0] + serial.len()].copy_from_slice(&replacement);
2990 assert_eq!(
2991 validate_crl_extensions(&parsed_merlin_crl(&malformed), 0),
2992 Err(X509ChainError::InvalidCrl(0))
2993 );
2994 }
2995 }
2996
2997 #[test]
2998 fn delta_crl_indicator_is_rejected_regardless_of_criticality() {
2999 use der::{Decode as _, Encode as _, asn1::OctetString};
3000 use x509_cert::{crl::CertificateList, ext::Extension};
3001
3002 let original = merlin_crl_der();
3003 for critical in [false, true] {
3004 let mut encoded: CertificateList =
3005 CertificateList::from_der(&original).expect("tracked Merlin CRL must decode");
3006 encoded
3007 .tbs_cert_list
3008 .crl_extensions
3009 .get_or_insert_default()
3010 .push(Extension {
3011 extn_id: der::asn1::ObjectIdentifier::new_unwrap("2.5.29.27"),
3012 critical,
3013 extn_value: OctetString::new([0x02, 0x01, 0x01])
3014 .expect("DER INTEGER extension payload must be valid"),
3015 });
3016 let encoded = encoded
3017 .to_der()
3018 .expect("delta CRL indicator test vector must encode");
3019 assert_eq!(
3020 validate_crl_extensions(&parsed_merlin_crl(&encoded), 0),
3021 Err(X509ChainError::InvalidCrl(0)),
3022 "delta CRL indicator criticality must not change unsupported semantics"
3023 );
3024 }
3025 }
3026
3027 #[test]
3028 fn remove_from_crl_is_rejected_in_a_complete_crl() {
3029 use der::{Decode as _, Encode as _, asn1::OctetString};
3030 use x509_cert::{crl::CertificateList, ext::Extension};
3031
3032 let original = merlin_crl_der();
3033 for (reason, accepted) in [(1_u8, true), (8_u8, false)] {
3034 let mut encoded: CertificateList =
3035 CertificateList::from_der(&original).expect("tracked Merlin CRL must decode");
3036 let revoked = encoded
3037 .tbs_cert_list
3038 .revoked_certificates
3039 .as_mut()
3040 .and_then(|entries| entries.first_mut())
3041 .expect("tracked Merlin CRL must contain a revoked entry");
3042 revoked
3043 .crl_entry_extensions
3044 .get_or_insert_default()
3045 .push(Extension {
3046 extn_id: der::asn1::ObjectIdentifier::new_unwrap("2.5.29.21"),
3047 critical: false,
3048 extn_value: OctetString::new([0x0a, 0x01, reason])
3049 .expect("DER ENUMERATED extension payload must be valid"),
3050 });
3051 let encoded = encoded
3052 .to_der()
3053 .expect("reason-code CRL test vector must encode");
3054 let result = validate_crl_extensions(&parsed_merlin_crl(&encoded), 0);
3055 if accepted {
3056 assert_eq!(result, Ok(()), "ordinary revocation reasons remain valid");
3057 } else {
3058 assert_eq!(result, Err(X509ChainError::InvalidCrl(0)));
3059 }
3060 }
3061 }
3062
3063 #[test]
3064 fn unevaluable_uri_names_fail_closed_for_both_constraint_forms() {
3065 use x509_parser::extensions::GeneralSubtree;
3066
3067 let uri = GeneralName::URI("urn:example:opaque");
3070 for constraints in [
3071 NameConstraints {
3072 permitted_subtrees: Some(vec![GeneralSubtree {
3073 base: GeneralName::URI(".example.com"),
3074 }]),
3075 excluded_subtrees: None,
3076 },
3077 NameConstraints {
3078 permitted_subtrees: None,
3079 excluded_subtrees: Some(vec![GeneralSubtree {
3080 base: GeneralName::URI(".example.com"),
3081 }]),
3082 },
3083 ] {
3084 assert_eq!(
3085 validate_general_name(&uri, &constraints, 0, 1),
3086 Err(X509ChainError::NameConstraintViolation {
3087 position: 0,
3088 constraining_position: 1,
3089 })
3090 );
3091 }
3092 }
3093
3094 #[test]
3095 fn unknown_critical_certificate_extension_fails_closed() {
3096 let root = rcgen::CertifiedIssuer::self_signed(
3097 generated_certificate_params("critical-extension root", true),
3098 rcgen::KeyPair::generate().expect("root key generation should succeed"),
3099 )
3100 .expect("root should be self-signable");
3101 let mut leaf_params = generated_certificate_params("critical-extension leaf", false);
3102 let mut extension =
3103 rcgen::CustomExtension::from_oid_content(&[1, 2, 3, 4], vec![0x05, 0x00]);
3104 extension.set_criticality(true);
3105 leaf_params.custom_extensions.push(extension);
3106 let leaf = leaf_params
3107 .signed_by(
3108 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3109 &root,
3110 )
3111 .expect("root should sign leaf certificate");
3112
3113 assert_eq!(
3114 verify_generated_path(
3115 vec![leaf.der().to_vec(), root.der().to_vec()],
3116 root.der().to_vec(),
3117 ),
3118 Err(X509ChainError::UnsupportedCriticalExtension {
3119 position: 0,
3120 oid: "1.2.3.4".into(),
3121 })
3122 );
3123 }
3124
3125 #[test]
3126 fn duplicate_certificate_extension_oids_fail_closed() {
3127 let root = rcgen::CertifiedIssuer::self_signed(
3130 generated_certificate_params("duplicate-extension root", true),
3131 rcgen::KeyPair::generate().expect("root key generation should succeed"),
3132 )
3133 .expect("root should be self-signable");
3134 let mut leaf_params = generated_certificate_params("duplicate-extension leaf", false);
3135 for _ in 0..2 {
3136 leaf_params
3137 .custom_extensions
3138 .push(rcgen::CustomExtension::from_oid_content(
3139 &[1, 2, 3, 4],
3140 vec![0x05, 0x00],
3141 ));
3142 }
3143 let leaf = leaf_params
3144 .signed_by(
3145 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3146 &root,
3147 )
3148 .expect("root should sign leaf certificate");
3149
3150 assert_eq!(
3151 verify_generated_path(
3152 vec![leaf.der().to_vec(), root.der().to_vec()],
3153 root.der().to_vec(),
3154 ),
3155 Err(X509ChainError::DuplicateExtension {
3156 position: 0,
3157 oid: "1.2.3.4".into(),
3158 })
3159 );
3160 }
3161
3162 #[test]
3163 fn invalid_certificate_serial_numbers_fail_path_validation() {
3164 for serial in [vec![0], vec![1; 21]] {
3165 let root = rcgen::CertifiedIssuer::self_signed(
3166 generated_certificate_params("serial root", true),
3167 rcgen::KeyPair::generate().expect("root key generation should succeed"),
3168 )
3169 .expect("root should be self-signable");
3170 let mut leaf_params = generated_certificate_params("invalid serial leaf", false);
3171 leaf_params.serial_number = Some(rcgen::SerialNumber::from_slice(&serial));
3172 let leaf = leaf_params
3173 .signed_by(
3174 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3175 &root,
3176 )
3177 .expect("root should sign leaf certificate");
3178
3179 assert!(matches!(
3180 verify_generated_path(
3181 vec![leaf.der().to_vec(), root.der().to_vec()],
3182 root.der().to_vec(),
3183 ),
3184 Err(X509ChainError::InvalidDer {
3185 kind: "certificate serial number",
3186 ..
3187 })
3188 ));
3189 }
3190
3191 assert!(validate_positive_serial_bytes(&[0x80], "certificate serial number").is_err());
3192 assert!(validate_positive_serial_bytes(&[1; 20], "certificate serial number").is_ok());
3193
3194 let root = rcgen::CertifiedIssuer::self_signed(
3195 generated_certificate_params("serial-padding root", true),
3196 rcgen::KeyPair::generate().expect("root key generation should succeed"),
3197 )
3198 .expect("root should be self-signable");
3199 let mut leaf_params = generated_certificate_params("serial-padding leaf", false);
3200 leaf_params.serial_number = Some(rcgen::SerialNumber::from_slice(&[0x80; 20]));
3201 let leaf = leaf_params
3202 .signed_by(
3203 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3204 &root,
3205 )
3206 .expect("root should sign a maximum-magnitude serial");
3207
3208 verify_generated_path(
3209 vec![leaf.der().to_vec(), root.der().to_vec()],
3210 root.der().to_vec(),
3211 )
3212 .expect("a 20-octet magnitude may require a DER sign-padding octet");
3213 }
3214
3215 #[test]
3216 fn name_constraints_are_rejected_on_end_entity_certificates() {
3217 let root = rcgen::CertifiedIssuer::self_signed(
3220 generated_certificate_params("name-placement root", true),
3221 rcgen::KeyPair::generate().expect("root key generation should succeed"),
3222 )
3223 .expect("root should be self-signable");
3224 let mut leaf_params = generated_certificate_params("name-placement leaf", false);
3225 leaf_params.name_constraints = Some(rcgen::NameConstraints {
3226 permitted_subtrees: vec![rcgen::GeneralSubtree::DnsName("example.com".into())],
3227 excluded_subtrees: Vec::new(),
3228 });
3229 let leaf = leaf_params
3230 .signed_by(
3231 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3232 &root,
3233 )
3234 .expect("root should sign leaf certificate");
3235
3236 assert!(matches!(
3237 verify_generated_path(
3238 vec![leaf.der().to_vec(), root.der().to_vec()],
3239 root.der().to_vec(),
3240 ),
3241 Err(X509ChainError::InvalidNameConstraints { position: 0 })
3242 ));
3243 }
3244
3245 #[test]
3246 fn dsa_certificate_rejects_mismatched_inner_signature_algorithm() {
3247 let (_, mut certificate) = X509Certificate::from_der(include_bytes!(
3251 "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/balor.der"
3252 ))
3253 .expect("the tracked Merlin certificate is valid DER");
3254 let (_, issuer) = X509Certificate::from_der(include_bytes!(
3255 "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/ca.der"
3256 ))
3257 .expect("the tracked Merlin issuer is a DER certificate");
3258 assert!(verify_certificate_signature(&certificate, &issuer));
3259
3260 certificate.tbs_certificate.signature = issuer.public_key().algorithm.clone();
3261
3262 assert_ne!(
3263 certificate.tbs_certificate.signature,
3264 certificate.signature_algorithm
3265 );
3266 assert!(!verify_certificate_signature(&certificate, &issuer));
3267 }
3268
3269 #[test]
3270 fn dsa_sha1_crl_signature_uses_the_same_fallback_as_certificates() {
3271 let xml = include_str!(
3272 "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt-crl.xml"
3273 );
3274 let document = Document::parse(xml).expect("the tracked Merlin document is valid XML");
3275 let key_info_node = document
3276 .descendants()
3277 .find(|node| node.has_tag_name((XMLDSIG_NS, "KeyInfo")))
3278 .expect("the Merlin document contains KeyInfo");
3279 let key_info = parse_key_info(key_info_node).expect("the Merlin KeyInfo is valid");
3280 let KeyInfoSource::X509Data(info) = &key_info.sources[0] else {
3281 panic!("expected X509Data")
3282 };
3283 let (_, issuer) = X509Certificate::from_der(include_bytes!(
3284 "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/ca.der"
3285 ))
3286 .expect("the tracked Merlin issuer is a DER certificate");
3287 let (_, crl) = CertificateRevocationList::from_der(&info.crls[0])
3288 .expect("the tracked Merlin CRL is valid DER");
3289
3290 assert!(verify_crl_signature(&crl, &issuer));
3291 }
3292
3293 #[test]
3294 fn dsa_crl_rejects_mismatched_inner_signature_algorithm() {
3295 let xml = include_str!(
3296 "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt-crl.xml"
3297 );
3298 let document = Document::parse(xml).expect("the tracked Merlin document is valid XML");
3299 let key_info_node = document
3300 .descendants()
3301 .find(|node| node.has_tag_name((XMLDSIG_NS, "KeyInfo")))
3302 .expect("the Merlin document contains KeyInfo");
3303 let key_info = parse_key_info(key_info_node).expect("the Merlin KeyInfo is valid");
3304 let KeyInfoSource::X509Data(info) = &key_info.sources[0] else {
3305 panic!("expected X509Data")
3306 };
3307 let (_, issuer) = X509Certificate::from_der(include_bytes!(
3308 "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/ca.der"
3309 ))
3310 .expect("the tracked Merlin issuer is a DER certificate");
3311 let (_, mut crl) = CertificateRevocationList::from_der(&info.crls[0])
3312 .expect("the tracked Merlin CRL is valid DER");
3313 assert!(verify_crl_signature(&crl, &issuer));
3314
3315 crl.tbs_cert_list.signature = issuer.public_key().algorithm.clone();
3316
3317 assert_ne!(crl.tbs_cert_list.signature, crl.signature_algorithm);
3318 assert!(!verify_crl_signature(&crl, &issuer));
3319 }
3320}