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 validate_crl_issuer_key_usage(issuer, position + 1)?;
1718 let time_valid = crl.next_update().is_some_and(|next| {
1721 crl.last_update() <= verification_time && verification_time <= next
1722 });
1723 if !time_valid {
1724 return Err(X509ChainError::InvalidCrl(*crl_index));
1725 }
1726 if crl.iter_revoked_certificates().any(|revoked| {
1727 revoked.raw_serial() == cert.raw_serial()
1728 && revoked.revocation_date <= verification_time
1729 }) {
1730 return Err(X509ChainError::Revoked(position));
1731 }
1732 }
1733 }
1734 Ok(())
1735}
1736
1737fn validate_crl_issuer_key_usage(
1739 issuer: &X509Certificate<'_>,
1740 position: usize,
1741) -> Result<(), X509ChainError> {
1742 let usage = issuer
1746 .key_usage()
1747 .map_err(|error| X509ChainError::InvalidDer {
1748 kind: "certificate KeyUsage",
1749 message: error.to_string(),
1750 })?;
1751 let authorized = match issuer.version() {
1752 x509_parser::x509::X509Version::V1 | x509_parser::x509::X509Version::V2 => {
1753 usage.is_none_or(|usage| usage.value.crl_sign())
1754 }
1755 x509_parser::x509::X509Version::V3 => usage.is_some_and(|usage| usage.value.crl_sign()),
1756 version => {
1757 return Err(X509ChainError::InvalidDer {
1761 kind: "certificate version",
1762 message: format!("unsupported X.509 version value {}", version.0),
1763 });
1764 }
1765 };
1766 if !authorized {
1767 return Err(X509ChainError::InvalidKeyUsage {
1768 position,
1769 required: "cRLSign",
1770 });
1771 }
1772 Ok(())
1773}
1774
1775#[cfg(test)]
1776mod tests {
1777 use std::str::FromStr as _;
1778
1779 use super::*;
1780 use crate::xml::dom::Document;
1781 use crate::xmldsig::{KeyInfoSource, parse::XMLDSIG_NS, parse_key_info};
1782 use p256::pkcs8::EncodePublicKey;
1783 use sha2::{Digest, Sha256, Sha384};
1784 use signature::hazmat::PrehashSigner;
1785 use std::time::Duration;
1786 use x509_parser::oid_registry::{OID_SIG_ECDSA_WITH_SHA256, OID_SIG_ECDSA_WITH_SHA384, Oid};
1787
1788 #[test]
1789 fn crl_key_usage_absence_depends_on_certificate_version() {
1790 let params = rcgen::CertificateParams::new(Vec::new()).expect("empty SANs are valid");
1792 let certificate = params
1793 .self_signed(&rcgen::KeyPair::generate().expect("test key generation"))
1794 .expect("test certificate signing");
1795 let (_, mut parsed) =
1796 X509Certificate::from_der(certificate.der()).expect("generated certificate DER");
1797 assert!(parsed.key_usage().expect("generated extensions").is_none());
1798 for version in [
1799 x509_parser::x509::X509Version::V1,
1800 x509_parser::x509::X509Version::V2,
1801 ] {
1802 parsed.tbs_certificate.version = version;
1803 assert_eq!(validate_crl_issuer_key_usage(&parsed, 1), Ok(()));
1804 }
1805 parsed.tbs_certificate.version = x509_parser::x509::X509Version::V3;
1806 assert_eq!(
1807 validate_crl_issuer_key_usage(&parsed, 1),
1808 Err(X509ChainError::InvalidKeyUsage {
1809 position: 1,
1810 required: "cRLSign"
1811 })
1812 );
1813 parsed.tbs_certificate.version = x509_parser::x509::X509Version(3);
1814 assert!(matches!(
1815 validate_crl_issuer_key_usage(&parsed, 1),
1816 Err(X509ChainError::InvalidDer {
1817 kind: "certificate version",
1818 ..
1819 })
1820 ));
1821 }
1822
1823 fn generated_certificate_params(common_name: &str, is_ca: bool) -> rcgen::CertificateParams {
1824 let mut params = rcgen::CertificateParams::new(Vec::new())
1825 .expect("empty SAN list should produce valid certificate parameters");
1826 params
1827 .distinguished_name
1828 .push(rcgen::DnType::CommonName, common_name);
1829 if is_ca {
1830 params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
1831 params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
1832 }
1833 params
1834 }
1835
1836 fn verify_generated_path(
1837 certificates: Vec<Vec<u8>>,
1838 trusted_anchor: Vec<u8>,
1839 ) -> Result<(), X509ChainError> {
1840 verify_generated_path_with_eku(certificates, trusted_anchor, None)
1841 }
1842
1843 fn verify_generated_path_with_eku(
1844 certificates: Vec<Vec<u8>>,
1845 trusted_anchor: Vec<u8>,
1846 allowed_extended_key_usages: Option<&HashSet<ExtendedKeyPurpose>>,
1847 ) -> Result<(), X509ChainError> {
1848 let info = X509DataInfo {
1849 certificate_chain: (0..certificates.len()).collect(),
1850 certificates,
1851 ..X509DataInfo::default()
1852 };
1853 let anchors = vec![trusted_anchor];
1854 verify_x509_certificate_chain(
1855 &info,
1856 &X509ChainOptions {
1857 trusted_certs: &anchors,
1858 verification_time: SystemTime::now(),
1859 max_chain_depth: info.certificate_chain.len(),
1860 check_crls: false,
1861 allowed_extended_key_usages,
1862 rsa_keys: RsaKeyPolicy::default(),
1863 dsa_keys: DsaKeyPolicy::default(),
1864 },
1865 )
1866 }
1867
1868 #[test]
1869 fn noncritical_ca_basic_constraints_remain_path_compatible() {
1870 let mut params = generated_certificate_params("non-critical authority", false);
1873 params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
1874 params
1875 .custom_extensions
1876 .push(rcgen::CustomExtension::from_oid_content(
1877 &[2, 5, 29, 19],
1878 vec![0x30, 0x03, 0x01, 0x01, 0xff],
1879 ));
1880 let certificate = params
1881 .self_signed(&rcgen::KeyPair::generate().expect("CA key generation should succeed"))
1882 .expect("test CA should be self-signable");
1883 let parsed = parse_certificate(certificate.der()).expect("test CA DER should parse");
1884
1885 assert_eq!(validate_ca_constraints(&parsed, 1), Ok(()));
1886 }
1887
1888 #[test]
1889 fn restricted_leaf_eku_requires_an_approved_purpose() {
1890 let root = rcgen::CertifiedIssuer::self_signed(
1893 generated_certificate_params("EKU authority", true),
1894 rcgen::KeyPair::generate().expect("root key generation should succeed"),
1895 )
1896 .expect("root should be self-signable");
1897 let mut leaf_params = generated_certificate_params("TLS-only signer", false);
1898 leaf_params.key_usages = vec![rcgen::KeyUsagePurpose::DigitalSignature];
1899 leaf_params.extended_key_usages = vec![rcgen::ExtendedKeyUsagePurpose::ServerAuth];
1900 let leaf = leaf_params
1901 .signed_by(
1902 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
1903 &root,
1904 )
1905 .expect("root should sign leaf certificate");
1906 let leaf_der = leaf.der().to_vec();
1907 let root_der = root.der().to_vec();
1908
1909 assert!(matches!(
1910 verify_generated_path(vec![leaf_der.clone(), root_der.clone()], root_der.clone(),),
1911 Err(X509ChainError::InvalidKeyUsage {
1912 position: 0,
1913 required: "an approved extended key usage",
1914 })
1915 ));
1916
1917 let allowed = HashSet::from([ExtendedKeyPurpose::ServerAuth]);
1918 verify_generated_path_with_eku(vec![leaf_der, root_der.clone()], root_der, Some(&allowed))
1919 .expect("an explicitly approved leaf purpose must be accepted");
1920 }
1921
1922 #[test]
1923 fn critical_leaf_eku_uses_the_same_purpose_policy() {
1924 let root = rcgen::CertifiedIssuer::self_signed(
1927 generated_certificate_params("critical EKU authority", true),
1928 rcgen::KeyPair::generate().expect("root key generation should succeed"),
1929 )
1930 .expect("root should be self-signable");
1931 let mut leaf_params = generated_certificate_params("critical TLS signer", false);
1932 leaf_params.key_usages = vec![rcgen::KeyUsagePurpose::DigitalSignature];
1933 let mut extension = rcgen::CustomExtension::from_oid_content(
1934 &[2, 5, 29, 37],
1935 vec![
1936 0x30, 0x0a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01,
1937 ],
1938 );
1939 extension.set_criticality(true);
1940 leaf_params.custom_extensions.push(extension);
1941 let leaf = leaf_params
1942 .signed_by(
1943 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
1944 &root,
1945 )
1946 .expect("root should sign leaf certificate");
1947 let allowed = HashSet::from([ExtendedKeyPurpose::ServerAuth]);
1948
1949 verify_generated_path_with_eku(
1950 vec![leaf.der().to_vec(), root.der().to_vec()],
1951 root.der().to_vec(),
1952 Some(&allowed),
1953 )
1954 .expect("approved critical EKU must be processed rather than rejected as unknown");
1955 }
1956
1957 #[test]
1958 fn issuer_eku_restricts_the_entire_certificate_path() {
1959 for (issuer_purpose_der, accepted) in [
1962 (
1963 vec![
1964 0x30, 0x0a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02,
1965 ],
1966 false,
1967 ),
1968 (
1969 vec![
1970 0x30, 0x0a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01,
1971 ],
1972 true,
1973 ),
1974 ] {
1975 let mut root_params =
1976 generated_certificate_params("purpose-constrained authority", true);
1977 let mut extension =
1978 rcgen::CustomExtension::from_oid_content(&[2, 5, 29, 37], issuer_purpose_der);
1979 extension.set_criticality(true);
1980 root_params.custom_extensions.push(extension);
1981 let root = rcgen::CertifiedIssuer::self_signed(
1982 root_params,
1983 rcgen::KeyPair::generate().expect("root key generation should succeed"),
1984 )
1985 .expect("root should be self-signable");
1986 let mut leaf_params = generated_certificate_params("TLS server signer", false);
1987 leaf_params.key_usages = vec![rcgen::KeyUsagePurpose::DigitalSignature];
1988 leaf_params.extended_key_usages = vec![rcgen::ExtendedKeyUsagePurpose::ServerAuth];
1989 let leaf = leaf_params
1990 .signed_by(
1991 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
1992 &root,
1993 )
1994 .expect("root should sign leaf certificate");
1995 let allowed = HashSet::from([
1996 ExtendedKeyPurpose::ServerAuth,
1997 ExtendedKeyPurpose::ClientAuth,
1998 ]);
1999 let result = verify_generated_path_with_eku(
2000 vec![leaf.der().to_vec(), root.der().to_vec()],
2001 root.der().to_vec(),
2002 Some(&allowed),
2003 );
2004
2005 if accepted {
2006 result.expect("a shared allowed purpose must satisfy the complete path");
2007 } else {
2008 assert!(matches!(
2009 result,
2010 Err(X509ChainError::InvalidKeyUsage {
2011 position: 1,
2012 required: "an approved extended key usage",
2013 })
2014 ));
2015 }
2016 }
2017 }
2018
2019 #[test]
2020 fn any_extended_key_usage_does_not_restrict_xml_signing() {
2021 let root = rcgen::CertifiedIssuer::self_signed(
2024 generated_certificate_params("any EKU authority", true),
2025 rcgen::KeyPair::generate().expect("root key generation should succeed"),
2026 )
2027 .expect("root should be self-signable");
2028 let mut leaf_params = generated_certificate_params("unrestricted signer", false);
2029 leaf_params.key_usages = vec![rcgen::KeyUsagePurpose::DigitalSignature];
2030 leaf_params.extended_key_usages = vec![rcgen::ExtendedKeyUsagePurpose::Any];
2031 let leaf = leaf_params
2032 .signed_by(
2033 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2034 &root,
2035 )
2036 .expect("root should sign leaf certificate");
2037
2038 verify_generated_path(
2039 vec![leaf.der().to_vec(), root.der().to_vec()],
2040 root.der().to_vec(),
2041 )
2042 .expect("anyExtendedKeyUsage must remain unrestricted");
2043 }
2044
2045 #[test]
2046 fn x509_ecdsa_hash_oid_does_not_select_the_issuer_curve() {
2047 let data = b"certificate tbs bytes";
2052
2053 let p384_key = p384::ecdsa::SigningKey::from_slice(&[0x42; 48])
2054 .expect("fixed P-384 test key must be valid");
2055 let p384_signature: p384::ecdsa::Signature = p384_key
2056 .sign_prehash(&Sha256::digest(data))
2057 .expect("P-384 must sign a SHA-256 prehash");
2058 let p384_spki = p384_key
2059 .verifying_key()
2060 .to_public_key_der()
2061 .expect("P-384 SPKI must encode");
2062 assert!(
2063 verify_x509_signature_with_provider(
2064 &AlgorithmIdentifier::new(OID_SIG_ECDSA_WITH_SHA256, None),
2065 p384_signature.to_der().as_bytes(),
2066 data,
2067 p384_spki.as_bytes(),
2068 crate::provider::default_provider(),
2069 )
2070 .expect("P-384 with SHA-256 must be a supported X.509 pairing")
2071 );
2072
2073 let p256_key = p256::ecdsa::SigningKey::from_slice(&[0x24; 32])
2074 .expect("fixed P-256 test key must be valid");
2075 let p256_signature: p256::ecdsa::Signature = p256_key
2076 .sign_prehash(&Sha384::digest(data))
2077 .expect("P-256 must sign a SHA-384 prehash");
2078 let p256_spki = p256_key
2079 .verifying_key()
2080 .to_public_key_der()
2081 .expect("P-256 SPKI must encode");
2082 assert!(
2083 verify_x509_signature_with_provider(
2084 &AlgorithmIdentifier::new(OID_SIG_ECDSA_WITH_SHA384, None),
2085 p256_signature.to_der().as_bytes(),
2086 data,
2087 p256_spki.as_bytes(),
2088 crate::provider::default_provider(),
2089 )
2090 .expect("P-256 with SHA-384 must be a supported X.509 pairing")
2091 );
2092 }
2093
2094 #[test]
2095 fn path_edge_signature_check_does_not_repeat_name_matching() {
2096 let issuer_key = rcgen::KeyPair::generate().expect("issuer key generation should succeed");
2100 let issuer_key_pem = issuer_key.serialize_pem();
2101 let mut signing_params = rcgen::CertificateParams::new(Vec::new())
2102 .expect("empty issuer SAN list should be valid");
2103 signing_params
2104 .distinguished_name
2105 .push(rcgen::DnType::CommonName, "signing name");
2106 signing_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
2107 signing_params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
2108 let signing_issuer = rcgen::CertifiedIssuer::self_signed(signing_params, issuer_key)
2109 .expect("issuer certificate should be self-signable");
2110
2111 let mut alternate_params = rcgen::CertificateParams::new(Vec::new())
2112 .expect("empty alternate SAN list should be valid");
2113 alternate_params
2114 .distinguished_name
2115 .push(rcgen::DnType::CommonName, "name already matched by caller");
2116 alternate_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
2117 alternate_params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
2118 let alternate_issuer = rcgen::CertifiedIssuer::self_signed(
2119 alternate_params,
2120 rcgen::KeyPair::from_pem(&issuer_key_pem)
2121 .expect("serialized issuer key should parse again"),
2122 )
2123 .expect("alternate issuer certificate should be self-signable");
2124
2125 let leaf = rcgen::CertificateParams::new(Vec::new())
2126 .expect("empty leaf SAN list should be valid")
2127 .signed_by(
2128 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2129 &signing_issuer,
2130 )
2131 .expect("issuer should sign leaf certificate");
2132
2133 assert!(certificate_signature_matches(
2134 leaf.der(),
2135 alternate_issuer.der()
2136 ));
2137 }
2138
2139 #[test]
2140 fn certificate_path_edge_preserves_ed25519_verification() {
2141 let issuer_key = rcgen::KeyPair::generate_for(&rcgen::PKCS_ED25519)
2145 .expect("Ed25519 issuer key generation should succeed");
2146 let mut issuer_params = rcgen::CertificateParams::new(Vec::new())
2147 .expect("empty issuer SAN list should be valid");
2148 issuer_params
2149 .distinguished_name
2150 .push(rcgen::DnType::CommonName, "Ed25519 issuer");
2151 issuer_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
2152 issuer_params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
2153 let issuer = rcgen::CertifiedIssuer::self_signed(issuer_params, issuer_key)
2154 .expect("Ed25519 issuer certificate should be self-signable");
2155 let leaf_key = rcgen::KeyPair::generate_for(&rcgen::PKCS_ED25519)
2156 .expect("Ed25519 leaf key generation should succeed");
2157 let leaf = rcgen::CertificateParams::new(Vec::new())
2158 .expect("empty leaf SAN list should be valid")
2159 .signed_by(&leaf_key, &issuer)
2160 .expect("Ed25519 issuer should sign leaf certificate");
2161
2162 assert!(certificate_signature_matches(leaf.der(), issuer.der()));
2163 }
2164
2165 #[test]
2166 fn every_modeled_non_parameterized_x509_algorithm_reaches_the_provider() {
2167 for (oid, expected) in [
2171 (
2172 "2.16.840.1.101.3.4.3.1",
2173 X509SignatureAlgorithm::Dsa(super::super::DigestAlgorithm::Sha224),
2174 ),
2175 (
2176 "2.16.840.1.101.3.4.3.2",
2177 X509SignatureAlgorithm::Dsa(super::super::DigestAlgorithm::Sha256),
2178 ),
2179 (
2180 "2.16.840.1.101.3.4.3.3",
2181 X509SignatureAlgorithm::Dsa(super::super::DigestAlgorithm::Sha384),
2182 ),
2183 (
2184 "2.16.840.1.101.3.4.3.4",
2185 X509SignatureAlgorithm::Dsa(super::super::DigestAlgorithm::Sha512),
2186 ),
2187 (
2188 "1.2.840.10045.4.1",
2189 X509SignatureAlgorithm::Ecdsa(super::super::DigestAlgorithm::Sha1),
2190 ),
2191 (
2192 "1.2.840.113549.1.1.14",
2193 X509SignatureAlgorithm::RsaPkcs1v15(super::super::DigestAlgorithm::Sha224),
2194 ),
2195 (
2196 "1.2.840.10045.4.3.1",
2197 X509SignatureAlgorithm::Ecdsa(super::super::DigestAlgorithm::Sha224),
2198 ),
2199 (
2200 "1.2.840.10045.4.3.4",
2201 X509SignatureAlgorithm::Ecdsa(super::super::DigestAlgorithm::Sha512),
2202 ),
2203 ] {
2204 let identifier = AlgorithmIdentifier::new(
2205 Oid::from_str(oid).expect("static signature OID must parse"),
2206 None,
2207 );
2208 assert_eq!(x509_signature_algorithm(&identifier), Ok(expected), "{oid}");
2209 }
2210 }
2211
2212 #[test]
2213 fn x509_signature_parameters_follow_each_algorithm_profile() {
2214 use x509_parser::asn1_rs::{Any, Tag};
2215
2216 for oid in [
2219 "1.2.840.10040.4.3",
2220 "2.16.840.1.101.3.4.3.1",
2221 "2.16.840.1.101.3.4.3.2",
2222 "1.2.840.10045.4.1",
2223 "1.2.840.10045.4.3.1",
2224 "1.2.840.10045.4.3.2",
2225 "1.3.101.112",
2226 ] {
2227 let identifier = AlgorithmIdentifier::new(
2228 Oid::from_str(oid).expect("static signature OID must parse"),
2229 Some(Any::from_tag_and_data(Tag::Null, &[])),
2230 );
2231 assert!(matches!(
2232 x509_signature_algorithm(&identifier),
2233 Err(X509ChainError::InvalidDer {
2234 kind: "X.509 signature AlgorithmIdentifier parameters",
2235 ..
2236 })
2237 ));
2238 }
2239
2240 for (oid, digest) in [
2243 (
2244 "1.2.840.113549.1.1.14",
2245 super::super::DigestAlgorithm::Sha224,
2246 ),
2247 (
2248 "1.2.840.113549.1.1.11",
2249 super::super::DigestAlgorithm::Sha256,
2250 ),
2251 ] {
2252 let rsa_oid = Oid::from_str(oid).expect("static RSA signature OID must parse");
2253 for parameters in [None, Some(Any::from_tag_and_data(Tag::Null, &[]))] {
2254 assert_eq!(
2255 x509_signature_algorithm(&AlgorithmIdentifier::new(
2256 rsa_oid.clone(),
2257 parameters,
2258 )),
2259 Ok(X509SignatureAlgorithm::RsaPkcs1v15(digest)),
2260 );
2261 }
2262 assert!(matches!(
2263 x509_signature_algorithm(&AlgorithmIdentifier::new(
2264 rsa_oid,
2265 Some(Any::from_tag_and_data(Tag::OctetString, &[])),
2266 )),
2267 Err(X509ChainError::InvalidDer {
2268 kind: "X.509 signature AlgorithmIdentifier parameters",
2269 ..
2270 })
2271 ));
2272 }
2273 }
2274
2275 #[test]
2276 fn unknown_x509_signature_algorithm_remains_diagnosable() {
2277 let oid = "1.2.3.4.5";
2278 let identifier = AlgorithmIdentifier::new(
2279 Oid::from_str(oid).expect("static unknown OID must parse"),
2280 None,
2281 );
2282
2283 assert_eq!(
2284 x509_signature_algorithm(&identifier),
2285 Err(X509ChainError::UnsupportedSignatureAlgorithm { oid: oid.into() })
2286 );
2287 }
2288
2289 #[test]
2290 fn parses_rsa_pss_certificate_parameters_without_xml_dsig_loss() {
2291 let der = [
2294 0x30, 0x41, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0a, 0x30,
2295 0x34, 0xa0, 0x0f, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04,
2296 0x02, 0x01, 0x05, 0x00, 0xa1, 0x1c, 0x30, 0x1a, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
2297 0xf7, 0x0d, 0x01, 0x01, 0x08, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65,
2298 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0xa2, 0x03, 0x02, 0x01, 0x20,
2299 ];
2300 let (rest, identifier) = AlgorithmIdentifier::from_der(&der)
2301 .expect("standard SHA-256 RSA-PSS AlgorithmIdentifier must parse");
2302 assert!(rest.is_empty());
2303
2304 assert_eq!(
2305 x509_signature_algorithm(&identifier),
2306 Ok(X509SignatureAlgorithm::RsaPss {
2307 digest: super::super::DigestAlgorithm::Sha256,
2308 mgf_digest: super::super::DigestAlgorithm::Sha256,
2309 salt_len: 32,
2310 })
2311 );
2312 }
2313
2314 #[test]
2315 fn parses_sha224_rsa_pss_certificate_parameters() {
2316 let der = [
2319 0x30, 0x41, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0a, 0x30,
2320 0x34, 0xa0, 0x0f, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04,
2321 0x02, 0x04, 0x05, 0x00, 0xa1, 0x1c, 0x30, 0x1a, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
2322 0xf7, 0x0d, 0x01, 0x01, 0x08, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65,
2323 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0xa2, 0x03, 0x02, 0x01, 0x1c,
2324 ];
2325 let (rest, identifier) = AlgorithmIdentifier::from_der(&der)
2326 .expect("standard SHA-224 RSA-PSS AlgorithmIdentifier must parse");
2327 assert!(rest.is_empty());
2328
2329 assert_eq!(
2330 x509_signature_algorithm(&identifier),
2331 Ok(X509SignatureAlgorithm::RsaPss {
2332 digest: super::super::DigestAlgorithm::Sha224,
2333 mgf_digest: super::super::DigestAlgorithm::Sha224,
2334 salt_len: 28,
2335 })
2336 );
2337 }
2338
2339 #[test]
2340 fn dsa_rollover_replaces_embedded_root_before_depth_validation() {
2341 let leaf = include_bytes!(
2342 "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/balor.der"
2343 )
2344 .to_vec();
2345 let embedded_root =
2346 include_bytes!("../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/ca.der")
2347 .to_vec();
2348
2349 let mut rollover_anchor = embedded_root.clone();
2353 *rollover_anchor
2354 .last_mut()
2355 .expect("certificate is non-empty") ^= 1;
2356 parse_certificate(&rollover_anchor).expect("modified trust anchor remains valid DER");
2357 let anchors = vec![rollover_anchor];
2358 let info = X509DataInfo {
2359 certificates: vec![leaf, embedded_root],
2360 certificate_chain: vec![0, 1],
2361 ..X509DataInfo::default()
2362 };
2363 let options = X509ChainOptions {
2364 trusted_certs: &anchors,
2365 verification_time: UNIX_EPOCH + Duration::from_secs(1_104_580_800),
2366 max_chain_depth: 2,
2367 check_crls: false,
2368 allowed_extended_key_usages: None,
2369 rsa_keys: RsaKeyPolicy::default(),
2370 dsa_keys: DsaKeyPolicy {
2371 minimum_modulus_bits: 1024,
2372 },
2373 };
2374
2375 verify_x509_certificate_chain(&info, &options)
2376 .expect("the stale DSA root must be replaced by the configured anchor");
2377 }
2378
2379 #[test]
2380 fn dsa_issuer_key_uses_the_configured_strength_policy() {
2381 let leaf = include_bytes!(
2382 "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/balor.der"
2383 )
2384 .to_vec();
2385 let anchor =
2386 include_bytes!("../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/ca.der")
2387 .to_vec();
2388 let anchors = vec![anchor.clone()];
2389 let info = X509DataInfo {
2390 certificates: vec![leaf, anchor],
2391 certificate_chain: vec![0, 1],
2392 ..X509DataInfo::default()
2393 };
2394 let options = X509ChainOptions {
2395 trusted_certs: &anchors,
2396 verification_time: UNIX_EPOCH + Duration::from_secs(1_104_580_800),
2397 max_chain_depth: 2,
2398 check_crls: false,
2399 allowed_extended_key_usages: None,
2400 rsa_keys: RsaKeyPolicy::default(),
2401 dsa_keys: DsaKeyPolicy::default(),
2402 };
2403
2404 assert!(matches!(
2405 verify_x509_certificate_chain(&info, &options),
2406 Err(X509ChainError::KeyPolicy {
2407 position: 1,
2408 source: crate::policy::PolicyViolation::KeySize {
2409 key_type: "DSA",
2410 minimum_bits: 2048,
2411 actual_bits: 1024,
2412 ..
2413 }
2414 })
2415 ));
2416 }
2417
2418 #[test]
2419 fn path_length_excludes_self_issued_rollover_certificates() {
2420 let mut root_params = generated_certificate_params("rollover path authority", true);
2423 root_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Constrained(0));
2424 let root = rcgen::CertifiedIssuer::self_signed(
2425 root_params,
2426 rcgen::KeyPair::generate().expect("root key generation should succeed"),
2427 )
2428 .expect("root should be self-signable");
2429 let rollover_params = generated_certificate_params("rollover path authority", true);
2430 let rollover_key =
2431 rcgen::KeyPair::generate().expect("rollover key generation should succeed");
2432 let rollover_certificate = rollover_params
2433 .signed_by(&rollover_key, &root)
2434 .expect("root should sign same-name rollover certificate");
2435 let rollover_issuer = rcgen::Issuer::from_params(&rollover_params, &rollover_key);
2436 let leaf = generated_certificate_params("rollover path leaf", false)
2437 .signed_by(
2438 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2439 &rollover_issuer,
2440 )
2441 .expect("rollover key should sign leaf certificate");
2442
2443 verify_generated_path(
2444 vec![
2445 leaf.der().to_vec(),
2446 rollover_certificate.der().to_vec(),
2447 root.der().to_vec(),
2448 ],
2449 root.der().to_vec(),
2450 )
2451 .expect("self-issued rollover must not consume a zero path-length allowance");
2452 }
2453
2454 #[test]
2455 fn ca_name_constraints_reject_disallowed_dns_names() {
2456 let mut root_params = generated_certificate_params("constrained authority", true);
2457 root_params.name_constraints = Some(rcgen::NameConstraints {
2458 permitted_subtrees: vec![rcgen::GeneralSubtree::DnsName("example.com".into())],
2459 excluded_subtrees: vec![rcgen::GeneralSubtree::DnsName("blocked.example.com".into())],
2460 });
2461 let root = rcgen::CertifiedIssuer::self_signed(
2462 root_params,
2463 rcgen::KeyPair::generate().expect("root key generation should succeed"),
2464 )
2465 .expect("constrained root should be self-signable");
2466
2467 for (dns_name, accepted) in [
2468 ("www.example.com", true),
2469 ("blocked.example.com", false),
2470 ("www.example.net", false),
2471 ] {
2472 let leaf = rcgen::CertificateParams::new(vec![dns_name.into()])
2473 .expect("DNS SAN should be valid")
2474 .signed_by(
2475 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2476 &root,
2477 )
2478 .expect("root should sign leaf certificate");
2479 assert_eq!(
2480 verify_generated_path(
2481 vec![leaf.der().to_vec(), root.der().to_vec()],
2482 root.der().to_vec(),
2483 )
2484 .is_ok(),
2485 accepted,
2486 "unexpected name-constraint result for {dns_name}"
2487 );
2488 }
2489 }
2490
2491 #[test]
2492 fn rfc5280_dns_names_require_preferred_name_syntax() {
2493 let mut root_params = generated_certificate_params("DNS syntax authority", true);
2494 root_params.name_constraints = Some(rcgen::NameConstraints {
2495 permitted_subtrees: vec![rcgen::GeneralSubtree::DnsName("example.com".into())],
2496 excluded_subtrees: Vec::new(),
2497 });
2498 let root = rcgen::CertifiedIssuer::self_signed(
2499 root_params,
2500 rcgen::KeyPair::generate().expect("root key generation should succeed"),
2501 )
2502 .expect("constrained root should be self-signable");
2503
2504 let mut leaf_params = generated_certificate_params("malformed DNS leaf", false);
2505 let dns_name = b"bad..example.com";
2506 let mut san_der = vec![
2507 0x30,
2508 u8::try_from(dns_name.len() + 2).expect("test SAN must fit short-form DER"),
2509 0x82,
2510 ];
2511 san_der.push(u8::try_from(dns_name.len()).expect("test DNS name must fit short-form DER"));
2512 san_der.extend_from_slice(dns_name);
2513 leaf_params
2514 .custom_extensions
2515 .push(rcgen::CustomExtension::from_oid_content(
2516 &[2, 5, 29, 17],
2517 san_der,
2518 ));
2519 let leaf = leaf_params
2520 .signed_by(
2521 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2522 &root,
2523 )
2524 .expect("root should sign malformed-DNS leaf");
2525
2526 assert!(matches!(
2527 verify_generated_path(
2528 vec![leaf.der().to_vec(), root.der().to_vec()],
2529 root.der().to_vec(),
2530 ),
2531 Err(X509ChainError::InvalidDer {
2532 kind: "certificate DNS name",
2533 ..
2534 })
2535 ));
2536
2537 for dns_name in ["*.example.com", "_signing.example.com"] {
2538 assert!(validate_rfc5280_dns_name(dns_name).is_err(), "{dns_name}");
2539 }
2540 }
2541
2542 #[test]
2543 fn name_constraint_matchers_cover_email_uri_and_ip_forms() {
2544 assert!(email_within_subtree("ops@example.com", "example.com"));
2547 assert!(email_within_subtree("ops@example.com", "ops@example.com"));
2548 assert!(!email_within_subtree(
2549 "other@example.com",
2550 "ops@example.com"
2551 ));
2552 assert_eq!(
2553 uri_host("https://user@api.example.com:8443/path"),
2554 Some("api.example.com")
2555 );
2556 assert_eq!(
2557 uri_host("https://user@other@example.com/path"),
2558 None,
2559 "a second userinfo delimiter must not expose a constraint-matchable host"
2560 );
2561 assert!(dns_name_within_subtree(
2562 uri_host("https://api.example.com/path").expect("URI must expose a DNS host"),
2563 ".example.com",
2564 false,
2565 ));
2566 assert!(
2567 ip_address_within_subtree(&[192, 0, 2, 42], &[192, 0, 2, 0, 255, 255, 255, 0],)
2568 .expect("valid IPv4 constraint must evaluate")
2569 );
2570 assert!(
2571 !ip_address_within_subtree(&[192, 0, 3, 42], &[192, 0, 2, 0, 255, 255, 255, 0],)
2572 .expect("valid non-matching IPv4 constraint must evaluate")
2573 );
2574 assert!(matches!(
2575 ip_address_within_subtree(&[192, 0, 2, 42], &[192, 0, 2, 0, 255, 0, 255, 0],),
2576 Err(X509ChainError::InvalidDer {
2577 kind: "IP name constraint",
2578 ..
2579 })
2580 ));
2581 }
2582
2583 #[test]
2584 fn malformed_ip_name_constraints_fail_before_matching() {
2585 use x509_parser::extensions::GeneralSubtree;
2586
2587 let name = GeneralName::IPAddress(&[192, 0, 2, 42]);
2588 for malformed in [
2589 &[192, 0, 2, 0, 255, 255, 255][..],
2590 &[192, 0, 2, 0, 255, 0, 255, 0][..],
2591 ] {
2592 for permitted in [true, false] {
2593 let subtree = GeneralSubtree {
2594 base: GeneralName::IPAddress(malformed),
2595 };
2596 let constraints = NameConstraints {
2597 permitted_subtrees: permitted.then(|| vec![subtree.clone()]),
2598 excluded_subtrees: (!permitted).then(|| vec![subtree]),
2599 };
2600 assert!(matches!(
2601 validate_general_name(&name, &constraints, 0, 1),
2602 Err(X509ChainError::InvalidDer {
2603 kind: "IP name constraint",
2604 ..
2605 })
2606 ));
2607 }
2608 }
2609 }
2610
2611 #[test]
2612 fn malformed_string_name_constraints_fail_before_matching() {
2613 use x509_parser::extensions::GeneralSubtree;
2614
2615 for malformed in [
2618 GeneralName::DNSName(""),
2619 GeneralName::DNSName("example..com"),
2620 GeneralName::RFC822Name("@example.com"),
2621 GeneralName::RFC822Name("bad..local@example.com"),
2622 GeneralName::URI("https://example.com"),
2623 ] {
2624 let constraints = NameConstraints {
2625 permitted_subtrees: None,
2626 excluded_subtrees: Some(vec![GeneralSubtree { base: malformed }]),
2627 };
2628 assert!(matches!(
2629 ensure_supported_name_constraints(&constraints, 1),
2630 Err(X509ChainError::InvalidDer {
2631 kind: "string name constraint",
2632 ..
2633 })
2634 ));
2635 }
2636
2637 for valid in [
2638 GeneralName::DNSName("example.com"),
2639 GeneralName::DNSName(".example.com"),
2640 GeneralName::RFC822Name("ops@example.com"),
2641 GeneralName::RFC822Name("example.com"),
2642 GeneralName::URI(".example.com"),
2643 ] {
2644 let constraints = NameConstraints {
2645 permitted_subtrees: Some(vec![GeneralSubtree { base: valid }]),
2646 excluded_subtrees: None,
2647 };
2648 ensure_supported_name_constraints(&constraints, 1)
2649 .expect("valid string constraints must remain supported");
2650 }
2651 }
2652
2653 #[test]
2654 fn empty_name_constraint_collections_are_rejected() {
2655 use der::Encode as _;
2656 use x509_cert::ext::pkix::NameConstraints as EncodedNameConstraints;
2657
2658 for constraints in [
2661 EncodedNameConstraints {
2662 permitted_subtrees: None,
2663 excluded_subtrees: None,
2664 },
2665 EncodedNameConstraints {
2666 permitted_subtrees: Some(Vec::new()),
2667 excluded_subtrees: None,
2668 },
2669 EncodedNameConstraints {
2670 permitted_subtrees: None,
2671 excluded_subtrees: Some(Vec::new()),
2672 },
2673 ] {
2674 let der = constraints
2675 .to_der()
2676 .expect("malformed NameConstraints test input must encode");
2677 assert!(matches!(
2678 validate_name_constraints_der(&der, 1),
2679 Err(X509ChainError::InvalidNameConstraints { position: 1 })
2680 ));
2681 }
2682 }
2683
2684 #[test]
2685 fn unsupported_name_constraint_distances_fail_path_validation() {
2686 use der::{Encode as _, asn1::Ia5String};
2687 use x509_cert::ext::pkix::{
2688 NameConstraints as EncodedNameConstraints,
2689 constraints::name::GeneralSubtree as EncodedGeneralSubtree,
2690 name::GeneralName as EncodedGeneralName,
2691 };
2692
2693 for (permitted, minimum, maximum) in [
2697 (true, 1, None),
2698 (false, 1, None),
2699 (true, 0, Some(1)),
2700 (false, 0, Some(1)),
2701 ] {
2702 let dns_name = if permitted {
2703 "example.com"
2704 } else {
2705 "blocked.example.com"
2706 };
2707 let subtree = EncodedGeneralSubtree {
2708 base: EncodedGeneralName::DnsName(
2709 Ia5String::new(dns_name.as_bytes()).expect("valid DNS IA5String"),
2710 ),
2711 minimum,
2712 maximum,
2713 };
2714 let constraints = EncodedNameConstraints {
2715 permitted_subtrees: permitted.then(|| vec![subtree.clone()]),
2716 excluded_subtrees: (!permitted).then(|| vec![subtree]),
2717 };
2718 let mut extension = rcgen::CustomExtension::from_oid_content(
2719 &[2, 5, 29, 30],
2720 constraints
2721 .to_der()
2722 .expect("NameConstraints must encode as DER"),
2723 );
2724 extension.set_criticality(true);
2725
2726 let mut root_params = generated_certificate_params("distance authority", true);
2727 root_params.custom_extensions.push(extension);
2728 let root = rcgen::CertifiedIssuer::self_signed(
2729 root_params,
2730 rcgen::KeyPair::generate().expect("root key generation should succeed"),
2731 )
2732 .expect("constrained root should be self-signable");
2733 let leaf = rcgen::CertificateParams::new(vec!["www.example.com".into()])
2734 .expect("leaf DNS SAN should be valid")
2735 .signed_by(
2736 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2737 &root,
2738 )
2739 .expect("root should sign leaf certificate");
2740
2741 assert!(matches!(
2742 verify_generated_path(
2743 vec![leaf.der().to_vec(), root.der().to_vec()],
2744 root.der().to_vec(),
2745 ),
2746 Err(X509ChainError::InvalidNameConstraints { position: 1 })
2747 ));
2748 }
2749 }
2750
2751 #[test]
2752 fn name_constraints_cover_subject_email_and_directory_name() {
2753 let mut permitted_directory = rcgen::DistinguishedName::new();
2756 permitted_directory.push(rcgen::DnType::OrganizationName, "Example Corp");
2757 let mut root_params = generated_certificate_params("name authority", true);
2758 root_params.name_constraints = Some(rcgen::NameConstraints {
2759 permitted_subtrees: vec![
2760 rcgen::GeneralSubtree::Rfc822Name("example.com".into()),
2761 rcgen::GeneralSubtree::DirectoryName(permitted_directory),
2762 ],
2763 excluded_subtrees: Vec::new(),
2764 });
2765 let root = rcgen::CertifiedIssuer::self_signed(
2766 root_params,
2767 rcgen::KeyPair::generate().expect("root key generation should succeed"),
2768 )
2769 .expect("constrained root should be self-signable");
2770
2771 for (organization, email, accepted) in [
2772 ("Example Corp", "ops@example.com", true),
2773 ("Other Corp", "ops@example.com", false),
2774 ("Example Corp", "ops@example.net", false),
2775 ("Example Corp", "bad..local@example.com", false),
2776 ] {
2777 let mut leaf_params = generated_certificate_params("name-constrained leaf", false);
2778 leaf_params.distinguished_name = rcgen::DistinguishedName::new();
2779 leaf_params
2780 .distinguished_name
2781 .push(rcgen::DnType::OrganizationName, organization);
2782 leaf_params
2783 .distinguished_name
2784 .push(rcgen::DnType::CommonName, "name-constrained leaf");
2785 leaf_params.distinguished_name.push(
2786 rcgen::DnType::CustomDnType(vec![1, 2, 840, 113549, 1, 9, 1]),
2787 rcgen::DnValue::Ia5String(
2788 email
2789 .try_into()
2790 .expect("test email must be a valid IA5String"),
2791 ),
2792 );
2793 let leaf = leaf_params
2794 .signed_by(
2795 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2796 &root,
2797 )
2798 .expect("root should sign leaf certificate");
2799 let result = verify_generated_path(
2800 vec![leaf.der().to_vec(), root.der().to_vec()],
2801 root.der().to_vec(),
2802 );
2803 assert_eq!(
2804 result.is_ok(),
2805 accepted,
2806 "unexpected subject constraint result for {organization} / {email}: {result:?}",
2807 );
2808 }
2809 }
2810
2811 #[test]
2812 fn empty_subject_requires_a_critical_nonempty_san() {
2813 let root = rcgen::CertifiedIssuer::self_signed(
2814 generated_certificate_params("subject identity authority", true),
2815 rcgen::KeyPair::generate().expect("root key generation should succeed"),
2816 )
2817 .expect("root should be self-signable");
2818
2819 let mut missing_san = rcgen::CertificateParams::new(Vec::new())
2820 .expect("empty SAN list should produce certificate parameters");
2821 missing_san.distinguished_name = rcgen::DistinguishedName::new();
2822 let missing_san = missing_san
2823 .signed_by(
2824 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2825 &root,
2826 )
2827 .expect("test issuer should sign an empty-subject certificate");
2828
2829 let mut noncritical_san = rcgen::CertificateParams::new(Vec::new())
2830 .expect("empty SAN list should produce certificate parameters");
2831 noncritical_san.distinguished_name = rcgen::DistinguishedName::new();
2832 noncritical_san
2836 .custom_extensions
2837 .push(rcgen::CustomExtension::from_oid_content(
2838 &[2, 5, 29, 17],
2839 vec![0x30, 0x03, 0x82, 0x01, b'a'],
2840 ));
2841 let noncritical_san = noncritical_san
2842 .signed_by(
2843 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2844 &root,
2845 )
2846 .expect("test issuer should sign a non-critical-SAN certificate");
2847
2848 for leaf in [missing_san, noncritical_san] {
2849 assert!(matches!(
2850 verify_generated_path(
2851 vec![leaf.der().to_vec(), root.der().to_vec()],
2852 root.der().to_vec(),
2853 ),
2854 Err(X509ChainError::InvalidDer {
2855 kind: "certificate subject identity",
2856 ..
2857 })
2858 ));
2859 }
2860 }
2861
2862 #[test]
2863 fn empty_subject_with_critical_san_skips_directory_name_constraints() {
2864 let mut permitted_directory = rcgen::DistinguishedName::new();
2867 permitted_directory.push(rcgen::DnType::OrganizationName, "Example Corp");
2868 let mut root_params = generated_certificate_params("empty-subject authority", true);
2869 root_params.name_constraints = Some(rcgen::NameConstraints {
2870 permitted_subtrees: vec![rcgen::GeneralSubtree::DirectoryName(permitted_directory)],
2871 excluded_subtrees: Vec::new(),
2872 });
2873 let root = rcgen::CertifiedIssuer::self_signed(
2874 root_params,
2875 rcgen::KeyPair::generate().expect("root key generation should succeed"),
2876 )
2877 .expect("constrained root should be self-signable");
2878 let mut leaf_params = rcgen::CertificateParams::new(vec!["allowed.example".into()])
2879 .expect("DNS SAN should be valid");
2880 leaf_params.distinguished_name = rcgen::DistinguishedName::new();
2881 let leaf = leaf_params
2882 .signed_by(
2883 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2884 &root,
2885 )
2886 .expect("root should sign empty-subject leaf");
2887
2888 verify_generated_path(
2889 vec![leaf.der().to_vec(), root.der().to_vec()],
2890 root.der().to_vec(),
2891 )
2892 .expect("only present name forms should be constrained");
2893 }
2894
2895 #[test]
2896 fn malformed_general_names_in_san_fail_path_validation() {
2897 let root = rcgen::CertifiedIssuer::self_signed(
2898 generated_certificate_params("malformed-SAN root", true),
2899 rcgen::KeyPair::generate().expect("root key generation should succeed"),
2900 )
2901 .expect("root should be self-signable");
2902 for empty_subject in [true, false] {
2903 let mut leaf_params = generated_certificate_params("malformed-SAN leaf", false);
2904 if empty_subject {
2905 leaf_params.distinguished_name = rcgen::DistinguishedName::new();
2906 }
2907 let mut malformed_san = rcgen::CustomExtension::from_oid_content(
2909 &[2, 5, 29, 17],
2910 vec![0x30, 0x03, 0x82, 0x01, 0xff],
2911 );
2912 malformed_san.set_criticality(true);
2913 leaf_params.custom_extensions.push(malformed_san);
2914 let leaf = leaf_params
2915 .signed_by(
2916 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2917 &root,
2918 )
2919 .expect("root should sign malformed-SAN leaf");
2920
2921 assert!(matches!(
2922 verify_generated_path(
2923 vec![leaf.der().to_vec(), root.der().to_vec()],
2924 root.der().to_vec(),
2925 ),
2926 Err(X509ChainError::InvalidDer {
2927 kind: "certificate subject identity",
2928 ..
2929 })
2930 ));
2931 }
2932 }
2933
2934 #[test]
2935 fn typed_subject_alternative_names_require_rfc5280_syntax() {
2936 let root = rcgen::CertifiedIssuer::self_signed(
2937 generated_certificate_params("typed-SAN root", true),
2938 rcgen::KeyPair::generate().expect("root key generation should succeed"),
2939 )
2940 .expect("root should be self-signable");
2941
2942 for (tag, value) in [
2943 (0x81, b"operator@".as_slice()),
2944 (0x81, b"first..last@example.com".as_slice()),
2945 (0x86, b"relative/path".as_slice()),
2946 (0x86, b"https://example.com/%zz".as_slice()),
2947 (0x86, b"https://user@other@example.com/path".as_slice()),
2948 (0x86, b"file:///path".as_slice()),
2949 (0x87, &[192, 0, 2][..]),
2950 ] {
2951 for empty_subject in [false, true] {
2952 let mut leaf_params = generated_certificate_params("typed-SAN leaf", false);
2953 if empty_subject {
2954 leaf_params.distinguished_name = rcgen::DistinguishedName::new();
2955 }
2956 let mut san_der = vec![
2957 0x30,
2958 u8::try_from(value.len() + 2).expect("test SAN must fit short-form DER"),
2959 tag,
2960 u8::try_from(value.len()).expect("test GeneralName must fit short-form DER"),
2961 ];
2962 san_der.extend_from_slice(value);
2963 let mut san = rcgen::CustomExtension::from_oid_content(&[2, 5, 29, 17], san_der);
2964 san.set_criticality(true);
2965 leaf_params.custom_extensions.push(san);
2966 let leaf = leaf_params
2967 .signed_by(
2968 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2969 &root,
2970 )
2971 .expect("root should sign typed-SAN leaf");
2972
2973 assert!(matches!(
2974 verify_generated_path(
2975 vec![leaf.der().to_vec(), root.der().to_vec()],
2976 root.der().to_vec(),
2977 ),
2978 Err(X509ChainError::InvalidDer {
2979 kind: "certificate subject identity",
2980 ..
2981 })
2982 ));
2983 }
2984 }
2985
2986 for (tag, value) in [
2987 (0x81, b"operator@example.com".as_slice()),
2988 (0x81, b"operator@[192.0.2.1]".as_slice()),
2989 (0x81, b"operator@[IPv6:2001:db8::1]".as_slice()),
2990 (0x81, br#""operator desk"@example.com"#.as_slice()),
2991 (0x86, b"urn:example:operator".as_slice()),
2992 (
2993 0x86,
2994 b"https://operator@example.com:8443/path?q=1#id".as_slice(),
2995 ),
2996 (0x87, &[192, 0, 2, 1][..]),
2997 (
2998 0x87,
2999 &[0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1][..],
3000 ),
3001 ] {
3002 let mut leaf_params = rcgen::CertificateParams::new(Vec::new())
3003 .expect("empty SAN list should produce certificate parameters");
3004 leaf_params.distinguished_name = rcgen::DistinguishedName::new();
3005 let mut san_der = vec![
3006 0x30,
3007 u8::try_from(value.len() + 2).expect("test SAN must fit short-form DER"),
3008 tag,
3009 u8::try_from(value.len()).expect("test GeneralName must fit short-form DER"),
3010 ];
3011 san_der.extend_from_slice(value);
3012 let mut san = rcgen::CustomExtension::from_oid_content(&[2, 5, 29, 17], san_der);
3013 san.set_criticality(true);
3014 leaf_params.custom_extensions.push(san);
3015 let leaf = leaf_params
3016 .signed_by(
3017 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3018 &root,
3019 )
3020 .expect("root should sign typed-SAN leaf");
3021
3022 verify_generated_path(
3023 vec![leaf.der().to_vec(), root.der().to_vec()],
3024 root.der().to_vec(),
3025 )
3026 .expect("valid typed SAN identity must satisfy an empty subject");
3027 }
3028 }
3029
3030 fn parsed_merlin_crl(der: &[u8]) -> CertificateRevocationList<'_> {
3031 CertificateRevocationList::from_der(der)
3032 .expect("modified Merlin CRL must remain parseable")
3033 .1
3034 }
3035
3036 fn merlin_crl_der() -> Vec<u8> {
3037 let xml = include_str!(
3038 "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt-crl.xml"
3039 );
3040 let document = Document::parse(xml).expect("tracked Merlin document must parse");
3041 let key_info_node = document
3042 .descendants()
3043 .find(|node| node.has_tag_name((XMLDSIG_NS, "KeyInfo")))
3044 .expect("tracked Merlin document contains KeyInfo");
3045 let key_info = parse_key_info(key_info_node).expect("tracked Merlin KeyInfo must parse");
3046 let KeyInfoSource::X509Data(info) = &key_info.sources[0] else {
3047 panic!("expected X509Data")
3048 };
3049 info.crls[0].clone()
3050 }
3051
3052 #[test]
3053 fn duplicate_crl_and_entry_extension_oids_fail_closed() {
3054 use der::{Decode as _, Encode as _};
3055 use x509_cert::crl::CertificateList;
3056
3057 let original = merlin_crl_der();
3058 let mut duplicate_crl: CertificateList =
3059 CertificateList::from_der(&original).expect("tracked Merlin CRL must decode");
3060 let extensions = duplicate_crl
3061 .tbs_cert_list
3062 .crl_extensions
3063 .as_mut()
3064 .expect("tracked Merlin CRL must contain extensions");
3065 extensions.push(extensions[0].clone());
3066 let duplicate_crl = duplicate_crl
3067 .to_der()
3068 .expect("duplicate CRL extension test vector must encode");
3069 assert_eq!(
3070 validate_crl_extensions(&parsed_merlin_crl(&duplicate_crl), 0),
3071 Err(X509ChainError::InvalidCrl(0))
3072 );
3073
3074 let mut duplicate_entry: CertificateList =
3075 CertificateList::from_der(&original).expect("tracked Merlin CRL must decode");
3076 let duplicate = duplicate_entry
3077 .tbs_cert_list
3078 .crl_extensions
3079 .as_ref()
3080 .and_then(|extensions| extensions.first())
3081 .expect("tracked Merlin CRL must contain an extension")
3082 .clone();
3083 let revoked = duplicate_entry
3084 .tbs_cert_list
3085 .revoked_certificates
3086 .as_mut()
3087 .and_then(|entries| entries.first_mut())
3088 .expect("tracked Merlin CRL must contain a revoked entry");
3089 revoked.crl_entry_extensions = Some(vec![duplicate.clone(), duplicate]);
3090 let duplicate_entry = duplicate_entry
3091 .to_der()
3092 .expect("duplicate entry extension test vector must encode");
3093 assert_eq!(
3094 validate_crl_extensions(&parsed_merlin_crl(&duplicate_entry), 0),
3095 Err(X509ChainError::InvalidCrl(0))
3096 );
3097 }
3098
3099 #[test]
3100 fn malformed_revoked_certificate_serials_fail_closed() {
3101 let original = merlin_crl_der();
3104 let serial = parsed_merlin_crl(&original)
3105 .iter_revoked_certificates()
3106 .next()
3107 .expect("tracked Merlin CRL must contain a revoked entry")
3108 .raw_serial()
3109 .to_vec();
3110 let offsets = original
3111 .windows(serial.len())
3112 .enumerate()
3113 .filter_map(|(offset, bytes)| (bytes == serial).then_some(offset))
3114 .collect::<Vec<_>>();
3115 assert_eq!(
3116 offsets.len(),
3117 1,
3118 "revoked serial fixture must be unambiguous"
3119 );
3120
3121 for replacement in [vec![0; serial.len()], {
3122 let mut negative = serial.clone();
3123 negative[0] = 0x80;
3124 negative
3125 }] {
3126 let mut malformed = original.clone();
3127 malformed[offsets[0]..offsets[0] + serial.len()].copy_from_slice(&replacement);
3128 assert_eq!(
3129 validate_crl_extensions(&parsed_merlin_crl(&malformed), 0),
3130 Err(X509ChainError::InvalidCrl(0))
3131 );
3132 }
3133 }
3134
3135 #[test]
3136 fn delta_crl_indicator_is_rejected_regardless_of_criticality() {
3137 use der::{Decode as _, Encode as _, asn1::OctetString};
3138 use x509_cert::{crl::CertificateList, ext::Extension};
3139
3140 let original = merlin_crl_der();
3141 for critical in [false, true] {
3142 let mut encoded: CertificateList =
3143 CertificateList::from_der(&original).expect("tracked Merlin CRL must decode");
3144 encoded
3145 .tbs_cert_list
3146 .crl_extensions
3147 .get_or_insert_default()
3148 .push(Extension {
3149 extn_id: der::asn1::ObjectIdentifier::new_unwrap("2.5.29.27"),
3150 critical,
3151 extn_value: OctetString::new([0x02, 0x01, 0x01])
3152 .expect("DER INTEGER extension payload must be valid"),
3153 });
3154 let encoded = encoded
3155 .to_der()
3156 .expect("delta CRL indicator test vector must encode");
3157 assert_eq!(
3158 validate_crl_extensions(&parsed_merlin_crl(&encoded), 0),
3159 Err(X509ChainError::InvalidCrl(0)),
3160 "delta CRL indicator criticality must not change unsupported semantics"
3161 );
3162 }
3163 }
3164
3165 #[test]
3166 fn remove_from_crl_is_rejected_in_a_complete_crl() {
3167 use der::{Decode as _, Encode as _, asn1::OctetString};
3168 use x509_cert::{crl::CertificateList, ext::Extension};
3169
3170 let original = merlin_crl_der();
3171 for (reason, accepted) in [(1_u8, true), (8_u8, false)] {
3172 let mut encoded: CertificateList =
3173 CertificateList::from_der(&original).expect("tracked Merlin CRL must decode");
3174 let revoked = encoded
3175 .tbs_cert_list
3176 .revoked_certificates
3177 .as_mut()
3178 .and_then(|entries| entries.first_mut())
3179 .expect("tracked Merlin CRL must contain a revoked entry");
3180 revoked
3181 .crl_entry_extensions
3182 .get_or_insert_default()
3183 .push(Extension {
3184 extn_id: der::asn1::ObjectIdentifier::new_unwrap("2.5.29.21"),
3185 critical: false,
3186 extn_value: OctetString::new([0x0a, 0x01, reason])
3187 .expect("DER ENUMERATED extension payload must be valid"),
3188 });
3189 let encoded = encoded
3190 .to_der()
3191 .expect("reason-code CRL test vector must encode");
3192 let result = validate_crl_extensions(&parsed_merlin_crl(&encoded), 0);
3193 if accepted {
3194 assert_eq!(result, Ok(()), "ordinary revocation reasons remain valid");
3195 } else {
3196 assert_eq!(result, Err(X509ChainError::InvalidCrl(0)));
3197 }
3198 }
3199 }
3200
3201 #[test]
3202 fn unevaluable_uri_names_fail_closed_for_both_constraint_forms() {
3203 use x509_parser::extensions::GeneralSubtree;
3204
3205 let uri = GeneralName::URI("urn:example:opaque");
3208 for constraints in [
3209 NameConstraints {
3210 permitted_subtrees: Some(vec![GeneralSubtree {
3211 base: GeneralName::URI(".example.com"),
3212 }]),
3213 excluded_subtrees: None,
3214 },
3215 NameConstraints {
3216 permitted_subtrees: None,
3217 excluded_subtrees: Some(vec![GeneralSubtree {
3218 base: GeneralName::URI(".example.com"),
3219 }]),
3220 },
3221 ] {
3222 assert_eq!(
3223 validate_general_name(&uri, &constraints, 0, 1),
3224 Err(X509ChainError::NameConstraintViolation {
3225 position: 0,
3226 constraining_position: 1,
3227 })
3228 );
3229 }
3230 }
3231
3232 #[test]
3233 fn unknown_critical_certificate_extension_fails_closed() {
3234 let root = rcgen::CertifiedIssuer::self_signed(
3235 generated_certificate_params("critical-extension root", true),
3236 rcgen::KeyPair::generate().expect("root key generation should succeed"),
3237 )
3238 .expect("root should be self-signable");
3239 let mut leaf_params = generated_certificate_params("critical-extension leaf", false);
3240 let mut extension =
3241 rcgen::CustomExtension::from_oid_content(&[1, 2, 3, 4], vec![0x05, 0x00]);
3242 extension.set_criticality(true);
3243 leaf_params.custom_extensions.push(extension);
3244 let leaf = leaf_params
3245 .signed_by(
3246 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3247 &root,
3248 )
3249 .expect("root should sign leaf certificate");
3250
3251 assert_eq!(
3252 verify_generated_path(
3253 vec![leaf.der().to_vec(), root.der().to_vec()],
3254 root.der().to_vec(),
3255 ),
3256 Err(X509ChainError::UnsupportedCriticalExtension {
3257 position: 0,
3258 oid: "1.2.3.4".into(),
3259 })
3260 );
3261 }
3262
3263 #[test]
3264 fn duplicate_certificate_extension_oids_fail_closed() {
3265 let root = rcgen::CertifiedIssuer::self_signed(
3268 generated_certificate_params("duplicate-extension root", true),
3269 rcgen::KeyPair::generate().expect("root key generation should succeed"),
3270 )
3271 .expect("root should be self-signable");
3272 let mut leaf_params = generated_certificate_params("duplicate-extension leaf", false);
3273 for _ in 0..2 {
3274 leaf_params
3275 .custom_extensions
3276 .push(rcgen::CustomExtension::from_oid_content(
3277 &[1, 2, 3, 4],
3278 vec![0x05, 0x00],
3279 ));
3280 }
3281 let leaf = leaf_params
3282 .signed_by(
3283 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3284 &root,
3285 )
3286 .expect("root should sign leaf certificate");
3287
3288 assert_eq!(
3289 verify_generated_path(
3290 vec![leaf.der().to_vec(), root.der().to_vec()],
3291 root.der().to_vec(),
3292 ),
3293 Err(X509ChainError::DuplicateExtension {
3294 position: 0,
3295 oid: "1.2.3.4".into(),
3296 })
3297 );
3298 }
3299
3300 #[test]
3301 fn invalid_certificate_serial_numbers_fail_path_validation() {
3302 for serial in [vec![0], vec![1; 21]] {
3303 let root = rcgen::CertifiedIssuer::self_signed(
3304 generated_certificate_params("serial root", true),
3305 rcgen::KeyPair::generate().expect("root key generation should succeed"),
3306 )
3307 .expect("root should be self-signable");
3308 let mut leaf_params = generated_certificate_params("invalid serial leaf", false);
3309 leaf_params.serial_number = Some(rcgen::SerialNumber::from_slice(&serial));
3310 let leaf = leaf_params
3311 .signed_by(
3312 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3313 &root,
3314 )
3315 .expect("root should sign leaf certificate");
3316
3317 assert!(matches!(
3318 verify_generated_path(
3319 vec![leaf.der().to_vec(), root.der().to_vec()],
3320 root.der().to_vec(),
3321 ),
3322 Err(X509ChainError::InvalidDer {
3323 kind: "certificate serial number",
3324 ..
3325 })
3326 ));
3327 }
3328
3329 assert!(validate_positive_serial_bytes(&[0x80], "certificate serial number").is_err());
3330 assert!(validate_positive_serial_bytes(&[1; 20], "certificate serial number").is_ok());
3331
3332 let root = rcgen::CertifiedIssuer::self_signed(
3333 generated_certificate_params("serial-padding root", true),
3334 rcgen::KeyPair::generate().expect("root key generation should succeed"),
3335 )
3336 .expect("root should be self-signable");
3337 let mut leaf_params = generated_certificate_params("serial-padding leaf", false);
3338 leaf_params.serial_number = Some(rcgen::SerialNumber::from_slice(&[0x80; 20]));
3339 let leaf = leaf_params
3340 .signed_by(
3341 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3342 &root,
3343 )
3344 .expect("root should sign a maximum-magnitude serial");
3345
3346 verify_generated_path(
3347 vec![leaf.der().to_vec(), root.der().to_vec()],
3348 root.der().to_vec(),
3349 )
3350 .expect("a 20-octet magnitude may require a DER sign-padding octet");
3351 }
3352
3353 #[test]
3354 fn name_constraints_are_rejected_on_end_entity_certificates() {
3355 let root = rcgen::CertifiedIssuer::self_signed(
3358 generated_certificate_params("name-placement root", true),
3359 rcgen::KeyPair::generate().expect("root key generation should succeed"),
3360 )
3361 .expect("root should be self-signable");
3362 let mut leaf_params = generated_certificate_params("name-placement leaf", false);
3363 leaf_params.name_constraints = Some(rcgen::NameConstraints {
3364 permitted_subtrees: vec![rcgen::GeneralSubtree::DnsName("example.com".into())],
3365 excluded_subtrees: Vec::new(),
3366 });
3367 let leaf = leaf_params
3368 .signed_by(
3369 &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3370 &root,
3371 )
3372 .expect("root should sign leaf certificate");
3373
3374 assert!(matches!(
3375 verify_generated_path(
3376 vec![leaf.der().to_vec(), root.der().to_vec()],
3377 root.der().to_vec(),
3378 ),
3379 Err(X509ChainError::InvalidNameConstraints { position: 0 })
3380 ));
3381 }
3382
3383 #[test]
3384 fn dsa_certificate_rejects_mismatched_inner_signature_algorithm() {
3385 let (_, mut certificate) = X509Certificate::from_der(include_bytes!(
3389 "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/balor.der"
3390 ))
3391 .expect("the tracked Merlin certificate is valid DER");
3392 let (_, issuer) = X509Certificate::from_der(include_bytes!(
3393 "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/ca.der"
3394 ))
3395 .expect("the tracked Merlin issuer is a DER certificate");
3396 assert!(verify_certificate_signature(&certificate, &issuer));
3397
3398 certificate.tbs_certificate.signature = issuer.public_key().algorithm.clone();
3399
3400 assert_ne!(
3401 certificate.tbs_certificate.signature,
3402 certificate.signature_algorithm
3403 );
3404 assert!(!verify_certificate_signature(&certificate, &issuer));
3405 }
3406
3407 #[test]
3408 fn dsa_sha1_crl_signature_uses_the_same_fallback_as_certificates() {
3409 let xml = include_str!(
3410 "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt-crl.xml"
3411 );
3412 let document = Document::parse(xml).expect("the tracked Merlin document is valid XML");
3413 let key_info_node = document
3414 .descendants()
3415 .find(|node| node.has_tag_name((XMLDSIG_NS, "KeyInfo")))
3416 .expect("the Merlin document contains KeyInfo");
3417 let key_info = parse_key_info(key_info_node).expect("the Merlin KeyInfo is valid");
3418 let KeyInfoSource::X509Data(info) = &key_info.sources[0] else {
3419 panic!("expected X509Data")
3420 };
3421 let (_, issuer) = X509Certificate::from_der(include_bytes!(
3422 "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/ca.der"
3423 ))
3424 .expect("the tracked Merlin issuer is a DER certificate");
3425 let (_, crl) = CertificateRevocationList::from_der(&info.crls[0])
3426 .expect("the tracked Merlin CRL is valid DER");
3427
3428 assert!(verify_crl_signature(&crl, &issuer));
3429 }
3430
3431 #[test]
3432 fn dsa_crl_rejects_mismatched_inner_signature_algorithm() {
3433 let xml = include_str!(
3434 "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt-crl.xml"
3435 );
3436 let document = Document::parse(xml).expect("the tracked Merlin document is valid XML");
3437 let key_info_node = document
3438 .descendants()
3439 .find(|node| node.has_tag_name((XMLDSIG_NS, "KeyInfo")))
3440 .expect("the Merlin document contains KeyInfo");
3441 let key_info = parse_key_info(key_info_node).expect("the Merlin KeyInfo is valid");
3442 let KeyInfoSource::X509Data(info) = &key_info.sources[0] else {
3443 panic!("expected X509Data")
3444 };
3445 let (_, issuer) = X509Certificate::from_der(include_bytes!(
3446 "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/ca.der"
3447 ))
3448 .expect("the tracked Merlin issuer is a DER certificate");
3449 let (_, mut crl) = CertificateRevocationList::from_der(&info.crls[0])
3450 .expect("the tracked Merlin CRL is valid DER");
3451 assert!(verify_crl_signature(&crl, &issuer));
3452
3453 crl.tbs_cert_list.signature = issuer.public_key().algorithm.clone();
3454
3455 assert_ne!(crl.tbs_cert_list.signature, crl.signature_algorithm);
3456 assert!(!verify_crl_signature(&crl, &issuer));
3457 }
3458}