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