1use crate::xml::dom::{Document, Node};
20use der::{
21 Decode,
22 asn1::{Ia5StringRef, ObjectIdentifier},
23};
24use std::collections::BTreeMap;
25use x509_cert::ext::pkix::name::DirectoryString;
26use x509_cert::name::Name;
27use x509_parser::extensions::ParsedExtension;
28use x509_parser::prelude::FromDer;
29use x509_parser::public_key::PublicKey;
30use x509_parser::x509::X509Name;
31
32#[cfg(test)]
33use super::digest::compute_digest;
34use super::digest::{DigestAlgorithm, compute_digest_with_provider, constant_time_eq};
35use super::transforms::{self, Transform};
36use super::whitespace::{
37 XmlBase64NormalizeLimitedError, is_xml_whitespace_only, normalize_xml_base64_text,
38 normalize_xml_base64_text_with_limit,
39};
40use super::x509::certificate_signature_matches_with_provider;
41use crate::c14n::C14nAlgorithm;
42use crate::c14n::xml_base::{
43 XmlBaseResolutionBudget, resolve_uri_from_node_with_document_base_with_budget,
44};
45
46pub(crate) use crate::hard_limits::SIGNATURE_REFERENCE_CEILING as MAX_REFERENCES_PER_SIGNATURE;
47#[cfg(test)]
48use crate::hard_limits::X509_CHAIN_DEPTH_CEILING as MAX_X509_CHAIN_DEPTH;
49
50pub(crate) const XMLDSIG_NS: &str = "http://www.w3.org/2000/09/xmldsig#";
52pub(crate) const XMLDSIG11_NS: &str = "http://www.w3.org/2009/xmldsig11#";
54const MAX_DER_ENCODED_KEY_VALUE_LEN: usize = 8192;
55const MAX_DER_ENCODED_KEY_VALUE_TEXT_LEN: usize = 65_536;
56const MAX_DER_ENCODED_KEY_VALUE_BASE64_LEN: usize = MAX_DER_ENCODED_KEY_VALUE_LEN.div_ceil(3) * 4;
57const MAX_KEY_NAME_TEXT_LEN: usize = 4096;
58const MAX_KEY_INFO_CHILD_COUNT: usize = 64;
59const MAX_HMAC_OUTPUT_LENGTH_TEXT_LEN: usize = 32;
60const MAX_RETRIEVAL_XPATH_TEXT_LEN: usize = 256;
61const MAX_RSA_MODULUS_LEN: usize = 1024;
62const MAX_RSA_EXPONENT_LEN: usize = 8;
63pub(crate) const EC_P256_OID: &str = "1.2.840.10045.3.1.7";
64pub(crate) const EC_P384_OID: &str = "1.3.132.0.34";
65pub(crate) const EC_P521_OID: &str = "1.3.132.0.35";
66const MAX_EC_PUBLIC_KEY_LEN: usize = 133;
67const MAX_X509_BASE64_TEXT_LEN: usize = 262_144;
68const MAX_X509_BASE64_NORMALIZED_LEN: usize = MAX_X509_BASE64_TEXT_LEN;
69pub(crate) const MAX_X509_DECODED_BINARY_LEN: usize =
70 MAX_X509_BASE64_NORMALIZED_LEN.div_ceil(4) * 3;
71const MAX_X509_SUBJECT_NAME_TEXT_LEN: usize = 16_384;
72const MAX_X509_ISSUER_NAME_TEXT_LEN: usize = 16_384;
73const MAX_X509_SERIAL_NUMBER_RAW_TEXT_LEN: usize = 16_384;
74const MAX_X509_SERIAL_NUMBER_VALUE_DIGITS: usize = 49;
78const MAX_X509_SERIAL_NUMBER_BYTES: usize = 20;
79const MAX_X509_DATA_ENTRY_COUNT: usize = 64;
80pub(crate) const MAX_X509_DATA_TOTAL_BINARY_LEN: usize = 1_048_576;
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
84#[non_exhaustive]
85pub enum SignatureAlgorithm {
86 DsaSha1,
88 DsaSha256,
90 HmacSha1,
92 HmacSha224,
94 HmacSha256,
96 HmacSha384,
98 HmacSha512,
100 RsaSha1,
102 RsaSha224,
104 RsaSha256,
106 RsaSha384,
108 RsaSha512,
110 EcdsaSha1,
112 EcdsaSha224,
114 EcdsaSha256,
116 EcdsaSha384,
118 EcdsaSha512,
120}
121
122impl SignatureAlgorithm {
123 pub const ALL: [Self; 17] = [
125 Self::DsaSha1,
126 Self::DsaSha256,
127 Self::HmacSha1,
128 Self::HmacSha224,
129 Self::HmacSha256,
130 Self::HmacSha384,
131 Self::HmacSha512,
132 Self::RsaSha1,
133 Self::RsaSha224,
134 Self::RsaSha256,
135 Self::RsaSha384,
136 Self::RsaSha512,
137 Self::EcdsaSha1,
138 Self::EcdsaSha224,
139 Self::EcdsaSha256,
140 Self::EcdsaSha384,
141 Self::EcdsaSha512,
142 ];
143
144 pub(crate) const fn dsa_component_len(self) -> Option<usize> {
146 match self {
147 Self::DsaSha1 => Some(20),
148 Self::DsaSha256 => Some(32),
149 _ => None,
150 }
151 }
152
153 #[must_use]
155 pub const fn hmac_output_bits(self) -> Option<usize> {
156 match self {
157 Self::HmacSha1 => Some(160),
158 Self::HmacSha224 => Some(224),
159 Self::HmacSha256 => Some(256),
160 Self::HmacSha384 => Some(384),
161 Self::HmacSha512 => Some(512),
162 _ => None,
163 }
164 }
165
166 #[must_use]
168 pub fn from_uri(uri: &str) -> Option<Self> {
169 match uri {
170 "http://www.w3.org/2000/09/xmldsig#dsa-sha1" => Some(Self::DsaSha1),
171 "http://www.w3.org/2009/xmldsig11#dsa-sha256" => Some(Self::DsaSha256),
172 "http://www.w3.org/2000/09/xmldsig#hmac-sha1" => Some(Self::HmacSha1),
173 "http://www.w3.org/2001/04/xmldsig-more#hmac-sha224" => Some(Self::HmacSha224),
174 "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256" => Some(Self::HmacSha256),
175 "http://www.w3.org/2001/04/xmldsig-more#hmac-sha384" => Some(Self::HmacSha384),
176 "http://www.w3.org/2001/04/xmldsig-more#hmac-sha512" => Some(Self::HmacSha512),
177 "http://www.w3.org/2000/09/xmldsig#rsa-sha1" => Some(Self::RsaSha1),
178 "http://www.w3.org/2001/04/xmldsig-more#rsa-sha224" => Some(Self::RsaSha224),
179 "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" => Some(Self::RsaSha256),
180 "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384" => Some(Self::RsaSha384),
181 "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512" => Some(Self::RsaSha512),
182 "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1" => Some(Self::EcdsaSha1),
183 "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha224" => Some(Self::EcdsaSha224),
184 "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256" => Some(Self::EcdsaSha256),
185 "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384" => Some(Self::EcdsaSha384),
186 "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512" => Some(Self::EcdsaSha512),
187 _ => None,
188 }
189 }
190
191 #[must_use]
193 pub fn uri(self) -> &'static str {
194 match self {
195 Self::DsaSha1 => "http://www.w3.org/2000/09/xmldsig#dsa-sha1",
196 Self::DsaSha256 => "http://www.w3.org/2009/xmldsig11#dsa-sha256",
197 Self::HmacSha1 => "http://www.w3.org/2000/09/xmldsig#hmac-sha1",
198 Self::HmacSha224 => "http://www.w3.org/2001/04/xmldsig-more#hmac-sha224",
199 Self::HmacSha256 => "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256",
200 Self::HmacSha384 => "http://www.w3.org/2001/04/xmldsig-more#hmac-sha384",
201 Self::HmacSha512 => "http://www.w3.org/2001/04/xmldsig-more#hmac-sha512",
202 Self::RsaSha1 => "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
203 Self::RsaSha224 => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha224",
204 Self::RsaSha256 => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
205 Self::RsaSha384 => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384",
206 Self::RsaSha512 => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512",
207 Self::EcdsaSha1 => "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1",
208 Self::EcdsaSha224 => "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha224",
209 Self::EcdsaSha256 => "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256",
210 Self::EcdsaSha384 => "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384",
211 Self::EcdsaSha512 => "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512",
212 }
213 }
214
215 #[must_use]
217 pub fn signing_allowed(self) -> bool {
218 !matches!(
219 self,
220 Self::RsaSha1 | Self::DsaSha1 | Self::HmacSha1 | Self::EcdsaSha1
221 )
222 }
223}
224
225#[derive(Debug)]
227#[non_exhaustive]
228pub struct SignedInfo {
229 pub c14n_method: C14nAlgorithm,
231 pub signature_method: SignatureAlgorithm,
233 pub hmac_output_length_bits: Option<usize>,
235 pub references: Vec<Reference>,
237}
238
239#[derive(Debug)]
241pub struct Reference {
242 pub uri: Option<String>,
244 pub id: Option<String>,
246 pub ref_type: Option<String>,
248 pub transforms: Vec<Transform>,
250 pub digest_method: DigestAlgorithm,
252 pub digest_value: Vec<u8>,
254}
255
256#[derive(Debug, Default, Clone, PartialEq, Eq)]
258#[non_exhaustive]
259pub struct KeyInfo {
260 pub sources: Vec<KeyInfoSource>,
262}
263
264impl KeyInfo {
265 pub(crate) fn embedded_candidate_count(&self) -> usize {
271 self.sources.iter().fold(0usize, |count, source| {
272 count.saturating_add(match source {
273 KeyInfoSource::KeyValue(_) | KeyInfoSource::DerEncodedKeyValue(_) => 1,
274 KeyInfoSource::X509Data(info) => info.certificates.len(),
275 KeyInfoSource::KeyName(_)
276 | KeyInfoSource::RetrievalMethod { .. }
277 | KeyInfoSource::KeyInfoReference { .. } => 0,
278 })
279 })
280 }
281}
282
283#[derive(Debug, Clone, PartialEq, Eq)]
285#[non_exhaustive]
286pub enum KeyInfoSource {
287 KeyName(String),
289 KeyValue(KeyValueInfo),
291 X509Data(X509DataInfo),
293 DerEncodedKeyValue(Vec<u8>),
295 RetrievalMethod {
297 uri: String,
299 resource_type: Option<String>,
301 transforms: RetrievalMethodTransforms,
303 },
304 KeyInfoReference {
306 uri: String,
308 },
309}
310
311#[derive(Debug, Clone, PartialEq, Eq, Hash)]
313#[non_exhaustive]
314pub enum RetrievalMethodTransforms {
315 None,
317 X509DataNodeSetFilter {
319 expression: String,
321 namespaces: BTreeMap<String, String>,
323 },
324 Unsupported,
328}
329
330#[derive(Debug, Clone, PartialEq, Eq)]
332#[non_exhaustive]
333pub enum KeyValueInfo {
334 Dsa {
336 p: Option<Vec<u8>>,
338 q: Option<Vec<u8>>,
340 g: Option<Vec<u8>>,
342 y: Vec<u8>,
344 },
345 Rsa {
347 modulus: Vec<u8>,
349 exponent: Vec<u8>,
351 },
352 Ec {
354 curve_oid: String,
356 public_key: Vec<u8>,
358 },
359 InvalidEcKeyValue,
361 Unsupported {
363 namespace: Option<String>,
365 local_name: String,
367 },
368}
369
370#[derive(Debug, Default, Clone, PartialEq, Eq)]
372#[non_exhaustive]
373pub struct X509DataInfo {
374 pub certificates: Vec<Vec<u8>>,
378 pub subject_names: Vec<String>,
380 pub issuer_serials: Vec<(String, String)>,
382 pub skis: Vec<Vec<u8>>,
384 pub crls: Vec<Vec<u8>>,
386 pub digests: Vec<(String, Vec<u8>)>,
388 pub parsed_certificates: Vec<ParsedX509Certificate>,
392 pub certificate_chain: Vec<usize>,
394}
395
396#[derive(Debug, Clone, PartialEq, Eq)]
398#[non_exhaustive]
399pub struct ParsedX509Certificate {
400 pub subject_dn: String,
402 pub issuer_dn: String,
404 pub serial_number: Vec<u8>,
406 pub serial_number_hex: String,
408 pub subject_key_identifier: Option<Vec<u8>>,
410 pub public_key: X509PublicKeyInfo,
412}
413
414#[derive(Debug, Clone, PartialEq, Eq)]
416#[non_exhaustive]
417pub enum X509PublicKeyInfo {
418 Rsa {
420 modulus: Vec<u8>,
422 exponent: Vec<u8>,
424 },
425 Ec {
427 curve_oid: String,
429 public_key: Vec<u8>,
431 },
432 Unsupported {
434 algorithm_oid: String,
436 },
437}
438
439#[derive(Debug, thiserror::Error)]
441#[non_exhaustive]
442pub enum ParseError {
443 #[error("XMLDSig policy violation: {0}")]
445 Policy(#[from] crate::policy::PolicyViolation),
446
447 #[error("cryptographic provider error: {0}")]
449 Provider(#[from] crate::provider::ProviderError),
450
451 #[error("missing required element: <{element}>")]
453 MissingElement {
454 element: &'static str,
456 },
457
458 #[error("invalid structure: {0}")]
460 InvalidStructure(String),
461
462 #[error("unsupported algorithm: {uri}")]
464 UnsupportedAlgorithm {
465 uri: String,
467 },
468
469 #[error("base64 decode error: {0}")]
471 Base64(String),
472
473 #[error(
475 "digest length mismatch for {algorithm}: expected {expected} bytes, got {actual} bytes"
476 )]
477 DigestLengthMismatch {
478 algorithm: &'static str,
480 expected: usize,
482 actual: usize,
484 },
485
486 #[error("transform error: {0}")]
488 Transform(#[from] super::types::TransformError),
489}
490
491#[must_use]
493pub fn find_signature_node<'a>(doc: &'a Document<'a>) -> Option<Node<'a, 'a>> {
494 doc.descendants().find(|n| {
495 n.is_element()
496 && n.tag_name().name() == "Signature"
497 && n.tag_name().namespace() == Some(XMLDSIG_NS)
498 })
499}
500
501pub fn parse_signed_info(signed_info_node: Node) -> Result<SignedInfo, ParseError> {
506 parse_signed_info_with_xpath_budget(
507 signed_info_node,
508 &mut transforms::XPathSignatureParseBudget::default(),
509 )
510}
511
512pub(crate) fn parse_signed_info_with_xpath_budget(
513 signed_info_node: Node,
514 xpath_budget: &mut transforms::XPathSignatureParseBudget,
515) -> Result<SignedInfo, ParseError> {
516 verify_ds_element(signed_info_node, "SignedInfo")?;
517
518 let mut children = element_children(signed_info_node);
519
520 let c14n_node = children.next().ok_or(ParseError::MissingElement {
522 element: "CanonicalizationMethod",
523 })?;
524 verify_ds_element(c14n_node, "CanonicalizationMethod")?;
525 let c14n_uri = required_algorithm_attr(c14n_node, "CanonicalizationMethod")?;
526 let mut c14n_method =
527 C14nAlgorithm::from_uri(c14n_uri).ok_or_else(|| ParseError::UnsupportedAlgorithm {
528 uri: c14n_uri.to_string(),
529 })?;
530 if let Some(prefix_list) = parse_inclusive_prefixes(c14n_node)? {
531 if c14n_method.mode() == crate::c14n::C14nMode::Exclusive1_0 {
532 c14n_method = c14n_method.with_prefix_list(&prefix_list);
533 } else {
534 return Err(ParseError::UnsupportedAlgorithm {
535 uri: c14n_uri.to_string(),
536 });
537 }
538 }
539
540 let sig_method_node = children.next().ok_or(ParseError::MissingElement {
542 element: "SignatureMethod",
543 })?;
544 verify_ds_element(sig_method_node, "SignatureMethod")?;
545 let sig_uri = required_algorithm_attr(sig_method_node, "SignatureMethod")?;
546 let signature_method =
547 SignatureAlgorithm::from_uri(sig_uri).ok_or_else(|| ParseError::UnsupportedAlgorithm {
548 uri: sig_uri.to_string(),
549 })?;
550 let hmac_output_length_bits = parse_hmac_output_length(sig_method_node, signature_method)?;
551
552 let mut references = Vec::new();
554 for child in children {
555 verify_ds_element(child, "Reference")?;
556 if references.len() == crate::hard_limits::SIGNATURE_REFERENCE_CEILING {
557 return Err(crate::policy::PolicyViolation::ResourceLimit {
558 resource: crate::policy::resource_name::SIGNATURE_REFERENCES,
559 maximum: crate::hard_limits::SIGNATURE_REFERENCE_CEILING,
560 actual: references.len().saturating_add(1),
561 }
562 .into());
563 }
564 references.push(parse_reference_with_xpath_budget(child, xpath_budget)?);
565 }
566 if references.is_empty() {
567 return Err(ParseError::MissingElement {
568 element: "Reference",
569 });
570 }
571
572 Ok(SignedInfo {
573 c14n_method,
574 signature_method,
575 hmac_output_length_bits,
576 references,
577 })
578}
579
580#[derive(Clone, Copy)]
581struct ByteAlignedHmacOutputLength(usize);
582
583impl ByteAlignedHmacOutputLength {
584 fn parse(text: &str, maximum_bits: usize) -> Result<Self, ParseError> {
585 let bits = text
586 .trim()
587 .parse::<usize>()
588 .map_err(|_| ParseError::InvalidStructure("invalid HMACOutputLength".into()))?;
589 if bits == 0 || bits > maximum_bits || !bits.is_multiple_of(8) {
593 return Err(ParseError::InvalidStructure(format!(
594 "HMACOutputLength must be a positive byte-aligned value no greater than {maximum_bits}"
595 )));
596 }
597 Ok(Self(bits))
598 }
599
600 const fn bits(self) -> usize {
601 self.0
602 }
603}
604
605fn parse_hmac_output_length(
606 node: Node<'_, '_>,
607 algorithm: SignatureAlgorithm,
608) -> Result<Option<usize>, ParseError> {
609 ensure_no_non_whitespace_text(node, "SignatureMethod")?;
610 let mut children = element_children(node);
611 let Some(child) = children.next() else {
612 return Ok(None);
613 };
614 let Some(maximum_bits) = algorithm.hmac_output_bits() else {
615 return Err(ParseError::InvalidStructure(
616 "SignatureMethod parameters do not match the selected algorithm".into(),
617 ));
618 };
619 if child.tag_name().namespace() != Some(XMLDSIG_NS)
620 || child.tag_name().name() != "HMACOutputLength"
621 || children.next().is_some()
622 {
623 return Err(ParseError::InvalidStructure(
624 "SignatureMethod parameters do not match the selected algorithm".into(),
625 ));
626 }
627 ensure_no_element_children(child, "HMACOutputLength")?;
628 let text =
629 collect_text_content_bounded(child, MAX_HMAC_OUTPUT_LENGTH_TEXT_LEN, "HMACOutputLength")?;
630 Ok(Some(
631 ByteAlignedHmacOutputLength::parse(&text, maximum_bits)?.bits(),
632 ))
633}
634
635pub fn parse_reference(reference_node: Node) -> Result<Reference, ParseError> {
639 parse_reference_with_xpath_budget(
640 reference_node,
641 &mut transforms::XPathSignatureParseBudget::default(),
642 )
643}
644
645pub(crate) fn parse_reference_with_xpath_budget(
646 reference_node: Node,
647 xpath_budget: &mut transforms::XPathSignatureParseBudget,
648) -> Result<Reference, ParseError> {
649 verify_ds_element(reference_node, "Reference")?;
650 ensure_no_non_whitespace_text(reference_node, "Reference")?;
651 let uri = reference_node.attribute("URI").map(String::from);
652 let id = reference_node.attribute("Id").map(String::from);
653 let ref_type = reference_node.attribute("Type").map(String::from);
654
655 let mut children = element_children(reference_node);
656
657 let mut transforms = Vec::new();
659 let mut transform_error = None;
660 let (transforms_node, digest_method_node) =
661 reference_transforms_and_digest_method(&mut children)?;
662
663 if let Some(transforms_node) = transforms_node {
664 match transforms::parse_transforms_with_budget(transforms_node, xpath_budget) {
665 Ok(parsed) => transforms = parsed,
666 Err(error) => transform_error = Some(error),
667 }
668 }
669
670 let digest_uri = required_algorithm_attr(digest_method_node, "DigestMethod")?;
672 let digest_method =
673 DigestAlgorithm::from_uri(digest_uri).ok_or_else(|| ParseError::UnsupportedAlgorithm {
674 uri: digest_uri.to_string(),
675 })?;
676
677 let digest_value_node = children.next().ok_or(ParseError::MissingElement {
679 element: "DigestValue",
680 })?;
681 verify_ds_element(digest_value_node, "DigestValue")?;
682 let digest_value = decode_digest_value_children(digest_value_node, digest_method)?;
683
684 if let Some(unexpected) = children.next() {
686 return Err(ParseError::InvalidStructure(format!(
687 "unexpected element <{}> after <DigestValue> in <Reference>",
688 unexpected.tag_name().name()
689 )));
690 }
691
692 if let Some(error) = transform_error {
696 return Err(ParseError::Transform(error));
697 }
698
699 Ok(Reference {
700 uri,
701 id,
702 ref_type,
703 transforms,
704 digest_method,
705 digest_value,
706 })
707}
708
709pub(crate) fn reference_digest_method(
710 reference_node: Node<'_, '_>,
711) -> Result<DigestAlgorithm, ParseError> {
712 verify_ds_element(reference_node, "Reference")?;
713 let mut children = element_children(reference_node);
714 let (_, digest_method_node) = reference_transforms_and_digest_method(&mut children)?;
715 let uri = required_algorithm_attr(digest_method_node, "DigestMethod")?;
716 DigestAlgorithm::from_uri(uri).ok_or_else(|| ParseError::UnsupportedAlgorithm {
717 uri: uri.to_owned(),
718 })
719}
720
721fn reference_transforms_and_digest_method<'a, 'input>(
722 children: &mut impl Iterator<Item = Node<'a, 'input>>,
723) -> Result<(Option<Node<'a, 'input>>, Node<'a, 'input>), ParseError> {
724 let first = children.next().ok_or(ParseError::MissingElement {
725 element: "DigestMethod",
726 })?;
727 let transforms_node = is_ds_element(first, "Transforms").then_some(first);
728 let digest_method_node = if transforms_node.is_some() {
729 children.next().ok_or(ParseError::MissingElement {
730 element: "DigestMethod",
731 })?
732 } else {
733 first
734 };
735 verify_ds_element(digest_method_node, "DigestMethod")?;
736 Ok((transforms_node, digest_method_node))
737}
738
739pub fn parse_key_info(key_info_node: Node) -> Result<KeyInfo, ParseError> {
752 parse_key_info_with_provider(key_info_node, crate::provider::default_provider())
753}
754
755pub(crate) fn parse_key_info_with_provider(
756 key_info_node: Node,
757 provider: &dyn crate::provider::CryptoProvider,
758) -> Result<KeyInfo, ParseError> {
759 let xml_base_budget = XmlBaseResolutionBudget::default();
760 parse_key_info_with_policy_budgets(
761 key_info_node,
762 provider,
763 &xml_base_budget,
764 &crate::policy::ResourcePolicy::default(),
765 )
766}
767
768pub(crate) fn parse_key_info_with_policy_budgets(
769 key_info_node: Node,
770 provider: &dyn crate::provider::CryptoProvider,
771 xml_base_budget: &XmlBaseResolutionBudget,
772 resources: &crate::policy::ResourcePolicy,
773) -> Result<KeyInfo, ParseError> {
774 parse_key_info_with_policy_budgets_and_document_base(
775 key_info_node,
776 provider,
777 xml_base_budget,
778 resources,
779 None,
780 )
781}
782
783pub(crate) fn parse_key_info_with_policy_budgets_and_document_base(
784 key_info_node: Node,
785 provider: &dyn crate::provider::CryptoProvider,
786 xml_base_budget: &XmlBaseResolutionBudget,
787 resources: &crate::policy::ResourcePolicy,
788 document_base: Option<&str>,
789) -> Result<KeyInfo, ParseError> {
790 verify_ds_element(key_info_node, "KeyInfo")?;
791 ensure_no_non_whitespace_text(key_info_node, "KeyInfo")?;
792
793 let mut sources = Vec::new();
794 let mut x509_total_binary_len = 0usize;
795 let mut embedded_candidate_preflight_count = 0usize;
799 for (index, child) in element_children(key_info_node).enumerate() {
800 if index >= MAX_KEY_INFO_CHILD_COUNT {
801 return Err(ParseError::InvalidStructure(
802 "KeyInfo contains too many child elements".into(),
803 ));
804 }
805 match (child.tag_name().namespace(), child.tag_name().name()) {
806 (Some(XMLDSIG_NS), "KeyName") => {
807 ensure_no_element_children(child, "KeyName")?;
808 let key_name =
809 collect_text_content_bounded(child, MAX_KEY_NAME_TEXT_LEN, "KeyName")?;
810 sources.push(KeyInfoSource::KeyName(key_name));
811 }
812 (Some(XMLDSIG_NS), "KeyValue") => {
813 charge_embedded_key_candidate(&mut embedded_candidate_preflight_count, resources)?;
814 let key_value = parse_key_value_dispatch(child)?;
815 sources.push(KeyInfoSource::KeyValue(key_value));
816 }
817 (Some(XMLDSIG_NS), "X509Data") => {
818 let x509 = parse_x509_data_dispatch_with_budget_and_provider(
819 child,
820 &mut x509_total_binary_len,
821 &mut embedded_candidate_preflight_count,
822 provider,
823 resources,
824 )?;
825 sources.push(KeyInfoSource::X509Data(x509));
826 }
827 (Some(XMLDSIG_NS), "RetrievalMethod") => {
828 ensure_no_non_whitespace_text(child, "RetrievalMethod")?;
829 let lexical_uri = child.attribute("URI").ok_or_else(|| {
830 ParseError::InvalidStructure("RetrievalMethod requires URI".into())
831 })?;
832 if lexical_uri.len() > MAX_KEY_NAME_TEXT_LEN {
833 return Err(ParseError::InvalidStructure(
834 "RetrievalMethod URI exceeds maximum length".into(),
835 ));
836 }
837 let uri = if lexical_uri.is_empty() || lexical_uri.starts_with('#') {
838 lexical_uri.to_owned()
839 } else {
840 resolve_uri_from_node_with_document_base_with_budget(
843 child,
844 lexical_uri,
845 document_base,
846 xml_base_budget,
847 )
848 .map_err(|error| ParseError::InvalidStructure(error.to_string()))?
849 };
850 let resource_type = child.attribute("Type");
851 if resource_type.is_some_and(|value| value.len() > MAX_KEY_NAME_TEXT_LEN) {
852 return Err(ParseError::InvalidStructure(
853 "RetrievalMethod Type exceeds maximum length".into(),
854 ));
855 }
856 let resource_type = resource_type.map(str::to_owned);
857 let transforms = if resource_type.as_deref()
858 == Some("http://www.w3.org/2000/09/xmldsig#X509Data")
859 {
860 parse_retrieval_method_transforms(child, resources)?
861 } else if element_children(child).next().is_some() {
862 RetrievalMethodTransforms::Unsupported
863 } else {
864 RetrievalMethodTransforms::None
865 };
866 sources.push(KeyInfoSource::RetrievalMethod {
867 uri,
868 resource_type,
869 transforms,
870 });
871 }
872 (Some(XMLDSIG11_NS), "DEREncodedKeyValue") => {
873 charge_embedded_key_candidate(&mut embedded_candidate_preflight_count, resources)?;
874 ensure_no_element_children(child, "DEREncodedKeyValue")?;
875 let der = decode_der_encoded_key_value_base64(child)?;
876 sources.push(KeyInfoSource::DerEncodedKeyValue(der));
877 }
878 (Some(XMLDSIG11_NS), "KeyInfoReference") => {
879 ensure_no_element_children(child, "KeyInfoReference")?;
880 ensure_no_non_whitespace_text(child, "KeyInfoReference")?;
881 let lexical_uri = child.attribute("URI").ok_or_else(|| {
882 ParseError::InvalidStructure("KeyInfoReference requires URI".into())
883 })?;
884 if lexical_uri.len() > MAX_KEY_NAME_TEXT_LEN {
885 return Err(ParseError::InvalidStructure(
886 "KeyInfoReference URI exceeds maximum length".into(),
887 ));
888 }
889 let uri = if lexical_uri.is_empty() || lexical_uri.starts_with('#') {
890 lexical_uri.to_owned()
891 } else {
892 resolve_uri_from_node_with_document_base_with_budget(
893 child,
894 lexical_uri,
895 document_base,
896 xml_base_budget,
897 )
898 .map_err(|error| ParseError::InvalidStructure(error.to_string()))?
899 };
900 sources.push(KeyInfoSource::KeyInfoReference { uri });
901 }
902 _ => {}
903 }
904 }
905
906 Ok(KeyInfo { sources })
907}
908
909fn charge_embedded_key_candidate(
910 embedded_key_candidates: &mut usize,
911 resources: &crate::policy::ResourcePolicy,
912) -> Result<(), ParseError> {
913 *embedded_key_candidates = embedded_key_candidates.saturating_add(1);
914 resources.validate_key_candidates(*embedded_key_candidates)?;
915 Ok(())
916}
917
918fn parse_retrieval_method_transforms(
919 node: Node<'_, '_>,
920 resources: &crate::policy::ResourcePolicy,
921) -> Result<RetrievalMethodTransforms, ParseError> {
922 let mut children = element_children(node);
923 let Some(transforms) = children.next() else {
924 return Ok(RetrievalMethodTransforms::None);
925 };
926 if children.next().is_some()
927 || transforms.tag_name().namespace() != Some(XMLDSIG_NS)
928 || transforms.tag_name().name() != "Transforms"
929 {
930 return Err(ParseError::InvalidStructure(
931 "RetrievalMethod accepts only one optional ds:Transforms child".into(),
932 ));
933 }
934 ensure_no_non_whitespace_text(transforms, "Transforms")?;
935 let mut transform_children = element_children(transforms);
936 let transform = transform_children.next().ok_or_else(|| {
937 ParseError::InvalidStructure("RetrievalMethod Transforms must not be empty".into())
938 })?;
939 if transform_children.next().is_some()
940 || transform.tag_name().namespace() != Some(XMLDSIG_NS)
941 || transform.tag_name().name() != "Transform"
942 || transform.attribute("Algorithm") != Some(transforms::XPATH_TRANSFORM_URI)
943 {
944 return Err(ParseError::InvalidStructure(
945 "unsupported RetrievalMethod transform chain".into(),
946 ));
947 }
948 ensure_no_non_whitespace_text(transform, "Transform")?;
949 let mut parameters = element_children(transform);
950 let xpath = parameters.next().ok_or_else(|| {
951 ParseError::InvalidStructure("RetrievalMethod XPath parameter is missing".into())
952 })?;
953 if parameters.next().is_some()
954 || xpath.tag_name().namespace() != Some(XMLDSIG_NS)
955 || xpath.tag_name().name() != "XPath"
956 {
957 return Err(ParseError::InvalidStructure(
958 "unsupported RetrievalMethod transform chain".into(),
959 ));
960 }
961 ensure_no_element_children(xpath, "XPath")?;
962 let expression =
963 collect_text_content_bounded(xpath, MAX_RETRIEVAL_XPATH_TEXT_LEN, "RetrievalMethod XPath")?;
964 let normalized_expression = expression.trim();
965 let selects_x509_data = normalized_expression
966 .strip_prefix("ancestor-or-self::")
967 .and_then(|step| step.split_once(':'))
968 .is_some_and(|(prefix, local)| {
969 local == "X509Data" && xpath.lookup_namespace_uri(Some(prefix)) == Some(XMLDSIG_NS)
970 });
971 if !selects_x509_data {
972 return Err(ParseError::InvalidStructure(
973 "unsupported RetrievalMethod XPath selection".into(),
974 ));
975 }
976 let namespaces = transforms::collect_xpath_namespaces_with_resources(xpath, resources)?;
977 Ok(RetrievalMethodTransforms::X509DataNodeSetFilter {
978 expression,
979 namespaces,
980 })
981}
982
983fn element_children<'a>(node: Node<'a, 'a>) -> impl Iterator<Item = Node<'a, 'a>> {
987 node.children().filter(|n| n.is_element())
988}
989
990fn verify_ds_element(node: Node, expected_name: &'static str) -> Result<(), ParseError> {
992 verify_namespaced_element(node, expected_name, XMLDSIG_NS, "ds")
993}
994
995fn verify_dsig11_element(node: Node, expected_name: &'static str) -> Result<(), ParseError> {
997 verify_namespaced_element(node, expected_name, XMLDSIG11_NS, "dsig11")
998}
999
1000fn verify_namespaced_element(
1001 node: Node,
1002 expected_name: &'static str,
1003 expected_namespace: &'static str,
1004 diagnostic_prefix: &'static str,
1005) -> Result<(), ParseError> {
1006 if !node.is_element() {
1007 return Err(ParseError::InvalidStructure(format!(
1008 "expected element <{expected_name}>, got non-element node"
1009 )));
1010 }
1011 let tag = node.tag_name();
1012 if tag.name() != expected_name || tag.namespace() != Some(expected_namespace) {
1013 return Err(ParseError::InvalidStructure(format!(
1014 "expected <{diagnostic_prefix}:{expected_name}>, got <{}{}>",
1015 tag.namespace()
1016 .map(|ns| format!("{{{ns}}}"))
1017 .unwrap_or_default(),
1018 tag.name()
1019 )));
1020 }
1021 Ok(())
1022}
1023
1024fn required_algorithm_attr<'a>(
1026 node: Node<'a, 'a>,
1027 element_name: &'static str,
1028) -> Result<&'a str, ParseError> {
1029 node.attribute("Algorithm").ok_or_else(|| {
1030 ParseError::InvalidStructure(format!("missing Algorithm attribute on <{element_name}>"))
1031 })
1032}
1033
1034fn parse_inclusive_prefixes(node: Node) -> Result<Option<String>, ParseError> {
1040 const EXCLUSIVE_C14N_NS_URI: &str = "http://www.w3.org/2001/10/xml-exc-c14n#";
1041
1042 for child in node.children() {
1043 if child.is_element() {
1044 let tag = child.tag_name();
1045 if tag.name() == "InclusiveNamespaces" && tag.namespace() == Some(EXCLUSIVE_C14N_NS_URI)
1046 {
1047 return child
1048 .attribute("PrefixList")
1049 .map(str::to_string)
1050 .ok_or_else(|| {
1051 ParseError::InvalidStructure(
1052 "missing PrefixList attribute on <InclusiveNamespaces>".into(),
1053 )
1054 })
1055 .map(Some);
1056 }
1057 }
1058 }
1059
1060 Ok(None)
1061}
1062
1063fn parse_key_value_dispatch(node: Node) -> Result<KeyValueInfo, ParseError> {
1064 verify_ds_element(node, "KeyValue")?;
1065 ensure_no_non_whitespace_text(node, "KeyValue")?;
1066
1067 let mut children = element_children(node);
1068 let Some(first_child) = children.next() else {
1069 return Err(ParseError::InvalidStructure(
1070 "KeyValue must contain exactly one key-value child".into(),
1071 ));
1072 };
1073 if children.next().is_some() {
1074 return Err(ParseError::InvalidStructure(
1075 "KeyValue must contain exactly one key-value child".into(),
1076 ));
1077 }
1078
1079 match (
1080 first_child.tag_name().namespace(),
1081 first_child.tag_name().name(),
1082 ) {
1083 (Some(XMLDSIG_NS), "RSAKeyValue") => parse_rsa_key_value(first_child),
1084 (Some(XMLDSIG_NS), "DSAKeyValue") => parse_dsa_key_value(first_child),
1085 (Some(XMLDSIG11_NS), "ECKeyValue") => parse_ec_key_value(first_child),
1086 (namespace, child_name) => Ok(KeyValueInfo::Unsupported {
1087 namespace: namespace.map(str::to_string),
1088 local_name: child_name.to_string(),
1089 }),
1090 }
1091}
1092
1093fn parse_dsa_key_value(node: Node<'_, '_>) -> Result<KeyValueInfo, ParseError> {
1094 verify_ds_element(node, "DSAKeyValue")?;
1095 ensure_no_non_whitespace_text(node, "DSAKeyValue")?;
1096 let children = element_children(node).collect::<Vec<_>>();
1097 let mut index = 0;
1098 let p = take_dsa_crypto_binary(&children, &mut index, "P")?;
1099 let q = take_dsa_crypto_binary(&children, &mut index, "Q")?;
1100 if p.is_some() != q.is_some() {
1101 return Err(ParseError::InvalidStructure(
1102 "DSAKeyValue P and Q must be present together".into(),
1103 ));
1104 }
1105 let g = take_dsa_crypto_binary(&children, &mut index, "G")?;
1106 let y = take_dsa_crypto_binary(&children, &mut index, "Y")?
1107 .ok_or_else(|| ParseError::InvalidStructure("DSAKeyValue requires Y".into()))?;
1108 let _j = take_dsa_crypto_binary(&children, &mut index, "J")?;
1109 let seed = take_dsa_crypto_binary(&children, &mut index, "Seed")?;
1110 let counter = take_dsa_crypto_binary(&children, &mut index, "PgenCounter")?;
1111 if seed.is_some() != counter.is_some() {
1112 return Err(ParseError::InvalidStructure(
1113 "DSAKeyValue Seed and PgenCounter must be present together".into(),
1114 ));
1115 }
1116 if index != children.len() {
1117 return Err(ParseError::InvalidStructure(
1118 "DSAKeyValue children do not match the XMLDSig schema order".into(),
1119 ));
1120 }
1121 Ok(KeyValueInfo::Dsa { p, q, g, y })
1122}
1123
1124fn take_dsa_crypto_binary(
1125 children: &[Node<'_, '_>],
1126 index: &mut usize,
1127 name: &'static str,
1128) -> Result<Option<Vec<u8>>, ParseError> {
1129 let Some(&child) = children.get(*index) else {
1130 return Ok(None);
1131 };
1132 if !is_ds_element(child, name) {
1133 return Ok(None);
1134 }
1135 *index += 1;
1136 ensure_no_element_children(child, name)?;
1137 decode_crypto_binary(child, name, MAX_RSA_MODULUS_LEN).map(Some)
1138}
1139
1140fn is_ds_element(node: Node<'_, '_>, name: &str) -> bool {
1141 node.tag_name().namespace() == Some(XMLDSIG_NS) && node.tag_name().name() == name
1142}
1143
1144fn parse_ec_key_value(node: Node<'_, '_>) -> Result<KeyValueInfo, ParseError> {
1145 verify_dsig11_element(node, "ECKeyValue")?;
1146 ensure_no_non_whitespace_text(node, "ECKeyValue")?;
1147
1148 let mut children = element_children(node);
1149 let Some(named_curve_node) = children.next() else {
1150 return Ok(KeyValueInfo::InvalidEcKeyValue);
1151 };
1152 if named_curve_node.tag_name().namespace() == Some(XMLDSIG11_NS)
1153 && named_curve_node.tag_name().name() == "ECParameters"
1154 {
1155 return Ok(KeyValueInfo::Unsupported {
1156 namespace: Some(XMLDSIG11_NS.to_string()),
1157 local_name: "ECKeyValue".into(),
1158 });
1159 }
1160 if named_curve_node.tag_name().namespace() != Some(XMLDSIG11_NS)
1161 || named_curve_node.tag_name().name() != "NamedCurve"
1162 {
1163 return Ok(KeyValueInfo::InvalidEcKeyValue);
1164 }
1165 ensure_no_element_children(named_curve_node, "NamedCurve")?;
1166 ensure_no_non_whitespace_text(named_curve_node, "NamedCurve")?;
1167 let Some((curve_oid, expected_public_key_len)) =
1168 (match parse_ec_named_curve_oid(named_curve_node) {
1169 Ok(curve) => curve,
1170 Err(_) => return Ok(KeyValueInfo::InvalidEcKeyValue),
1171 })
1172 else {
1173 return Ok(KeyValueInfo::Unsupported {
1174 namespace: Some(XMLDSIG11_NS.to_string()),
1175 local_name: "ECKeyValue".into(),
1176 });
1177 };
1178
1179 let Some(public_key_node) = children.next() else {
1180 return Ok(KeyValueInfo::InvalidEcKeyValue);
1181 };
1182 if public_key_node.tag_name().namespace() != Some(XMLDSIG11_NS)
1183 || public_key_node.tag_name().name() != "PublicKey"
1184 {
1185 return Ok(KeyValueInfo::InvalidEcKeyValue);
1186 }
1187 ensure_no_element_children(public_key_node, "PublicKey")?;
1188 if children.next().is_some() {
1189 return Ok(KeyValueInfo::InvalidEcKeyValue);
1190 }
1191
1192 let public_key = match decode_crypto_binary(public_key_node, "PublicKey", MAX_EC_PUBLIC_KEY_LEN)
1193 {
1194 Ok(public_key) => public_key,
1195 Err(_) => return Ok(KeyValueInfo::InvalidEcKeyValue),
1196 };
1197 if validate_ec_public_key_point(&public_key, expected_public_key_len).is_err() {
1198 return Ok(KeyValueInfo::InvalidEcKeyValue);
1199 }
1200
1201 Ok(KeyValueInfo::Ec {
1202 curve_oid,
1203 public_key,
1204 })
1205}
1206
1207fn parse_ec_named_curve_oid(node: Node<'_, '_>) -> Result<Option<(String, usize)>, ParseError> {
1208 let uri = node.attribute("URI").ok_or_else(|| {
1209 ParseError::InvalidStructure("ECKeyValue NamedCurve must include URI attribute".into())
1210 })?;
1211 let curve_oid = uri.strip_prefix("urn:oid:").unwrap_or(uri);
1212 if curve_oid.is_empty() {
1213 return Err(ParseError::InvalidStructure(
1214 "ECKeyValue NamedCurve URI must not be empty".into(),
1215 ));
1216 }
1217 let Some(public_key_len) = ec_public_key_len(curve_oid) else {
1218 return Ok(None);
1219 };
1220 Ok(Some((curve_oid.to_string(), public_key_len)))
1221}
1222
1223fn ec_public_key_len(curve_oid: &str) -> Option<usize> {
1224 match curve_oid {
1225 EC_P256_OID => Some(65),
1226 EC_P384_OID => Some(97),
1227 EC_P521_OID => Some(133),
1228 _ => None,
1229 }
1230}
1231
1232fn validate_ec_public_key_point(public_key: &[u8], expected_len: usize) -> Result<(), ParseError> {
1233 if public_key.len() != expected_len {
1234 return Err(ParseError::InvalidStructure(
1235 "ECKeyValue PublicKey length does not match NamedCurve".into(),
1236 ));
1237 }
1238 if public_key.first().copied() != Some(0x04) {
1239 return Err(ParseError::InvalidStructure(
1240 "ECKeyValue PublicKey must be an uncompressed SEC1 point".into(),
1241 ));
1242 }
1243 Ok(())
1244}
1245
1246fn parse_rsa_key_value(node: Node<'_, '_>) -> Result<KeyValueInfo, ParseError> {
1247 verify_ds_element(node, "RSAKeyValue")?;
1248 ensure_no_non_whitespace_text(node, "RSAKeyValue")?;
1249
1250 let mut children = element_children(node);
1251 let modulus_node = children.next().ok_or_else(|| {
1252 ParseError::InvalidStructure("RSAKeyValue requires Modulus and Exponent".into())
1253 })?;
1254 verify_ds_element(modulus_node, "Modulus")?;
1255 ensure_no_element_children(modulus_node, "Modulus")?;
1256
1257 let exponent_node = children.next().ok_or_else(|| {
1258 ParseError::InvalidStructure("RSAKeyValue requires Modulus and Exponent".into())
1259 })?;
1260 verify_ds_element(exponent_node, "Exponent")?;
1261 ensure_no_element_children(exponent_node, "Exponent")?;
1262 if children.next().is_some() {
1263 return Err(ParseError::InvalidStructure(
1264 "RSAKeyValue must contain exactly Modulus followed by Exponent".into(),
1265 ));
1266 }
1267
1268 Ok(KeyValueInfo::Rsa {
1269 modulus: decode_crypto_binary(modulus_node, "Modulus", MAX_RSA_MODULUS_LEN)?,
1270 exponent: decode_crypto_binary(exponent_node, "Exponent", MAX_RSA_EXPONENT_LEN)?,
1271 })
1272}
1273
1274fn decode_crypto_binary(
1275 node: Node<'_, '_>,
1276 element_name: &'static str,
1277 max_decoded_len: usize,
1278) -> Result<Vec<u8>, ParseError> {
1279 use base64::Engine;
1280 use base64::engine::general_purpose::STANDARD;
1281
1282 let max_base64_len = max_decoded_len.div_ceil(3) * 4;
1283 let mut cleaned = String::with_capacity(max_base64_len);
1284 for text in node
1285 .children()
1286 .filter(|child| child.is_text())
1287 .filter_map(|child| child.text())
1288 {
1289 normalize_xml_base64_text_with_limit(text, &mut cleaned, max_base64_len).map_err(
1290 |err| match err {
1291 XmlBase64NormalizeLimitedError::InvalidWhitespace(err) => {
1292 ParseError::Base64(format!(
1293 "invalid XML whitespace U+{:04X} in {element_name}",
1294 err.invalid_byte
1295 ))
1296 }
1297 XmlBase64NormalizeLimitedError::TooLong(_) => ParseError::InvalidStructure(
1298 format!("{element_name} exceeds maximum allowed base64 length"),
1299 ),
1300 },
1301 )?;
1302 }
1303
1304 let value = STANDARD
1305 .decode(&cleaned)
1306 .map_err(|err| ParseError::Base64(format!("{element_name}: {err}")))?;
1307 if value.is_empty() {
1308 return Err(ParseError::InvalidStructure(format!(
1309 "{element_name} must not be empty"
1310 )));
1311 }
1312 if value.len() > max_decoded_len {
1313 return Err(ParseError::InvalidStructure(format!(
1314 "{element_name} exceeds maximum allowed binary length"
1315 )));
1316 }
1317 Ok(value)
1318}
1319
1320pub(crate) fn parse_x509_data_dispatch_with_budget_and_provider(
1321 node: Node,
1322 total_binary_len: &mut usize,
1323 embedded_key_candidates: &mut usize,
1324 provider: &dyn crate::provider::CryptoProvider,
1325 resources: &crate::policy::ResourcePolicy,
1326) -> Result<X509DataInfo, ParseError> {
1327 verify_ds_element(node, "X509Data")?;
1328 ensure_no_non_whitespace_text(node, "X509Data")?;
1329
1330 let mut info = X509DataInfo::default();
1331 for child in element_children(node) {
1332 match (child.tag_name().namespace(), child.tag_name().name()) {
1333 (Some(XMLDSIG_NS), "X509Certificate") => {
1334 charge_embedded_key_candidate(embedded_key_candidates, resources)?;
1335 ensure_no_element_children(child, "X509Certificate")?;
1336 ensure_x509_data_entry_budget(&info)?;
1337 let cert = decode_x509_base64(child, "X509Certificate")?;
1338 add_x509_data_usage(total_binary_len, cert.len())?;
1339 let parsed_cert = parse_x509_certificate(cert.as_slice())?;
1340 info.parsed_certificates.push(parsed_cert);
1341 info.certificates.push(cert);
1342 }
1343 (Some(XMLDSIG_NS), "X509SubjectName") => {
1344 ensure_no_element_children(child, "X509SubjectName")?;
1345 ensure_x509_data_entry_budget(&info)?;
1346 let subject_name = collect_text_content_bounded(
1347 child,
1348 MAX_X509_SUBJECT_NAME_TEXT_LEN,
1349 "X509SubjectName",
1350 )?;
1351 info.subject_names.push(subject_name);
1352 }
1353 (Some(XMLDSIG_NS), "X509IssuerSerial") => {
1354 ensure_x509_data_entry_budget(&info)?;
1355 let issuer_serial = parse_x509_issuer_serial(child)?;
1356 info.issuer_serials.push(issuer_serial);
1357 }
1358 (Some(XMLDSIG_NS), "X509SKI") => {
1359 ensure_no_element_children(child, "X509SKI")?;
1360 ensure_x509_data_entry_budget(&info)?;
1361 let ski = decode_x509_base64(child, "X509SKI")?;
1362 add_x509_data_usage(total_binary_len, ski.len())?;
1363 info.skis.push(ski);
1364 }
1365 (Some(XMLDSIG_NS), "X509CRL") => {
1366 ensure_no_element_children(child, "X509CRL")?;
1367 ensure_x509_data_entry_budget(&info)?;
1368 let crl = decode_x509_base64(child, "X509CRL")?;
1369 add_x509_data_usage(total_binary_len, crl.len())?;
1370 info.crls.push(crl);
1371 }
1372 (Some(XMLDSIG11_NS), "X509Digest") => {
1373 ensure_no_element_children(child, "X509Digest")?;
1374 ensure_x509_data_entry_budget(&info)?;
1375 let algorithm = required_algorithm_attr(child, "X509Digest")?;
1376 let digest = decode_x509_base64(child, "X509Digest")?;
1377 add_x509_data_usage(total_binary_len, digest.len())?;
1378 info.digests.push((algorithm.to_string(), digest));
1379 }
1380 (Some(XMLDSIG_NS), child_name) | (Some(XMLDSIG11_NS), child_name) => {
1381 return Err(ParseError::InvalidStructure(format!(
1382 "X509Data contains unsupported XMLDSig child element <{child_name}>"
1383 )));
1384 }
1385 _ => {}
1386 }
1387 }
1388
1389 info.certificate_chain = build_x509_certificate_chain(&info, provider)?;
1390 Ok(info)
1391}
1392
1393fn build_x509_certificate_chain(
1394 info: &X509DataInfo,
1395 provider: &dyn crate::provider::CryptoProvider,
1396) -> Result<Vec<usize>, ParseError> {
1397 if info.parsed_certificates.is_empty() {
1398 return Ok(Vec::new());
1399 }
1400
1401 let signing_idx = select_x509_signing_certificate(info, provider)?;
1402 build_x509_certificate_chain_from(info, signing_idx, provider).map_err(ParseError::from)
1403}
1404
1405#[derive(Debug, Clone, PartialEq, Eq)]
1406pub(crate) enum X509ChainBuildError {
1407 InconsistentMetadata,
1408 DepthExceeded,
1409 Cycle,
1410 IssuerSignatureMismatch,
1411 AmbiguousIssuer,
1412 UnsupportedSignatureAlgorithm { oid: String },
1413 Provider(crate::provider::ProviderError),
1414}
1415
1416impl From<X509ChainBuildError> for ParseError {
1417 fn from(error: X509ChainBuildError) -> Self {
1418 let reason = match error {
1419 X509ChainBuildError::InconsistentMetadata => {
1420 "X509Data certificate metadata is inconsistent"
1421 }
1422 X509ChainBuildError::DepthExceeded => {
1423 "X509Data certificate chain exceeds maximum depth"
1424 }
1425 X509ChainBuildError::Cycle => "X509Data certificate chain contains a cycle",
1426 X509ChainBuildError::IssuerSignatureMismatch => {
1427 "X509Data issuer candidates do not verify the certificate signature"
1428 }
1429 X509ChainBuildError::AmbiguousIssuer => {
1430 "X509Data certificate chain contains ambiguous issuer certificates"
1431 }
1432 X509ChainBuildError::UnsupportedSignatureAlgorithm { oid } => {
1433 return Self::InvalidStructure(format!(
1434 "X509Data certificate chain uses unsupported signature algorithm {oid}"
1435 ));
1436 }
1437 X509ChainBuildError::Provider(error) => return Self::Provider(error),
1438 };
1439 Self::InvalidStructure(reason.into())
1440 }
1441}
1442
1443pub(crate) fn build_x509_certificate_chain_from(
1445 info: &X509DataInfo,
1446 signing_idx: usize,
1447 provider: &dyn crate::provider::CryptoProvider,
1448) -> Result<Vec<usize>, X509ChainBuildError> {
1449 if signing_idx >= info.parsed_certificates.len()
1450 || info.parsed_certificates.len() != info.certificates.len()
1451 {
1452 return Err(X509ChainBuildError::InconsistentMetadata);
1453 }
1454 let mut chain = vec![signing_idx];
1455
1456 loop {
1457 let current_idx = *chain
1458 .last()
1459 .expect("chain starts with signing certificate index");
1460 let current = &info.parsed_certificates[current_idx];
1461 if distinguished_names_equal(¤t.subject_dn, ¤t.issuer_dn) {
1462 break;
1463 }
1464
1465 let candidates = info
1466 .parsed_certificates
1467 .iter()
1468 .enumerate()
1469 .filter(|(idx, cert)| {
1470 *idx != current_idx
1471 && distinguished_names_equal(&cert.subject_dn, ¤t.issuer_dn)
1472 })
1473 .map(|(idx, _)| idx)
1474 .collect::<Vec<_>>();
1475
1476 let issuer_idx = match candidates.as_slice() {
1477 [] => break,
1478 [issuer_idx] => *issuer_idx,
1479 _ => {
1480 let mut verified = Vec::new();
1481 let mut unsupported_oid = None;
1482 for issuer_idx in candidates {
1483 match certificate_signature_matches_with_provider(
1484 &info.certificates[current_idx],
1485 &info.certificates[issuer_idx],
1486 provider,
1487 ) {
1488 Ok(true) => verified.push(issuer_idx),
1489 Ok(false) => {}
1490 Err(super::X509ChainError::Provider(
1491 crate::provider::ProviderError::Unsupported {
1492 operation: crate::provider::ProviderOperation::VerifyCertificate,
1493 algorithm: Some(oid),
1494 },
1495 )) => {
1496 unsupported_oid.get_or_insert(oid);
1497 }
1498 Err(super::X509ChainError::Provider(error)) => {
1499 return Err(X509ChainBuildError::Provider(error));
1500 }
1501 Err(super::X509ChainError::UnsupportedSignatureAlgorithm { oid }) => {
1502 unsupported_oid.get_or_insert(oid);
1503 }
1504 Err(_) => return Err(X509ChainBuildError::IssuerSignatureMismatch),
1505 }
1506 }
1507 match verified.as_slice() {
1508 [issuer_idx] => *issuer_idx,
1509 [] => {
1510 if let Some(oid) = unsupported_oid {
1511 return Err(X509ChainBuildError::UnsupportedSignatureAlgorithm { oid });
1512 }
1513 return Err(X509ChainBuildError::IssuerSignatureMismatch);
1514 }
1515 _ => return Err(X509ChainBuildError::AmbiguousIssuer),
1516 }
1517 }
1518 };
1519 if chain.contains(&issuer_idx) {
1520 return Err(X509ChainBuildError::Cycle);
1521 }
1522 if chain.len() == crate::hard_limits::X509_CHAIN_DEPTH_CEILING {
1523 return Err(X509ChainBuildError::DepthExceeded);
1524 }
1525 chain.push(issuer_idx);
1526 }
1527
1528 Ok(chain)
1529}
1530
1531pub(crate) fn build_x509_certificate_paths_to_trusted_prefix(
1535 info: &X509DataInfo,
1536 signing_idx: usize,
1537 trusted_prefix_len: usize,
1538 max_depth: usize,
1539 max_candidate_paths: usize,
1540 provider: &dyn crate::provider::CryptoProvider,
1541) -> Result<Vec<Vec<usize>>, X509ChainBuildError> {
1542 if trusted_prefix_len > info.certificates.len() {
1543 return Err(X509ChainBuildError::InconsistentMetadata);
1544 }
1545 build_x509_certificate_paths(
1546 info,
1547 signing_idx,
1548 |index| index < trusted_prefix_len,
1549 false,
1550 max_depth,
1551 max_candidate_paths,
1552 provider,
1553 )
1554}
1555
1556pub(crate) fn build_x509_certificate_paths_to_selector_targets(
1560 info: &X509DataInfo,
1561 signing_idx: usize,
1562 targets: &[usize],
1563 max_depth: usize,
1564 max_candidate_paths: usize,
1565 provider: &dyn crate::provider::CryptoProvider,
1566) -> Result<Vec<Vec<usize>>, X509ChainBuildError> {
1567 if targets
1568 .iter()
1569 .any(|index| *index >= info.certificates.len())
1570 {
1571 return Err(X509ChainBuildError::InconsistentMetadata);
1572 }
1573 build_x509_certificate_paths(
1574 info,
1575 signing_idx,
1576 |index| targets.contains(&index),
1577 true,
1578 max_depth,
1579 max_candidate_paths,
1580 provider,
1581 )
1582}
1583
1584fn build_x509_certificate_paths(
1585 info: &X509DataInfo,
1586 signing_idx: usize,
1587 is_terminal: impl Fn(usize) -> bool,
1588 continue_after_terminal: bool,
1589 max_depth: usize,
1590 max_candidate_paths: usize,
1591 provider: &dyn crate::provider::CryptoProvider,
1592) -> Result<Vec<Vec<usize>>, X509ChainBuildError> {
1593 if signing_idx >= info.parsed_certificates.len()
1594 || info.parsed_certificates.len() != info.certificates.len()
1595 {
1596 return Err(X509ChainBuildError::InconsistentMetadata);
1597 }
1598 if max_candidate_paths == 0 {
1599 return Err(X509ChainBuildError::AmbiguousIssuer);
1600 }
1601
1602 let mut pending = vec![vec![signing_idx]];
1603 let mut completed = Vec::new();
1604 let mut generated_paths = 1usize;
1605 let mut depth_exceeded = false;
1606 let mut unsupported_oid = None;
1607 let mut issuer_cache = vec![None; info.parsed_certificates.len()];
1608 while let Some(path) = pending.pop() {
1609 let current_idx = *path
1610 .last()
1611 .expect("candidate path starts with signing certificate index");
1612 if is_terminal(current_idx) {
1613 completed.push(path.clone());
1614 if !continue_after_terminal {
1615 continue;
1616 }
1617 }
1618 if path.len() == max_depth {
1619 depth_exceeded = true;
1620 continue;
1621 }
1622
1623 let current = &info.parsed_certificates[current_idx];
1624 if issuer_cache[current_idx].is_none() {
1625 let mut verified = Vec::new();
1626 for (issuer_idx, issuer) in info.parsed_certificates.iter().enumerate() {
1627 if !distinguished_names_equal(&issuer.subject_dn, ¤t.issuer_dn) {
1628 continue;
1629 }
1630 match certificate_signature_matches_with_provider(
1631 &info.certificates[current_idx],
1632 &info.certificates[issuer_idx],
1633 provider,
1634 ) {
1635 Ok(true) => verified.push(issuer_idx),
1636 Ok(false) => {}
1637 Err(super::X509ChainError::Provider(
1638 crate::provider::ProviderError::Unsupported {
1639 operation: crate::provider::ProviderOperation::VerifyCertificate,
1640 algorithm: Some(oid),
1641 },
1642 )) => {
1643 unsupported_oid.get_or_insert(oid);
1646 continue;
1647 }
1648 Err(super::X509ChainError::Provider(error)) => {
1649 return Err(X509ChainBuildError::Provider(error));
1650 }
1651 Err(super::X509ChainError::UnsupportedSignatureAlgorithm { oid }) => {
1652 unsupported_oid.get_or_insert(oid);
1655 break;
1656 }
1657 Err(_) => return Err(X509ChainBuildError::IssuerSignatureMismatch),
1658 }
1659 }
1660 issuer_cache[current_idx] = Some(verified);
1661 }
1662 let issuers = issuer_cache[current_idx]
1663 .as_ref()
1664 .expect("issuer cache entry was initialized");
1665 let issuers = issuers
1666 .iter()
1667 .copied()
1668 .filter(|issuer_idx| !path.contains(issuer_idx))
1669 .collect::<Vec<_>>();
1670 if generated_paths.saturating_add(issuers.len()) > max_candidate_paths {
1671 return Err(X509ChainBuildError::AmbiguousIssuer);
1672 }
1673 generated_paths += issuers.len();
1674 for issuer_idx in issuers {
1675 let mut candidate = path.clone();
1676 candidate.push(issuer_idx);
1677 pending.push(candidate);
1678 }
1679 }
1680
1681 if completed.is_empty() {
1682 if let Some(oid) = unsupported_oid {
1683 return Err(X509ChainBuildError::UnsupportedSignatureAlgorithm { oid });
1684 }
1685 if depth_exceeded {
1686 return Err(X509ChainBuildError::DepthExceeded);
1687 }
1688 }
1689 Ok(completed)
1690}
1691
1692fn select_x509_signing_certificate(
1693 info: &X509DataInfo,
1694 provider: &dyn crate::provider::CryptoProvider,
1695) -> Result<usize, ParseError> {
1696 let has_lookup_identifiers = x509_data_has_lookup_identifiers(info);
1697 let mut candidates = Vec::new();
1698 if has_lookup_identifiers {
1699 for (idx, (parsed, der)) in info
1700 .parsed_certificates
1701 .iter()
1702 .zip(&info.certificates)
1703 .enumerate()
1704 {
1705 if x509_certificate_matches_any_selector(info, parsed, der, provider)? {
1706 candidates.push(idx);
1707 }
1708 }
1709 if !x509_selector_categories_match_chain(info, provider)? {
1710 return Err(ParseError::InvalidStructure(
1711 "X509Data lookup identifiers do not match the embedded certificate chain".into(),
1712 ));
1713 }
1714 }
1715
1716 match candidates.as_slice() {
1717 [idx] => return Ok(*idx),
1718 [] if has_lookup_identifiers => {
1719 return Err(ParseError::InvalidStructure(
1720 "X509Data lookup identifiers do not match any embedded certificate".into(),
1721 ));
1722 }
1723 [] => {}
1724 _ => {}
1725 }
1726
1727 let leaf_candidates = info
1728 .parsed_certificates
1729 .iter()
1730 .enumerate()
1731 .filter(|(_, cert)| {
1732 !distinguished_names_equal(&cert.subject_dn, &cert.issuer_dn)
1733 && !info
1734 .parsed_certificates
1735 .iter()
1736 .any(|other| distinguished_names_equal(&other.issuer_dn, &cert.subject_dn))
1737 })
1738 .map(|(idx, _)| idx)
1739 .collect::<Vec<_>>();
1740
1741 let selected_leaves = leaf_candidates
1742 .iter()
1743 .filter(|idx| !has_lookup_identifiers || candidates.contains(idx))
1744 .copied()
1745 .collect::<Vec<_>>();
1746
1747 match selected_leaves.as_slice() {
1748 [idx] => Ok(*idx),
1749 [] if !has_lookup_identifiers => Ok(0),
1750 [] => Err(ParseError::InvalidStructure(
1751 "X509Data lookup identifiers match multiple certificates without a unique signing certificate"
1752 .into(),
1753 )),
1754 _ => Err(ParseError::InvalidStructure(
1755 if has_lookup_identifiers {
1756 "X509Data lookup identifiers match multiple certificates"
1757 } else {
1758 "X509Data contains multiple possible signing certificates"
1759 }
1760 .into(),
1761 )),
1762 }
1763}
1764
1765pub(crate) fn x509_data_has_lookup_identifiers(info: &X509DataInfo) -> bool {
1766 !info.subject_names.is_empty()
1767 || !info.issuer_serials.is_empty()
1768 || !info.skis.is_empty()
1769 || !info.digests.is_empty()
1770}
1771
1772pub(crate) fn x509_certificate_matches_any_selector(
1773 info: &X509DataInfo,
1774 certificate: &ParsedX509Certificate,
1775 certificate_der: &[u8],
1776 provider: &dyn crate::provider::CryptoProvider,
1777) -> Result<bool, ParseError> {
1778 let subject_match = info
1779 .subject_names
1780 .iter()
1781 .any(|subject| distinguished_names_equal(subject, &certificate.subject_dn));
1782 let mut issuer_serial_match = false;
1783 for (issuer, serial) in &info.issuer_serials {
1784 let serial_hex = x509_serial_decimal_to_hex(serial).ok_or_else(|| {
1785 ParseError::InvalidStructure(
1786 "X509Data lookup identifiers contain an invalid serial number".into(),
1787 )
1788 })?;
1789 issuer_serial_match |= distinguished_names_equal(issuer, &certificate.issuer_dn)
1790 && serial_hex == certificate.serial_number_hex;
1791 }
1792 let ski_match = certificate
1793 .subject_key_identifier
1794 .as_ref()
1795 .is_some_and(|certificate_ski| info.skis.iter().any(|ski| ski == certificate_ski));
1796 let mut digest_match = false;
1797 for (algorithm_uri, expected) in &info.digests {
1798 let algorithm = DigestAlgorithm::from_uri(algorithm_uri).ok_or_else(|| {
1799 ParseError::UnsupportedAlgorithm {
1800 uri: algorithm_uri.clone(),
1801 }
1802 })?;
1803 digest_match |= constant_time_eq(
1804 &compute_digest_with_provider(provider, algorithm, certificate_der)?,
1805 expected,
1806 );
1807 }
1808 Ok(subject_match || issuer_serial_match || ski_match || digest_match)
1809}
1810
1811pub fn x509_certificate_matches_selectors(
1817 info: &X509DataInfo,
1818 certificate_der: &[u8],
1819 provider: &dyn crate::provider::CryptoProvider,
1820) -> Result<bool, ParseError> {
1821 let mut candidate = info.clone();
1822 candidate.certificates = vec![certificate_der.to_vec()];
1823 candidate.parsed_certificates = vec![parse_x509_certificate(certificate_der)?];
1824 candidate.certificate_chain = vec![0];
1825 x509_selector_categories_match_chain(&candidate, provider)
1826}
1827
1828pub(crate) fn x509_selector_categories_match_chain(
1829 info: &X509DataInfo,
1830 provider: &dyn crate::provider::CryptoProvider,
1831) -> Result<bool, ParseError> {
1832 let subject_match = info.subject_names.iter().all(|subject| {
1833 info.parsed_certificates
1834 .iter()
1835 .any(|certificate| distinguished_names_equal(subject, &certificate.subject_dn))
1836 });
1837
1838 let mut issuer_serial_match = true;
1839 for (issuer, serial) in &info.issuer_serials {
1840 let serial_hex = x509_serial_decimal_to_hex(serial).ok_or_else(|| {
1841 ParseError::InvalidStructure(
1842 "X509Data lookup identifiers contain an invalid serial number".into(),
1843 )
1844 })?;
1845 issuer_serial_match &= info.parsed_certificates.iter().any(|certificate| {
1846 distinguished_names_equal(issuer, &certificate.issuer_dn)
1847 && serial_hex == certificate.serial_number_hex
1848 });
1849 }
1850
1851 let ski_match = info.skis.iter().all(|ski| {
1852 info.parsed_certificates.iter().any(|certificate| {
1853 certificate
1854 .subject_key_identifier
1855 .as_ref()
1856 .is_some_and(|certificate_ski| ski == certificate_ski)
1857 })
1858 });
1859
1860 let mut digest_match = true;
1861 for (algorithm_uri, expected) in &info.digests {
1862 let algorithm = DigestAlgorithm::from_uri(algorithm_uri).ok_or_else(|| {
1863 ParseError::UnsupportedAlgorithm {
1864 uri: algorithm_uri.clone(),
1865 }
1866 })?;
1867 let mut category_match = false;
1868 for certificate in &info.certificates {
1869 category_match |= constant_time_eq(
1870 &compute_digest_with_provider(provider, algorithm, certificate)?,
1871 expected,
1872 );
1873 }
1874 digest_match &= category_match;
1875 }
1876
1877 Ok(subject_match && issuer_serial_match && ski_match && digest_match)
1878}
1879
1880fn x509_attribute_values_equal(
1881 left: &x509_cert::attr::AttributeTypeAndValue,
1882 right: &x509_cert::attr::AttributeTypeAndValue,
1883) -> bool {
1884 if left.oid != right.oid {
1885 return false;
1886 }
1887 const EMAIL_ADDRESS: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.9.1");
1888 const DOMAIN_COMPONENT: ObjectIdentifier =
1889 ObjectIdentifier::new_unwrap("0.9.2342.19200300.100.1.25");
1890 if left.oid == EMAIL_ADDRESS {
1891 let (Ok(left), Ok(right)) = (
1892 Ia5StringRef::try_from(&left.value),
1893 Ia5StringRef::try_from(&right.value),
1894 ) else {
1895 return false;
1896 };
1897 let (Some((left_local, left_domain)), Some((right_local, right_domain))) = (
1898 left.as_str().rsplit_once('@'),
1899 right.as_str().rsplit_once('@'),
1900 ) else {
1901 return false;
1902 };
1903 return left_local == right_local && left_domain.eq_ignore_ascii_case(right_domain);
1904 }
1905 if left.oid == DOMAIN_COMPONENT {
1906 let (Ok(left), Ok(right)) = (
1907 Ia5StringRef::try_from(&left.value),
1908 Ia5StringRef::try_from(&right.value),
1909 ) else {
1910 return false;
1911 };
1912 return left.as_str().eq_ignore_ascii_case(right.as_str());
1913 }
1914 match (
1915 DirectoryString::try_from(&left.value),
1916 DirectoryString::try_from(&right.value),
1917 ) {
1918 (Ok(left), Ok(right)) => {
1919 let Ok(left) =
1922 x520_stringprep::x520_stringprep_to_case_ignore_string(left.value().as_ref())
1923 else {
1924 return false;
1925 };
1926 let Ok(right) =
1927 x520_stringprep::x520_stringprep_to_case_ignore_string(right.value().as_ref())
1928 else {
1929 return false;
1930 };
1931 left.trim_matches(' ') == right.trim_matches(' ')
1932 }
1933 _ => left.value == right.value,
1934 }
1935}
1936
1937fn x509_rdns_equal(
1938 left: &x509_cert::name::RelativeDistinguishedName,
1939 right: &x509_cert::name::RelativeDistinguishedName,
1940) -> bool {
1941 if left.len() != right.len() {
1942 return false;
1943 }
1944 let right = right.iter().collect::<Vec<_>>();
1946 let mut matched = vec![false; right.len()];
1947 left.iter().all(|left_attribute| {
1948 right
1949 .iter()
1950 .enumerate()
1951 .find(|(index, right_attribute)| {
1952 !matched[*index] && x509_attribute_values_equal(left_attribute, right_attribute)
1953 })
1954 .is_some_and(|(index, _)| {
1955 matched[index] = true;
1956 true
1957 })
1958 })
1959}
1960
1961fn trailing_whitespace_is_escaped(value: &str) -> bool {
1962 let Some((&last, prefix)) = value.as_bytes().split_last() else {
1963 return false;
1964 };
1965 if !matches!(last, b' ' | b'\t' | b'\r' | b'\n') {
1966 return false;
1967 }
1968 prefix
1969 .iter()
1970 .rev()
1971 .take_while(|byte| **byte == b'\\')
1972 .count()
1973 % 2
1974 == 1
1975}
1976
1977fn parse_distinguished_name(value: &str) -> Option<Name> {
1978 let mut normalized = String::with_capacity(value.len());
1979 let mut chars = value
1980 .trim_start_matches([' ', '\t', '\r', '\n'])
1981 .chars()
1982 .peekable();
1983 let mut escaped = false;
1984
1985 while let Some(ch) = chars.next() {
1986 if escaped {
1987 normalized.push(ch);
1988 escaped = false;
1989 continue;
1990 }
1991 if ch == '\\' {
1992 normalized.push(ch);
1993 escaped = true;
1994 continue;
1995 }
1996 if matches!(ch, ',' | '+') {
1997 while normalized
1998 .chars()
1999 .next_back()
2000 .is_some_and(|last| matches!(last, ' ' | '\t' | '\r' | '\n'))
2001 && !trailing_whitespace_is_escaped(&normalized)
2002 {
2003 normalized.pop();
2004 }
2005 normalized.push(ch);
2006 while chars
2007 .next_if(|next| matches!(next, ' ' | '\t' | '\r' | '\n'))
2008 .is_some()
2009 {}
2010 continue;
2011 }
2012 normalized.push(ch);
2013 }
2014
2015 while normalized
2016 .chars()
2017 .next_back()
2018 .is_some_and(|last| matches!(last, ' ' | '\t' | '\r' | '\n'))
2019 && !trailing_whitespace_is_escaped(&normalized)
2020 {
2021 normalized.pop();
2022 }
2023 normalized.parse().ok()
2024}
2025
2026pub(crate) fn distinguished_names_equal(left: &str, right: &str) -> bool {
2027 parse_distinguished_name(left)
2028 .zip(parse_distinguished_name(right))
2029 .is_some_and(|(left, right)| {
2030 left.len() == right.len()
2031 && left
2032 .iter_rdn()
2033 .zip(right.iter_rdn())
2034 .all(|(left, right)| x509_rdns_equal(left, right))
2035 })
2036}
2037
2038pub(crate) fn distinguished_name_within_subtree(name: &str, subtree: &str) -> bool {
2039 parse_distinguished_name(name)
2040 .zip(parse_distinguished_name(subtree))
2041 .is_some_and(|(name, subtree)| {
2042 subtree.len() <= name.len()
2043 && name
2044 .iter_rdn()
2045 .zip(subtree.iter_rdn())
2046 .all(|(name, subtree)| x509_rdns_equal(name, subtree))
2047 })
2048}
2049
2050fn ensure_x509_data_entry_budget(info: &X509DataInfo) -> Result<(), ParseError> {
2051 let total_entries = info.certificates.len()
2052 + info.subject_names.len()
2053 + info.issuer_serials.len()
2054 + info.skis.len()
2055 + info.crls.len()
2056 + info.digests.len();
2057 if total_entries >= MAX_X509_DATA_ENTRY_COUNT {
2058 return Err(ParseError::InvalidStructure(
2059 "X509Data contains too many entries".into(),
2060 ));
2061 }
2062 Ok(())
2063}
2064
2065fn add_x509_data_usage(total_binary_len: &mut usize, delta: usize) -> Result<(), ParseError> {
2066 *total_binary_len = total_binary_len.checked_add(delta).ok_or_else(|| {
2067 ParseError::InvalidStructure("X509Data exceeds maximum allowed total binary length".into())
2068 })?;
2069 if *total_binary_len > MAX_X509_DATA_TOTAL_BINARY_LEN {
2070 return Err(ParseError::InvalidStructure(
2071 "X509Data exceeds maximum allowed total binary length".into(),
2072 ));
2073 }
2074 Ok(())
2075}
2076
2077fn decode_x509_base64(
2078 node: Node<'_, '_>,
2079 element_name: &'static str,
2080) -> Result<Vec<u8>, ParseError> {
2081 use base64::Engine;
2082 use base64::engine::general_purpose::STANDARD;
2083
2084 let mut cleaned = String::new();
2085 let mut raw_text_len = 0usize;
2086 for text in node
2087 .children()
2088 .filter(|child| child.is_text())
2089 .filter_map(|child| child.text())
2090 {
2091 if raw_text_len.saturating_add(text.len()) > MAX_X509_BASE64_TEXT_LEN {
2092 return Err(ParseError::InvalidStructure(format!(
2093 "{element_name} exceeds maximum allowed text length"
2094 )));
2095 }
2096 raw_text_len = raw_text_len.saturating_add(text.len());
2097 normalize_xml_base64_text(text, &mut cleaned).map_err(|err| {
2098 ParseError::Base64(format!(
2099 "invalid XML whitespace U+{:04X} in {element_name}",
2100 err.invalid_byte
2101 ))
2102 })?;
2103 if cleaned.len() > MAX_X509_BASE64_NORMALIZED_LEN {
2104 return Err(ParseError::InvalidStructure(format!(
2105 "{element_name} exceeds maximum allowed base64 length"
2106 )));
2107 }
2108 }
2109
2110 let decoded = STANDARD
2111 .decode(&cleaned)
2112 .map_err(|e| ParseError::Base64(format!("{element_name}: {e}")))?;
2113 if decoded.is_empty() {
2114 return Err(ParseError::InvalidStructure(format!(
2115 "{element_name} must not be empty"
2116 )));
2117 }
2118 if decoded.len() > MAX_X509_DECODED_BINARY_LEN {
2119 return Err(ParseError::InvalidStructure(format!(
2120 "{element_name} exceeds maximum allowed binary length"
2121 )));
2122 }
2123 Ok(decoded)
2124}
2125
2126pub(crate) fn parse_x509_certificate(cert_der: &[u8]) -> Result<ParsedX509Certificate, ParseError> {
2127 let (rest, cert) =
2128 x509_parser::certificate::X509Certificate::from_der(cert_der).map_err(|err| {
2129 ParseError::InvalidStructure(format!("X509Certificate is not valid DER X.509: {err}"))
2130 })?;
2131 if !rest.is_empty() {
2132 return Err(ParseError::InvalidStructure(
2133 "X509Certificate contains trailing bytes after DER certificate".into(),
2134 ));
2135 }
2136
2137 let subject_dn = x509_name_to_rfc4514(cert.subject())?;
2142 let issuer_dn = x509_name_to_rfc4514(cert.issuer())?;
2143 let serial_number = cert.tbs_certificate.raw_serial().to_vec();
2144 let serial_number_hex = format_x509_serial_value_hex(&serial_number);
2145
2146 let subject_key_identifier = cert.extensions().iter().find_map(|ext| {
2147 if let ParsedExtension::SubjectKeyIdentifier(ski) = ext.parsed_extension() {
2148 Some(ski.0.to_vec())
2149 } else {
2150 None
2151 }
2152 });
2153
2154 let spki = cert.public_key();
2155 let public_key = match spki.parsed().map_err(|err| {
2156 ParseError::InvalidStructure(format!("X509Certificate public key parse error: {err}"))
2157 })? {
2158 PublicKey::RSA(rsa) => {
2159 let modulus = trim_leading_zeroes(rsa.modulus);
2160 let exponent = trim_leading_zeroes(rsa.exponent);
2161 if modulus.is_empty() || exponent.is_empty() {
2162 return Err(ParseError::InvalidStructure(
2163 "X509Certificate RSA key contains empty modulus or exponent".into(),
2164 ));
2165 }
2166 X509PublicKeyInfo::Rsa { modulus, exponent }
2167 }
2168 PublicKey::EC(ec_point) => {
2169 let Some(params) = spki.algorithm.parameters.as_ref() else {
2170 return Err(ParseError::InvalidStructure(
2171 "X509Certificate EC key is missing curve parameters".into(),
2172 ));
2173 };
2174
2175 match params.as_oid() {
2176 Ok(oid) => X509PublicKeyInfo::Ec {
2177 curve_oid: oid.to_id_string(),
2178 public_key: ec_point.data().to_vec(),
2179 },
2180 Err(_) => X509PublicKeyInfo::Unsupported {
2181 algorithm_oid: spki.algorithm.algorithm.to_id_string(),
2182 },
2183 }
2184 }
2185 _ => X509PublicKeyInfo::Unsupported {
2186 algorithm_oid: spki.algorithm.algorithm.to_id_string(),
2187 },
2188 };
2189
2190 Ok(ParsedX509Certificate {
2191 subject_dn,
2192 issuer_dn,
2193 serial_number,
2194 serial_number_hex,
2195 subject_key_identifier,
2196 public_key,
2197 })
2198}
2199
2200pub(crate) fn x509_name_to_rfc4514(name: &X509Name<'_>) -> Result<String, ParseError> {
2201 let name = Name::from_der(name.as_raw()).map_err(|error| {
2202 ParseError::InvalidStructure(format!(
2203 "X509Certificate distinguished name is invalid DER: {error}"
2204 ))
2205 })?;
2206 Ok(name.to_string())
2207}
2208
2209fn format_x509_serial_hex(serial: &[u8]) -> String {
2210 serial
2211 .iter()
2212 .map(|byte| format!("{byte:02X}"))
2213 .collect::<String>()
2214}
2215
2216fn format_x509_serial_value_hex(serial: &[u8]) -> String {
2217 let first_non_zero = serial
2218 .iter()
2219 .position(|byte| *byte != 0)
2220 .unwrap_or(serial.len());
2221 let canonical = if first_non_zero == serial.len() {
2222 &[0]
2223 } else {
2224 &serial[first_non_zero..]
2225 };
2226 format_x509_serial_hex(canonical)
2227}
2228
2229fn x509_serial_decimal_to_hex(serial: &str) -> Option<String> {
2230 let serial = serial.trim();
2231 let serial = serial.strip_prefix('+').unwrap_or(serial);
2232 let serial = serial.trim_start_matches('0');
2233 let serial = if serial.is_empty() { "0" } else { serial };
2234 if serial.len() > MAX_X509_SERIAL_NUMBER_VALUE_DIGITS
2235 || !serial.bytes().all(|byte| byte.is_ascii_digit())
2236 {
2237 return None;
2238 }
2239
2240 let mut bytes = [0_u8; MAX_X509_SERIAL_NUMBER_BYTES];
2241 for digit in serial.bytes().map(|byte| byte - b'0') {
2242 let mut carry = u16::from(digit);
2243 for byte in bytes.iter_mut().rev() {
2244 let value = u16::from(*byte) * 10 + carry;
2245 *byte = value as u8;
2246 carry = value >> 8;
2247 }
2248 if carry != 0 {
2249 return None;
2250 }
2251 }
2252
2253 if bytes.iter().all(|byte| *byte == 0) {
2254 return None;
2255 }
2256
2257 Some(format_x509_serial_value_hex(&bytes))
2258}
2259
2260fn trim_leading_zeroes(bytes: &[u8]) -> Vec<u8> {
2261 let first_non_zero = bytes
2262 .iter()
2263 .position(|byte| *byte != 0)
2264 .unwrap_or(bytes.len());
2265 bytes[first_non_zero..].to_vec()
2266}
2267
2268fn parse_x509_issuer_serial(node: Node<'_, '_>) -> Result<(String, String), ParseError> {
2269 verify_ds_element(node, "X509IssuerSerial")?;
2270 ensure_no_non_whitespace_text(node, "X509IssuerSerial")?;
2271
2272 let children = element_children(node).collect::<Vec<_>>();
2273 if children.len() != 2 {
2274 return Err(ParseError::InvalidStructure(
2275 "X509IssuerSerial must contain exactly X509IssuerName then X509SerialNumber".into(),
2276 ));
2277 }
2278 if !matches!(
2279 (
2280 children[0].tag_name().namespace(),
2281 children[0].tag_name().name()
2282 ),
2283 (Some(XMLDSIG_NS), "X509IssuerName")
2284 ) {
2285 return Err(ParseError::InvalidStructure(
2286 "X509IssuerSerial must contain X509IssuerName as the first child element".into(),
2287 ));
2288 }
2289 if !matches!(
2290 (
2291 children[1].tag_name().namespace(),
2292 children[1].tag_name().name()
2293 ),
2294 (Some(XMLDSIG_NS), "X509SerialNumber")
2295 ) {
2296 return Err(ParseError::InvalidStructure(
2297 "X509IssuerSerial must contain X509SerialNumber as the second child element".into(),
2298 ));
2299 }
2300
2301 let issuer_node = children[0];
2302 ensure_no_element_children(issuer_node, "X509IssuerName")?;
2303 let issuer_name =
2304 collect_text_content_bounded(issuer_node, MAX_X509_ISSUER_NAME_TEXT_LEN, "X509IssuerName")?;
2305
2306 let serial_node = children[1];
2307 ensure_no_element_children(serial_node, "X509SerialNumber")?;
2308 let serial_number = collect_x509_serial_number(serial_node)?;
2309 if issuer_name.trim().is_empty() {
2310 return Err(ParseError::InvalidStructure(
2311 "X509IssuerSerial requires non-empty X509IssuerName and X509SerialNumber".into(),
2312 ));
2313 }
2314
2315 Ok((issuer_name, serial_number))
2316}
2317
2318fn base64_decode_digest(b64: &str, digest_method: DigestAlgorithm) -> Result<Vec<u8>, ParseError> {
2322 use base64::Engine;
2323 use base64::engine::general_purpose::STANDARD;
2324
2325 let expected = digest_method.output_len();
2326 let max_base64_len = expected.div_ceil(3) * 4;
2327 let mut cleaned = String::with_capacity(b64.len().min(max_base64_len));
2328 normalize_xml_base64_text(b64, &mut cleaned).map_err(|err| {
2329 ParseError::Base64(format!(
2330 "invalid XML whitespace U+{:04X} in DigestValue",
2331 err.invalid_byte
2332 ))
2333 })?;
2334 if cleaned.len() > max_base64_len {
2335 return Err(ParseError::Base64(
2336 "DigestValue exceeds maximum allowed base64 length".into(),
2337 ));
2338 }
2339 let digest = STANDARD
2340 .decode(&cleaned)
2341 .map_err(|e| ParseError::Base64(e.to_string()))?;
2342 let actual = digest.len();
2343 if actual != expected {
2344 return Err(ParseError::DigestLengthMismatch {
2345 algorithm: digest_method.uri(),
2346 expected,
2347 actual,
2348 });
2349 }
2350 Ok(digest)
2351}
2352
2353fn decode_digest_value_children(
2354 digest_value_node: Node<'_, '_>,
2355 digest_method: DigestAlgorithm,
2356) -> Result<Vec<u8>, ParseError> {
2357 let max_base64_len = digest_method.output_len().div_ceil(3) * 4;
2358 let mut cleaned = String::with_capacity(max_base64_len);
2359
2360 for child in digest_value_node.children() {
2361 if child.is_element() {
2362 return Err(ParseError::InvalidStructure(
2363 "DigestValue must not contain element children".into(),
2364 ));
2365 }
2366 if let Some(text) = child.text() {
2367 normalize_xml_base64_text(text, &mut cleaned).map_err(|err| {
2368 ParseError::Base64(format!(
2369 "invalid XML whitespace U+{:04X} in DigestValue",
2370 err.invalid_byte
2371 ))
2372 })?;
2373 if cleaned.len() > max_base64_len {
2374 return Err(ParseError::Base64(
2375 "DigestValue exceeds maximum allowed base64 length".into(),
2376 ));
2377 }
2378 }
2379 }
2380
2381 base64_decode_digest(&cleaned, digest_method)
2382}
2383
2384fn decode_der_encoded_key_value_base64(node: Node<'_, '_>) -> Result<Vec<u8>, ParseError> {
2385 use base64::Engine;
2386 use base64::engine::general_purpose::STANDARD;
2387
2388 let mut cleaned = String::new();
2389 let mut raw_text_len = 0usize;
2390 for text in node
2391 .children()
2392 .filter(|child| child.is_text())
2393 .filter_map(|child| child.text())
2394 {
2395 if raw_text_len.saturating_add(text.len()) > MAX_DER_ENCODED_KEY_VALUE_TEXT_LEN {
2396 return Err(ParseError::InvalidStructure(
2397 "DEREncodedKeyValue exceeds maximum allowed text length".into(),
2398 ));
2399 }
2400 raw_text_len = raw_text_len.saturating_add(text.len());
2401 normalize_xml_base64_text(text, &mut cleaned).map_err(|err| {
2402 ParseError::Base64(format!(
2403 "invalid XML whitespace U+{:04X} in base64 text",
2404 err.invalid_byte
2405 ))
2406 })?;
2407 if cleaned.len() > MAX_DER_ENCODED_KEY_VALUE_BASE64_LEN {
2408 return Err(ParseError::InvalidStructure(
2409 "DEREncodedKeyValue exceeds maximum allowed length".into(),
2410 ));
2411 }
2412 }
2413
2414 let der = STANDARD
2415 .decode(&cleaned)
2416 .map_err(|e| ParseError::Base64(e.to_string()))?;
2417 if der.is_empty() {
2418 return Err(ParseError::InvalidStructure(
2419 "DEREncodedKeyValue must not be empty".into(),
2420 ));
2421 }
2422 if der.len() > MAX_DER_ENCODED_KEY_VALUE_LEN {
2423 return Err(ParseError::InvalidStructure(
2424 "DEREncodedKeyValue exceeds maximum allowed length".into(),
2425 ));
2426 }
2427 Ok(der)
2428}
2429
2430fn collect_text_content_bounded(
2431 node: Node<'_, '_>,
2432 max_len: usize,
2433 element_name: &'static str,
2434) -> Result<String, ParseError> {
2435 let mut text = String::new();
2436 for chunk in node
2437 .children()
2438 .filter_map(|child| child.is_text().then(|| child.text()).flatten())
2439 {
2440 if text.len().saturating_add(chunk.len()) > max_len {
2441 return Err(ParseError::InvalidStructure(format!(
2442 "{element_name} exceeds maximum allowed text length"
2443 )));
2444 }
2445 text.push_str(chunk);
2446 }
2447 Ok(text)
2448}
2449
2450fn collect_x509_serial_number(node: Node<'_, '_>) -> Result<String, ParseError> {
2451 let mut serial = String::with_capacity(MAX_X509_SERIAL_NUMBER_VALUE_DIGITS);
2452 let mut raw_text_len = 0usize;
2453 let mut trailing_whitespace = false;
2454 let mut explicit_positive = false;
2455 let mut saw_digit = false;
2456
2457 for chunk in node
2458 .children()
2459 .filter_map(|child| child.is_text().then(|| child.text()).flatten())
2460 {
2461 raw_text_len = raw_text_len.saturating_add(chunk.len());
2462 if raw_text_len > MAX_X509_SERIAL_NUMBER_RAW_TEXT_LEN {
2463 return Err(ParseError::InvalidStructure(
2464 "X509SerialNumber exceeds maximum allowed text length".into(),
2465 ));
2466 }
2467 for byte in chunk.bytes() {
2468 if matches!(byte, b' ' | b'\t' | b'\r' | b'\n') {
2469 trailing_whitespace |= explicit_positive || saw_digit;
2470 continue;
2471 }
2472 if byte == b'+' && !saw_digit && !explicit_positive && !trailing_whitespace {
2473 explicit_positive = true;
2474 continue;
2475 }
2476 if trailing_whitespace || !byte.is_ascii_digit() {
2477 return Err(ParseError::InvalidStructure(
2478 "invalid X509SerialNumber decimal value".into(),
2479 ));
2480 }
2481 saw_digit = true;
2482 if byte == b'0' && serial.is_empty() {
2483 continue;
2484 }
2485 if serial.len() == MAX_X509_SERIAL_NUMBER_VALUE_DIGITS {
2486 return Err(ParseError::InvalidStructure(
2487 "X509SerialNumber exceeds maximum allowed decimal value".into(),
2488 ));
2489 }
2490 serial.push(char::from(byte));
2491 }
2492 }
2493
2494 if !saw_digit {
2495 return Err(ParseError::InvalidStructure(
2496 "X509IssuerSerial requires non-empty X509IssuerName and X509SerialNumber".into(),
2497 ));
2498 }
2499 if serial.is_empty() {
2500 serial.push('0');
2501 }
2502 if x509_serial_decimal_to_hex(&serial).is_none() {
2503 return Err(ParseError::InvalidStructure(
2504 "invalid X509SerialNumber decimal value or RFC 5280 range".into(),
2505 ));
2506 }
2507
2508 Ok(serial)
2509}
2510
2511fn ensure_no_element_children(node: Node<'_, '_>, element_name: &str) -> Result<(), ParseError> {
2512 if node.children().any(|child| child.is_element()) {
2513 return Err(ParseError::InvalidStructure(format!(
2514 "{element_name} must not contain child elements"
2515 )));
2516 }
2517 Ok(())
2518}
2519
2520fn ensure_no_non_whitespace_text(node: Node<'_, '_>, element_name: &str) -> Result<(), ParseError> {
2521 for child in node.children().filter(|child| child.is_text()) {
2522 if let Some(text) = child.text()
2523 && !is_xml_whitespace_only(text)
2524 {
2525 return Err(ParseError::InvalidStructure(format!(
2526 "{element_name} must not contain non-whitespace mixed content"
2527 )));
2528 }
2529 }
2530 Ok(())
2531}
2532
2533#[cfg(test)]
2534#[expect(clippy::unwrap_used, reason = "tests use trusted XML fixtures")]
2535mod tests {
2536 use super::*;
2537 use crate::xmldsig::TransformError;
2538 use base64::Engine;
2539
2540 fn fixture_rsa_cert_base64() -> String {
2541 fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem")
2542 }
2543
2544 fn fixture_cert_base64(path: &str) -> String {
2545 match path {
2546 "../../tests/fixtures/keys/rsa/rsa-2048-cert.pem" => {
2547 include_str!("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem")
2548 }
2549 "../../tests/fixtures/keys/rsa/rsa-4096-cert.pem" => {
2550 include_str!("../../tests/fixtures/keys/rsa/rsa-4096-cert.pem")
2551 }
2552 "../../tests/fixtures/keys/ca2cert.pem" => {
2553 include_str!("../../tests/fixtures/keys/ca2cert.pem")
2554 }
2555 "../../tests/fixtures/keys/cacert.pem" => {
2556 include_str!("../../tests/fixtures/keys/cacert.pem")
2557 }
2558 _ => unreachable!("unknown certificate fixture"),
2559 }
2560 .lines()
2561 .skip_while(|line| *line != "-----BEGIN CERTIFICATE-----")
2562 .skip(1)
2563 .take_while(|line| *line != "-----END CERTIFICATE-----")
2564 .collect::<String>()
2565 }
2566
2567 #[test]
2570 fn signature_algorithm_from_uri_rsa_sha256() {
2571 assert_eq!(
2572 SignatureAlgorithm::from_uri("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"),
2573 Some(SignatureAlgorithm::RsaSha256)
2574 );
2575 }
2576
2577 #[test]
2578 fn signature_algorithm_from_uri_rsa_sha1() {
2579 assert_eq!(
2580 SignatureAlgorithm::from_uri("http://www.w3.org/2000/09/xmldsig#rsa-sha1"),
2581 Some(SignatureAlgorithm::RsaSha1)
2582 );
2583 }
2584
2585 #[test]
2586 fn signature_algorithm_from_uri_ecdsa_sha256() {
2587 assert_eq!(
2588 SignatureAlgorithm::from_uri("http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"),
2589 Some(SignatureAlgorithm::EcdsaSha256)
2590 );
2591 }
2592
2593 #[test]
2594 fn signature_algorithm_from_uri_unknown() {
2595 assert_eq!(
2596 SignatureAlgorithm::from_uri("http://example.com/unknown"),
2597 None
2598 );
2599 }
2600
2601 #[test]
2602 fn signature_algorithm_uri_round_trip() {
2603 for algo in SignatureAlgorithm::ALL {
2604 assert_eq!(
2605 SignatureAlgorithm::from_uri(algo.uri()),
2606 Some(algo),
2607 "round-trip failed for {algo:?}"
2608 );
2609 }
2610 }
2611
2612 #[test]
2613 fn legacy_algorithms_are_verify_only() {
2614 assert!(!SignatureAlgorithm::DsaSha1.signing_allowed());
2615 assert!(!SignatureAlgorithm::HmacSha1.signing_allowed());
2616 assert!(!SignatureAlgorithm::RsaSha1.signing_allowed());
2617 assert!(SignatureAlgorithm::RsaSha256.signing_allowed());
2618 assert!(SignatureAlgorithm::EcdsaSha256.signing_allowed());
2619 }
2620
2621 #[test]
2624 fn find_signature_in_saml() {
2625 let xml = r#"<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
2626 <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
2627 <ds:SignedInfo/>
2628 </ds:Signature>
2629 </samlp:Response>"#;
2630 let doc = Document::parse(xml).unwrap();
2631 let sig = find_signature_node(&doc);
2632 assert!(sig.is_some());
2633 assert_eq!(sig.unwrap().tag_name().name(), "Signature");
2634 }
2635
2636 #[test]
2637 fn find_signature_missing() {
2638 let xml = "<root><child/></root>";
2639 let doc = Document::parse(xml).unwrap();
2640 assert!(find_signature_node(&doc).is_none());
2641 }
2642
2643 #[test]
2644 fn find_signature_ignores_wrong_namespace() {
2645 let xml = r#"<root><Signature xmlns="http://example.com/fake"/></root>"#;
2646 let doc = Document::parse(xml).unwrap();
2647 assert!(find_signature_node(&doc).is_none());
2648 }
2649
2650 #[test]
2653 fn key_info_candidate_budget_precedes_key_value_parsing() {
2654 let document = Document::parse(
2657 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2658 <KeyValue><RSAKeyValue><Modulus>AQAB</Modulus></RSAKeyValue></KeyValue>
2659 </KeyInfo>"#,
2660 )
2661 .expect("fixed KeyInfo fixture must parse as XML");
2662 let resources = crate::policy::ResourcePolicy {
2663 max_key_candidates: 0,
2664 ..crate::policy::ResourcePolicy::default()
2665 };
2666
2667 let error = parse_key_info_with_policy_budgets(
2668 document.root_element(),
2669 crate::provider::default_provider(),
2670 &XmlBaseResolutionBudget::default(),
2671 &resources,
2672 )
2673 .expect_err("candidate policy must reject KeyValue before RSA parsing");
2674
2675 assert!(matches!(
2676 error,
2677 ParseError::Policy(crate::policy::PolicyViolation::ResourceLimit {
2678 resource: crate::policy::resource_name::KEY_CANDIDATES,
2679 maximum: 0,
2680 actual: 1,
2681 })
2682 ));
2683 }
2684
2685 #[test]
2686 fn key_info_candidate_budget_precedes_der_key_decoding() {
2687 let document = Document::parse(
2690 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
2691 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
2692 <dsig11:DEREncodedKeyValue>%%%invalid%%%</dsig11:DEREncodedKeyValue>
2693 </KeyInfo>"#,
2694 )
2695 .expect("fixed KeyInfo fixture must parse as XML");
2696 let resources = crate::policy::ResourcePolicy {
2697 max_key_candidates: 0,
2698 ..crate::policy::ResourcePolicy::default()
2699 };
2700
2701 let error = parse_key_info_with_policy_budgets(
2702 document.root_element(),
2703 crate::provider::default_provider(),
2704 &XmlBaseResolutionBudget::default(),
2705 &resources,
2706 )
2707 .expect_err("candidate policy must reject DEREncodedKeyValue before decoding");
2708
2709 assert!(matches!(
2710 error,
2711 ParseError::Policy(crate::policy::PolicyViolation::ResourceLimit {
2712 resource: crate::policy::resource_name::KEY_CANDIDATES,
2713 maximum: 0,
2714 actual: 1,
2715 })
2716 ));
2717 }
2718
2719 #[test]
2720 fn key_info_embedded_candidate_count_matches_materialized_key_kinds() {
2721 let key_info = KeyInfo {
2724 sources: vec![
2725 KeyInfoSource::KeyName("configured-key".into()),
2726 KeyInfoSource::KeyValue(KeyValueInfo::Unsupported {
2727 namespace: Some(XMLDSIG_NS.into()),
2728 local_name: "FutureKeyValue".into(),
2729 }),
2730 KeyInfoSource::DerEncodedKeyValue(vec![1]),
2731 KeyInfoSource::X509Data(X509DataInfo {
2732 certificates: vec![vec![2], vec![3]],
2733 ..X509DataInfo::default()
2734 }),
2735 KeyInfoSource::RetrievalMethod {
2736 uri: "urn:certificate".into(),
2737 resource_type: None,
2738 transforms: RetrievalMethodTransforms::None,
2739 },
2740 ],
2741 };
2742
2743 assert_eq!(key_info.embedded_candidate_count(), 4);
2744 }
2745
2746 #[test]
2747 fn parse_key_info_dispatches_supported_children() {
2748 let cert_base64 = fixture_rsa_cert_base64();
2749 let expected_cert = base64::engine::general_purpose::STANDARD
2750 .decode(&cert_base64)
2751 .expect("fixture PEM must contain valid base64");
2752 let cert_digest = base64::engine::general_purpose::STANDARD
2753 .encode(compute_digest(DigestAlgorithm::Sha256, &expected_cert));
2754 let xml = format!(
2755 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
2756 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
2757 <KeyName>idp-signing-key</KeyName>
2758 <KeyValue>
2759 <RSAKeyValue>
2760 <Modulus>AQAB</Modulus>
2761 <Exponent>AQAB</Exponent>
2762 </RSAKeyValue>
2763 </KeyValue>
2764 <X509Data>
2765 <X509Certificate>{cert_base64}</X509Certificate>
2766 <X509SubjectName>CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509SubjectName>
2767 <X509IssuerSerial>
2768 <X509IssuerName>Email=xmlsec@aleksey.com,CN=Aleksey Sanin,OU=Second level CA,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509IssuerName>
2769 <X509SerialNumber>680572598617295163017172295025714171905498632019</X509SerialNumber>
2770 </X509IssuerSerial>
2771 <X509SKI>bcOXN/nsVl8GatRbcKrPbzIbw0Y=</X509SKI>
2772 <X509CRL>BAUGBw==</X509CRL>
2773 <dsig11:X509Digest Algorithm="http://www.w3.org/2001/04/xmlenc#sha256">{cert_digest}</dsig11:X509Digest>
2774 </X509Data>
2775 <dsig11:DEREncodedKeyValue>AQIDBA==</dsig11:DEREncodedKeyValue>
2776 </KeyInfo>"#
2777 );
2778 let doc = Document::parse(&xml).unwrap();
2779
2780 let key_info = parse_key_info(doc.root_element()).unwrap();
2781 assert_eq!(key_info.sources.len(), 4);
2782
2783 assert_eq!(
2784 key_info.sources[0],
2785 KeyInfoSource::KeyName("idp-signing-key".to_string())
2786 );
2787 assert_eq!(
2788 key_info.sources[1],
2789 KeyInfoSource::KeyValue(KeyValueInfo::Rsa {
2790 modulus: vec![1, 0, 1],
2791 exponent: vec![1, 0, 1],
2792 })
2793 );
2794 let x509_info = match &key_info.sources[2] {
2795 KeyInfoSource::X509Data(x509) => x509,
2796 other => panic!("expected X509Data source, got {other:?}"),
2797 };
2798 assert_eq!(x509_info.certificates, vec![expected_cert]);
2799 assert_eq!(
2800 x509_info.subject_names,
2801 vec![
2802 "CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US"
2803 .to_string()
2804 ]
2805 );
2806 assert_eq!(
2807 x509_info.issuer_serials,
2808 vec![(
2809 "Email=xmlsec@aleksey.com,CN=Aleksey Sanin,OU=Second level CA,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US".to_string(),
2810 "680572598617295163017172295025714171905498632019".to_string()
2811 )]
2812 );
2813 assert_eq!(
2814 x509_info.skis,
2815 vec![vec![
2816 109, 195, 151, 55, 249, 236, 86, 95, 6, 106, 212, 91, 112, 170, 207, 111, 50, 27,
2817 195, 70
2818 ]]
2819 );
2820 assert_eq!(x509_info.crls, vec![vec![4, 5, 6, 7]]);
2821 assert_eq!(
2822 x509_info.digests,
2823 vec![(
2824 "http://www.w3.org/2001/04/xmlenc#sha256".to_string(),
2825 compute_digest(DigestAlgorithm::Sha256, &x509_info.certificates[0])
2826 )]
2827 );
2828 assert_eq!(x509_info.parsed_certificates.len(), 1);
2829 assert_eq!(x509_info.certificate_chain, vec![0]);
2830 let parsed_cert = &x509_info.parsed_certificates[0];
2831 assert!(!parsed_cert.subject_dn.is_empty());
2832 assert!(!parsed_cert.issuer_dn.is_empty());
2833 assert_eq!(
2834 parsed_cert.serial_number_hex,
2835 "7735EE487F6862DAF1B3956D961CCB0FA6F34F53"
2836 );
2837 assert!(parsed_cert.subject_key_identifier.is_some());
2838 assert!(matches!(
2839 parsed_cert.public_key,
2840 X509PublicKeyInfo::Rsa { .. }
2841 ));
2842
2843 assert_eq!(
2844 key_info.sources[3],
2845 KeyInfoSource::DerEncodedKeyValue(vec![1, 2, 3, 4])
2846 );
2847 }
2848
2849 #[test]
2850 fn parse_rsa_key_value_preserves_wrapped_crypto_binary() {
2851 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2853 <KeyValue><RSAKeyValue>
2854 <Modulus> AQID
2855BA== </Modulus>
2856 <Exponent> AQAB </Exponent>
2857 </RSAKeyValue></KeyValue>
2858 </KeyInfo>"##;
2859 let doc = Document::parse(xml).unwrap();
2860
2861 assert_eq!(
2862 parse_key_info(doc.root_element()).unwrap().sources,
2863 vec![KeyInfoSource::KeyValue(KeyValueInfo::Rsa {
2864 modulus: vec![1, 2, 3, 4],
2865 exponent: vec![1, 0, 1],
2866 })]
2867 );
2868 }
2869
2870 #[test]
2871 fn parse_rsa_key_value_rejects_reordered_parameters() {
2872 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2874 <KeyValue><RSAKeyValue>
2875 <Exponent>AQAB</Exponent><Modulus>AQID</Modulus>
2876 </RSAKeyValue></KeyValue>
2877 </KeyInfo>"##;
2878 let doc = Document::parse(xml).unwrap();
2879
2880 assert!(matches!(
2881 parse_key_info(doc.root_element()),
2882 Err(ParseError::InvalidStructure(_))
2883 ));
2884 }
2885
2886 #[test]
2887 fn parse_rsa_key_value_rejects_missing_exponent() {
2888 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2890 <KeyValue><RSAKeyValue><Modulus>AQID</Modulus></RSAKeyValue></KeyValue>
2891 </KeyInfo>"##;
2892 let doc = Document::parse(xml).unwrap();
2893
2894 assert!(matches!(
2895 parse_key_info(doc.root_element()),
2896 Err(ParseError::InvalidStructure(_))
2897 ));
2898 }
2899
2900 #[test]
2901 fn parse_rsa_key_value_rejects_duplicate_exponent() {
2902 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2904 <KeyValue><RSAKeyValue>
2905 <Modulus>AQID</Modulus><Exponent>AQAB</Exponent><Exponent>AQAB</Exponent>
2906 </RSAKeyValue></KeyValue>
2907 </KeyInfo>"##;
2908 let doc = Document::parse(xml).unwrap();
2909
2910 assert!(matches!(
2911 parse_key_info(doc.root_element()),
2912 Err(ParseError::InvalidStructure(_))
2913 ));
2914 }
2915
2916 #[test]
2917 fn parse_rsa_key_value_rejects_wrong_parameter_namespace() {
2918 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#" xmlns:bad="urn:bad">
2920 <KeyValue><RSAKeyValue>
2921 <bad:Modulus>AQID</bad:Modulus><Exponent>AQAB</Exponent>
2922 </RSAKeyValue></KeyValue>
2923 </KeyInfo>"#;
2924 let doc = Document::parse(xml).unwrap();
2925
2926 assert!(matches!(
2927 parse_key_info(doc.root_element()),
2928 Err(ParseError::InvalidStructure(_))
2929 ));
2930 }
2931
2932 #[test]
2933 fn parse_rsa_key_value_rejects_nested_crypto_binary() {
2934 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2936 <KeyValue><RSAKeyValue>
2937 <Modulus><chunk>AQID</chunk></Modulus><Exponent>AQAB</Exponent>
2938 </RSAKeyValue></KeyValue>
2939 </KeyInfo>"#;
2940 let doc = Document::parse(xml).unwrap();
2941
2942 assert!(matches!(
2943 parse_key_info(doc.root_element()),
2944 Err(ParseError::InvalidStructure(_))
2945 ));
2946 }
2947
2948 #[test]
2949 fn parse_rsa_key_value_rejects_malformed_base64() {
2950 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2952 <KeyValue><RSAKeyValue>
2953 <Modulus>%%%%</Modulus><Exponent>AQAB</Exponent>
2954 </RSAKeyValue></KeyValue>
2955 </KeyInfo>"#;
2956 let doc = Document::parse(xml).unwrap();
2957
2958 assert!(matches!(
2959 parse_key_info(doc.root_element()),
2960 Err(ParseError::Base64(_))
2961 ));
2962 }
2963
2964 #[test]
2965 fn parse_rsa_key_value_rejects_oversized_exponent_before_decode() {
2966 let exponent = "A".repeat(MAX_RSA_EXPONENT_LEN.div_ceil(3) * 4 + 1);
2968 let xml = format!(
2969 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2970 <KeyValue><RSAKeyValue>
2971 <Modulus>AQID</Modulus><Exponent>{exponent}</Exponent>
2972 </RSAKeyValue></KeyValue>
2973 </KeyInfo>"#
2974 );
2975 let doc = Document::parse(&xml).unwrap();
2976
2977 assert!(matches!(
2978 parse_key_info(doc.root_element()),
2979 Err(ParseError::InvalidStructure(_))
2980 ));
2981 }
2982
2983 #[test]
2984 fn parse_key_info_ignores_unknown_children() {
2985 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2986 <Foo>bar</Foo>
2987 <KeyName>ok</KeyName>
2988 </KeyInfo>"#;
2989 let doc = Document::parse(xml).unwrap();
2990
2991 let key_info = parse_key_info(doc.root_element()).unwrap();
2992 assert_eq!(key_info.sources, vec![KeyInfoSource::KeyName("ok".into())]);
2993 }
2994
2995 #[test]
2996 fn parse_key_info_keyvalue_requires_single_child() {
2997 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2998 <KeyValue/>
2999 </KeyInfo>"#;
3000 let doc = Document::parse(xml).unwrap();
3001
3002 let err = parse_key_info(doc.root_element()).unwrap_err();
3003 assert!(matches!(err, ParseError::InvalidStructure(_)));
3004 }
3005
3006 #[test]
3007 fn parse_key_info_accepts_empty_x509data() {
3008 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3009 <X509Data/>
3010 </KeyInfo>"#;
3011 let doc = Document::parse(xml).unwrap();
3012
3013 let key_info = parse_key_info(doc.root_element()).unwrap();
3014 assert_eq!(
3015 key_info.sources,
3016 vec![KeyInfoSource::X509Data(X509DataInfo::default())]
3017 );
3018 }
3019
3020 #[test]
3021 fn parse_key_info_rejects_unknown_xmlsig_child_in_x509data() {
3022 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3023 <X509Data>
3024 <Foo/>
3025 </X509Data>
3026 </KeyInfo>"#;
3027 let doc = Document::parse(xml).unwrap();
3028
3029 let err = parse_key_info(doc.root_element()).unwrap_err();
3030 assert!(matches!(err, ParseError::InvalidStructure(_)));
3031 }
3032
3033 #[test]
3034 fn parse_key_info_rejects_unknown_xmlsig11_child_in_x509data() {
3035 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
3036 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
3037 <X509Data>
3038 <dsig11:Foo/>
3039 </X509Data>
3040 </KeyInfo>"#;
3041 let doc = Document::parse(xml).unwrap();
3042
3043 let err = parse_key_info(doc.root_element()).unwrap_err();
3044 assert!(matches!(err, ParseError::InvalidStructure(_)));
3045 }
3046
3047 #[test]
3048 fn parse_key_info_rejects_x509_issuer_serial_without_required_children() {
3049 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3050 <X509Data>
3051 <X509IssuerSerial>
3052 <X509IssuerName>CN=CA</X509IssuerName>
3053 </X509IssuerSerial>
3054 </X509Data>
3055 </KeyInfo>"#;
3056 let doc = Document::parse(xml).unwrap();
3057
3058 let err = parse_key_info(doc.root_element()).unwrap_err();
3059 assert!(matches!(err, ParseError::InvalidStructure(_)));
3060 }
3061
3062 #[test]
3063 fn parse_key_info_rejects_x509_issuer_serial_with_duplicate_issuer_name() {
3064 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3065 <X509Data>
3066 <X509IssuerSerial>
3067 <X509IssuerName>CN=CA-1</X509IssuerName>
3068 <X509IssuerName>CN=CA-2</X509IssuerName>
3069 <X509SerialNumber>42</X509SerialNumber>
3070 </X509IssuerSerial>
3071 </X509Data>
3072 </KeyInfo>"#;
3073 let doc = Document::parse(xml).unwrap();
3074
3075 let err = parse_key_info(doc.root_element()).unwrap_err();
3076 assert!(matches!(err, ParseError::InvalidStructure(_)));
3077 }
3078
3079 #[test]
3080 fn parse_key_info_rejects_x509_issuer_serial_with_duplicate_serial_number() {
3081 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3082 <X509Data>
3083 <X509IssuerSerial>
3084 <X509IssuerName>CN=CA</X509IssuerName>
3085 <X509SerialNumber>1</X509SerialNumber>
3086 <X509SerialNumber>2</X509SerialNumber>
3087 </X509IssuerSerial>
3088 </X509Data>
3089 </KeyInfo>"#;
3090 let doc = Document::parse(xml).unwrap();
3091
3092 let err = parse_key_info(doc.root_element()).unwrap_err();
3093 assert!(matches!(err, ParseError::InvalidStructure(_)));
3094 }
3095
3096 #[test]
3097 fn parse_key_info_rejects_x509_issuer_serial_with_whitespace_only_values() {
3098 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3099 <X509Data>
3100 <X509IssuerSerial>
3101 <X509IssuerName> </X509IssuerName>
3102 <X509SerialNumber>
3103
3104 </X509SerialNumber>
3105 </X509IssuerSerial>
3106 </X509Data>
3107 </KeyInfo>"#;
3108 let doc = Document::parse(xml).unwrap();
3109
3110 let err = parse_key_info(doc.root_element()).unwrap_err();
3111 assert!(matches!(err, ParseError::InvalidStructure(_)));
3112 }
3113
3114 #[test]
3115 fn parse_key_info_rejects_x509_issuer_serial_with_wrong_child_order() {
3116 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3117 <X509Data>
3118 <X509IssuerSerial>
3119 <X509SerialNumber>42</X509SerialNumber>
3120 <X509IssuerName>CN=CA</X509IssuerName>
3121 </X509IssuerSerial>
3122 </X509Data>
3123 </KeyInfo>"#;
3124 let doc = Document::parse(xml).unwrap();
3125
3126 let err = parse_key_info(doc.root_element()).unwrap_err();
3127 assert!(matches!(err, ParseError::InvalidStructure(_)));
3128 }
3129
3130 #[test]
3131 fn parse_key_info_rejects_x509_issuer_serial_with_extra_child_element() {
3132 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
3133 xmlns:foo="urn:example:foo">
3134 <X509Data>
3135 <X509IssuerSerial>
3136 <X509IssuerName>CN=CA</X509IssuerName>
3137 <X509SerialNumber>42</X509SerialNumber>
3138 <foo:Extra/>
3139 </X509IssuerSerial>
3140 </X509Data>
3141 </KeyInfo>"#;
3142 let doc = Document::parse(xml).unwrap();
3143
3144 let err = parse_key_info(doc.root_element()).unwrap_err();
3145 assert!(matches!(err, ParseError::InvalidStructure(_)));
3146 }
3147
3148 #[test]
3149 fn parse_key_info_rejects_x509_digest_without_algorithm() {
3150 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
3151 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
3152 <X509Data>
3153 <dsig11:X509Digest>AQID</dsig11:X509Digest>
3154 </X509Data>
3155 </KeyInfo>"#;
3156 let doc = Document::parse(xml).unwrap();
3157
3158 let err = parse_key_info(doc.root_element()).unwrap_err();
3159 assert!(matches!(err, ParseError::InvalidStructure(_)));
3160 }
3161
3162 #[test]
3163 fn parse_key_info_rejects_invalid_x509_certificate_base64() {
3164 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3165 <X509Data>
3166 <X509Certificate>%%%invalid%%%</X509Certificate>
3167 </X509Data>
3168 </KeyInfo>"#;
3169 let doc = Document::parse(xml).unwrap();
3170
3171 let err = parse_key_info(doc.root_element()).unwrap_err();
3172 assert!(matches!(err, ParseError::Base64(_)));
3173 }
3174
3175 #[test]
3176 fn parse_key_info_rejects_x509_data_exceeding_entry_budget() {
3177 let subjects = (0..(MAX_X509_DATA_ENTRY_COUNT + 1))
3178 .map(|idx| format!("<X509SubjectName>CN={idx}</X509SubjectName>"))
3179 .collect::<Vec<_>>()
3180 .join("");
3181 let xml = format!(
3182 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><X509Data>{subjects}</X509Data></KeyInfo>"
3183 );
3184 let doc = Document::parse(&xml).unwrap();
3185
3186 let err = parse_key_info(doc.root_element()).unwrap_err();
3187 assert!(matches!(err, ParseError::InvalidStructure(_)));
3188 }
3189
3190 #[test]
3191 fn parse_key_info_rejects_x509_data_exceeding_total_binary_budget() {
3192 let payload = base64::engine::general_purpose::STANDARD.encode(vec![0u8; 190_000]);
3193 let entries = (0..6)
3194 .map(|_| format!("<X509SKI>{payload}</X509SKI>"))
3195 .collect::<Vec<_>>()
3196 .join("");
3197 let xml = format!(
3198 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><X509Data>{entries}</X509Data></KeyInfo>"
3199 );
3200 let doc = Document::parse(&xml).unwrap();
3201
3202 let err = parse_key_info(doc.root_element()).unwrap_err();
3203 assert!(matches!(err, ParseError::InvalidStructure(_)));
3204 }
3205
3206 #[test]
3207 fn parse_key_info_rejects_x509_certificate_with_invalid_der() {
3208 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3209 <X509Data>
3210 <X509Certificate>AQID</X509Certificate>
3211 </X509Data>
3212 </KeyInfo>"#;
3213 let doc = Document::parse(xml).unwrap();
3214
3215 let err = parse_key_info(doc.root_element()).unwrap_err();
3216 assert!(matches!(err, ParseError::InvalidStructure(_)));
3217 }
3218
3219 #[test]
3220 fn parse_key_info_rejects_x509_certificate_with_trailing_der_bytes() {
3221 let mut cert = base64::engine::general_purpose::STANDARD
3222 .decode(fixture_rsa_cert_base64())
3223 .unwrap();
3224 cert.extend_from_slice(&[0x00, 0x01]);
3225 let cert_base64 = base64::engine::general_purpose::STANDARD.encode(cert);
3226 let xml = format!(
3227 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3228 <X509Data>
3229 <X509Certificate>{cert_base64}</X509Certificate>
3230 </X509Data>
3231 </KeyInfo>"#
3232 );
3233 let doc = Document::parse(&xml).unwrap();
3234
3235 let err = parse_key_info(doc.root_element()).unwrap_err();
3236 assert!(matches!(err, ParseError::InvalidStructure(_)));
3237 }
3238
3239 #[test]
3240 fn parse_key_info_marks_unsupported_spki_algorithm_as_unsupported() {
3241 let xml = include_str!(
3242 "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt.xml"
3243 );
3244 let doc = Document::parse(xml).unwrap();
3245 let key_info_node = doc
3246 .descendants()
3247 .find(|node| {
3248 node.is_element()
3249 && node.tag_name().namespace() == Some(XMLDSIG_NS)
3250 && node.tag_name().name() == "KeyInfo"
3251 })
3252 .expect("fixture must contain ds:KeyInfo");
3253
3254 let key_info = parse_key_info(key_info_node).expect("KeyInfo parse should succeed");
3255 let x509_info = match &key_info.sources[0] {
3256 KeyInfoSource::X509Data(x509) => x509,
3257 other => panic!("expected X509Data source, got {other:?}"),
3258 };
3259 assert_eq!(x509_info.certificates.len(), 1);
3260 assert_eq!(x509_info.parsed_certificates.len(), 1);
3261 assert_eq!(x509_info.certificate_chain, vec![0]);
3262 let parsed_cert = &x509_info.parsed_certificates[0];
3263 assert!(!parsed_cert.subject_dn.is_empty());
3264 assert!(!parsed_cert.issuer_dn.is_empty());
3265 assert!(parsed_cert.subject_key_identifier.is_some());
3266 assert!(matches!(
3267 parsed_cert.public_key,
3268 X509PublicKeyInfo::Unsupported { .. }
3269 ));
3270 }
3271
3272 #[test]
3273 fn parse_key_info_orders_x509_certificate_chain_from_signing_cert() {
3274 let root = fixture_cert_base64("../../tests/fixtures/keys/cacert.pem");
3275 let intermediate = fixture_cert_base64("../../tests/fixtures/keys/ca2cert.pem");
3276 let leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
3277 let xml = format!(
3278 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3279 <X509Data>
3280 <X509Certificate>{root}</X509Certificate>
3281 <X509Certificate>{intermediate}</X509Certificate>
3282 <X509Certificate>{leaf}</X509Certificate>
3283 </X509Data>
3284 </KeyInfo>"#
3285 );
3286 let doc = Document::parse(&xml).unwrap();
3287
3288 let key_info = parse_key_info(doc.root_element()).unwrap();
3289 let x509_info = match &key_info.sources[0] {
3290 KeyInfoSource::X509Data(x509) => x509,
3291 other => panic!("expected X509Data source, got {other:?}"),
3292 };
3293
3294 assert_eq!(x509_info.certificate_chain, vec![2, 1, 0]);
3295 }
3296
3297 #[test]
3298 fn chain_builder_matches_x509_equivalent_distinguished_names() {
3299 let certificates = [
3303 fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem"),
3304 fixture_cert_base64("../../tests/fixtures/keys/ca2cert.pem"),
3305 fixture_cert_base64("../../tests/fixtures/keys/cacert.pem"),
3306 ]
3307 .map(|encoded| {
3308 base64::engine::general_purpose::STANDARD
3309 .decode(encoded)
3310 .unwrap()
3311 })
3312 .to_vec();
3313 let mut parsed_certificates = certificates
3314 .iter()
3315 .map(|certificate| parse_x509_certificate(certificate).unwrap())
3316 .collect::<Vec<_>>();
3317 parsed_certificates[0].issuer_dn = parsed_certificates[1].subject_dn.to_ascii_lowercase();
3318 parsed_certificates[1].issuer_dn = parsed_certificates[2].subject_dn.to_ascii_lowercase();
3319 let info = X509DataInfo {
3320 certificates,
3321 parsed_certificates,
3322 ..X509DataInfo::default()
3323 };
3324
3325 assert_eq!(
3326 select_x509_signing_certificate(&info, crate::provider::default_provider()).unwrap(),
3327 0
3328 );
3329 assert_eq!(
3330 build_x509_certificate_chain_from(&info, 0, crate::provider::default_provider())
3331 .unwrap(),
3332 vec![0, 1, 2]
3333 );
3334 }
3335
3336 #[test]
3337 fn parse_key_info_uses_issuer_serial_to_select_x509_signing_certificate() {
3338 let root = fixture_cert_base64("../../tests/fixtures/keys/cacert.pem");
3339 let intermediate = fixture_cert_base64("../../tests/fixtures/keys/ca2cert.pem");
3340 let leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
3341 let xml = format!(
3342 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3343 <X509Data>
3344 <X509IssuerSerial>
3345 <X509IssuerName>Email=xmlsec@aleksey.com,CN=Aleksey Sanin,OU=Second level CA,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509IssuerName>
3346 <X509SerialNumber>680572598617295163017172295025714171905498632019</X509SerialNumber>
3347 </X509IssuerSerial>
3348 <X509Certificate>{root}</X509Certificate>
3349 <X509Certificate>{intermediate}</X509Certificate>
3350 <X509Certificate>{leaf}</X509Certificate>
3351 </X509Data>
3352 </KeyInfo>"#
3353 );
3354 let doc = Document::parse(&xml).unwrap();
3355
3356 let key_info = parse_key_info(doc.root_element()).unwrap();
3357 let x509_info = match &key_info.sources[0] {
3358 KeyInfoSource::X509Data(x509) => x509,
3359 other => panic!("expected X509Data source, got {other:?}"),
3360 };
3361
3362 assert_eq!(x509_info.certificate_chain, vec![2, 1, 0]);
3363 }
3364
3365 #[test]
3366 fn parse_key_info_allows_selectors_for_multiple_chain_members() {
3367 let root = fixture_cert_base64("../../tests/fixtures/keys/cacert.pem");
3370 let intermediate = fixture_cert_base64("../../tests/fixtures/keys/ca2cert.pem");
3371 let leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
3372 let xml = format!(
3373 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3374 <X509Data>
3375 <X509SubjectName>CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509SubjectName>
3376 <X509SKI>0X0XrEVCio75sBcl1TxymJ2IOiU=</X509SKI>
3377 <X509Certificate>{root}</X509Certificate>
3378 <X509Certificate>{intermediate}</X509Certificate>
3379 <X509Certificate>{leaf}</X509Certificate>
3380 </X509Data>
3381 </KeyInfo>"#
3382 );
3383 let doc = Document::parse(&xml).unwrap();
3384
3385 let key_info = parse_key_info(doc.root_element()).unwrap();
3386 let x509_info = match &key_info.sources[0] {
3387 KeyInfoSource::X509Data(x509) => x509,
3388 other => panic!("expected X509Data source, got {other:?}"),
3389 };
3390
3391 assert_eq!(x509_info.certificate_chain, vec![2, 1, 0]);
3392 }
3393
3394 #[test]
3395 fn parse_key_info_uses_decimal_issuer_serial_to_select_x509_signing_certificate() {
3396 let serial = "680572598617295163017172295025714171905498632019";
3397 let padded_serial = format!("{}{}", "0".repeat(64), serial);
3398 assert_eq!(
3399 x509_serial_decimal_to_hex(&padded_serial).as_deref(),
3400 Some("7735EE487F6862DAF1B3956D961CCB0FA6F34F53")
3401 );
3402 let root = fixture_cert_base64("../../tests/fixtures/keys/cacert.pem");
3403 let intermediate = fixture_cert_base64("../../tests/fixtures/keys/ca2cert.pem");
3404 let leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
3405 let other_leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-4096-cert.pem");
3406 let xml = format!(
3407 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3408 <X509Data>
3409 <X509IssuerSerial>
3410 <X509IssuerName>Email=xmlsec@aleksey.com,CN=Aleksey Sanin,OU=Second level CA,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509IssuerName>
3411 <X509SerialNumber>{padded_serial}</X509SerialNumber>
3412 </X509IssuerSerial>
3413 <X509Certificate>{root}</X509Certificate>
3414 <X509Certificate>{intermediate}</X509Certificate>
3415 <X509Certificate>{leaf}</X509Certificate>
3416 <X509Certificate>{other_leaf}</X509Certificate>
3417 </X509Data>
3418 </KeyInfo>"#
3419 );
3420 let doc = Document::parse(&xml).unwrap();
3421
3422 let key_info = parse_key_info(doc.root_element()).unwrap();
3423 let x509_info = match &key_info.sources[0] {
3424 KeyInfoSource::X509Data(x509) => x509,
3425 other => panic!("expected X509Data source, got {other:?}"),
3426 };
3427
3428 assert_eq!(x509_info.certificate_chain, vec![2, 1, 0]);
3429 }
3430
3431 #[test]
3432 fn parse_key_info_rejects_ambiguous_x509_signing_certificate_candidates() {
3433 let first_leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
3434 let second_leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-4096-cert.pem");
3435 let xml = format!(
3436 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3437 <X509Data>
3438 <X509Certificate>{first_leaf}</X509Certificate>
3439 <X509Certificate>{second_leaf}</X509Certificate>
3440 </X509Data>
3441 </KeyInfo>"#
3442 );
3443 let doc = Document::parse(&xml).unwrap();
3444
3445 let err = parse_key_info(doc.root_element()).unwrap_err();
3446 assert!(matches!(err, ParseError::InvalidStructure(_)));
3447 }
3448
3449 #[test]
3450 fn parse_key_info_rejects_unmatched_x509_lookup_identifier() {
3451 let cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
3452 let xml = format!(
3453 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3454 <X509Data>
3455 <X509SubjectName>CN=Not The Embedded Certificate</X509SubjectName>
3456 <X509Certificate>{cert}</X509Certificate>
3457 </X509Data>
3458 </KeyInfo>"#
3459 );
3460 let doc = Document::parse(&xml).unwrap();
3461
3462 let err = parse_key_info(doc.root_element()).unwrap_err();
3463 assert!(
3464 matches!(err, ParseError::InvalidStructure(message) if message.contains("lookup identifiers"))
3465 );
3466 }
3467
3468 #[test]
3469 fn parse_key_info_rejects_partially_matched_selector_category() {
3470 let cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
3473 let xml = format!(
3474 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3475 <X509Data>
3476 <X509SubjectName>CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509SubjectName>
3477 <X509SubjectName>CN=Not In The Embedded Chain</X509SubjectName>
3478 <X509Certificate>{cert}</X509Certificate>
3479 </X509Data>
3480 </KeyInfo>"#
3481 );
3482 let doc = Document::parse(&xml).unwrap();
3483
3484 let err = parse_key_info(doc.root_element()).unwrap_err();
3485 assert!(
3486 matches!(err, ParseError::InvalidStructure(message) if message.contains("lookup identifiers"))
3487 );
3488 }
3489
3490 #[test]
3491 fn parse_key_info_rejects_malformed_issuer_serial_even_with_matching_subject() {
3492 let cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
3495 let xml = format!(
3496 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3497 <X509Data>
3498 <X509SubjectName>CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509SubjectName>
3499 <X509IssuerSerial>
3500 <X509IssuerName>Email=xmlsec@aleksey.com,CN=Aleksey Sanin,OU=Second level CA,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509IssuerName>
3501 <X509SerialNumber>not-a-decimal-serial</X509SerialNumber>
3502 </X509IssuerSerial>
3503 <X509Certificate>{cert}</X509Certificate>
3504 </X509Data>
3505 </KeyInfo>"#
3506 );
3507 let doc = Document::parse(&xml).unwrap();
3508
3509 let err = parse_key_info(doc.root_element()).unwrap_err();
3510 assert!(
3511 matches!(err, ParseError::InvalidStructure(message) if message.contains("invalid X509SerialNumber"))
3512 );
3513 }
3514
3515 #[test]
3516 fn parse_key_info_rejects_unmatched_ski_even_with_matching_subject() {
3517 let cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
3518 let xml = format!(
3519 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3520 <X509Data>
3521 <X509SubjectName>CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509SubjectName>
3522 <X509SKI>AQIDBA==</X509SKI>
3523 <X509Certificate>{cert}</X509Certificate>
3524 </X509Data>
3525 </KeyInfo>"#
3526 );
3527 let doc = Document::parse(&xml).unwrap();
3528
3529 let err = parse_key_info(doc.root_element()).unwrap_err();
3530 assert!(
3531 matches!(err, ParseError::InvalidStructure(message) if message.contains("lookup identifiers"))
3532 );
3533 }
3534
3535 #[test]
3536 fn parse_key_info_rejects_lookup_hints_for_different_certificates() {
3537 let first_cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
3538 let second_cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-4096-cert.pem");
3539 let xml = format!(
3540 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3541 <X509Data>
3542 <X509SubjectName>CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509SubjectName>
3543 <X509SKI>60zMLKCfzQ3qnXAzABzRNpdgQ8Q=</X509SKI>
3544 <X509Certificate>{first_cert}</X509Certificate>
3545 <X509Certificate>{second_cert}</X509Certificate>
3546 </X509Data>
3547 </KeyInfo>"#
3548 );
3549 let doc = Document::parse(&xml).unwrap();
3550
3551 let err = parse_key_info(doc.root_element()).unwrap_err();
3552 assert!(
3553 matches!(err, ParseError::InvalidStructure(message) if message.contains("lookup identifiers match multiple certificates"))
3554 );
3555 }
3556
3557 #[test]
3558 fn configured_certificate_matching_requires_every_x509_selector_category() {
3559 let certificate = base64::engine::general_purpose::STANDARD
3562 .decode(fixture_rsa_cert_base64())
3563 .unwrap();
3564 let parsed = parse_x509_certificate(&certificate).unwrap();
3565 let digest = compute_digest_with_provider(
3566 crate::provider::default_provider(),
3567 DigestAlgorithm::Sha256,
3568 &certificate,
3569 )
3570 .unwrap();
3571 let matching = X509DataInfo {
3572 subject_names: vec![parsed.subject_dn.clone()],
3573 issuer_serials: vec![(
3574 parsed.issuer_dn.clone(),
3575 "680572598617295163017172295025714171905498632019".into(),
3576 )],
3577 skis: vec![parsed.subject_key_identifier.clone().unwrap()],
3578 digests: vec![(DigestAlgorithm::Sha256.uri().into(), digest)],
3579 ..X509DataInfo::default()
3580 };
3581
3582 assert!(
3583 x509_certificate_matches_selectors(
3584 &matching,
3585 &certificate,
3586 crate::provider::default_provider()
3587 )
3588 .unwrap()
3589 );
3590 for mismatching in [
3591 X509DataInfo {
3592 subject_names: vec!["CN=other".into()],
3593 ..matching.clone()
3594 },
3595 X509DataInfo {
3596 issuer_serials: vec![(parsed.issuer_dn.clone(), "1".into())],
3597 ..matching.clone()
3598 },
3599 X509DataInfo {
3600 skis: vec![vec![0]],
3601 ..matching.clone()
3602 },
3603 X509DataInfo {
3604 digests: vec![(DigestAlgorithm::Sha256.uri().into(), vec![0; 32])],
3605 ..matching.clone()
3606 },
3607 ] {
3608 assert!(
3609 !x509_certificate_matches_selectors(
3610 &mismatching,
3611 &certificate,
3612 crate::provider::default_provider()
3613 )
3614 .unwrap()
3615 );
3616 }
3617 }
3618
3619 #[test]
3620 fn build_x509_certificate_chain_rejects_chain_exceeding_max_depth() {
3621 let parsed_certificates: Vec<ParsedX509Certificate> = (0..=MAX_X509_CHAIN_DEPTH)
3622 .map(|idx| ParsedX509Certificate {
3623 subject_dn: format!("CN=cert-{idx}"),
3624 issuer_dn: if idx == MAX_X509_CHAIN_DEPTH {
3625 format!("CN=cert-{idx}")
3626 } else {
3627 format!("CN=cert-{}", idx + 1)
3628 },
3629 serial_number: vec![u8::try_from(idx).unwrap()],
3630 serial_number_hex: format!("{idx:02X}"),
3631 subject_key_identifier: None,
3632 public_key: X509PublicKeyInfo::Unsupported {
3633 algorithm_oid: "1.2.3.4".into(),
3634 },
3635 })
3636 .collect();
3637 let certificates = vec![Vec::new(); parsed_certificates.len()];
3638 let info = X509DataInfo {
3639 certificates,
3640 parsed_certificates,
3641 ..X509DataInfo::default()
3642 };
3643
3644 let err =
3645 build_x509_certificate_chain(&info, crate::provider::default_provider()).unwrap_err();
3646 assert!(
3647 matches!(err, ParseError::InvalidStructure(message) if message.contains("maximum depth"))
3648 );
3649 }
3650
3651 #[test]
3652 fn x509_serial_hex_strips_der_sign_extension_zeroes() {
3653 assert_eq!(format_x509_serial_value_hex(&[0x00, 0xFF]), "FF");
3654 assert_eq!(format_x509_serial_value_hex(&[0x00, 0x7F]), "7F");
3655 assert_eq!(format_x509_serial_value_hex(&[0x00, 0x00]), "00");
3656 }
3657
3658 #[test]
3659 fn x509_serial_decimal_parser_enforces_rfc5280_positive_range() {
3660 let max_serial = "1461501637330902918203684832716283019655932542975";
3663 assert_eq!(
3664 x509_serial_decimal_to_hex(max_serial),
3665 Some("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".into())
3666 );
3667 assert_eq!(
3668 x509_serial_decimal_to_hex("730750818665451459101842416358141509827966271488"),
3669 Some("8000000000000000000000000000000000000000".into())
3670 );
3671 assert_eq!(
3672 x509_serial_decimal_to_hex("0000000000000000000000000000000000000000000000001"),
3673 Some("01".into())
3674 );
3675 assert_eq!(
3676 x509_serial_decimal_to_hex("00000000000000000000000000000000000000000000000001"),
3677 Some("01".into())
3678 );
3679 assert_eq!(x509_serial_decimal_to_hex("+1"), Some("01".into()));
3680
3681 for invalid in [
3682 "",
3683 "0",
3684 "000",
3685 "+0",
3686 "++1",
3687 "-1",
3688 "1a",
3689 "1461501637330902918203684832716283019655932542976",
3690 ] {
3691 assert_eq!(
3692 x509_serial_decimal_to_hex(invalid),
3693 None,
3694 "invalid serial {invalid:?} must be rejected"
3695 );
3696 }
3697 }
3698
3699 #[test]
3700 fn parse_x509_serial_normalizes_boundary_whitespace_and_rejects_overflow() {
3701 let max_serial = "1461501637330902918203684832716283019655932542975";
3704 let valid = format!(
3705 "<KeyInfo xmlns=\"{XMLDSIG_NS}\"><X509Data><X509IssuerSerial><X509IssuerName>CN=issuer</X509IssuerName><X509SerialNumber>\n {max_serial}\t</X509SerialNumber></X509IssuerSerial></X509Data></KeyInfo>"
3706 );
3707 let doc = Document::parse(&valid).unwrap();
3708 let parsed = parse_key_info(doc.root_element()).unwrap();
3709 let KeyInfoSource::X509Data(x509) = &parsed.sources[0] else {
3710 panic!("expected X509Data source");
3711 };
3712 assert_eq!(x509.issuer_serials[0].1, max_serial);
3713
3714 let explicit_positive = valid.replace(max_serial, "+42");
3715 let doc = Document::parse(&explicit_positive).unwrap();
3716 let parsed = parse_key_info(doc.root_element()).unwrap();
3717 let KeyInfoSource::X509Data(x509) = &parsed.sources[0] else {
3718 panic!("expected X509Data source");
3719 };
3720 assert_eq!(x509.issuer_serials[0].1, "42");
3721
3722 let overflow = valid.replace(
3723 max_serial,
3724 "1461501637330902918203684832716283019655932542976",
3725 );
3726 let doc = Document::parse(&overflow).unwrap();
3727 assert!(matches!(
3728 parse_key_info(doc.root_element()),
3729 Err(ParseError::InvalidStructure(message))
3730 if message.contains("invalid X509SerialNumber")
3731 ));
3732 }
3733
3734 #[test]
3735 fn issuer_selector_matches_a_sign_padded_twenty_octet_serial() {
3736 let serial_hex = "8000000000000000000000000000000000000000";
3739 let info = X509DataInfo {
3740 issuer_serials: vec![(
3741 "CN=issuer".into(),
3742 "730750818665451459101842416358141509827966271488".into(),
3743 )],
3744 parsed_certificates: vec![ParsedX509Certificate {
3745 subject_dn: "CN=leaf".into(),
3746 issuer_dn: "CN=issuer".into(),
3747 serial_number: [vec![0, 0x80], vec![0; 19]].concat(),
3748 serial_number_hex: serial_hex.into(),
3749 subject_key_identifier: None,
3750 public_key: X509PublicKeyInfo::Unsupported {
3751 algorithm_oid: "1.2.3.4".into(),
3752 },
3753 }],
3754 ..X509DataInfo::default()
3755 };
3756
3757 assert!(
3758 x509_selector_categories_match_chain(&info, crate::provider::default_provider())
3759 .unwrap()
3760 );
3761 }
3762
3763 #[test]
3764 fn distinguished_name_matching_preserves_rdn_order() {
3765 assert!(distinguished_names_equal(
3768 "CN=leaf, O=example",
3769 "CN=leaf,O=example"
3770 ));
3771 assert!(!distinguished_names_equal(
3772 "CN=leaf,O=example",
3773 "O=example,CN=leaf"
3774 ));
3775 }
3776
3777 #[test]
3778 fn distinguished_name_matching_applies_x520_string_preparation() {
3779 assert!(distinguished_names_equal(
3782 "CN= TEST key ,O=Example",
3783 "CN=test key,O=example"
3784 ));
3785 assert!(distinguished_names_equal(
3786 "CN=Straße,O=Example",
3787 "CN=STRASSE,O=EXAMPLE"
3788 ));
3789 assert!(distinguished_names_equal(
3790 "CN=test+OU=security,O=example",
3791 "OU=SECURITY+CN=TEST,O=EXAMPLE"
3792 ));
3793 assert!(!distinguished_names_equal(
3794 "1.2.3.4=#040141,O=example",
3795 "1.2.3.4=#040142,O=example"
3796 ));
3797 }
3798
3799 #[test]
3800 fn distinguished_name_matching_applies_ia5_matching_rules() {
3801 assert!(distinguished_names_equal(
3804 "EMAIL=ops@EXAMPLE.COM,DC=EXAMPLE,DC=COM",
3805 "EMAIL=ops@example.com,DC=example,DC=com"
3806 ));
3807 assert!(!distinguished_names_equal(
3808 "EMAIL=OPS@example.com,DC=example,DC=com",
3809 "EMAIL=ops@example.com,DC=example,DC=com"
3810 ));
3811 }
3812
3813 #[test]
3814 fn distinguished_name_matching_handles_rfc4514_escaped_values() {
3815 let value = " leading,plus+equals=slash\\trailing ";
3818 let mut params = rcgen::CertificateParams::new(Vec::new()).unwrap();
3819 params
3820 .distinguished_name
3821 .push(rcgen::DnType::CommonName, value);
3822 let key = rcgen::KeyPair::generate().unwrap();
3823 let certificate = params.self_signed(&key).unwrap();
3824 let parsed = parse_x509_certificate(certificate.der()).unwrap();
3825
3826 assert_eq!(
3827 parsed.subject_dn,
3828 r"CN=\ leading\,plus\+equals=slash\\trailing\ "
3829 );
3830 assert!(distinguished_names_equal(
3831 r"CN=\ leading\,plus\+equals=slash\\trailing\ ",
3832 &parsed.subject_dn
3833 ));
3834 assert!(distinguished_names_equal(
3835 "\n CN=\\ leading\\,plus\\+equals=slash\\\\trailing\\ \n",
3836 &parsed.subject_dn
3837 ));
3838 }
3839
3840 #[test]
3841 fn distinguished_name_trailing_escape_covers_all_xml_whitespace() {
3842 for whitespace in [' ', '\t', '\r', '\n'] {
3845 assert!(trailing_whitespace_is_escaped(&format!(
3846 "CN=value\\{whitespace}"
3847 )));
3848 assert!(!trailing_whitespace_is_escaped(&format!(
3849 "CN=value{whitespace}"
3850 )));
3851 }
3852 }
3853
3854 #[test]
3855 fn parse_key_info_accepts_large_textual_x509_entries_within_entry_budget() {
3856 let issuer_name = "C".repeat(MAX_X509_ISSUER_NAME_TEXT_LEN);
3857 let serial_number = "0".repeat(MAX_X509_SERIAL_NUMBER_VALUE_DIGITS - 1) + "1";
3858 let issuer_serials = (0..52)
3859 .map(|_| {
3860 format!(
3861 "<X509IssuerSerial><X509IssuerName>{issuer_name}</X509IssuerName><X509SerialNumber>{serial_number}</X509SerialNumber></X509IssuerSerial>"
3862 )
3863 })
3864 .collect::<Vec<_>>()
3865 .join("");
3866 let xml = format!(
3867 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><X509Data>{issuer_serials}</X509Data></KeyInfo>"
3868 );
3869 let doc = Document::parse(&xml).unwrap();
3870
3871 let key_info = parse_key_info(doc.root_element()).unwrap();
3872 let parsed = match &key_info.sources[0] {
3873 KeyInfoSource::X509Data(x509) => x509,
3874 _ => panic!("expected X509Data source"),
3875 };
3876 assert_eq!(parsed.issuer_serials.len(), 52);
3877 }
3878
3879 #[test]
3880 fn parse_key_info_bounds_raw_x509_serial_text() {
3881 let serial = "0".repeat(MAX_X509_SERIAL_NUMBER_RAW_TEXT_LEN + 1);
3884 let xml = format!(
3885 "<KeyInfo xmlns=\"{XMLDSIG_NS}\"><X509Data><X509IssuerSerial><X509IssuerName>CN=issuer</X509IssuerName><X509SerialNumber>{serial}</X509SerialNumber></X509IssuerSerial></X509Data></KeyInfo>"
3886 );
3887 let doc = Document::parse(&xml).unwrap();
3888
3889 let error = parse_key_info(doc.root_element()).unwrap_err();
3890
3891 assert!(matches!(
3892 error,
3893 ParseError::InvalidStructure(reason)
3894 if reason == "X509SerialNumber exceeds maximum allowed text length"
3895 ));
3896 }
3897
3898 #[test]
3899 fn parse_key_info_accepts_x509data_with_only_foreign_namespace_children() {
3900 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
3901 xmlns:foo="urn:example:foo">
3902 <X509Data>
3903 <foo:Bar/>
3904 </X509Data>
3905 </KeyInfo>"#;
3906 let doc = Document::parse(xml).unwrap();
3907
3908 let key_info = parse_key_info(doc.root_element()).unwrap();
3909 assert_eq!(
3910 key_info.sources,
3911 vec![KeyInfoSource::X509Data(X509DataInfo::default())]
3912 );
3913 }
3914
3915 #[test]
3916 fn parse_key_info_der_encoded_key_value_rejects_invalid_base64() {
3917 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
3918 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
3919 <dsig11:DEREncodedKeyValue>%%%invalid%%%</dsig11:DEREncodedKeyValue>
3920 </KeyInfo>"#;
3921 let doc = Document::parse(xml).unwrap();
3922
3923 let err = parse_key_info(doc.root_element()).unwrap_err();
3924 assert!(matches!(err, ParseError::Base64(_)));
3925 }
3926
3927 #[test]
3928 fn parse_key_info_der_encoded_key_value_accepts_xml_whitespace() {
3929 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
3930 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
3931 <dsig11:DEREncodedKeyValue>
3932 AQID
3933 BA==
3934 </dsig11:DEREncodedKeyValue>
3935 </KeyInfo>"#;
3936 let doc = Document::parse(xml).unwrap();
3937
3938 let key_info = parse_key_info(doc.root_element()).unwrap();
3939 assert_eq!(
3940 key_info.sources,
3941 vec![KeyInfoSource::DerEncodedKeyValue(vec![1, 2, 3, 4])]
3942 );
3943 }
3944
3945 #[test]
3946 fn parse_key_info_dispatches_dsig11_ec_keyvalue() {
3947 let public_key = "BJ/yaXNlq4FRObyJCBhb5jAz8GVzinK3bBGLjSDfjbJwNfydtgjnlS4EsDmxSRhWyJWq6GIqy5wvnaiARK04uB4=";
3948 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
3949 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
3950 <KeyValue>
3951 <dsig11:ECKeyValue>
3952 <dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/>
3953 <dsig11:PublicKey>BJ/yaXNlq4FRObyJCBhb5jAz8GVzinK3bBGLjSDfjbJwNfydtgjnlS4EsDmxSRhWyJWq6GIqy5wvnaiARK04uB4=</dsig11:PublicKey>
3954 </dsig11:ECKeyValue>
3955 </KeyValue>
3956 </KeyInfo>"#;
3957 let doc = Document::parse(xml).unwrap();
3958 let expected_public_key = base64::engine::general_purpose::STANDARD
3959 .decode(public_key)
3960 .expect("fixture EC point must be valid base64");
3961
3962 let key_info = parse_key_info(doc.root_element()).unwrap();
3963 assert_eq!(
3964 key_info.sources,
3965 vec![KeyInfoSource::KeyValue(KeyValueInfo::Ec {
3966 curve_oid: "1.2.840.10045.3.1.7".into(),
3967 public_key: expected_public_key,
3968 })]
3969 );
3970 }
3971
3972 #[test]
3973 fn parse_ec_key_value_accepts_bare_curve_oid() {
3974 use base64::Engine;
3975
3976 let encoded_public_key = "BO/yd/OZzDfjX4qivDY/vsUIuh6KWAxoxW5P4ukvwd+T6pVljWsX2UBJNNy5MdhTwB8e2YwB8kUbJwdsAS/XGi/fz8unFrs+lVlAgIs6s/xBYFbfUoRiAacD2SpVDe6XBA==";
3977 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
3978 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
3979 <KeyValue>
3980 <dsig11:ECKeyValue>
3981 <dsig11:NamedCurve URI="1.3.132.0.34"/>
3982 <dsig11:PublicKey>BO/yd/OZzDfjX4qivDY/vsUIuh6KWAxoxW5P4ukvwd+T6pVljWsX2UBJNNy5MdhTwB8e2YwB8kUbJwdsAS/XGi/fz8unFrs+lVlAgIs6s/xBYFbfUoRiAacD2SpVDe6XBA==</dsig11:PublicKey>
3983 </dsig11:ECKeyValue>
3984 </KeyValue>
3985 </KeyInfo>"#;
3986 let doc = Document::parse(xml).unwrap();
3987 let expected_public_key = base64::engine::general_purpose::STANDARD
3988 .decode(encoded_public_key)
3989 .unwrap();
3990
3991 let sources = parse_key_info(doc.root_element()).unwrap().sources;
3992
3993 assert!(matches!(
3994 &sources[0],
3995 KeyInfoSource::KeyValue(KeyValueInfo::Ec { curve_oid, public_key })
3996 if curve_oid == EC_P384_OID && public_key == &expected_public_key
3997 ));
3998 }
3999
4000 #[test]
4001 fn parse_ec_key_value_marks_ec_parameters_as_unsupported() {
4002 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
4003 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
4004 <KeyValue>
4005 <dsig11:ECKeyValue>
4006 <dsig11:ECParameters/>
4007 <dsig11:PublicKey>BA==</dsig11:PublicKey>
4008 </dsig11:ECKeyValue>
4009 </KeyValue>
4010 </KeyInfo>"#;
4011 let doc = Document::parse(xml).unwrap();
4012
4013 let key_info = parse_key_info(doc.root_element()).unwrap();
4014 assert_eq!(
4015 key_info.sources,
4016 vec![KeyInfoSource::KeyValue(KeyValueInfo::Unsupported {
4017 namespace: Some(XMLDSIG11_NS.to_string()),
4018 local_name: "ECKeyValue".into(),
4019 })]
4020 );
4021 }
4022
4023 #[test]
4024 fn parse_ec_key_value_marks_unsupported_curve_as_unsupported() {
4025 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
4026 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
4027 <KeyValue>
4028 <dsig11:ECKeyValue>
4029 <dsig11:NamedCurve URI="urn:oid:1.3.132.0.36"/>
4030 <dsig11:PublicKey>BA==</dsig11:PublicKey>
4031 </dsig11:ECKeyValue>
4032 </KeyValue>
4033 </KeyInfo>"#;
4034 let doc = Document::parse(xml).unwrap();
4035
4036 let key_info = parse_key_info(doc.root_element()).unwrap();
4037 assert_eq!(
4038 key_info.sources,
4039 vec![KeyInfoSource::KeyValue(KeyValueInfo::Unsupported {
4040 namespace: Some(XMLDSIG11_NS.to_string()),
4041 local_name: "ECKeyValue".into(),
4042 })]
4043 );
4044 }
4045
4046 #[test]
4047 fn parse_ec_key_value_marks_missing_named_curve_uri_invalid() {
4048 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
4049 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
4050 <KeyValue>
4051 <dsig11:ECKeyValue>
4052 <dsig11:NamedCurve/>
4053 <dsig11:PublicKey>BA==</dsig11:PublicKey>
4054 </dsig11:ECKeyValue>
4055 </KeyValue>
4056 </KeyInfo>"#;
4057 let doc = Document::parse(xml).unwrap();
4058
4059 let key_info = parse_key_info(doc.root_element()).unwrap();
4060 assert_eq!(
4061 key_info.sources,
4062 vec![KeyInfoSource::KeyValue(KeyValueInfo::InvalidEcKeyValue)]
4063 );
4064 }
4065
4066 #[test]
4067 fn parse_ec_key_value_marks_reordered_children_invalid() {
4068 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
4069 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
4070 <KeyValue>
4071 <dsig11:ECKeyValue>
4072 <dsig11:PublicKey>BJ/yaXNlq4FRObyJCBhb5jAz8GVzinK3bBGLjSDfjbJwNfydtgjnlS4EsDmxSRhWyJWq6GIqy5wvnaiARK04uB4=</dsig11:PublicKey>
4073 <dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/>
4074 </dsig11:ECKeyValue>
4075 </KeyValue>
4076 </KeyInfo>"#;
4077 let doc = Document::parse(xml).unwrap();
4078
4079 let key_info = parse_key_info(doc.root_element()).unwrap();
4080 assert_eq!(
4081 key_info.sources,
4082 vec![KeyInfoSource::KeyValue(KeyValueInfo::InvalidEcKeyValue)]
4083 );
4084 }
4085
4086 #[test]
4087 fn parse_ec_key_value_marks_non_uncompressed_point_invalid() {
4088 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
4089 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
4090 <KeyValue>
4091 <dsig11:ECKeyValue>
4092 <dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/>
4093 <dsig11:PublicKey>Ap/yaXNlq4FRObyJCBhb5jAz8GVzinK3bBGLjSDfjbJwNfydtgjnlS4EsDmxSRhWyJWq6GIqy5wvnaiARK04uB4=</dsig11:PublicKey>
4094 </dsig11:ECKeyValue>
4095 </KeyValue>
4096 </KeyInfo>"#;
4097 let doc = Document::parse(xml).unwrap();
4098
4099 let key_info = parse_key_info(doc.root_element()).unwrap();
4100 assert_eq!(
4101 key_info.sources,
4102 vec![KeyInfoSource::KeyValue(KeyValueInfo::InvalidEcKeyValue)]
4103 );
4104 }
4105
4106 #[test]
4107 fn parse_key_info_marks_ds_namespace_ec_keyvalue_as_unsupported() {
4108 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4109 <KeyValue>
4110 <ECKeyValue/>
4111 </KeyValue>
4112 </KeyInfo>"#;
4113 let doc = Document::parse(xml).unwrap();
4114
4115 let key_info = parse_key_info(doc.root_element()).unwrap();
4116 assert_eq!(
4117 key_info.sources,
4118 vec![KeyInfoSource::KeyValue(KeyValueInfo::Unsupported {
4119 namespace: Some(XMLDSIG_NS.to_string()),
4120 local_name: "ECKeyValue".into(),
4121 })]
4122 );
4123 }
4124
4125 #[test]
4126 fn parse_key_info_keeps_unsupported_keyvalue_child_as_marker() {
4127 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4128 <KeyValue>
4129 <FutureKeyValue/>
4130 </KeyValue>
4131 </KeyInfo>"#;
4132 let doc = Document::parse(xml).unwrap();
4133
4134 let key_info = parse_key_info(doc.root_element()).unwrap();
4135 assert_eq!(
4136 key_info.sources,
4137 vec![KeyInfoSource::KeyValue(KeyValueInfo::Unsupported {
4138 namespace: Some(XMLDSIG_NS.to_string()),
4139 local_name: "FutureKeyValue".into(),
4140 })]
4141 );
4142 }
4143
4144 #[test]
4145 fn parse_key_info_accepts_supported_x509_retrieval_xpath() {
4146 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4148 <RetrievalMethod Type="http://www.w3.org/2000/09/xmldsig#X509Data" URI="#keys">
4149 <Transforms><Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">
4150 <XPath xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">ancestor-or-self::dsig:X509Data</XPath>
4151 </Transform></Transforms>
4152 </RetrievalMethod>
4153 </KeyInfo>"##;
4154 let doc = Document::parse(xml).unwrap();
4155
4156 let key_info = parse_key_info(doc.root_element()).unwrap();
4157 assert!(matches!(
4158 key_info.sources.as_slice(),
4159 [KeyInfoSource::RetrievalMethod {
4160 uri,
4161 resource_type: Some(resource_type),
4162 transforms: RetrievalMethodTransforms::X509DataNodeSetFilter { .. },
4163 }] if uri == "#keys"
4164 && resource_type == "http://www.w3.org/2000/09/xmldsig#X509Data"
4165 ));
4166 }
4167
4168 #[test]
4169 fn retrieval_xpath_namespace_binding_policy_precedes_materialization() {
4170 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4173 <RetrievalMethod Type="http://www.w3.org/2000/09/xmldsig#X509Data" URI="#keys">
4174 <Transforms><Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">
4175 <XPath xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">ancestor-or-self::dsig:X509Data</XPath>
4176 </Transform></Transforms>
4177 </RetrievalMethod>
4178 </KeyInfo>"##;
4179 let document = Document::parse(xml).expect("fixed KeyInfo fixture must parse");
4180 let resources = crate::policy::ResourcePolicy {
4181 max_xpath_namespace_bindings: 0,
4182 ..crate::policy::ResourcePolicy::default()
4183 };
4184
4185 let error = parse_key_info_with_policy_budgets(
4186 document.root_element(),
4187 crate::provider::default_provider(),
4188 &XmlBaseResolutionBudget::default(),
4189 &resources,
4190 )
4191 .expect_err("zero namespace bindings must reject RetrievalMethod XPath");
4192
4193 assert!(matches!(
4194 error,
4195 ParseError::Transform(TransformError::Policy(
4196 crate::policy::PolicyViolation::ResourceLimit {
4197 resource: crate::policy::resource_name::XPATH_NAMESPACE_BINDINGS,
4198 maximum: 0,
4199 actual: 1,
4200 }
4201 ))
4202 ));
4203 }
4204
4205 #[test]
4206 fn retrieval_xpath_namespace_byte_policy_precedes_materialization() {
4207 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4210 <RetrievalMethod Type="http://www.w3.org/2000/09/xmldsig#X509Data" URI="#keys">
4211 <Transforms><Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">
4212 <XPath xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">ancestor-or-self::dsig:X509Data</XPath>
4213 </Transform></Transforms>
4214 </RetrievalMethod>
4215 </KeyInfo>"##;
4216 let document = Document::parse(xml).expect("fixed KeyInfo fixture must parse");
4217 let resources = crate::policy::ResourcePolicy {
4218 max_xpath_namespace_bytes: 0,
4219 ..crate::policy::ResourcePolicy::default()
4220 };
4221
4222 let error = parse_key_info_with_policy_budgets(
4223 document.root_element(),
4224 crate::provider::default_provider(),
4225 &XmlBaseResolutionBudget::default(),
4226 &resources,
4227 )
4228 .expect_err("zero namespace bytes must reject RetrievalMethod XPath");
4229
4230 assert!(matches!(
4231 error,
4232 ParseError::Transform(TransformError::Policy(
4233 crate::policy::PolicyViolation::ResourceLimit {
4234 resource: crate::policy::resource_name::XPATH_NAMESPACE_BYTES,
4235 maximum: 0,
4236 actual,
4237 }
4238 )) if actual == "dsig".len() + XMLDSIG_NS.len()
4239 ));
4240 }
4241
4242 #[test]
4243 fn parse_key_info_rejects_oversized_retrieval_method_type() {
4244 let oversized_type = "x".repeat(MAX_KEY_NAME_TEXT_LEN + 1);
4247 let xml = format!(
4248 r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"><RetrievalMethod URI="#key" Type="{oversized_type}"/></KeyInfo>"##
4249 );
4250 let document = Document::parse(&xml).unwrap();
4251
4252 assert!(matches!(
4253 parse_key_info(document.root_element()),
4254 Err(ParseError::InvalidStructure(reason))
4255 if reason == "RetrievalMethod Type exceeds maximum length"
4256 ));
4257 }
4258
4259 #[test]
4260 fn parse_key_info_bounds_retrieval_method_xml_base_chain() {
4261 let mut xml =
4264 format!(r#"<KeyInfo xmlns="{XMLDSIG_NS}"><RetrievalMethod URI="key.der"/></KeyInfo>"#);
4265 for _ in 0..65 {
4266 xml = format!(r#"<n xml:base="segment/">{xml}</n>"#);
4267 }
4268 let document = Document::parse(&xml).unwrap();
4269 let key_info = document
4270 .descendants()
4271 .find(|node| node.has_tag_name((XMLDSIG_NS, "KeyInfo")))
4272 .unwrap();
4273
4274 assert!(matches!(
4275 parse_key_info(key_info),
4276 Err(ParseError::InvalidStructure(reason))
4277 if reason.contains("XML Base resolution")
4278 ));
4279 }
4280
4281 #[test]
4282 fn parse_key_info_normalizes_external_retrieval_without_xml_base() {
4283 let xml = format!(
4286 r#"<KeyInfo xmlns="{XMLDSIG_NS}"><RetrievalMethod URI="https://example.test/a/../key.der"/></KeyInfo>"#
4287 );
4288 let document = Document::parse(&xml).unwrap();
4289 let key_info = parse_key_info(document.root_element()).unwrap();
4290
4291 assert!(matches!(
4292 key_info.sources.as_slice(),
4293 [KeyInfoSource::RetrievalMethod { uri, .. }]
4294 if uri == "https://example.test/key.der"
4295 ));
4296 }
4297
4298 #[test]
4299 fn parse_key_info_absolute_retrieval_bypasses_xml_base_chain() {
4300 let mut xml = format!(
4303 r#"<KeyInfo xmlns="{XMLDSIG_NS}"><RetrievalMethod URI="https://example.test/a/../key.der"/></KeyInfo>"#
4304 );
4305 for _ in 0..65 {
4306 xml = format!(r#"<n xml:base="segment/">{xml}</n>"#);
4307 }
4308 let document = Document::parse(&xml).unwrap();
4309 let key_info_node = document
4310 .descendants()
4311 .find(|node| node.has_tag_name((XMLDSIG_NS, "KeyInfo")))
4312 .unwrap();
4313 let key_info = parse_key_info(key_info_node)
4314 .expect("absolute RetrievalMethod must not consume ancestor-base budget");
4315
4316 assert!(matches!(
4317 key_info.sources.as_slice(),
4318 [KeyInfoSource::RetrievalMethod { uri, .. }]
4319 if uri == "https://example.test/key.der"
4320 ));
4321 }
4322
4323 #[test]
4324 fn parse_key_info_accepts_namespace_equivalent_retrieval_xpath_prefix() {
4325 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4326 <RetrievalMethod Type="http://www.w3.org/2000/09/xmldsig#X509Data" URI="#keys">
4327 <Transforms><Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">
4328 <XPath xmlns:ds="http://www.w3.org/2000/09/xmldsig#">ancestor-or-self::ds:X509Data</XPath>
4329 </Transform></Transforms>
4330 </RetrievalMethod>
4331 </KeyInfo>"##;
4332 let doc = Document::parse(xml).unwrap();
4333
4334 assert!(matches!(
4335 parse_key_info(doc.root_element())
4336 .unwrap()
4337 .sources
4338 .as_slice(),
4339 [KeyInfoSource::RetrievalMethod {
4340 transforms: RetrievalMethodTransforms::X509DataNodeSetFilter { .. },
4341 ..
4342 }]
4343 ));
4344 }
4345
4346 #[test]
4347 fn parse_key_info_reads_complete_retrieval_xpath_text() {
4348 let valid = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4351 <RetrievalMethod Type="http://www.w3.org/2000/09/xmldsig#X509Data" URI="#keys">
4352 <Transforms><Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">
4353 <XPath xmlns:ds="http://www.w3.org/2000/09/xmldsig#">ancestor-or-self::ds:X509<!-- split -->Data</XPath>
4354 </Transform></Transforms>
4355 </RetrievalMethod>
4356 </KeyInfo>"##;
4357 let document = Document::parse(valid).unwrap();
4358 assert!(parse_key_info(document.root_element()).is_ok());
4359
4360 let unsupported =
4361 valid.replace("X509<!-- split -->Data", "X509Data<!-- split -->[false()]");
4362 let document = Document::parse(&unsupported).unwrap();
4363 assert!(matches!(
4364 parse_key_info(document.root_element()),
4365 Err(ParseError::InvalidStructure(reason))
4366 if reason == "unsupported RetrievalMethod XPath selection"
4367 ));
4368 }
4369
4370 #[test]
4371 fn parse_dsa_key_value_accepts_schema_optional_parameters_and_rejects_half_pair() {
4372 let key_info = |parameters: &str| {
4373 format!(
4374 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"><KeyValue><DSAKeyValue>
4375 {parameters}
4376 </DSAKeyValue></KeyValue></KeyInfo>"#
4377 )
4378 };
4379 for parameters in [
4380 "<Y>AQ==</Y>",
4381 "<G>AQ==</G><Y>AQ==</Y>",
4382 "<P>AQ==</P><Q>AQ==</Q><Y>AQ==</Y>",
4383 "<P>AQ==</P><Q>AQ==</Q><G>AQ==</G><Y>AQ==</Y><J>AQ==</J>",
4384 "<Y>AQ==</Y><Seed>AQ==</Seed><PgenCounter>AQ==</PgenCounter>",
4385 "<Y>AQ==</Y><J>AQ==</J><Seed>AQ==</Seed><PgenCounter>AQ==</PgenCounter>",
4386 ] {
4387 let xml = key_info(parameters);
4388 let doc = Document::parse(&xml).unwrap();
4389 assert!(matches!(
4390 parse_key_info(doc.root_element())
4391 .unwrap()
4392 .sources
4393 .as_slice(),
4394 [KeyInfoSource::KeyValue(KeyValueInfo::Dsa { .. })]
4395 ));
4396 }
4397
4398 for invalid_parameters in [
4399 "<P>AQ==</P><Y>AQ==</Y>",
4400 "<Q>AQ==</Q><Y>AQ==</Y>",
4401 "<Y>AQ==</Y><Seed>AQ==</Seed>",
4402 "<Y>AQ==</Y><PgenCounter>AQ==</PgenCounter>",
4403 ] {
4404 let xml = key_info(invalid_parameters);
4405 let doc = Document::parse(&xml).unwrap();
4406 assert!(matches!(
4407 parse_key_info(doc.root_element()),
4408 Err(ParseError::InvalidStructure(_))
4409 ));
4410 }
4411 }
4412
4413 #[test]
4414 fn parse_dsa_crypto_binary_ignores_comment_nodes() {
4415 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4417 <KeyValue><DSAKeyValue><Y>AQ<!-- split -->ID</Y></DSAKeyValue></KeyValue>
4418 </KeyInfo>"#;
4419 let doc = Document::parse(xml).unwrap();
4420
4421 assert!(matches!(
4422 parse_key_info(doc.root_element())
4423 .unwrap()
4424 .sources
4425 .as_slice(),
4426 [KeyInfoSource::KeyValue(KeyValueInfo::Dsa { y, .. })] if y == &[1, 2, 3]
4427 ));
4428 }
4429
4430 #[test]
4431 fn parse_rsa_crypto_binary_ignores_comment_nodes() {
4432 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4434 <KeyValue><RSAKeyValue>
4435 <Modulus>AQ<!-- split -->ID</Modulus><Exponent>Aw==</Exponent>
4436 </RSAKeyValue></KeyValue>
4437 </KeyInfo>"#;
4438 let doc = Document::parse(xml).unwrap();
4439
4440 assert!(matches!(
4441 parse_key_info(doc.root_element())
4442 .unwrap()
4443 .sources
4444 .as_slice(),
4445 [KeyInfoSource::KeyValue(KeyValueInfo::Rsa { modulus, exponent })]
4446 if modulus == &[1, 2, 3] && exponent == &[3]
4447 ));
4448 }
4449
4450 #[test]
4451 fn parse_key_info_preserves_advisory_unsupported_retrieval_transform() {
4452 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4455 <RetrievalMethod URI="#keys" Type="urn:vendor:key"><Transforms>
4456 <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#base64"/>
4457 </Transforms></RetrievalMethod>
4458 <KeyName>fallback</KeyName>
4459 </KeyInfo>"##;
4460 let doc = Document::parse(xml).unwrap();
4461
4462 let key_info = parse_key_info(doc.root_element())
4463 .expect("unsupported advisory retrieval must not reject all KeyInfo sources");
4464 assert!(matches!(
4465 key_info.sources.as_slice(),
4466 [
4467 KeyInfoSource::RetrievalMethod { resource_type: Some(resource_type), .. },
4468 KeyInfoSource::KeyName(name),
4469 ] if resource_type == "urn:vendor:key" && name == "fallback"
4470 ));
4471 }
4472
4473 #[test]
4474 fn parse_key_info_preserves_empty_same_document_reference_uri() {
4475 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
4478 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
4479 <dsig11:KeyInfoReference URI=""/>
4480 </KeyInfo>"#;
4481 let document = Document::parse(xml).unwrap();
4482
4483 assert!(matches!(
4484 parse_key_info(document.root_element())
4485 .unwrap()
4486 .sources
4487 .as_slice(),
4488 [KeyInfoSource::KeyInfoReference { uri }] if uri.is_empty()
4489 ));
4490 }
4491
4492 #[test]
4493 fn parse_key_info_rejects_excessive_child_sources() {
4494 let children = (0..=64)
4496 .map(|index| format!(r#"<extension xmlns="urn:test" index="{index}"/>"#))
4497 .collect::<String>();
4498 let xml =
4499 format!(r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">{children}</KeyInfo>"#);
4500 let document = Document::parse(&xml).unwrap();
4501
4502 assert!(matches!(
4503 parse_key_info(document.root_element()),
4504 Err(ParseError::InvalidStructure(reason))
4505 if reason == "KeyInfo contains too many child elements"
4506 ));
4507 }
4508
4509 #[test]
4510 fn parse_key_info_rejects_keyname_with_child_elements() {
4511 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4512 <KeyName>ok<foo/></KeyName>
4513 </KeyInfo>"#;
4514 let doc = Document::parse(xml).unwrap();
4515
4516 let err = parse_key_info(doc.root_element()).unwrap_err();
4517 assert!(matches!(err, ParseError::InvalidStructure(_)));
4518 }
4519
4520 #[test]
4521 fn parse_key_info_preserves_keyname_text_without_trimming() {
4522 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4523 <KeyName> signing key </KeyName>
4524 </KeyInfo>"#;
4525 let doc = Document::parse(xml).unwrap();
4526
4527 let key_info = parse_key_info(doc.root_element()).unwrap();
4528 assert_eq!(
4529 key_info.sources,
4530 vec![KeyInfoSource::KeyName(" signing key ".into())]
4531 );
4532 }
4533
4534 #[test]
4535 fn parse_key_info_rejects_oversized_keyname_text() {
4536 let oversized = "A".repeat(4097);
4537 let xml = format!(
4538 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><KeyName>{oversized}</KeyName></KeyInfo>"
4539 );
4540 let doc = Document::parse(&xml).unwrap();
4541
4542 let err = parse_key_info(doc.root_element()).unwrap_err();
4543 assert!(matches!(err, ParseError::InvalidStructure(_)));
4544 }
4545
4546 #[test]
4547 fn parse_key_info_rejects_non_whitespace_mixed_content() {
4548 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">oops<KeyName>k</KeyName></KeyInfo>"#;
4549 let doc = Document::parse(xml).unwrap();
4550
4551 let err = parse_key_info(doc.root_element()).unwrap_err();
4552 assert!(matches!(err, ParseError::InvalidStructure(_)));
4553 }
4554
4555 #[test]
4556 fn parse_key_info_rejects_nbsp_as_non_xml_whitespace_mixed_content() {
4557 let xml = "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\u{00A0}<KeyName>k</KeyName></KeyInfo>";
4558 let doc = Document::parse(xml).unwrap();
4559
4560 let err = parse_key_info(doc.root_element()).unwrap_err();
4561 assert!(matches!(err, ParseError::InvalidStructure(_)));
4562 }
4563
4564 #[test]
4565 fn parse_key_info_der_encoded_key_value_rejects_oversized_payload() {
4566 let oversized =
4567 base64::engine::general_purpose::STANDARD
4568 .encode(vec![0u8; MAX_DER_ENCODED_KEY_VALUE_LEN + 1]);
4569 let xml = format!(
4570 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\" xmlns:dsig11=\"http://www.w3.org/2009/xmldsig11#\"><dsig11:DEREncodedKeyValue>{oversized}</dsig11:DEREncodedKeyValue></KeyInfo>"
4571 );
4572 let doc = Document::parse(&xml).unwrap();
4573
4574 let err = parse_key_info(doc.root_element()).unwrap_err();
4575 assert!(matches!(err, ParseError::InvalidStructure(_)));
4576 }
4577
4578 #[test]
4579 fn parse_key_info_der_encoded_key_value_rejects_empty_payload() {
4580 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
4581 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
4582 <dsig11:DEREncodedKeyValue>
4583
4584 </dsig11:DEREncodedKeyValue>
4585 </KeyInfo>"#;
4586 let doc = Document::parse(xml).unwrap();
4587
4588 let err = parse_key_info(doc.root_element()).unwrap_err();
4589 assert!(matches!(err, ParseError::InvalidStructure(_)));
4590 }
4591
4592 #[test]
4593 fn parse_key_info_der_encoded_key_value_non_xml_ascii_whitespace_is_not_parseable_xml() {
4594 let xml = "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\" xmlns:dsig11=\"http://www.w3.org/2009/xmldsig11#\"><dsig11:DEREncodedKeyValue>\u{000C}</dsig11:DEREncodedKeyValue></KeyInfo>";
4595 assert!(Document::parse(xml).is_err());
4596 }
4597
4598 #[test]
4601 fn parse_hmac_output_length_reads_all_text_nodes() {
4602 let xml = r#"<SignatureMethod xmlns="http://www.w3.org/2000/09/xmldsig#">
4604 <HMACOutputLength>8<!-- split -->0</HMACOutputLength>
4605 </SignatureMethod>"#;
4606 let document = Document::parse(xml).unwrap();
4607
4608 assert_eq!(
4609 parse_hmac_output_length(document.root_element(), SignatureAlgorithm::HmacSha1)
4610 .unwrap(),
4611 Some(80)
4612 );
4613 }
4614
4615 #[test]
4616 fn hmac_sha256_accepts_byte_aligned_output_length() {
4617 let document = Document::parse(
4618 r#"<ds:SignatureMethod xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Algorithm="http://www.w3.org/2001/04/xmldsig-more#hmac-sha256"><ds:HMACOutputLength>128</ds:HMACOutputLength></ds:SignatureMethod>"#,
4619 )
4620 .unwrap();
4621 let algorithm =
4622 SignatureAlgorithm::from_uri("http://www.w3.org/2001/04/xmldsig-more#hmac-sha256")
4623 .expect("HMAC-SHA256 must be recognized");
4624
4625 assert_eq!(
4626 parse_hmac_output_length(document.root_element(), algorithm).unwrap(),
4627 Some(128)
4628 );
4629 }
4630
4631 #[test]
4632 fn parse_hmac_output_length_rejects_hidden_suffix_text() {
4633 let xml = r#"<SignatureMethod xmlns="http://www.w3.org/2000/09/xmldsig#">
4635 <HMACOutputLength>80<!-- split -->0</HMACOutputLength>
4636 </SignatureMethod>"#;
4637 let document = Document::parse(xml).unwrap();
4638
4639 assert!(matches!(
4640 parse_hmac_output_length(document.root_element(), SignatureAlgorithm::HmacSha1),
4641 Err(ParseError::InvalidStructure(reason))
4642 if reason == "HMACOutputLength must be a positive byte-aligned value no greater than 160"
4643 ));
4644 }
4645
4646 #[test]
4647 fn xmldsig_1_1_rejects_non_octet_hmac_output_length() {
4648 let xml = r#"<SignatureMethod xmlns="http://www.w3.org/2000/09/xmldsig#">
4651 <HMACOutputLength>129</HMACOutputLength>
4652 </SignatureMethod>"#;
4653 let document = Document::parse(xml).unwrap();
4654
4655 assert!(matches!(
4656 parse_hmac_output_length(document.root_element(), SignatureAlgorithm::HmacSha256),
4657 Err(ParseError::InvalidStructure(reason))
4658 if reason == "HMACOutputLength must be a positive byte-aligned value no greater than 256"
4659 ));
4660 }
4661
4662 #[test]
4663 fn parse_signed_info_rsa_sha256_with_reference() {
4664 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4665 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
4666 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
4667 <Reference URI="">
4668 <Transforms>
4669 <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
4670 <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
4671 </Transforms>
4672 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
4673 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
4674 </Reference>
4675 </SignedInfo>"#;
4676 let doc = Document::parse(xml).unwrap();
4677 let si = parse_signed_info(doc.root_element()).unwrap();
4678
4679 assert_eq!(si.signature_method, SignatureAlgorithm::RsaSha256);
4680 assert_eq!(si.references.len(), 1);
4681
4682 let r = &si.references[0];
4683 assert_eq!(r.uri.as_deref(), Some(""));
4684 assert_eq!(r.digest_method, DigestAlgorithm::Sha256);
4685 assert_eq!(r.digest_value, vec![0u8; 32]);
4686 assert_eq!(r.transforms.len(), 2);
4687 }
4688
4689 #[test]
4690 fn parse_signed_info_multiple_references() {
4691 let xml = r##"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4692 <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
4693 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"/>
4694 <Reference URI="#a">
4695 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
4696 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
4697 </Reference>
4698 <Reference URI="#b">
4699 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
4700 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
4701 </Reference>
4702 </SignedInfo>"##;
4703 let doc = Document::parse(xml).unwrap();
4704 let si = parse_signed_info(doc.root_element()).unwrap();
4705
4706 assert_eq!(si.signature_method, SignatureAlgorithm::EcdsaSha256);
4707 assert_eq!(si.references.len(), 2);
4708 assert_eq!(si.references[0].uri.as_deref(), Some("#a"));
4709 assert_eq!(si.references[0].digest_method, DigestAlgorithm::Sha256);
4710 assert_eq!(si.references[1].uri.as_deref(), Some("#b"));
4711 assert_eq!(si.references[1].digest_method, DigestAlgorithm::Sha1);
4712 }
4713
4714 #[test]
4715 fn parse_signed_info_rejects_too_many_references() {
4716 let references = (0..=MAX_REFERENCES_PER_SIGNATURE)
4719 .map(|index| {
4720 format!(
4721 r##"<Reference URI="#item-{index}">
4722 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
4723 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
4724 </Reference>"##
4725 )
4726 })
4727 .collect::<String>();
4728 let xml = format!(
4729 r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4730 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
4731 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
4732 {references}
4733 </SignedInfo>"#
4734 );
4735 let document = Document::parse(&xml).expect("fixed oversized fixture must parse");
4736
4737 let error = parse_signed_info(document.root_element())
4738 .expect_err("the parser must reject the 65th Reference");
4739
4740 assert!(matches!(
4741 error,
4742 ParseError::Policy(crate::policy::PolicyViolation::ResourceLimit {
4743 resource: crate::policy::resource_name::SIGNATURE_REFERENCES,
4744 maximum: MAX_REFERENCES_PER_SIGNATURE,
4745 actual: 65,
4746 })
4747 ));
4748 }
4749
4750 #[test]
4751 fn parse_signed_info_bounds_xpath_expressions_across_references() {
4752 let filters = r#"<XPath xmlns="http://www.w3.org/2002/06/xmldsig-filter2" Filter="intersect">true()</XPath>"#
4755 .repeat(64);
4756 let filter_transform = format!(
4757 r#"<Transform Algorithm="http://www.w3.org/2002/06/xmldsig-filter2">{filters}</Transform>"#
4758 );
4759 let reference = |index, transforms: &str| {
4760 format!(
4761 r##"<Reference URI="#item-{index}">
4762 <Transforms>{transforms}</Transforms>
4763 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
4764 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
4765 </Reference>"##
4766 )
4767 };
4768 let signed_info = |references: &str| {
4769 format!(
4770 r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4771 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
4772 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
4773 {references}
4774 </SignedInfo>"#
4775 )
4776 };
4777
4778 let max_reference = reference(0, &filter_transform.repeat(64));
4779 let boundary_xml = signed_info(&max_reference);
4780 let boundary_document =
4781 Document::parse(&boundary_xml).expect("fixed boundary fixture must parse");
4782 parse_signed_info(boundary_document.root_element())
4783 .expect("one maximum-shaped Reference must remain accepted");
4784
4785 let extra_transform = r#"<Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116"><XPath>true()</XPath></Transform>"#;
4786 let xml = signed_info(&format!("{max_reference}{}", reference(1, extra_transform)));
4787 let document = Document::parse(&xml).expect("fixed aggregate fixture must parse");
4788
4789 let error = parse_signed_info(document.root_element())
4790 .expect_err("signature-wide XPath expression count must be bounded");
4791
4792 assert!(matches!(
4793 error,
4794 ParseError::Transform(TransformError::Policy(
4795 crate::policy::PolicyViolation::ResourceLimit {
4796 resource: "XPath expressions",
4797 ..
4798 }
4799 ))
4800 ));
4801 }
4802
4803 #[test]
4804 fn parse_reference_without_transforms() {
4805 let xml = r##"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4807 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
4808 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
4809 <Reference URI="#obj">
4810 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
4811 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
4812 </Reference>
4813 </SignedInfo>"##;
4814 let doc = Document::parse(xml).unwrap();
4815 let si = parse_signed_info(doc.root_element()).unwrap();
4816
4817 assert!(si.references[0].transforms.is_empty());
4818 }
4819
4820 #[test]
4821 fn parse_reference_with_all_attributes() {
4822 let xml = r##"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4823 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
4824 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
4825 <Reference URI="#data" Id="ref1" Type="http://www.w3.org/2000/09/xmldsig#Object">
4826 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
4827 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
4828 </Reference>
4829 </SignedInfo>"##;
4830 let doc = Document::parse(xml).unwrap();
4831 let si = parse_signed_info(doc.root_element()).unwrap();
4832 let r = &si.references[0];
4833
4834 assert_eq!(r.uri.as_deref(), Some("#data"));
4835 assert_eq!(r.id.as_deref(), Some("ref1"));
4836 assert_eq!(
4837 r.ref_type.as_deref(),
4838 Some("http://www.w3.org/2000/09/xmldsig#Object")
4839 );
4840 }
4841
4842 #[test]
4843 fn parse_reference_absent_uri() {
4844 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4846 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
4847 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
4848 <Reference>
4849 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
4850 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
4851 </Reference>
4852 </SignedInfo>"#;
4853 let doc = Document::parse(xml).unwrap();
4854 let si = parse_signed_info(doc.root_element()).unwrap();
4855 assert!(si.references[0].uri.is_none());
4856 }
4857
4858 #[test]
4859 fn parse_signed_info_preserves_inclusive_prefixes() {
4860 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
4861 xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#">
4862 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
4863 <ec:InclusiveNamespaces PrefixList="ds saml #default"/>
4864 </CanonicalizationMethod>
4865 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
4866 <Reference URI="">
4867 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
4868 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
4869 </Reference>
4870 </SignedInfo>"#;
4871 let doc = Document::parse(xml).unwrap();
4872
4873 let si = parse_signed_info(doc.root_element()).unwrap();
4874 assert!(si.c14n_method.inclusive_prefixes().contains("ds"));
4875 assert!(si.c14n_method.inclusive_prefixes().contains("saml"));
4876 assert!(si.c14n_method.inclusive_prefixes().contains(""));
4877 }
4878
4879 #[test]
4882 fn missing_canonicalization_method() {
4883 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4884 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
4885 <Reference URI="">
4886 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
4887 <DigestValue>dGVzdA==</DigestValue>
4888 </Reference>
4889 </SignedInfo>"#;
4890 let doc = Document::parse(xml).unwrap();
4891 let result = parse_signed_info(doc.root_element());
4892 assert!(result.is_err());
4893 assert!(matches!(
4895 result.unwrap_err(),
4896 ParseError::InvalidStructure(_)
4897 ));
4898 }
4899
4900 #[test]
4901 fn missing_signature_method() {
4902 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4903 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
4904 <Reference URI="">
4905 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
4906 <DigestValue>dGVzdA==</DigestValue>
4907 </Reference>
4908 </SignedInfo>"#;
4909 let doc = Document::parse(xml).unwrap();
4910 let result = parse_signed_info(doc.root_element());
4911 assert!(result.is_err());
4912 assert!(matches!(
4914 result.unwrap_err(),
4915 ParseError::InvalidStructure(_)
4916 ));
4917 }
4918
4919 #[test]
4920 fn no_references() {
4921 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4922 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
4923 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
4924 </SignedInfo>"#;
4925 let doc = Document::parse(xml).unwrap();
4926 let result = parse_signed_info(doc.root_element());
4927 assert!(matches!(
4928 result.unwrap_err(),
4929 ParseError::MissingElement {
4930 element: "Reference"
4931 }
4932 ));
4933 }
4934
4935 #[test]
4936 fn unsupported_c14n_algorithm() {
4937 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4938 <CanonicalizationMethod Algorithm="http://example.com/bogus-c14n"/>
4939 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
4940 <Reference URI="">
4941 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
4942 <DigestValue>dGVzdA==</DigestValue>
4943 </Reference>
4944 </SignedInfo>"#;
4945 let doc = Document::parse(xml).unwrap();
4946 let result = parse_signed_info(doc.root_element());
4947 assert!(matches!(
4948 result.unwrap_err(),
4949 ParseError::UnsupportedAlgorithm { .. }
4950 ));
4951 }
4952
4953 #[test]
4954 fn unsupported_signature_algorithm() {
4955 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4956 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
4957 <SignatureMethod Algorithm="http://example.com/bogus-sign"/>
4958 <Reference URI="">
4959 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
4960 <DigestValue>dGVzdA==</DigestValue>
4961 </Reference>
4962 </SignedInfo>"#;
4963 let doc = Document::parse(xml).unwrap();
4964 let result = parse_signed_info(doc.root_element());
4965 assert!(matches!(
4966 result.unwrap_err(),
4967 ParseError::UnsupportedAlgorithm { .. }
4968 ));
4969 }
4970
4971 #[test]
4972 fn unsupported_digest_algorithm() {
4973 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4974 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
4975 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
4976 <Reference URI="">
4977 <DigestMethod Algorithm="http://example.com/bogus-digest"/>
4978 <DigestValue>dGVzdA==</DigestValue>
4979 </Reference>
4980 </SignedInfo>"#;
4981 let doc = Document::parse(xml).unwrap();
4982 let result = parse_signed_info(doc.root_element());
4983 assert!(matches!(
4984 result.unwrap_err(),
4985 ParseError::UnsupportedAlgorithm { .. }
4986 ));
4987 }
4988
4989 #[test]
4990 fn missing_digest_method() {
4991 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4992 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
4993 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
4994 <Reference URI="">
4995 <DigestValue>dGVzdA==</DigestValue>
4996 </Reference>
4997 </SignedInfo>"#;
4998 let doc = Document::parse(xml).unwrap();
4999 let result = parse_signed_info(doc.root_element());
5000 assert!(result.is_err());
5002 }
5003
5004 #[test]
5005 fn missing_digest_value() {
5006 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5007 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5008 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5009 <Reference URI="">
5010 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5011 </Reference>
5012 </SignedInfo>"#;
5013 let doc = Document::parse(xml).unwrap();
5014 let result = parse_signed_info(doc.root_element());
5015 assert!(matches!(
5016 result.unwrap_err(),
5017 ParseError::MissingElement {
5018 element: "DigestValue"
5019 }
5020 ));
5021 }
5022
5023 #[test]
5024 fn invalid_base64_digest_value() {
5025 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5026 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5027 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5028 <Reference URI="">
5029 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5030 <DigestValue>!!!not-base64!!!</DigestValue>
5031 </Reference>
5032 </SignedInfo>"#;
5033 let doc = Document::parse(xml).unwrap();
5034 let result = parse_signed_info(doc.root_element());
5035 assert!(matches!(result.unwrap_err(), ParseError::Base64(_)));
5036 }
5037
5038 #[test]
5039 fn digest_value_length_must_match_digest_method() {
5040 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5041 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5042 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5043 <Reference URI="">
5044 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5045 <DigestValue>dGVzdA==</DigestValue>
5046 </Reference>
5047 </SignedInfo>"#;
5048 let doc = Document::parse(xml).unwrap();
5049
5050 let result = parse_signed_info(doc.root_element());
5051 assert!(matches!(
5052 result.unwrap_err(),
5053 ParseError::DigestLengthMismatch {
5054 algorithm: "http://www.w3.org/2001/04/xmlenc#sha256",
5055 expected: 32,
5056 actual: 4,
5057 }
5058 ));
5059 }
5060
5061 #[test]
5062 fn inclusive_prefixes_on_inclusive_c14n_is_rejected() {
5063 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
5064 xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#">
5065 <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315">
5066 <ec:InclusiveNamespaces PrefixList="ds"/>
5067 </CanonicalizationMethod>
5068 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5069 <Reference URI="">
5070 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
5071 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
5072 </Reference>
5073 </SignedInfo>"#;
5074 let doc = Document::parse(xml).unwrap();
5075
5076 let result = parse_signed_info(doc.root_element());
5077 assert!(matches!(
5078 result.unwrap_err(),
5079 ParseError::UnsupportedAlgorithm { .. }
5080 ));
5081 }
5082
5083 #[test]
5084 fn extra_element_after_digest_value() {
5085 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5086 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5087 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5088 <Reference URI="">
5089 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5090 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
5091 <Unexpected/>
5092 </Reference>
5093 </SignedInfo>"#;
5094 let doc = Document::parse(xml).unwrap();
5095 let result = parse_signed_info(doc.root_element());
5096 assert!(matches!(
5097 result.unwrap_err(),
5098 ParseError::InvalidStructure(_)
5099 ));
5100 }
5101
5102 #[test]
5103 fn digest_value_with_element_child_is_rejected() {
5104 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5105 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5106 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5107 <Reference URI="">
5108 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
5109 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAA=<Junk/>AAAA</DigestValue>
5110 </Reference>
5111 </SignedInfo>"#;
5112 let doc = Document::parse(xml).unwrap();
5113
5114 let result = parse_signed_info(doc.root_element());
5115 assert!(matches!(
5116 result.unwrap_err(),
5117 ParseError::InvalidStructure(_)
5118 ));
5119 }
5120
5121 #[test]
5122 fn wrong_namespace_on_signed_info() {
5123 let xml = r#"<SignedInfo xmlns="http://example.com/fake">
5124 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5125 </SignedInfo>"#;
5126 let doc = Document::parse(xml).unwrap();
5127 let result = parse_signed_info(doc.root_element());
5128 assert!(matches!(
5129 result.unwrap_err(),
5130 ParseError::InvalidStructure(_)
5131 ));
5132 }
5133
5134 #[test]
5137 fn base64_with_whitespace() {
5138 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5139 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5140 <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
5141 <Reference URI="">
5142 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
5143 <DigestValue>
5144 AAAAAAAA
5145 AAAAAAAAAAAAAAAAAAA=
5146 </DigestValue>
5147 </Reference>
5148 </SignedInfo>"#;
5149 let doc = Document::parse(xml).unwrap();
5150 let si = parse_signed_info(doc.root_element()).unwrap();
5151 assert_eq!(si.references[0].digest_value, vec![0u8; 20]);
5152 }
5153
5154 #[test]
5155 fn digest_value_ignores_processing_instruction_data() {
5156 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5159 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5160 <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
5161 <Reference URI="">
5162 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
5163 <DigestValue>AAAAAAAA<?ignored not-base64?>AAAAAAAAAAAAAAAAAAA=</DigestValue>
5164 </Reference>
5165 </SignedInfo>"#;
5166 let document = Document::parse(xml).expect("fixture must parse");
5167 let signed_info =
5168 parse_signed_info(document.root_element()).expect("PI must not alter digest data");
5169
5170 assert_eq!(signed_info.references[0].digest_value, vec![0_u8; 20]);
5171 }
5172
5173 #[test]
5174 fn base64_decode_digest_accepts_xml_whitespace_chars() {
5175 let digest =
5176 base64_decode_digest("AAAA\tAAAA\rAAAA\nAAAA AAAAAAAAAAA=", DigestAlgorithm::Sha1)
5177 .expect("XML whitespace in DigestValue must be accepted");
5178 assert_eq!(digest, vec![0u8; 20]);
5179 }
5180
5181 #[test]
5182 fn base64_decode_digest_rejects_non_xml_ascii_whitespace() {
5183 let err = base64_decode_digest(
5184 "AAAA\u{000C}AAAAAAAAAAAAAAAAAAAAAAA=",
5185 DigestAlgorithm::Sha1,
5186 )
5187 .expect_err("form-feed/vertical-tab in DigestValue must be rejected");
5188 assert!(matches!(err, ParseError::Base64(_)));
5189 }
5190
5191 #[test]
5192 fn base64_decode_digest_rejects_oversized_base64_before_decode() {
5193 let err = base64_decode_digest("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", DigestAlgorithm::Sha1)
5194 .expect_err("oversized DigestValue base64 must fail before decode");
5195 match err {
5196 ParseError::Base64(message) => {
5197 assert!(
5198 message.contains("DigestValue exceeds maximum allowed base64 length"),
5199 "unexpected message: {message}"
5200 );
5201 }
5202 other => panic!("expected ParseError::Base64, got {other:?}"),
5203 }
5204 }
5205
5206 #[test]
5209 fn saml_response_signed_info() {
5210 let xml = r##"<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
5211 <ds:SignedInfo>
5212 <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5213 <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5214 <ds:Reference URI="#_resp1">
5215 <ds:Transforms>
5216 <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
5217 <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5218 </ds:Transforms>
5219 <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5220 <ds:DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</ds:DigestValue>
5221 </ds:Reference>
5222 </ds:SignedInfo>
5223 <ds:SignatureValue>ZmFrZQ==</ds:SignatureValue>
5224 </ds:Signature>"##;
5225 let doc = Document::parse(xml).unwrap();
5226
5227 let sig_node = doc.root_element();
5229 let signed_info_node = sig_node
5230 .children()
5231 .find(|n| n.is_element() && n.tag_name().name() == "SignedInfo")
5232 .unwrap();
5233
5234 let si = parse_signed_info(signed_info_node).unwrap();
5235 assert_eq!(si.signature_method, SignatureAlgorithm::RsaSha256);
5236 assert_eq!(si.references.len(), 1);
5237 assert_eq!(si.references[0].uri.as_deref(), Some("#_resp1"));
5238 assert_eq!(si.references[0].transforms.len(), 2);
5239 assert_eq!(si.references[0].digest_value, vec![0u8; 32]);
5240 }
5241}