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