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