1use roxmltree::{Document, Node};
20use x509_parser::extensions::ParsedExtension;
21use x509_parser::prelude::FromDer;
22use x509_parser::public_key::PublicKey;
23
24use super::digest::{DigestAlgorithm, compute_digest, constant_time_eq};
25use super::transforms::{self, Transform};
26use super::whitespace::{
27 XmlBase64NormalizeLimitedError, is_xml_whitespace_only, normalize_xml_base64_text,
28 normalize_xml_base64_text_with_limit,
29};
30use crate::c14n::C14nAlgorithm;
31
32pub(crate) const XMLDSIG_NS: &str = "http://www.w3.org/2000/09/xmldsig#";
34pub(crate) const XMLDSIG11_NS: &str = "http://www.w3.org/2009/xmldsig11#";
36const MAX_DER_ENCODED_KEY_VALUE_LEN: usize = 8192;
37const MAX_DER_ENCODED_KEY_VALUE_TEXT_LEN: usize = 65_536;
38const MAX_DER_ENCODED_KEY_VALUE_BASE64_LEN: usize = MAX_DER_ENCODED_KEY_VALUE_LEN.div_ceil(3) * 4;
39const MAX_KEY_NAME_TEXT_LEN: usize = 4096;
40const MAX_RSA_MODULUS_LEN: usize = 1024;
41const MAX_RSA_EXPONENT_LEN: usize = 8;
42pub(crate) const EC_P256_OID: &str = "1.2.840.10045.3.1.7";
43pub(crate) const EC_P384_OID: &str = "1.3.132.0.34";
44const MAX_EC_PUBLIC_KEY_LEN: usize = 97;
45const MAX_X509_BASE64_TEXT_LEN: usize = 262_144;
46const MAX_X509_BASE64_NORMALIZED_LEN: usize = MAX_X509_BASE64_TEXT_LEN;
47const MAX_X509_DECODED_BINARY_LEN: usize = MAX_X509_BASE64_NORMALIZED_LEN.div_ceil(4) * 3;
48const MAX_X509_SUBJECT_NAME_TEXT_LEN: usize = 16_384;
49const MAX_X509_ISSUER_NAME_TEXT_LEN: usize = 16_384;
50const MAX_X509_SERIAL_NUMBER_TEXT_LEN: usize = 4096;
51const MAX_X509_DATA_ENTRY_COUNT: usize = 64;
52const MAX_X509_DATA_TOTAL_BINARY_LEN: usize = 1_048_576;
53const MAX_X509_CHAIN_DEPTH: usize = 9;
54pub(crate) const MAX_REFERENCES_PER_SIGNATURE: usize = 64;
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
58pub enum SignatureAlgorithm {
59 RsaSha1,
61 RsaSha256,
63 RsaSha384,
65 RsaSha512,
67 EcdsaP256Sha256,
69 EcdsaP384Sha384,
76}
77
78impl SignatureAlgorithm {
79 #[must_use]
81 pub fn from_uri(uri: &str) -> Option<Self> {
82 match uri {
83 "http://www.w3.org/2000/09/xmldsig#rsa-sha1" => Some(Self::RsaSha1),
84 "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" => Some(Self::RsaSha256),
85 "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384" => Some(Self::RsaSha384),
86 "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512" => Some(Self::RsaSha512),
87 "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256" => Some(Self::EcdsaP256Sha256),
88 "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384" => Some(Self::EcdsaP384Sha384),
89 _ => None,
90 }
91 }
92
93 #[must_use]
95 pub fn uri(self) -> &'static str {
96 match self {
97 Self::RsaSha1 => "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
98 Self::RsaSha256 => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
99 Self::RsaSha384 => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384",
100 Self::RsaSha512 => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512",
101 Self::EcdsaP256Sha256 => "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256",
102 Self::EcdsaP384Sha384 => "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384",
103 }
104 }
105
106 #[must_use]
108 pub fn signing_allowed(self) -> bool {
109 !matches!(self, Self::RsaSha1)
110 }
111}
112
113#[derive(Debug)]
115pub struct SignedInfo {
116 pub c14n_method: C14nAlgorithm,
118 pub signature_method: SignatureAlgorithm,
120 pub references: Vec<Reference>,
122}
123
124#[derive(Debug)]
126pub struct Reference {
127 pub uri: Option<String>,
129 pub id: Option<String>,
131 pub ref_type: Option<String>,
133 pub transforms: Vec<Transform>,
135 pub digest_method: DigestAlgorithm,
137 pub digest_value: Vec<u8>,
139}
140
141#[derive(Debug, Default, Clone, PartialEq, Eq)]
143#[non_exhaustive]
144pub struct KeyInfo {
145 pub sources: Vec<KeyInfoSource>,
147}
148
149#[derive(Debug, Clone, PartialEq, Eq)]
151#[non_exhaustive]
152pub enum KeyInfoSource {
153 KeyName(String),
155 KeyValue(KeyValueInfo),
157 X509Data(X509DataInfo),
159 DerEncodedKeyValue(Vec<u8>),
161}
162
163#[derive(Debug, Clone, PartialEq, Eq)]
165#[non_exhaustive]
166pub enum KeyValueInfo {
167 Rsa {
169 modulus: Vec<u8>,
171 exponent: Vec<u8>,
173 },
174 Ec {
176 curve_oid: String,
178 public_key: Vec<u8>,
180 },
181 InvalidEcKeyValue,
183 Unsupported {
185 namespace: Option<String>,
187 local_name: String,
189 },
190}
191
192#[derive(Debug, Default, Clone, PartialEq, Eq)]
194#[non_exhaustive]
195pub struct X509DataInfo {
196 pub certificates: Vec<Vec<u8>>,
200 pub subject_names: Vec<String>,
202 pub issuer_serials: Vec<(String, String)>,
204 pub skis: Vec<Vec<u8>>,
206 pub crls: Vec<Vec<u8>>,
208 pub digests: Vec<(String, Vec<u8>)>,
210 pub parsed_certificates: Vec<ParsedX509Certificate>,
214 pub certificate_chain: Vec<usize>,
216}
217
218#[derive(Debug, Clone, PartialEq, Eq)]
220#[non_exhaustive]
221pub struct ParsedX509Certificate {
222 pub subject_dn: String,
224 pub issuer_dn: String,
226 pub serial_number: Vec<u8>,
228 pub serial_number_hex: String,
230 pub subject_key_identifier: Option<Vec<u8>>,
232 pub public_key: X509PublicKeyInfo,
234}
235
236#[derive(Debug, Clone, PartialEq, Eq)]
238#[non_exhaustive]
239pub enum X509PublicKeyInfo {
240 Rsa {
242 modulus: Vec<u8>,
244 exponent: Vec<u8>,
246 },
247 Ec {
249 curve_oid: String,
251 public_key: Vec<u8>,
253 },
254 Unsupported {
256 algorithm_oid: String,
258 },
259}
260
261#[derive(Debug, thiserror::Error)]
263#[non_exhaustive]
264pub enum ParseError {
265 #[error("missing required element: <{element}>")]
267 MissingElement {
268 element: &'static str,
270 },
271
272 #[error("invalid structure: {0}")]
274 InvalidStructure(String),
275
276 #[error("SignedInfo contains more than {max} Reference elements")]
278 TooManyReferences {
279 max: usize,
281 },
282
283 #[error("unsupported algorithm: {uri}")]
285 UnsupportedAlgorithm {
286 uri: String,
288 },
289
290 #[error("base64 decode error: {0}")]
292 Base64(String),
293
294 #[error(
296 "digest length mismatch for {algorithm}: expected {expected} bytes, got {actual} bytes"
297 )]
298 DigestLengthMismatch {
299 algorithm: &'static str,
301 expected: usize,
303 actual: usize,
305 },
306
307 #[error("transform error: {0}")]
309 Transform(#[from] super::types::TransformError),
310}
311
312#[must_use]
314pub fn find_signature_node<'a>(doc: &'a Document<'a>) -> Option<Node<'a, 'a>> {
315 doc.descendants().find(|n| {
316 n.is_element()
317 && n.tag_name().name() == "Signature"
318 && n.tag_name().namespace() == Some(XMLDSIG_NS)
319 })
320}
321
322pub fn parse_signed_info(signed_info_node: Node) -> Result<SignedInfo, ParseError> {
327 parse_signed_info_with_xpath_budget(
328 signed_info_node,
329 &mut transforms::XPathSignatureParseBudget::default(),
330 )
331}
332
333pub(crate) fn parse_signed_info_with_xpath_budget(
334 signed_info_node: Node,
335 xpath_budget: &mut transforms::XPathSignatureParseBudget,
336) -> Result<SignedInfo, ParseError> {
337 verify_ds_element(signed_info_node, "SignedInfo")?;
338
339 let mut children = element_children(signed_info_node);
340
341 let c14n_node = children.next().ok_or(ParseError::MissingElement {
343 element: "CanonicalizationMethod",
344 })?;
345 verify_ds_element(c14n_node, "CanonicalizationMethod")?;
346 let c14n_uri = required_algorithm_attr(c14n_node, "CanonicalizationMethod")?;
347 let mut c14n_method =
348 C14nAlgorithm::from_uri(c14n_uri).ok_or_else(|| ParseError::UnsupportedAlgorithm {
349 uri: c14n_uri.to_string(),
350 })?;
351 if let Some(prefix_list) = parse_inclusive_prefixes(c14n_node)? {
352 if c14n_method.mode() == crate::c14n::C14nMode::Exclusive1_0 {
353 c14n_method = c14n_method.with_prefix_list(&prefix_list);
354 } else {
355 return Err(ParseError::UnsupportedAlgorithm {
356 uri: c14n_uri.to_string(),
357 });
358 }
359 }
360
361 let sig_method_node = children.next().ok_or(ParseError::MissingElement {
363 element: "SignatureMethod",
364 })?;
365 verify_ds_element(sig_method_node, "SignatureMethod")?;
366 let sig_uri = required_algorithm_attr(sig_method_node, "SignatureMethod")?;
367 let signature_method =
368 SignatureAlgorithm::from_uri(sig_uri).ok_or_else(|| ParseError::UnsupportedAlgorithm {
369 uri: sig_uri.to_string(),
370 })?;
371
372 let mut references = Vec::new();
374 for child in children {
375 verify_ds_element(child, "Reference")?;
376 if references.len() == MAX_REFERENCES_PER_SIGNATURE {
377 return Err(ParseError::TooManyReferences {
378 max: MAX_REFERENCES_PER_SIGNATURE,
379 });
380 }
381 references.push(parse_reference_with_xpath_budget(child, xpath_budget)?);
382 }
383 if references.is_empty() {
384 return Err(ParseError::MissingElement {
385 element: "Reference",
386 });
387 }
388
389 Ok(SignedInfo {
390 c14n_method,
391 signature_method,
392 references,
393 })
394}
395
396pub fn parse_reference(reference_node: Node) -> Result<Reference, ParseError> {
400 parse_reference_with_xpath_budget(
401 reference_node,
402 &mut transforms::XPathSignatureParseBudget::default(),
403 )
404}
405
406pub(crate) fn parse_reference_with_xpath_budget(
407 reference_node: Node,
408 xpath_budget: &mut transforms::XPathSignatureParseBudget,
409) -> Result<Reference, ParseError> {
410 verify_ds_element(reference_node, "Reference")?;
411 ensure_no_non_whitespace_text(reference_node, "Reference")?;
412 let uri = reference_node.attribute("URI").map(String::from);
413 let id = reference_node.attribute("Id").map(String::from);
414 let ref_type = reference_node.attribute("Type").map(String::from);
415
416 let mut children = element_children(reference_node);
417
418 let mut transforms = Vec::new();
420 let mut next = children.next().ok_or(ParseError::MissingElement {
421 element: "DigestMethod",
422 })?;
423
424 if next.tag_name().name() == "Transforms" && next.tag_name().namespace() == Some(XMLDSIG_NS) {
425 transforms = transforms::parse_transforms_with_budget(next, xpath_budget)?;
426 next = children.next().ok_or(ParseError::MissingElement {
427 element: "DigestMethod",
428 })?;
429 }
430
431 verify_ds_element(next, "DigestMethod")?;
433 let digest_uri = required_algorithm_attr(next, "DigestMethod")?;
434 let digest_method =
435 DigestAlgorithm::from_uri(digest_uri).ok_or_else(|| ParseError::UnsupportedAlgorithm {
436 uri: digest_uri.to_string(),
437 })?;
438
439 let digest_value_node = children.next().ok_or(ParseError::MissingElement {
441 element: "DigestValue",
442 })?;
443 verify_ds_element(digest_value_node, "DigestValue")?;
444 let digest_value = decode_digest_value_children(digest_value_node, digest_method)?;
445
446 if let Some(unexpected) = children.next() {
448 return Err(ParseError::InvalidStructure(format!(
449 "unexpected element <{}> after <DigestValue> in <Reference>",
450 unexpected.tag_name().name()
451 )));
452 }
453
454 Ok(Reference {
455 uri,
456 id,
457 ref_type,
458 transforms,
459 digest_method,
460 digest_value,
461 })
462}
463
464pub fn parse_key_info(key_info_node: Node) -> Result<KeyInfo, ParseError> {
477 verify_ds_element(key_info_node, "KeyInfo")?;
478 ensure_no_non_whitespace_text(key_info_node, "KeyInfo")?;
479
480 let mut sources = Vec::new();
481 for child in element_children(key_info_node) {
482 match (child.tag_name().namespace(), child.tag_name().name()) {
483 (Some(XMLDSIG_NS), "KeyName") => {
484 ensure_no_element_children(child, "KeyName")?;
485 let key_name =
486 collect_text_content_bounded(child, MAX_KEY_NAME_TEXT_LEN, "KeyName")?;
487 sources.push(KeyInfoSource::KeyName(key_name));
488 }
489 (Some(XMLDSIG_NS), "KeyValue") => {
490 let key_value = parse_key_value_dispatch(child)?;
491 sources.push(KeyInfoSource::KeyValue(key_value));
492 }
493 (Some(XMLDSIG_NS), "X509Data") => {
494 let x509 = parse_x509_data_dispatch(child)?;
495 sources.push(KeyInfoSource::X509Data(x509));
496 }
497 (Some(XMLDSIG11_NS), "DEREncodedKeyValue") => {
498 ensure_no_element_children(child, "DEREncodedKeyValue")?;
499 let der = decode_der_encoded_key_value_base64(child)?;
500 sources.push(KeyInfoSource::DerEncodedKeyValue(der));
501 }
502 _ => {}
503 }
504 }
505
506 Ok(KeyInfo { sources })
507}
508
509fn element_children<'a>(node: Node<'a, 'a>) -> impl Iterator<Item = Node<'a, 'a>> {
513 node.children().filter(|n| n.is_element())
514}
515
516fn verify_ds_element(node: Node, expected_name: &'static str) -> Result<(), ParseError> {
518 if !node.is_element() {
519 return Err(ParseError::InvalidStructure(format!(
520 "expected element <{expected_name}>, got non-element node"
521 )));
522 }
523 let tag = node.tag_name();
524 if tag.name() != expected_name || tag.namespace() != Some(XMLDSIG_NS) {
525 return Err(ParseError::InvalidStructure(format!(
526 "expected <ds:{expected_name}>, got <{}{}>",
527 tag.namespace()
528 .map(|ns| format!("{{{ns}}}"))
529 .unwrap_or_default(),
530 tag.name()
531 )));
532 }
533 Ok(())
534}
535
536fn verify_dsig11_element(node: Node, expected_name: &'static str) -> Result<(), ParseError> {
538 if !node.is_element() {
539 return Err(ParseError::InvalidStructure(format!(
540 "expected element <{expected_name}>, got non-element node"
541 )));
542 }
543 let tag = node.tag_name();
544 if tag.name() != expected_name || tag.namespace() != Some(XMLDSIG11_NS) {
545 return Err(ParseError::InvalidStructure(format!(
546 "expected <dsig11:{expected_name}>, got <{}{}>",
547 tag.namespace()
548 .map(|ns| format!("{{{ns}}}"))
549 .unwrap_or_default(),
550 tag.name()
551 )));
552 }
553 Ok(())
554}
555
556fn required_algorithm_attr<'a>(
558 node: Node<'a, 'a>,
559 element_name: &'static str,
560) -> Result<&'a str, ParseError> {
561 node.attribute("Algorithm").ok_or_else(|| {
562 ParseError::InvalidStructure(format!("missing Algorithm attribute on <{element_name}>"))
563 })
564}
565
566fn parse_inclusive_prefixes(node: Node) -> Result<Option<String>, ParseError> {
572 const EXCLUSIVE_C14N_NS_URI: &str = "http://www.w3.org/2001/10/xml-exc-c14n#";
573
574 for child in node.children() {
575 if child.is_element() {
576 let tag = child.tag_name();
577 if tag.name() == "InclusiveNamespaces" && tag.namespace() == Some(EXCLUSIVE_C14N_NS_URI)
578 {
579 return child
580 .attribute("PrefixList")
581 .map(str::to_string)
582 .ok_or_else(|| {
583 ParseError::InvalidStructure(
584 "missing PrefixList attribute on <InclusiveNamespaces>".into(),
585 )
586 })
587 .map(Some);
588 }
589 }
590 }
591
592 Ok(None)
593}
594
595fn parse_key_value_dispatch(node: Node) -> Result<KeyValueInfo, ParseError> {
596 verify_ds_element(node, "KeyValue")?;
597 ensure_no_non_whitespace_text(node, "KeyValue")?;
598
599 let mut children = element_children(node);
600 let Some(first_child) = children.next() else {
601 return Err(ParseError::InvalidStructure(
602 "KeyValue must contain exactly one key-value child".into(),
603 ));
604 };
605 if children.next().is_some() {
606 return Err(ParseError::InvalidStructure(
607 "KeyValue must contain exactly one key-value child".into(),
608 ));
609 }
610
611 match (
612 first_child.tag_name().namespace(),
613 first_child.tag_name().name(),
614 ) {
615 (Some(XMLDSIG_NS), "RSAKeyValue") => parse_rsa_key_value(first_child),
616 (Some(XMLDSIG11_NS), "ECKeyValue") => parse_ec_key_value(first_child),
617 (namespace, child_name) => Ok(KeyValueInfo::Unsupported {
618 namespace: namespace.map(str::to_string),
619 local_name: child_name.to_string(),
620 }),
621 }
622}
623
624fn parse_ec_key_value(node: Node<'_, '_>) -> Result<KeyValueInfo, ParseError> {
625 verify_dsig11_element(node, "ECKeyValue")?;
626 ensure_no_non_whitespace_text(node, "ECKeyValue")?;
627
628 let mut children = element_children(node);
629 let Some(named_curve_node) = children.next() else {
630 return Ok(KeyValueInfo::InvalidEcKeyValue);
631 };
632 if named_curve_node.tag_name().namespace() == Some(XMLDSIG11_NS)
633 && named_curve_node.tag_name().name() == "ECParameters"
634 {
635 return Ok(KeyValueInfo::Unsupported {
636 namespace: Some(XMLDSIG11_NS.to_string()),
637 local_name: "ECKeyValue".into(),
638 });
639 }
640 if named_curve_node.tag_name().namespace() != Some(XMLDSIG11_NS)
641 || named_curve_node.tag_name().name() != "NamedCurve"
642 {
643 return Ok(KeyValueInfo::InvalidEcKeyValue);
644 }
645 ensure_no_element_children(named_curve_node, "NamedCurve")?;
646 ensure_no_non_whitespace_text(named_curve_node, "NamedCurve")?;
647 let Some((curve_oid, expected_public_key_len)) =
648 (match parse_ec_named_curve_oid(named_curve_node) {
649 Ok(curve) => curve,
650 Err(_) => return Ok(KeyValueInfo::InvalidEcKeyValue),
651 })
652 else {
653 return Ok(KeyValueInfo::Unsupported {
654 namespace: Some(XMLDSIG11_NS.to_string()),
655 local_name: "ECKeyValue".into(),
656 });
657 };
658
659 let Some(public_key_node) = children.next() else {
660 return Ok(KeyValueInfo::InvalidEcKeyValue);
661 };
662 if public_key_node.tag_name().namespace() != Some(XMLDSIG11_NS)
663 || public_key_node.tag_name().name() != "PublicKey"
664 {
665 return Ok(KeyValueInfo::InvalidEcKeyValue);
666 }
667 ensure_no_element_children(public_key_node, "PublicKey")?;
668 if children.next().is_some() {
669 return Ok(KeyValueInfo::InvalidEcKeyValue);
670 }
671
672 let public_key = match decode_crypto_binary(public_key_node, "PublicKey", MAX_EC_PUBLIC_KEY_LEN)
673 {
674 Ok(public_key) => public_key,
675 Err(_) => return Ok(KeyValueInfo::InvalidEcKeyValue),
676 };
677 if validate_ec_public_key_point(&public_key, expected_public_key_len).is_err() {
678 return Ok(KeyValueInfo::InvalidEcKeyValue);
679 }
680
681 Ok(KeyValueInfo::Ec {
682 curve_oid,
683 public_key,
684 })
685}
686
687fn parse_ec_named_curve_oid(node: Node<'_, '_>) -> Result<Option<(String, usize)>, ParseError> {
688 let uri = node.attribute("URI").ok_or_else(|| {
689 ParseError::InvalidStructure("ECKeyValue NamedCurve must include URI attribute".into())
690 })?;
691 let curve_oid = uri.strip_prefix("urn:oid:").unwrap_or(uri);
692 if curve_oid.is_empty() {
693 return Err(ParseError::InvalidStructure(
694 "ECKeyValue NamedCurve URI must not be empty".into(),
695 ));
696 }
697 let Some(public_key_len) = ec_public_key_len(curve_oid) else {
698 return Ok(None);
699 };
700 Ok(Some((curve_oid.to_string(), public_key_len)))
701}
702
703fn ec_public_key_len(curve_oid: &str) -> Option<usize> {
704 match curve_oid {
705 EC_P256_OID => Some(65),
706 EC_P384_OID => Some(97),
707 _ => None,
708 }
709}
710
711fn validate_ec_public_key_point(public_key: &[u8], expected_len: usize) -> Result<(), ParseError> {
712 if public_key.len() != expected_len {
713 return Err(ParseError::InvalidStructure(
714 "ECKeyValue PublicKey length does not match NamedCurve".into(),
715 ));
716 }
717 if public_key.first().copied() != Some(0x04) {
718 return Err(ParseError::InvalidStructure(
719 "ECKeyValue PublicKey must be an uncompressed SEC1 point".into(),
720 ));
721 }
722 Ok(())
723}
724
725fn parse_rsa_key_value(node: Node<'_, '_>) -> Result<KeyValueInfo, ParseError> {
726 verify_ds_element(node, "RSAKeyValue")?;
727 ensure_no_non_whitespace_text(node, "RSAKeyValue")?;
728
729 let mut children = element_children(node);
730 let modulus_node = children.next().ok_or_else(|| {
731 ParseError::InvalidStructure("RSAKeyValue requires Modulus and Exponent".into())
732 })?;
733 verify_ds_element(modulus_node, "Modulus")?;
734 ensure_no_element_children(modulus_node, "Modulus")?;
735
736 let exponent_node = children.next().ok_or_else(|| {
737 ParseError::InvalidStructure("RSAKeyValue requires Modulus and Exponent".into())
738 })?;
739 verify_ds_element(exponent_node, "Exponent")?;
740 ensure_no_element_children(exponent_node, "Exponent")?;
741 if children.next().is_some() {
742 return Err(ParseError::InvalidStructure(
743 "RSAKeyValue must contain exactly Modulus followed by Exponent".into(),
744 ));
745 }
746
747 Ok(KeyValueInfo::Rsa {
748 modulus: decode_crypto_binary(modulus_node, "Modulus", MAX_RSA_MODULUS_LEN)?,
749 exponent: decode_crypto_binary(exponent_node, "Exponent", MAX_RSA_EXPONENT_LEN)?,
750 })
751}
752
753fn decode_crypto_binary(
754 node: Node<'_, '_>,
755 element_name: &'static str,
756 max_decoded_len: usize,
757) -> Result<Vec<u8>, ParseError> {
758 use base64::Engine;
759 use base64::engine::general_purpose::STANDARD;
760
761 let max_base64_len = max_decoded_len.div_ceil(3) * 4;
762 let mut cleaned = String::with_capacity(max_base64_len);
763 for text in node.children().filter_map(|child| child.text()) {
764 normalize_xml_base64_text_with_limit(text, &mut cleaned, max_base64_len).map_err(
765 |err| match err {
766 XmlBase64NormalizeLimitedError::InvalidWhitespace(err) => {
767 ParseError::Base64(format!(
768 "invalid XML whitespace U+{:04X} in {element_name}",
769 err.invalid_byte
770 ))
771 }
772 XmlBase64NormalizeLimitedError::TooLong(_) => ParseError::InvalidStructure(
773 format!("{element_name} exceeds maximum allowed base64 length"),
774 ),
775 },
776 )?;
777 }
778
779 let value = STANDARD
780 .decode(&cleaned)
781 .map_err(|err| ParseError::Base64(format!("{element_name}: {err}")))?;
782 if value.is_empty() {
783 return Err(ParseError::InvalidStructure(format!(
784 "{element_name} must not be empty"
785 )));
786 }
787 if value.len() > max_decoded_len {
788 return Err(ParseError::InvalidStructure(format!(
789 "{element_name} exceeds maximum allowed binary length"
790 )));
791 }
792 Ok(value)
793}
794
795fn parse_x509_data_dispatch(node: Node) -> Result<X509DataInfo, ParseError> {
796 verify_ds_element(node, "X509Data")?;
797 ensure_no_non_whitespace_text(node, "X509Data")?;
798
799 let mut info = X509DataInfo::default();
800 let mut total_binary_len = 0usize;
801 for child in element_children(node) {
802 match (child.tag_name().namespace(), child.tag_name().name()) {
803 (Some(XMLDSIG_NS), "X509Certificate") => {
804 ensure_no_element_children(child, "X509Certificate")?;
805 ensure_x509_data_entry_budget(&info)?;
806 let cert = decode_x509_base64(child, "X509Certificate")?;
807 add_x509_data_usage(&mut total_binary_len, cert.len())?;
808 let parsed_cert = parse_x509_certificate(cert.as_slice())?;
809 info.parsed_certificates.push(parsed_cert);
810 info.certificates.push(cert);
811 }
812 (Some(XMLDSIG_NS), "X509SubjectName") => {
813 ensure_no_element_children(child, "X509SubjectName")?;
814 ensure_x509_data_entry_budget(&info)?;
815 let subject_name = collect_text_content_bounded(
816 child,
817 MAX_X509_SUBJECT_NAME_TEXT_LEN,
818 "X509SubjectName",
819 )?;
820 info.subject_names.push(subject_name);
821 }
822 (Some(XMLDSIG_NS), "X509IssuerSerial") => {
823 ensure_x509_data_entry_budget(&info)?;
824 let issuer_serial = parse_x509_issuer_serial(child)?;
825 info.issuer_serials.push(issuer_serial);
826 }
827 (Some(XMLDSIG_NS), "X509SKI") => {
828 ensure_no_element_children(child, "X509SKI")?;
829 ensure_x509_data_entry_budget(&info)?;
830 let ski = decode_x509_base64(child, "X509SKI")?;
831 add_x509_data_usage(&mut total_binary_len, ski.len())?;
832 info.skis.push(ski);
833 }
834 (Some(XMLDSIG_NS), "X509CRL") => {
835 ensure_no_element_children(child, "X509CRL")?;
836 ensure_x509_data_entry_budget(&info)?;
837 let crl = decode_x509_base64(child, "X509CRL")?;
838 add_x509_data_usage(&mut total_binary_len, crl.len())?;
839 info.crls.push(crl);
840 }
841 (Some(XMLDSIG11_NS), "X509Digest") => {
842 ensure_no_element_children(child, "X509Digest")?;
843 ensure_x509_data_entry_budget(&info)?;
844 let algorithm = required_algorithm_attr(child, "X509Digest")?;
845 let digest = decode_x509_base64(child, "X509Digest")?;
846 add_x509_data_usage(&mut total_binary_len, digest.len())?;
847 info.digests.push((algorithm.to_string(), digest));
848 }
849 (Some(XMLDSIG_NS), child_name) | (Some(XMLDSIG11_NS), child_name) => {
850 return Err(ParseError::InvalidStructure(format!(
851 "X509Data contains unsupported XMLDSig child element <{child_name}>"
852 )));
853 }
854 _ => {}
855 }
856 }
857
858 info.certificate_chain = build_x509_certificate_chain(&info)?;
859 Ok(info)
860}
861
862fn build_x509_certificate_chain(info: &X509DataInfo) -> Result<Vec<usize>, ParseError> {
863 if info.parsed_certificates.is_empty() {
864 return Ok(Vec::new());
865 }
866
867 let signing_idx = select_x509_signing_certificate(info)?;
868 let mut chain = vec![signing_idx];
869
870 loop {
871 if chain.len() > MAX_X509_CHAIN_DEPTH {
872 return Err(ParseError::InvalidStructure(
873 "X509Data certificate chain exceeds maximum depth".into(),
874 ));
875 }
876
877 let current_idx = *chain
878 .last()
879 .expect("chain starts with signing certificate index");
880 let current = &info.parsed_certificates[current_idx];
881 if current.subject_dn == current.issuer_dn {
882 break;
883 }
884
885 let candidates = info
886 .parsed_certificates
887 .iter()
888 .enumerate()
889 .filter(|(idx, cert)| *idx != current_idx && cert.subject_dn == current.issuer_dn)
890 .map(|(idx, _)| idx)
891 .collect::<Vec<_>>();
892
893 match candidates.as_slice() {
894 [] => break,
895 [issuer_idx] => {
896 if chain.contains(issuer_idx) {
897 return Err(ParseError::InvalidStructure(
898 "X509Data certificate chain contains a cycle".into(),
899 ));
900 }
901 if chain.len() == MAX_X509_CHAIN_DEPTH {
902 return Err(ParseError::InvalidStructure(
903 "X509Data certificate chain exceeds maximum depth".into(),
904 ));
905 }
906 chain.push(*issuer_idx);
907 }
908 _ => {
909 return Err(ParseError::InvalidStructure(
910 "X509Data certificate chain contains ambiguous issuer certificates".into(),
911 ));
912 }
913 }
914 }
915
916 Ok(chain)
917}
918
919fn select_x509_signing_certificate(info: &X509DataInfo) -> Result<usize, ParseError> {
920 let has_lookup_identifiers = x509_data_has_lookup_identifiers(info);
921 let mut candidates = Vec::new();
922 if has_lookup_identifiers {
923 for (idx, (parsed, der)) in info
924 .parsed_certificates
925 .iter()
926 .zip(&info.certificates)
927 .enumerate()
928 {
929 if x509_certificate_matches_any_selector(info, parsed, der)? {
930 candidates.push(idx);
931 }
932 }
933 if !x509_selector_categories_match_chain(info)? {
934 return Err(ParseError::InvalidStructure(
935 "X509Data lookup identifiers do not match the embedded certificate chain".into(),
936 ));
937 }
938 }
939
940 match candidates.as_slice() {
941 [idx] => return Ok(*idx),
942 [] if has_lookup_identifiers => {
943 return Err(ParseError::InvalidStructure(
944 "X509Data lookup identifiers do not match any embedded certificate".into(),
945 ));
946 }
947 [] => {}
948 _ => {}
949 }
950
951 let leaf_candidates = info
952 .parsed_certificates
953 .iter()
954 .enumerate()
955 .filter(|(_, cert)| {
956 cert.subject_dn != cert.issuer_dn
957 && !info
958 .parsed_certificates
959 .iter()
960 .any(|other| other.issuer_dn == cert.subject_dn)
961 })
962 .map(|(idx, _)| idx)
963 .collect::<Vec<_>>();
964
965 let selected_leaves = leaf_candidates
966 .iter()
967 .filter(|idx| !has_lookup_identifiers || candidates.contains(idx))
968 .copied()
969 .collect::<Vec<_>>();
970
971 match selected_leaves.as_slice() {
972 [idx] => Ok(*idx),
973 [] if !has_lookup_identifiers => Ok(0),
974 [] => Err(ParseError::InvalidStructure(
975 "X509Data lookup identifiers match multiple certificates without a unique signing certificate"
976 .into(),
977 )),
978 _ => Err(ParseError::InvalidStructure(
979 if has_lookup_identifiers {
980 "X509Data lookup identifiers match multiple certificates"
981 } else {
982 "X509Data contains multiple possible signing certificates"
983 }
984 .into(),
985 )),
986 }
987}
988
989pub(crate) fn x509_data_has_lookup_identifiers(info: &X509DataInfo) -> bool {
990 !info.subject_names.is_empty()
991 || !info.issuer_serials.is_empty()
992 || !info.skis.is_empty()
993 || !info.digests.is_empty()
994}
995
996pub(crate) fn x509_certificate_matches_any_selector(
997 info: &X509DataInfo,
998 certificate: &ParsedX509Certificate,
999 certificate_der: &[u8],
1000) -> Result<bool, ParseError> {
1001 let subject_match = info
1002 .subject_names
1003 .iter()
1004 .any(|subject| subject.trim() == certificate.subject_dn);
1005 let mut issuer_serial_match = false;
1006 for (issuer, serial) in &info.issuer_serials {
1007 let serial_hex = x509_serial_decimal_to_hex(serial).ok_or_else(|| {
1008 ParseError::InvalidStructure(
1009 "X509Data lookup identifiers contain an invalid serial number".into(),
1010 )
1011 })?;
1012 issuer_serial_match |=
1013 issuer.trim() == certificate.issuer_dn && serial_hex == certificate.serial_number_hex;
1014 }
1015 let ski_match = certificate
1016 .subject_key_identifier
1017 .as_ref()
1018 .is_some_and(|certificate_ski| info.skis.iter().any(|ski| ski == certificate_ski));
1019 let mut digest_match = false;
1020 for (algorithm_uri, expected) in &info.digests {
1021 let algorithm = DigestAlgorithm::from_uri(algorithm_uri).ok_or_else(|| {
1022 ParseError::UnsupportedAlgorithm {
1023 uri: algorithm_uri.clone(),
1024 }
1025 })?;
1026 digest_match |= constant_time_eq(&compute_digest(algorithm, certificate_der), expected);
1027 }
1028 Ok(subject_match || issuer_serial_match || ski_match || digest_match)
1029}
1030
1031pub(crate) fn x509_selector_categories_match_chain(
1032 info: &X509DataInfo,
1033) -> Result<bool, ParseError> {
1034 let subject_match = info.subject_names.iter().all(|subject| {
1035 info.parsed_certificates
1036 .iter()
1037 .any(|certificate| subject.trim() == certificate.subject_dn)
1038 });
1039
1040 let mut issuer_serial_match = true;
1041 for (issuer, serial) in &info.issuer_serials {
1042 let serial_hex = x509_serial_decimal_to_hex(serial).ok_or_else(|| {
1043 ParseError::InvalidStructure(
1044 "X509Data lookup identifiers contain an invalid serial number".into(),
1045 )
1046 })?;
1047 issuer_serial_match &= info.parsed_certificates.iter().any(|certificate| {
1048 issuer.trim() == certificate.issuer_dn && serial_hex == certificate.serial_number_hex
1049 });
1050 }
1051
1052 let ski_match = info.skis.iter().all(|ski| {
1053 info.parsed_certificates.iter().any(|certificate| {
1054 certificate
1055 .subject_key_identifier
1056 .as_ref()
1057 .is_some_and(|certificate_ski| ski == certificate_ski)
1058 })
1059 });
1060
1061 let mut digest_match = true;
1062 for (algorithm_uri, expected) in &info.digests {
1063 let algorithm = DigestAlgorithm::from_uri(algorithm_uri).ok_or_else(|| {
1064 ParseError::UnsupportedAlgorithm {
1065 uri: algorithm_uri.clone(),
1066 }
1067 })?;
1068 digest_match &= info
1069 .certificates
1070 .iter()
1071 .any(|certificate| constant_time_eq(&compute_digest(algorithm, certificate), expected));
1072 }
1073
1074 Ok(subject_match && issuer_serial_match && ski_match && digest_match)
1075}
1076
1077fn ensure_x509_data_entry_budget(info: &X509DataInfo) -> Result<(), ParseError> {
1078 let total_entries = info.certificates.len()
1079 + info.subject_names.len()
1080 + info.issuer_serials.len()
1081 + info.skis.len()
1082 + info.crls.len()
1083 + info.digests.len();
1084 if total_entries >= MAX_X509_DATA_ENTRY_COUNT {
1085 return Err(ParseError::InvalidStructure(
1086 "X509Data contains too many entries".into(),
1087 ));
1088 }
1089 Ok(())
1090}
1091
1092fn add_x509_data_usage(total_binary_len: &mut usize, delta: usize) -> Result<(), ParseError> {
1093 *total_binary_len = total_binary_len.checked_add(delta).ok_or_else(|| {
1094 ParseError::InvalidStructure("X509Data exceeds maximum allowed total binary length".into())
1095 })?;
1096 if *total_binary_len > MAX_X509_DATA_TOTAL_BINARY_LEN {
1097 return Err(ParseError::InvalidStructure(
1098 "X509Data exceeds maximum allowed total binary length".into(),
1099 ));
1100 }
1101 Ok(())
1102}
1103
1104fn decode_x509_base64(
1105 node: Node<'_, '_>,
1106 element_name: &'static str,
1107) -> Result<Vec<u8>, ParseError> {
1108 use base64::Engine;
1109 use base64::engine::general_purpose::STANDARD;
1110
1111 let mut cleaned = String::new();
1112 let mut raw_text_len = 0usize;
1113 for text in node
1114 .children()
1115 .filter(|child| child.is_text())
1116 .filter_map(|child| child.text())
1117 {
1118 if raw_text_len.saturating_add(text.len()) > MAX_X509_BASE64_TEXT_LEN {
1119 return Err(ParseError::InvalidStructure(format!(
1120 "{element_name} exceeds maximum allowed text length"
1121 )));
1122 }
1123 raw_text_len = raw_text_len.saturating_add(text.len());
1124 normalize_xml_base64_text(text, &mut cleaned).map_err(|err| {
1125 ParseError::Base64(format!(
1126 "invalid XML whitespace U+{:04X} in {element_name}",
1127 err.invalid_byte
1128 ))
1129 })?;
1130 if cleaned.len() > MAX_X509_BASE64_NORMALIZED_LEN {
1131 return Err(ParseError::InvalidStructure(format!(
1132 "{element_name} exceeds maximum allowed base64 length"
1133 )));
1134 }
1135 }
1136
1137 let decoded = STANDARD
1138 .decode(&cleaned)
1139 .map_err(|e| ParseError::Base64(format!("{element_name}: {e}")))?;
1140 if decoded.is_empty() {
1141 return Err(ParseError::InvalidStructure(format!(
1142 "{element_name} must not be empty"
1143 )));
1144 }
1145 if decoded.len() > MAX_X509_DECODED_BINARY_LEN {
1146 return Err(ParseError::InvalidStructure(format!(
1147 "{element_name} exceeds maximum allowed binary length"
1148 )));
1149 }
1150 Ok(decoded)
1151}
1152
1153pub(crate) fn parse_x509_certificate(cert_der: &[u8]) -> Result<ParsedX509Certificate, ParseError> {
1154 let (rest, cert) =
1155 x509_parser::certificate::X509Certificate::from_der(cert_der).map_err(|err| {
1156 ParseError::InvalidStructure(format!("X509Certificate is not valid DER X.509: {err}"))
1157 })?;
1158 if !rest.is_empty() {
1159 return Err(ParseError::InvalidStructure(
1160 "X509Certificate contains trailing bytes after DER certificate".into(),
1161 ));
1162 }
1163
1164 let subject_dn = cert.subject().to_string();
1165 let issuer_dn = cert.issuer().to_string();
1166 let serial_number = cert.tbs_certificate.raw_serial().to_vec();
1167 let serial_number_hex = format_x509_serial_value_hex(&serial_number);
1168
1169 let subject_key_identifier = cert.extensions().iter().find_map(|ext| {
1170 if let ParsedExtension::SubjectKeyIdentifier(ski) = ext.parsed_extension() {
1171 Some(ski.0.to_vec())
1172 } else {
1173 None
1174 }
1175 });
1176
1177 let spki = cert.public_key();
1178 let public_key = match spki.parsed().map_err(|err| {
1179 ParseError::InvalidStructure(format!("X509Certificate public key parse error: {err}"))
1180 })? {
1181 PublicKey::RSA(rsa) => {
1182 let modulus = trim_leading_zeroes(rsa.modulus);
1183 let exponent = trim_leading_zeroes(rsa.exponent);
1184 if modulus.is_empty() || exponent.is_empty() {
1185 return Err(ParseError::InvalidStructure(
1186 "X509Certificate RSA key contains empty modulus or exponent".into(),
1187 ));
1188 }
1189 X509PublicKeyInfo::Rsa { modulus, exponent }
1190 }
1191 PublicKey::EC(ec_point) => {
1192 let Some(params) = spki.algorithm.parameters.as_ref() else {
1193 return Err(ParseError::InvalidStructure(
1194 "X509Certificate EC key is missing curve parameters".into(),
1195 ));
1196 };
1197
1198 match params.as_oid() {
1199 Ok(oid) => X509PublicKeyInfo::Ec {
1200 curve_oid: oid.to_id_string(),
1201 public_key: ec_point.data().to_vec(),
1202 },
1203 Err(_) => X509PublicKeyInfo::Unsupported {
1204 algorithm_oid: spki.algorithm.algorithm.to_id_string(),
1205 },
1206 }
1207 }
1208 _ => X509PublicKeyInfo::Unsupported {
1209 algorithm_oid: spki.algorithm.algorithm.to_id_string(),
1210 },
1211 };
1212
1213 Ok(ParsedX509Certificate {
1214 subject_dn,
1215 issuer_dn,
1216 serial_number,
1217 serial_number_hex,
1218 subject_key_identifier,
1219 public_key,
1220 })
1221}
1222
1223fn format_x509_serial_hex(serial: &[u8]) -> String {
1224 serial
1225 .iter()
1226 .map(|byte| format!("{byte:02X}"))
1227 .collect::<String>()
1228}
1229
1230fn format_x509_serial_value_hex(serial: &[u8]) -> String {
1231 let first_non_zero = serial
1232 .iter()
1233 .position(|byte| *byte != 0)
1234 .unwrap_or(serial.len());
1235 let canonical = if first_non_zero == serial.len() {
1236 &[0]
1237 } else {
1238 &serial[first_non_zero..]
1239 };
1240 format_x509_serial_hex(canonical)
1241}
1242
1243fn x509_serial_decimal_to_hex(serial: &str) -> Option<String> {
1244 let serial = serial.trim();
1245 let serial = serial.strip_prefix('+').unwrap_or(serial);
1246 if serial.is_empty() || !serial.bytes().all(|byte| byte.is_ascii_digit()) {
1247 return None;
1248 }
1249
1250 let mut bytes = Vec::<u8>::new();
1251 for digit in serial.bytes().map(|byte| byte - b'0') {
1252 let mut carry = u16::from(digit);
1253 for byte in bytes.iter_mut().rev() {
1254 let value = u16::from(*byte) * 10 + carry;
1255 *byte = value as u8;
1256 carry = value >> 8;
1257 }
1258 while carry > 0 {
1259 bytes.insert(0, carry as u8);
1260 carry >>= 8;
1261 }
1262 }
1263
1264 Some(format_x509_serial_value_hex(&bytes))
1265}
1266
1267fn trim_leading_zeroes(bytes: &[u8]) -> Vec<u8> {
1268 let first_non_zero = bytes
1269 .iter()
1270 .position(|byte| *byte != 0)
1271 .unwrap_or(bytes.len());
1272 bytes[first_non_zero..].to_vec()
1273}
1274
1275fn parse_x509_issuer_serial(node: Node<'_, '_>) -> Result<(String, String), ParseError> {
1276 verify_ds_element(node, "X509IssuerSerial")?;
1277 ensure_no_non_whitespace_text(node, "X509IssuerSerial")?;
1278
1279 let children = element_children(node).collect::<Vec<_>>();
1280 if children.len() != 2 {
1281 return Err(ParseError::InvalidStructure(
1282 "X509IssuerSerial must contain exactly X509IssuerName then X509SerialNumber".into(),
1283 ));
1284 }
1285 if !matches!(
1286 (
1287 children[0].tag_name().namespace(),
1288 children[0].tag_name().name()
1289 ),
1290 (Some(XMLDSIG_NS), "X509IssuerName")
1291 ) {
1292 return Err(ParseError::InvalidStructure(
1293 "X509IssuerSerial must contain X509IssuerName as the first child element".into(),
1294 ));
1295 }
1296 if !matches!(
1297 (
1298 children[1].tag_name().namespace(),
1299 children[1].tag_name().name()
1300 ),
1301 (Some(XMLDSIG_NS), "X509SerialNumber")
1302 ) {
1303 return Err(ParseError::InvalidStructure(
1304 "X509IssuerSerial must contain X509SerialNumber as the second child element".into(),
1305 ));
1306 }
1307
1308 let issuer_node = children[0];
1309 ensure_no_element_children(issuer_node, "X509IssuerName")?;
1310 let issuer_name =
1311 collect_text_content_bounded(issuer_node, MAX_X509_ISSUER_NAME_TEXT_LEN, "X509IssuerName")?;
1312
1313 let serial_node = children[1];
1314 ensure_no_element_children(serial_node, "X509SerialNumber")?;
1315 let serial_number = collect_text_content_bounded(
1316 serial_node,
1317 MAX_X509_SERIAL_NUMBER_TEXT_LEN,
1318 "X509SerialNumber",
1319 )?;
1320 if issuer_name.trim().is_empty() || serial_number.trim().is_empty() {
1321 return Err(ParseError::InvalidStructure(
1322 "X509IssuerSerial requires non-empty X509IssuerName and X509SerialNumber".into(),
1323 ));
1324 }
1325
1326 Ok((issuer_name, serial_number))
1327}
1328
1329fn base64_decode_digest(b64: &str, digest_method: DigestAlgorithm) -> Result<Vec<u8>, ParseError> {
1333 use base64::Engine;
1334 use base64::engine::general_purpose::STANDARD;
1335
1336 let expected = digest_method.output_len();
1337 let max_base64_len = expected.div_ceil(3) * 4;
1338 let mut cleaned = String::with_capacity(b64.len().min(max_base64_len));
1339 normalize_xml_base64_text(b64, &mut cleaned).map_err(|err| {
1340 ParseError::Base64(format!(
1341 "invalid XML whitespace U+{:04X} in DigestValue",
1342 err.invalid_byte
1343 ))
1344 })?;
1345 if cleaned.len() > max_base64_len {
1346 return Err(ParseError::Base64(
1347 "DigestValue exceeds maximum allowed base64 length".into(),
1348 ));
1349 }
1350 let digest = STANDARD
1351 .decode(&cleaned)
1352 .map_err(|e| ParseError::Base64(e.to_string()))?;
1353 let actual = digest.len();
1354 if actual != expected {
1355 return Err(ParseError::DigestLengthMismatch {
1356 algorithm: digest_method.uri(),
1357 expected,
1358 actual,
1359 });
1360 }
1361 Ok(digest)
1362}
1363
1364fn decode_digest_value_children(
1365 digest_value_node: Node<'_, '_>,
1366 digest_method: DigestAlgorithm,
1367) -> Result<Vec<u8>, ParseError> {
1368 let max_base64_len = digest_method.output_len().div_ceil(3) * 4;
1369 let mut cleaned = String::with_capacity(max_base64_len);
1370
1371 for child in digest_value_node.children() {
1372 if child.is_element() {
1373 return Err(ParseError::InvalidStructure(
1374 "DigestValue must not contain element children".into(),
1375 ));
1376 }
1377 if let Some(text) = child.text() {
1378 normalize_xml_base64_text(text, &mut cleaned).map_err(|err| {
1379 ParseError::Base64(format!(
1380 "invalid XML whitespace U+{:04X} in DigestValue",
1381 err.invalid_byte
1382 ))
1383 })?;
1384 if cleaned.len() > max_base64_len {
1385 return Err(ParseError::Base64(
1386 "DigestValue exceeds maximum allowed base64 length".into(),
1387 ));
1388 }
1389 }
1390 }
1391
1392 base64_decode_digest(&cleaned, digest_method)
1393}
1394
1395fn decode_der_encoded_key_value_base64(node: Node<'_, '_>) -> Result<Vec<u8>, ParseError> {
1396 use base64::Engine;
1397 use base64::engine::general_purpose::STANDARD;
1398
1399 let mut cleaned = String::new();
1400 let mut raw_text_len = 0usize;
1401 for text in node
1402 .children()
1403 .filter(|child| child.is_text())
1404 .filter_map(|child| child.text())
1405 {
1406 if raw_text_len.saturating_add(text.len()) > MAX_DER_ENCODED_KEY_VALUE_TEXT_LEN {
1407 return Err(ParseError::InvalidStructure(
1408 "DEREncodedKeyValue exceeds maximum allowed text length".into(),
1409 ));
1410 }
1411 raw_text_len = raw_text_len.saturating_add(text.len());
1412 normalize_xml_base64_text(text, &mut cleaned).map_err(|err| {
1413 ParseError::Base64(format!(
1414 "invalid XML whitespace U+{:04X} in base64 text",
1415 err.invalid_byte
1416 ))
1417 })?;
1418 if cleaned.len() > MAX_DER_ENCODED_KEY_VALUE_BASE64_LEN {
1419 return Err(ParseError::InvalidStructure(
1420 "DEREncodedKeyValue exceeds maximum allowed length".into(),
1421 ));
1422 }
1423 }
1424
1425 let der = STANDARD
1426 .decode(&cleaned)
1427 .map_err(|e| ParseError::Base64(e.to_string()))?;
1428 if der.is_empty() {
1429 return Err(ParseError::InvalidStructure(
1430 "DEREncodedKeyValue must not be empty".into(),
1431 ));
1432 }
1433 if der.len() > MAX_DER_ENCODED_KEY_VALUE_LEN {
1434 return Err(ParseError::InvalidStructure(
1435 "DEREncodedKeyValue exceeds maximum allowed length".into(),
1436 ));
1437 }
1438 Ok(der)
1439}
1440
1441fn collect_text_content_bounded(
1442 node: Node<'_, '_>,
1443 max_len: usize,
1444 element_name: &'static str,
1445) -> Result<String, ParseError> {
1446 let mut text = String::new();
1447 for chunk in node
1448 .children()
1449 .filter_map(|child| child.is_text().then(|| child.text()).flatten())
1450 {
1451 if text.len().saturating_add(chunk.len()) > max_len {
1452 return Err(ParseError::InvalidStructure(format!(
1453 "{element_name} exceeds maximum allowed text length"
1454 )));
1455 }
1456 text.push_str(chunk);
1457 }
1458 Ok(text)
1459}
1460
1461fn ensure_no_element_children(node: Node<'_, '_>, element_name: &str) -> Result<(), ParseError> {
1462 if node.children().any(|child| child.is_element()) {
1463 return Err(ParseError::InvalidStructure(format!(
1464 "{element_name} must not contain child elements"
1465 )));
1466 }
1467 Ok(())
1468}
1469
1470fn ensure_no_non_whitespace_text(node: Node<'_, '_>, element_name: &str) -> Result<(), ParseError> {
1471 for child in node.children().filter(|child| child.is_text()) {
1472 if let Some(text) = child.text()
1473 && !is_xml_whitespace_only(text)
1474 {
1475 return Err(ParseError::InvalidStructure(format!(
1476 "{element_name} must not contain non-whitespace mixed content"
1477 )));
1478 }
1479 }
1480 Ok(())
1481}
1482
1483#[cfg(test)]
1484#[expect(clippy::unwrap_used, reason = "tests use trusted XML fixtures")]
1485mod tests {
1486 use super::*;
1487 use base64::Engine;
1488
1489 fn fixture_rsa_cert_base64() -> String {
1490 fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem")
1491 }
1492
1493 fn fixture_cert_base64(path: &str) -> String {
1494 match path {
1495 "../../tests/fixtures/keys/rsa/rsa-2048-cert.pem" => {
1496 include_str!("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem")
1497 }
1498 "../../tests/fixtures/keys/rsa/rsa-4096-cert.pem" => {
1499 include_str!("../../tests/fixtures/keys/rsa/rsa-4096-cert.pem")
1500 }
1501 "../../tests/fixtures/keys/ca2cert.pem" => {
1502 include_str!("../../tests/fixtures/keys/ca2cert.pem")
1503 }
1504 "../../tests/fixtures/keys/cacert.pem" => {
1505 include_str!("../../tests/fixtures/keys/cacert.pem")
1506 }
1507 _ => unreachable!("unknown certificate fixture"),
1508 }
1509 .lines()
1510 .skip_while(|line| *line != "-----BEGIN CERTIFICATE-----")
1511 .skip(1)
1512 .take_while(|line| *line != "-----END CERTIFICATE-----")
1513 .collect::<String>()
1514 }
1515
1516 #[test]
1519 fn signature_algorithm_from_uri_rsa_sha256() {
1520 assert_eq!(
1521 SignatureAlgorithm::from_uri("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"),
1522 Some(SignatureAlgorithm::RsaSha256)
1523 );
1524 }
1525
1526 #[test]
1527 fn signature_algorithm_from_uri_rsa_sha1() {
1528 assert_eq!(
1529 SignatureAlgorithm::from_uri("http://www.w3.org/2000/09/xmldsig#rsa-sha1"),
1530 Some(SignatureAlgorithm::RsaSha1)
1531 );
1532 }
1533
1534 #[test]
1535 fn signature_algorithm_from_uri_ecdsa_sha256() {
1536 assert_eq!(
1537 SignatureAlgorithm::from_uri("http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"),
1538 Some(SignatureAlgorithm::EcdsaP256Sha256)
1539 );
1540 }
1541
1542 #[test]
1543 fn signature_algorithm_from_uri_unknown() {
1544 assert_eq!(
1545 SignatureAlgorithm::from_uri("http://example.com/unknown"),
1546 None
1547 );
1548 }
1549
1550 #[test]
1551 fn signature_algorithm_uri_round_trip() {
1552 for algo in [
1553 SignatureAlgorithm::RsaSha1,
1554 SignatureAlgorithm::RsaSha256,
1555 SignatureAlgorithm::RsaSha384,
1556 SignatureAlgorithm::RsaSha512,
1557 SignatureAlgorithm::EcdsaP256Sha256,
1558 SignatureAlgorithm::EcdsaP384Sha384,
1559 ] {
1560 assert_eq!(
1561 SignatureAlgorithm::from_uri(algo.uri()),
1562 Some(algo),
1563 "round-trip failed for {algo:?}"
1564 );
1565 }
1566 }
1567
1568 #[test]
1569 fn rsa_sha1_verify_only() {
1570 assert!(!SignatureAlgorithm::RsaSha1.signing_allowed());
1571 assert!(SignatureAlgorithm::RsaSha256.signing_allowed());
1572 assert!(SignatureAlgorithm::EcdsaP256Sha256.signing_allowed());
1573 }
1574
1575 #[test]
1578 fn find_signature_in_saml() {
1579 let xml = r#"<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
1580 <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
1581 <ds:SignedInfo/>
1582 </ds:Signature>
1583 </samlp:Response>"#;
1584 let doc = Document::parse(xml).unwrap();
1585 let sig = find_signature_node(&doc);
1586 assert!(sig.is_some());
1587 assert_eq!(sig.unwrap().tag_name().name(), "Signature");
1588 }
1589
1590 #[test]
1591 fn find_signature_missing() {
1592 let xml = "<root><child/></root>";
1593 let doc = Document::parse(xml).unwrap();
1594 assert!(find_signature_node(&doc).is_none());
1595 }
1596
1597 #[test]
1598 fn find_signature_ignores_wrong_namespace() {
1599 let xml = r#"<root><Signature xmlns="http://example.com/fake"/></root>"#;
1600 let doc = Document::parse(xml).unwrap();
1601 assert!(find_signature_node(&doc).is_none());
1602 }
1603
1604 #[test]
1607 fn parse_key_info_dispatches_supported_children() {
1608 let cert_base64 = fixture_rsa_cert_base64();
1609 let expected_cert = base64::engine::general_purpose::STANDARD
1610 .decode(&cert_base64)
1611 .expect("fixture PEM must contain valid base64");
1612 let cert_digest = base64::engine::general_purpose::STANDARD
1613 .encode(compute_digest(DigestAlgorithm::Sha256, &expected_cert));
1614 let xml = format!(
1615 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
1616 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
1617 <KeyName>idp-signing-key</KeyName>
1618 <KeyValue>
1619 <RSAKeyValue>
1620 <Modulus>AQAB</Modulus>
1621 <Exponent>AQAB</Exponent>
1622 </RSAKeyValue>
1623 </KeyValue>
1624 <X509Data>
1625 <X509Certificate>{cert_base64}</X509Certificate>
1626 <X509SubjectName>C=US, ST=California, O=XML Security Library (http://www.aleksey.com/xmlsec), CN=Test Key rsa-2048</X509SubjectName>
1627 <X509IssuerSerial>
1628 <X509IssuerName>C=US, ST=California, O=XML Security Library (http://www.aleksey.com/xmlsec), OU=Second level CA, CN=Aleksey Sanin, Email=xmlsec@aleksey.com</X509IssuerName>
1629 <X509SerialNumber>680572598617295163017172295025714171905498632019</X509SerialNumber>
1630 </X509IssuerSerial>
1631 <X509SKI>bcOXN/nsVl8GatRbcKrPbzIbw0Y=</X509SKI>
1632 <X509CRL>BAUGBw==</X509CRL>
1633 <dsig11:X509Digest Algorithm="http://www.w3.org/2001/04/xmlenc#sha256">{cert_digest}</dsig11:X509Digest>
1634 </X509Data>
1635 <dsig11:DEREncodedKeyValue>AQIDBA==</dsig11:DEREncodedKeyValue>
1636 </KeyInfo>"#
1637 );
1638 let doc = Document::parse(&xml).unwrap();
1639
1640 let key_info = parse_key_info(doc.root_element()).unwrap();
1641 assert_eq!(key_info.sources.len(), 4);
1642
1643 assert_eq!(
1644 key_info.sources[0],
1645 KeyInfoSource::KeyName("idp-signing-key".to_string())
1646 );
1647 assert_eq!(
1648 key_info.sources[1],
1649 KeyInfoSource::KeyValue(KeyValueInfo::Rsa {
1650 modulus: vec![1, 0, 1],
1651 exponent: vec![1, 0, 1],
1652 })
1653 );
1654 let x509_info = match &key_info.sources[2] {
1655 KeyInfoSource::X509Data(x509) => x509,
1656 other => panic!("expected X509Data source, got {other:?}"),
1657 };
1658 assert_eq!(x509_info.certificates, vec![expected_cert]);
1659 assert_eq!(
1660 x509_info.subject_names,
1661 vec![
1662 "C=US, ST=California, O=XML Security Library (http://www.aleksey.com/xmlsec), CN=Test Key rsa-2048"
1663 .to_string()
1664 ]
1665 );
1666 assert_eq!(
1667 x509_info.issuer_serials,
1668 vec![(
1669 "C=US, ST=California, O=XML Security Library (http://www.aleksey.com/xmlsec), OU=Second level CA, CN=Aleksey Sanin, Email=xmlsec@aleksey.com".to_string(),
1670 "680572598617295163017172295025714171905498632019".to_string()
1671 )]
1672 );
1673 assert_eq!(
1674 x509_info.skis,
1675 vec![vec![
1676 109, 195, 151, 55, 249, 236, 86, 95, 6, 106, 212, 91, 112, 170, 207, 111, 50, 27,
1677 195, 70
1678 ]]
1679 );
1680 assert_eq!(x509_info.crls, vec![vec![4, 5, 6, 7]]);
1681 assert_eq!(
1682 x509_info.digests,
1683 vec![(
1684 "http://www.w3.org/2001/04/xmlenc#sha256".to_string(),
1685 compute_digest(DigestAlgorithm::Sha256, &x509_info.certificates[0])
1686 )]
1687 );
1688 assert_eq!(x509_info.parsed_certificates.len(), 1);
1689 assert_eq!(x509_info.certificate_chain, vec![0]);
1690 let parsed_cert = &x509_info.parsed_certificates[0];
1691 assert!(!parsed_cert.subject_dn.is_empty());
1692 assert!(!parsed_cert.issuer_dn.is_empty());
1693 assert_eq!(
1694 parsed_cert.serial_number_hex,
1695 "7735EE487F6862DAF1B3956D961CCB0FA6F34F53"
1696 );
1697 assert!(parsed_cert.subject_key_identifier.is_some());
1698 assert!(matches!(
1699 parsed_cert.public_key,
1700 X509PublicKeyInfo::Rsa { .. }
1701 ));
1702
1703 assert_eq!(
1704 key_info.sources[3],
1705 KeyInfoSource::DerEncodedKeyValue(vec![1, 2, 3, 4])
1706 );
1707 }
1708
1709 #[test]
1710 fn parse_rsa_key_value_preserves_wrapped_crypto_binary() {
1711 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1713 <KeyValue><RSAKeyValue>
1714 <Modulus> AQID
1715BA== </Modulus>
1716 <Exponent> AQAB </Exponent>
1717 </RSAKeyValue></KeyValue>
1718 </KeyInfo>"#;
1719 let doc = Document::parse(xml).unwrap();
1720
1721 assert_eq!(
1722 parse_key_info(doc.root_element()).unwrap().sources,
1723 vec![KeyInfoSource::KeyValue(KeyValueInfo::Rsa {
1724 modulus: vec![1, 2, 3, 4],
1725 exponent: vec![1, 0, 1],
1726 })]
1727 );
1728 }
1729
1730 #[test]
1731 fn parse_rsa_key_value_rejects_reordered_parameters() {
1732 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1734 <KeyValue><RSAKeyValue>
1735 <Exponent>AQAB</Exponent><Modulus>AQID</Modulus>
1736 </RSAKeyValue></KeyValue>
1737 </KeyInfo>"#;
1738 let doc = Document::parse(xml).unwrap();
1739
1740 assert!(matches!(
1741 parse_key_info(doc.root_element()),
1742 Err(ParseError::InvalidStructure(_))
1743 ));
1744 }
1745
1746 #[test]
1747 fn parse_rsa_key_value_rejects_missing_exponent() {
1748 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1750 <KeyValue><RSAKeyValue><Modulus>AQID</Modulus></RSAKeyValue></KeyValue>
1751 </KeyInfo>"#;
1752 let doc = Document::parse(xml).unwrap();
1753
1754 assert!(matches!(
1755 parse_key_info(doc.root_element()),
1756 Err(ParseError::InvalidStructure(_))
1757 ));
1758 }
1759
1760 #[test]
1761 fn parse_rsa_key_value_rejects_duplicate_exponent() {
1762 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1764 <KeyValue><RSAKeyValue>
1765 <Modulus>AQID</Modulus><Exponent>AQAB</Exponent><Exponent>AQAB</Exponent>
1766 </RSAKeyValue></KeyValue>
1767 </KeyInfo>"#;
1768 let doc = Document::parse(xml).unwrap();
1769
1770 assert!(matches!(
1771 parse_key_info(doc.root_element()),
1772 Err(ParseError::InvalidStructure(_))
1773 ));
1774 }
1775
1776 #[test]
1777 fn parse_rsa_key_value_rejects_wrong_parameter_namespace() {
1778 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#" xmlns:bad="urn:bad">
1780 <KeyValue><RSAKeyValue>
1781 <bad:Modulus>AQID</bad:Modulus><Exponent>AQAB</Exponent>
1782 </RSAKeyValue></KeyValue>
1783 </KeyInfo>"#;
1784 let doc = Document::parse(xml).unwrap();
1785
1786 assert!(matches!(
1787 parse_key_info(doc.root_element()),
1788 Err(ParseError::InvalidStructure(_))
1789 ));
1790 }
1791
1792 #[test]
1793 fn parse_rsa_key_value_rejects_nested_crypto_binary() {
1794 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1796 <KeyValue><RSAKeyValue>
1797 <Modulus><chunk>AQID</chunk></Modulus><Exponent>AQAB</Exponent>
1798 </RSAKeyValue></KeyValue>
1799 </KeyInfo>"#;
1800 let doc = Document::parse(xml).unwrap();
1801
1802 assert!(matches!(
1803 parse_key_info(doc.root_element()),
1804 Err(ParseError::InvalidStructure(_))
1805 ));
1806 }
1807
1808 #[test]
1809 fn parse_rsa_key_value_rejects_malformed_base64() {
1810 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1812 <KeyValue><RSAKeyValue>
1813 <Modulus>%%%%</Modulus><Exponent>AQAB</Exponent>
1814 </RSAKeyValue></KeyValue>
1815 </KeyInfo>"#;
1816 let doc = Document::parse(xml).unwrap();
1817
1818 assert!(matches!(
1819 parse_key_info(doc.root_element()),
1820 Err(ParseError::Base64(_))
1821 ));
1822 }
1823
1824 #[test]
1825 fn parse_rsa_key_value_rejects_oversized_exponent_before_decode() {
1826 let exponent = "A".repeat(MAX_RSA_EXPONENT_LEN.div_ceil(3) * 4 + 1);
1828 let xml = format!(
1829 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1830 <KeyValue><RSAKeyValue>
1831 <Modulus>AQID</Modulus><Exponent>{exponent}</Exponent>
1832 </RSAKeyValue></KeyValue>
1833 </KeyInfo>"#
1834 );
1835 let doc = Document::parse(&xml).unwrap();
1836
1837 assert!(matches!(
1838 parse_key_info(doc.root_element()),
1839 Err(ParseError::InvalidStructure(_))
1840 ));
1841 }
1842
1843 #[test]
1844 fn parse_key_info_ignores_unknown_children() {
1845 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1846 <Foo>bar</Foo>
1847 <KeyName>ok</KeyName>
1848 </KeyInfo>"#;
1849 let doc = Document::parse(xml).unwrap();
1850
1851 let key_info = parse_key_info(doc.root_element()).unwrap();
1852 assert_eq!(key_info.sources, vec![KeyInfoSource::KeyName("ok".into())]);
1853 }
1854
1855 #[test]
1856 fn parse_key_info_keyvalue_requires_single_child() {
1857 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1858 <KeyValue/>
1859 </KeyInfo>"#;
1860 let doc = Document::parse(xml).unwrap();
1861
1862 let err = parse_key_info(doc.root_element()).unwrap_err();
1863 assert!(matches!(err, ParseError::InvalidStructure(_)));
1864 }
1865
1866 #[test]
1867 fn parse_key_info_accepts_empty_x509data() {
1868 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1869 <X509Data/>
1870 </KeyInfo>"#;
1871 let doc = Document::parse(xml).unwrap();
1872
1873 let key_info = parse_key_info(doc.root_element()).unwrap();
1874 assert_eq!(
1875 key_info.sources,
1876 vec![KeyInfoSource::X509Data(X509DataInfo::default())]
1877 );
1878 }
1879
1880 #[test]
1881 fn parse_key_info_rejects_unknown_xmlsig_child_in_x509data() {
1882 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1883 <X509Data>
1884 <Foo/>
1885 </X509Data>
1886 </KeyInfo>"#;
1887 let doc = Document::parse(xml).unwrap();
1888
1889 let err = parse_key_info(doc.root_element()).unwrap_err();
1890 assert!(matches!(err, ParseError::InvalidStructure(_)));
1891 }
1892
1893 #[test]
1894 fn parse_key_info_rejects_unknown_xmlsig11_child_in_x509data() {
1895 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
1896 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
1897 <X509Data>
1898 <dsig11:Foo/>
1899 </X509Data>
1900 </KeyInfo>"#;
1901 let doc = Document::parse(xml).unwrap();
1902
1903 let err = parse_key_info(doc.root_element()).unwrap_err();
1904 assert!(matches!(err, ParseError::InvalidStructure(_)));
1905 }
1906
1907 #[test]
1908 fn parse_key_info_rejects_x509_issuer_serial_without_required_children() {
1909 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1910 <X509Data>
1911 <X509IssuerSerial>
1912 <X509IssuerName>CN=CA</X509IssuerName>
1913 </X509IssuerSerial>
1914 </X509Data>
1915 </KeyInfo>"#;
1916 let doc = Document::parse(xml).unwrap();
1917
1918 let err = parse_key_info(doc.root_element()).unwrap_err();
1919 assert!(matches!(err, ParseError::InvalidStructure(_)));
1920 }
1921
1922 #[test]
1923 fn parse_key_info_rejects_x509_issuer_serial_with_duplicate_issuer_name() {
1924 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1925 <X509Data>
1926 <X509IssuerSerial>
1927 <X509IssuerName>CN=CA-1</X509IssuerName>
1928 <X509IssuerName>CN=CA-2</X509IssuerName>
1929 <X509SerialNumber>42</X509SerialNumber>
1930 </X509IssuerSerial>
1931 </X509Data>
1932 </KeyInfo>"#;
1933 let doc = Document::parse(xml).unwrap();
1934
1935 let err = parse_key_info(doc.root_element()).unwrap_err();
1936 assert!(matches!(err, ParseError::InvalidStructure(_)));
1937 }
1938
1939 #[test]
1940 fn parse_key_info_rejects_x509_issuer_serial_with_duplicate_serial_number() {
1941 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1942 <X509Data>
1943 <X509IssuerSerial>
1944 <X509IssuerName>CN=CA</X509IssuerName>
1945 <X509SerialNumber>1</X509SerialNumber>
1946 <X509SerialNumber>2</X509SerialNumber>
1947 </X509IssuerSerial>
1948 </X509Data>
1949 </KeyInfo>"#;
1950 let doc = Document::parse(xml).unwrap();
1951
1952 let err = parse_key_info(doc.root_element()).unwrap_err();
1953 assert!(matches!(err, ParseError::InvalidStructure(_)));
1954 }
1955
1956 #[test]
1957 fn parse_key_info_rejects_x509_issuer_serial_with_whitespace_only_values() {
1958 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1959 <X509Data>
1960 <X509IssuerSerial>
1961 <X509IssuerName> </X509IssuerName>
1962 <X509SerialNumber>
1963
1964 </X509SerialNumber>
1965 </X509IssuerSerial>
1966 </X509Data>
1967 </KeyInfo>"#;
1968 let doc = Document::parse(xml).unwrap();
1969
1970 let err = parse_key_info(doc.root_element()).unwrap_err();
1971 assert!(matches!(err, ParseError::InvalidStructure(_)));
1972 }
1973
1974 #[test]
1975 fn parse_key_info_rejects_x509_issuer_serial_with_wrong_child_order() {
1976 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1977 <X509Data>
1978 <X509IssuerSerial>
1979 <X509SerialNumber>42</X509SerialNumber>
1980 <X509IssuerName>CN=CA</X509IssuerName>
1981 </X509IssuerSerial>
1982 </X509Data>
1983 </KeyInfo>"#;
1984 let doc = Document::parse(xml).unwrap();
1985
1986 let err = parse_key_info(doc.root_element()).unwrap_err();
1987 assert!(matches!(err, ParseError::InvalidStructure(_)));
1988 }
1989
1990 #[test]
1991 fn parse_key_info_rejects_x509_issuer_serial_with_extra_child_element() {
1992 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
1993 xmlns:foo="urn:example:foo">
1994 <X509Data>
1995 <X509IssuerSerial>
1996 <X509IssuerName>CN=CA</X509IssuerName>
1997 <X509SerialNumber>42</X509SerialNumber>
1998 <foo:Extra/>
1999 </X509IssuerSerial>
2000 </X509Data>
2001 </KeyInfo>"#;
2002 let doc = Document::parse(xml).unwrap();
2003
2004 let err = parse_key_info(doc.root_element()).unwrap_err();
2005 assert!(matches!(err, ParseError::InvalidStructure(_)));
2006 }
2007
2008 #[test]
2009 fn parse_key_info_rejects_x509_digest_without_algorithm() {
2010 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
2011 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
2012 <X509Data>
2013 <dsig11:X509Digest>AQID</dsig11:X509Digest>
2014 </X509Data>
2015 </KeyInfo>"#;
2016 let doc = Document::parse(xml).unwrap();
2017
2018 let err = parse_key_info(doc.root_element()).unwrap_err();
2019 assert!(matches!(err, ParseError::InvalidStructure(_)));
2020 }
2021
2022 #[test]
2023 fn parse_key_info_rejects_invalid_x509_certificate_base64() {
2024 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2025 <X509Data>
2026 <X509Certificate>%%%invalid%%%</X509Certificate>
2027 </X509Data>
2028 </KeyInfo>"#;
2029 let doc = Document::parse(xml).unwrap();
2030
2031 let err = parse_key_info(doc.root_element()).unwrap_err();
2032 assert!(matches!(err, ParseError::Base64(_)));
2033 }
2034
2035 #[test]
2036 fn parse_key_info_rejects_x509_data_exceeding_entry_budget() {
2037 let subjects = (0..(MAX_X509_DATA_ENTRY_COUNT + 1))
2038 .map(|idx| format!("<X509SubjectName>CN={idx}</X509SubjectName>"))
2039 .collect::<Vec<_>>()
2040 .join("");
2041 let xml = format!(
2042 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><X509Data>{subjects}</X509Data></KeyInfo>"
2043 );
2044 let doc = Document::parse(&xml).unwrap();
2045
2046 let err = parse_key_info(doc.root_element()).unwrap_err();
2047 assert!(matches!(err, ParseError::InvalidStructure(_)));
2048 }
2049
2050 #[test]
2051 fn parse_key_info_rejects_x509_data_exceeding_total_binary_budget() {
2052 let payload = base64::engine::general_purpose::STANDARD.encode(vec![0u8; 190_000]);
2053 let entries = (0..6)
2054 .map(|_| format!("<X509SKI>{payload}</X509SKI>"))
2055 .collect::<Vec<_>>()
2056 .join("");
2057 let xml = format!(
2058 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><X509Data>{entries}</X509Data></KeyInfo>"
2059 );
2060 let doc = Document::parse(&xml).unwrap();
2061
2062 let err = parse_key_info(doc.root_element()).unwrap_err();
2063 assert!(matches!(err, ParseError::InvalidStructure(_)));
2064 }
2065
2066 #[test]
2067 fn parse_key_info_rejects_x509_certificate_with_invalid_der() {
2068 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2069 <X509Data>
2070 <X509Certificate>AQID</X509Certificate>
2071 </X509Data>
2072 </KeyInfo>"#;
2073 let doc = Document::parse(xml).unwrap();
2074
2075 let err = parse_key_info(doc.root_element()).unwrap_err();
2076 assert!(matches!(err, ParseError::InvalidStructure(_)));
2077 }
2078
2079 #[test]
2080 fn parse_key_info_rejects_x509_certificate_with_trailing_der_bytes() {
2081 let mut cert = base64::engine::general_purpose::STANDARD
2082 .decode(fixture_rsa_cert_base64())
2083 .unwrap();
2084 cert.extend_from_slice(&[0x00, 0x01]);
2085 let cert_base64 = base64::engine::general_purpose::STANDARD.encode(cert);
2086 let xml = format!(
2087 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2088 <X509Data>
2089 <X509Certificate>{cert_base64}</X509Certificate>
2090 </X509Data>
2091 </KeyInfo>"#
2092 );
2093 let doc = Document::parse(&xml).unwrap();
2094
2095 let err = parse_key_info(doc.root_element()).unwrap_err();
2096 assert!(matches!(err, ParseError::InvalidStructure(_)));
2097 }
2098
2099 #[test]
2100 fn parse_key_info_marks_unsupported_spki_algorithm_as_unsupported() {
2101 let xml = include_str!(
2102 "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt.xml"
2103 );
2104 let doc = Document::parse(xml).unwrap();
2105 let key_info_node = doc
2106 .descendants()
2107 .find(|node| {
2108 node.is_element()
2109 && node.tag_name().namespace() == Some(XMLDSIG_NS)
2110 && node.tag_name().name() == "KeyInfo"
2111 })
2112 .expect("fixture must contain ds:KeyInfo");
2113
2114 let key_info = parse_key_info(key_info_node).expect("KeyInfo parse should succeed");
2115 let x509_info = match &key_info.sources[0] {
2116 KeyInfoSource::X509Data(x509) => x509,
2117 other => panic!("expected X509Data source, got {other:?}"),
2118 };
2119 assert_eq!(x509_info.certificates.len(), 1);
2120 assert_eq!(x509_info.parsed_certificates.len(), 1);
2121 assert_eq!(x509_info.certificate_chain, vec![0]);
2122 let parsed_cert = &x509_info.parsed_certificates[0];
2123 assert!(!parsed_cert.subject_dn.is_empty());
2124 assert!(!parsed_cert.issuer_dn.is_empty());
2125 assert!(parsed_cert.subject_key_identifier.is_some());
2126 assert!(matches!(
2127 parsed_cert.public_key,
2128 X509PublicKeyInfo::Unsupported { .. }
2129 ));
2130 }
2131
2132 #[test]
2133 fn parse_key_info_orders_x509_certificate_chain_from_signing_cert() {
2134 let root = fixture_cert_base64("../../tests/fixtures/keys/cacert.pem");
2135 let intermediate = fixture_cert_base64("../../tests/fixtures/keys/ca2cert.pem");
2136 let leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
2137 let xml = format!(
2138 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2139 <X509Data>
2140 <X509Certificate>{root}</X509Certificate>
2141 <X509Certificate>{intermediate}</X509Certificate>
2142 <X509Certificate>{leaf}</X509Certificate>
2143 </X509Data>
2144 </KeyInfo>"#
2145 );
2146 let doc = Document::parse(&xml).unwrap();
2147
2148 let key_info = parse_key_info(doc.root_element()).unwrap();
2149 let x509_info = match &key_info.sources[0] {
2150 KeyInfoSource::X509Data(x509) => x509,
2151 other => panic!("expected X509Data source, got {other:?}"),
2152 };
2153
2154 assert_eq!(x509_info.certificate_chain, vec![2, 1, 0]);
2155 }
2156
2157 #[test]
2158 fn parse_key_info_uses_issuer_serial_to_select_x509_signing_certificate() {
2159 let root = fixture_cert_base64("../../tests/fixtures/keys/cacert.pem");
2160 let intermediate = fixture_cert_base64("../../tests/fixtures/keys/ca2cert.pem");
2161 let leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
2162 let xml = format!(
2163 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2164 <X509Data>
2165 <X509IssuerSerial>
2166 <X509IssuerName>C=US, ST=California, O=XML Security Library (http://www.aleksey.com/xmlsec), OU=Second level CA, CN=Aleksey Sanin, Email=xmlsec@aleksey.com</X509IssuerName>
2167 <X509SerialNumber>680572598617295163017172295025714171905498632019</X509SerialNumber>
2168 </X509IssuerSerial>
2169 <X509Certificate>{root}</X509Certificate>
2170 <X509Certificate>{intermediate}</X509Certificate>
2171 <X509Certificate>{leaf}</X509Certificate>
2172 </X509Data>
2173 </KeyInfo>"#
2174 );
2175 let doc = Document::parse(&xml).unwrap();
2176
2177 let key_info = parse_key_info(doc.root_element()).unwrap();
2178 let x509_info = match &key_info.sources[0] {
2179 KeyInfoSource::X509Data(x509) => x509,
2180 other => panic!("expected X509Data source, got {other:?}"),
2181 };
2182
2183 assert_eq!(x509_info.certificate_chain, vec![2, 1, 0]);
2184 }
2185
2186 #[test]
2187 fn parse_key_info_allows_selectors_for_multiple_chain_members() {
2188 let root = fixture_cert_base64("../../tests/fixtures/keys/cacert.pem");
2191 let intermediate = fixture_cert_base64("../../tests/fixtures/keys/ca2cert.pem");
2192 let leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
2193 let xml = format!(
2194 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2195 <X509Data>
2196 <X509SubjectName>C=US, ST=California, O=XML Security Library (http://www.aleksey.com/xmlsec), CN=Test Key rsa-2048</X509SubjectName>
2197 <X509SKI>0X0XrEVCio75sBcl1TxymJ2IOiU=</X509SKI>
2198 <X509Certificate>{root}</X509Certificate>
2199 <X509Certificate>{intermediate}</X509Certificate>
2200 <X509Certificate>{leaf}</X509Certificate>
2201 </X509Data>
2202 </KeyInfo>"#
2203 );
2204 let doc = Document::parse(&xml).unwrap();
2205
2206 let key_info = parse_key_info(doc.root_element()).unwrap();
2207 let x509_info = match &key_info.sources[0] {
2208 KeyInfoSource::X509Data(x509) => x509,
2209 other => panic!("expected X509Data source, got {other:?}"),
2210 };
2211
2212 assert_eq!(x509_info.certificate_chain, vec![2, 1, 0]);
2213 }
2214
2215 #[test]
2216 fn parse_key_info_uses_decimal_issuer_serial_to_select_x509_signing_certificate() {
2217 assert_eq!(
2218 x509_serial_decimal_to_hex("680572598617295163017172295025714171905498632019")
2219 .as_deref(),
2220 Some("7735EE487F6862DAF1B3956D961CCB0FA6F34F53")
2221 );
2222 let root = fixture_cert_base64("../../tests/fixtures/keys/cacert.pem");
2223 let intermediate = fixture_cert_base64("../../tests/fixtures/keys/ca2cert.pem");
2224 let leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
2225 let other_leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-4096-cert.pem");
2226 let xml = format!(
2227 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2228 <X509Data>
2229 <X509IssuerSerial>
2230 <X509IssuerName>C=US, ST=California, O=XML Security Library (http://www.aleksey.com/xmlsec), OU=Second level CA, CN=Aleksey Sanin, Email=xmlsec@aleksey.com</X509IssuerName>
2231 <X509SerialNumber>680572598617295163017172295025714171905498632019</X509SerialNumber>
2232 </X509IssuerSerial>
2233 <X509Certificate>{root}</X509Certificate>
2234 <X509Certificate>{intermediate}</X509Certificate>
2235 <X509Certificate>{leaf}</X509Certificate>
2236 <X509Certificate>{other_leaf}</X509Certificate>
2237 </X509Data>
2238 </KeyInfo>"#
2239 );
2240 let doc = Document::parse(&xml).unwrap();
2241
2242 let key_info = parse_key_info(doc.root_element()).unwrap();
2243 let x509_info = match &key_info.sources[0] {
2244 KeyInfoSource::X509Data(x509) => x509,
2245 other => panic!("expected X509Data source, got {other:?}"),
2246 };
2247
2248 assert_eq!(x509_info.certificate_chain, vec![2, 1, 0]);
2249 }
2250
2251 #[test]
2252 fn parse_key_info_rejects_ambiguous_x509_signing_certificate_candidates() {
2253 let first_leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
2254 let second_leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-4096-cert.pem");
2255 let xml = format!(
2256 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2257 <X509Data>
2258 <X509Certificate>{first_leaf}</X509Certificate>
2259 <X509Certificate>{second_leaf}</X509Certificate>
2260 </X509Data>
2261 </KeyInfo>"#
2262 );
2263 let doc = Document::parse(&xml).unwrap();
2264
2265 let err = parse_key_info(doc.root_element()).unwrap_err();
2266 assert!(matches!(err, ParseError::InvalidStructure(_)));
2267 }
2268
2269 #[test]
2270 fn parse_key_info_rejects_unmatched_x509_lookup_identifier() {
2271 let cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
2272 let xml = format!(
2273 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2274 <X509Data>
2275 <X509SubjectName>CN=Not The Embedded Certificate</X509SubjectName>
2276 <X509Certificate>{cert}</X509Certificate>
2277 </X509Data>
2278 </KeyInfo>"#
2279 );
2280 let doc = Document::parse(&xml).unwrap();
2281
2282 let err = parse_key_info(doc.root_element()).unwrap_err();
2283 assert!(
2284 matches!(err, ParseError::InvalidStructure(message) if message.contains("lookup identifiers"))
2285 );
2286 }
2287
2288 #[test]
2289 fn parse_key_info_rejects_partially_matched_selector_category() {
2290 let cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
2293 let xml = format!(
2294 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2295 <X509Data>
2296 <X509SubjectName>C=US, ST=California, O=XML Security Library (http://www.aleksey.com/xmlsec), CN=Test Key rsa-2048</X509SubjectName>
2297 <X509SubjectName>CN=Not In The Embedded Chain</X509SubjectName>
2298 <X509Certificate>{cert}</X509Certificate>
2299 </X509Data>
2300 </KeyInfo>"#
2301 );
2302 let doc = Document::parse(&xml).unwrap();
2303
2304 let err = parse_key_info(doc.root_element()).unwrap_err();
2305 assert!(
2306 matches!(err, ParseError::InvalidStructure(message) if message.contains("lookup identifiers"))
2307 );
2308 }
2309
2310 #[test]
2311 fn parse_key_info_rejects_malformed_issuer_serial_even_with_matching_subject() {
2312 let cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
2313 let xml = format!(
2314 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2315 <X509Data>
2316 <X509SubjectName>C=US, ST=California, O=XML Security Library (http://www.aleksey.com/xmlsec), CN=Test Key rsa-2048</X509SubjectName>
2317 <X509IssuerSerial>
2318 <X509IssuerName>C=US, ST=California, O=XML Security Library (http://www.aleksey.com/xmlsec), OU=Second level CA, CN=Aleksey Sanin, Email=xmlsec@aleksey.com</X509IssuerName>
2319 <X509SerialNumber>not-a-decimal-serial</X509SerialNumber>
2320 </X509IssuerSerial>
2321 <X509Certificate>{cert}</X509Certificate>
2322 </X509Data>
2323 </KeyInfo>"#
2324 );
2325 let doc = Document::parse(&xml).unwrap();
2326
2327 let err = parse_key_info(doc.root_element()).unwrap_err();
2328 assert!(
2329 matches!(err, ParseError::InvalidStructure(message) if message.contains("lookup identifiers"))
2330 );
2331 }
2332
2333 #[test]
2334 fn parse_key_info_rejects_unmatched_ski_even_with_matching_subject() {
2335 let cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
2336 let xml = format!(
2337 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2338 <X509Data>
2339 <X509SubjectName>C=US, ST=California, O=XML Security Library (http://www.aleksey.com/xmlsec), CN=Test Key rsa-2048</X509SubjectName>
2340 <X509SKI>AQIDBA==</X509SKI>
2341 <X509Certificate>{cert}</X509Certificate>
2342 </X509Data>
2343 </KeyInfo>"#
2344 );
2345 let doc = Document::parse(&xml).unwrap();
2346
2347 let err = parse_key_info(doc.root_element()).unwrap_err();
2348 assert!(
2349 matches!(err, ParseError::InvalidStructure(message) if message.contains("lookup identifiers"))
2350 );
2351 }
2352
2353 #[test]
2354 fn parse_key_info_rejects_lookup_hints_for_different_certificates() {
2355 let first_cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
2356 let second_cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-4096-cert.pem");
2357 let xml = format!(
2358 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2359 <X509Data>
2360 <X509SubjectName>C=US, ST=California, O=XML Security Library (http://www.aleksey.com/xmlsec), CN=Test Key rsa-2048</X509SubjectName>
2361 <X509SKI>60zMLKCfzQ3qnXAzABzRNpdgQ8Q=</X509SKI>
2362 <X509Certificate>{first_cert}</X509Certificate>
2363 <X509Certificate>{second_cert}</X509Certificate>
2364 </X509Data>
2365 </KeyInfo>"#
2366 );
2367 let doc = Document::parse(&xml).unwrap();
2368
2369 let err = parse_key_info(doc.root_element()).unwrap_err();
2370 assert!(
2371 matches!(err, ParseError::InvalidStructure(message) if message.contains("lookup identifiers match multiple certificates"))
2372 );
2373 }
2374
2375 #[test]
2376 fn build_x509_certificate_chain_rejects_chain_exceeding_max_depth() {
2377 let parsed_certificates = (0..=MAX_X509_CHAIN_DEPTH)
2378 .map(|idx| ParsedX509Certificate {
2379 subject_dn: format!("CN=cert-{idx}"),
2380 issuer_dn: if idx == MAX_X509_CHAIN_DEPTH {
2381 format!("CN=cert-{idx}")
2382 } else {
2383 format!("CN=cert-{}", idx + 1)
2384 },
2385 serial_number: vec![u8::try_from(idx).unwrap()],
2386 serial_number_hex: format!("{idx:02X}"),
2387 subject_key_identifier: None,
2388 public_key: X509PublicKeyInfo::Unsupported {
2389 algorithm_oid: "1.2.3.4".into(),
2390 },
2391 })
2392 .collect();
2393 let info = X509DataInfo {
2394 parsed_certificates,
2395 ..X509DataInfo::default()
2396 };
2397
2398 let err = build_x509_certificate_chain(&info).unwrap_err();
2399 assert!(
2400 matches!(err, ParseError::InvalidStructure(message) if message.contains("maximum depth"))
2401 );
2402 }
2403
2404 #[test]
2405 fn x509_serial_hex_strips_der_sign_extension_zeroes() {
2406 assert_eq!(format_x509_serial_value_hex(&[0x00, 0xFF]), "FF");
2407 assert_eq!(format_x509_serial_value_hex(&[0x00, 0x7F]), "7F");
2408 assert_eq!(format_x509_serial_value_hex(&[0x00, 0x00]), "00");
2409 }
2410
2411 #[test]
2412 fn parse_key_info_accepts_large_textual_x509_entries_within_entry_budget() {
2413 let issuer_name = "C".repeat(MAX_X509_ISSUER_NAME_TEXT_LEN);
2414 let serial_number = "7".repeat(MAX_X509_SERIAL_NUMBER_TEXT_LEN);
2415 let issuer_serials = (0..52)
2416 .map(|_| {
2417 format!(
2418 "<X509IssuerSerial><X509IssuerName>{issuer_name}</X509IssuerName><X509SerialNumber>{serial_number}</X509SerialNumber></X509IssuerSerial>"
2419 )
2420 })
2421 .collect::<Vec<_>>()
2422 .join("");
2423 let xml = format!(
2424 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><X509Data>{issuer_serials}</X509Data></KeyInfo>"
2425 );
2426 let doc = Document::parse(&xml).unwrap();
2427
2428 let key_info = parse_key_info(doc.root_element()).unwrap();
2429 let parsed = match &key_info.sources[0] {
2430 KeyInfoSource::X509Data(x509) => x509,
2431 _ => panic!("expected X509Data source"),
2432 };
2433 assert_eq!(parsed.issuer_serials.len(), 52);
2434 }
2435
2436 #[test]
2437 fn parse_key_info_accepts_x509data_with_only_foreign_namespace_children() {
2438 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
2439 xmlns:foo="urn:example:foo">
2440 <X509Data>
2441 <foo:Bar/>
2442 </X509Data>
2443 </KeyInfo>"#;
2444 let doc = Document::parse(xml).unwrap();
2445
2446 let key_info = parse_key_info(doc.root_element()).unwrap();
2447 assert_eq!(
2448 key_info.sources,
2449 vec![KeyInfoSource::X509Data(X509DataInfo::default())]
2450 );
2451 }
2452
2453 #[test]
2454 fn parse_key_info_der_encoded_key_value_rejects_invalid_base64() {
2455 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
2456 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
2457 <dsig11:DEREncodedKeyValue>%%%invalid%%%</dsig11:DEREncodedKeyValue>
2458 </KeyInfo>"#;
2459 let doc = Document::parse(xml).unwrap();
2460
2461 let err = parse_key_info(doc.root_element()).unwrap_err();
2462 assert!(matches!(err, ParseError::Base64(_)));
2463 }
2464
2465 #[test]
2466 fn parse_key_info_der_encoded_key_value_accepts_xml_whitespace() {
2467 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
2468 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
2469 <dsig11:DEREncodedKeyValue>
2470 AQID
2471 BA==
2472 </dsig11:DEREncodedKeyValue>
2473 </KeyInfo>"#;
2474 let doc = Document::parse(xml).unwrap();
2475
2476 let key_info = parse_key_info(doc.root_element()).unwrap();
2477 assert_eq!(
2478 key_info.sources,
2479 vec![KeyInfoSource::DerEncodedKeyValue(vec![1, 2, 3, 4])]
2480 );
2481 }
2482
2483 #[test]
2484 fn parse_key_info_dispatches_dsig11_ec_keyvalue() {
2485 let public_key = "BJ/yaXNlq4FRObyJCBhb5jAz8GVzinK3bBGLjSDfjbJwNfydtgjnlS4EsDmxSRhWyJWq6GIqy5wvnaiARK04uB4=";
2486 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
2487 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
2488 <KeyValue>
2489 <dsig11:ECKeyValue>
2490 <dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/>
2491 <dsig11:PublicKey>BJ/yaXNlq4FRObyJCBhb5jAz8GVzinK3bBGLjSDfjbJwNfydtgjnlS4EsDmxSRhWyJWq6GIqy5wvnaiARK04uB4=</dsig11:PublicKey>
2492 </dsig11:ECKeyValue>
2493 </KeyValue>
2494 </KeyInfo>"#;
2495 let doc = Document::parse(xml).unwrap();
2496 let expected_public_key = base64::engine::general_purpose::STANDARD
2497 .decode(public_key)
2498 .expect("fixture EC point must be valid base64");
2499
2500 let key_info = parse_key_info(doc.root_element()).unwrap();
2501 assert_eq!(
2502 key_info.sources,
2503 vec![KeyInfoSource::KeyValue(KeyValueInfo::Ec {
2504 curve_oid: "1.2.840.10045.3.1.7".into(),
2505 public_key: expected_public_key,
2506 })]
2507 );
2508 }
2509
2510 #[test]
2511 fn parse_ec_key_value_accepts_bare_curve_oid() {
2512 use base64::Engine;
2513
2514 let encoded_public_key = "BO/yd/OZzDfjX4qivDY/vsUIuh6KWAxoxW5P4ukvwd+T6pVljWsX2UBJNNy5MdhTwB8e2YwB8kUbJwdsAS/XGi/fz8unFrs+lVlAgIs6s/xBYFbfUoRiAacD2SpVDe6XBA==";
2515 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
2516 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
2517 <KeyValue>
2518 <dsig11:ECKeyValue>
2519 <dsig11:NamedCurve URI="1.3.132.0.34"/>
2520 <dsig11:PublicKey>BO/yd/OZzDfjX4qivDY/vsUIuh6KWAxoxW5P4ukvwd+T6pVljWsX2UBJNNy5MdhTwB8e2YwB8kUbJwdsAS/XGi/fz8unFrs+lVlAgIs6s/xBYFbfUoRiAacD2SpVDe6XBA==</dsig11:PublicKey>
2521 </dsig11:ECKeyValue>
2522 </KeyValue>
2523 </KeyInfo>"#;
2524 let doc = Document::parse(xml).unwrap();
2525 let expected_public_key = base64::engine::general_purpose::STANDARD
2526 .decode(encoded_public_key)
2527 .unwrap();
2528
2529 let sources = parse_key_info(doc.root_element()).unwrap().sources;
2530
2531 assert!(matches!(
2532 &sources[0],
2533 KeyInfoSource::KeyValue(KeyValueInfo::Ec { curve_oid, public_key })
2534 if curve_oid == EC_P384_OID && public_key == &expected_public_key
2535 ));
2536 }
2537
2538 #[test]
2539 fn parse_ec_key_value_marks_ec_parameters_as_unsupported() {
2540 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
2541 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
2542 <KeyValue>
2543 <dsig11:ECKeyValue>
2544 <dsig11:ECParameters/>
2545 <dsig11:PublicKey>BA==</dsig11:PublicKey>
2546 </dsig11:ECKeyValue>
2547 </KeyValue>
2548 </KeyInfo>"#;
2549 let doc = Document::parse(xml).unwrap();
2550
2551 let key_info = parse_key_info(doc.root_element()).unwrap();
2552 assert_eq!(
2553 key_info.sources,
2554 vec![KeyInfoSource::KeyValue(KeyValueInfo::Unsupported {
2555 namespace: Some(XMLDSIG11_NS.to_string()),
2556 local_name: "ECKeyValue".into(),
2557 })]
2558 );
2559 }
2560
2561 #[test]
2562 fn parse_ec_key_value_marks_unsupported_curve_as_unsupported() {
2563 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
2564 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
2565 <KeyValue>
2566 <dsig11:ECKeyValue>
2567 <dsig11:NamedCurve URI="urn:oid:1.3.132.0.35"/>
2568 <dsig11:PublicKey>BA==</dsig11:PublicKey>
2569 </dsig11:ECKeyValue>
2570 </KeyValue>
2571 </KeyInfo>"#;
2572 let doc = Document::parse(xml).unwrap();
2573
2574 let key_info = parse_key_info(doc.root_element()).unwrap();
2575 assert_eq!(
2576 key_info.sources,
2577 vec![KeyInfoSource::KeyValue(KeyValueInfo::Unsupported {
2578 namespace: Some(XMLDSIG11_NS.to_string()),
2579 local_name: "ECKeyValue".into(),
2580 })]
2581 );
2582 }
2583
2584 #[test]
2585 fn parse_ec_key_value_marks_missing_named_curve_uri_invalid() {
2586 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
2587 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
2588 <KeyValue>
2589 <dsig11:ECKeyValue>
2590 <dsig11:NamedCurve/>
2591 <dsig11:PublicKey>BA==</dsig11:PublicKey>
2592 </dsig11:ECKeyValue>
2593 </KeyValue>
2594 </KeyInfo>"#;
2595 let doc = Document::parse(xml).unwrap();
2596
2597 let key_info = parse_key_info(doc.root_element()).unwrap();
2598 assert_eq!(
2599 key_info.sources,
2600 vec![KeyInfoSource::KeyValue(KeyValueInfo::InvalidEcKeyValue)]
2601 );
2602 }
2603
2604 #[test]
2605 fn parse_ec_key_value_marks_reordered_children_invalid() {
2606 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
2607 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
2608 <KeyValue>
2609 <dsig11:ECKeyValue>
2610 <dsig11:PublicKey>BJ/yaXNlq4FRObyJCBhb5jAz8GVzinK3bBGLjSDfjbJwNfydtgjnlS4EsDmxSRhWyJWq6GIqy5wvnaiARK04uB4=</dsig11:PublicKey>
2611 <dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/>
2612 </dsig11:ECKeyValue>
2613 </KeyValue>
2614 </KeyInfo>"#;
2615 let doc = Document::parse(xml).unwrap();
2616
2617 let key_info = parse_key_info(doc.root_element()).unwrap();
2618 assert_eq!(
2619 key_info.sources,
2620 vec![KeyInfoSource::KeyValue(KeyValueInfo::InvalidEcKeyValue)]
2621 );
2622 }
2623
2624 #[test]
2625 fn parse_ec_key_value_marks_non_uncompressed_point_invalid() {
2626 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
2627 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
2628 <KeyValue>
2629 <dsig11:ECKeyValue>
2630 <dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/>
2631 <dsig11:PublicKey>Ap/yaXNlq4FRObyJCBhb5jAz8GVzinK3bBGLjSDfjbJwNfydtgjnlS4EsDmxSRhWyJWq6GIqy5wvnaiARK04uB4=</dsig11:PublicKey>
2632 </dsig11:ECKeyValue>
2633 </KeyValue>
2634 </KeyInfo>"#;
2635 let doc = Document::parse(xml).unwrap();
2636
2637 let key_info = parse_key_info(doc.root_element()).unwrap();
2638 assert_eq!(
2639 key_info.sources,
2640 vec![KeyInfoSource::KeyValue(KeyValueInfo::InvalidEcKeyValue)]
2641 );
2642 }
2643
2644 #[test]
2645 fn parse_key_info_marks_ds_namespace_ec_keyvalue_as_unsupported() {
2646 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2647 <KeyValue>
2648 <ECKeyValue/>
2649 </KeyValue>
2650 </KeyInfo>"#;
2651 let doc = Document::parse(xml).unwrap();
2652
2653 let key_info = parse_key_info(doc.root_element()).unwrap();
2654 assert_eq!(
2655 key_info.sources,
2656 vec![KeyInfoSource::KeyValue(KeyValueInfo::Unsupported {
2657 namespace: Some(XMLDSIG_NS.to_string()),
2658 local_name: "ECKeyValue".into(),
2659 })]
2660 );
2661 }
2662
2663 #[test]
2664 fn parse_key_info_keeps_unsupported_keyvalue_child_as_marker() {
2665 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2666 <KeyValue>
2667 <DSAKeyValue/>
2668 </KeyValue>
2669 </KeyInfo>"#;
2670 let doc = Document::parse(xml).unwrap();
2671
2672 let key_info = parse_key_info(doc.root_element()).unwrap();
2673 assert_eq!(
2674 key_info.sources,
2675 vec![KeyInfoSource::KeyValue(KeyValueInfo::Unsupported {
2676 namespace: Some(XMLDSIG_NS.to_string()),
2677 local_name: "DSAKeyValue".into(),
2678 })]
2679 );
2680 }
2681
2682 #[test]
2683 fn parse_key_info_rejects_keyname_with_child_elements() {
2684 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2685 <KeyName>ok<foo/></KeyName>
2686 </KeyInfo>"#;
2687 let doc = Document::parse(xml).unwrap();
2688
2689 let err = parse_key_info(doc.root_element()).unwrap_err();
2690 assert!(matches!(err, ParseError::InvalidStructure(_)));
2691 }
2692
2693 #[test]
2694 fn parse_key_info_preserves_keyname_text_without_trimming() {
2695 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2696 <KeyName> signing key </KeyName>
2697 </KeyInfo>"#;
2698 let doc = Document::parse(xml).unwrap();
2699
2700 let key_info = parse_key_info(doc.root_element()).unwrap();
2701 assert_eq!(
2702 key_info.sources,
2703 vec![KeyInfoSource::KeyName(" signing key ".into())]
2704 );
2705 }
2706
2707 #[test]
2708 fn parse_key_info_rejects_oversized_keyname_text() {
2709 let oversized = "A".repeat(4097);
2710 let xml = format!(
2711 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><KeyName>{oversized}</KeyName></KeyInfo>"
2712 );
2713 let doc = Document::parse(&xml).unwrap();
2714
2715 let err = parse_key_info(doc.root_element()).unwrap_err();
2716 assert!(matches!(err, ParseError::InvalidStructure(_)));
2717 }
2718
2719 #[test]
2720 fn parse_key_info_rejects_non_whitespace_mixed_content() {
2721 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">oops<KeyName>k</KeyName></KeyInfo>"#;
2722 let doc = Document::parse(xml).unwrap();
2723
2724 let err = parse_key_info(doc.root_element()).unwrap_err();
2725 assert!(matches!(err, ParseError::InvalidStructure(_)));
2726 }
2727
2728 #[test]
2729 fn parse_key_info_rejects_nbsp_as_non_xml_whitespace_mixed_content() {
2730 let xml = "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\u{00A0}<KeyName>k</KeyName></KeyInfo>";
2731 let doc = Document::parse(xml).unwrap();
2732
2733 let err = parse_key_info(doc.root_element()).unwrap_err();
2734 assert!(matches!(err, ParseError::InvalidStructure(_)));
2735 }
2736
2737 #[test]
2738 fn parse_key_info_der_encoded_key_value_rejects_oversized_payload() {
2739 let oversized =
2740 base64::engine::general_purpose::STANDARD
2741 .encode(vec![0u8; MAX_DER_ENCODED_KEY_VALUE_LEN + 1]);
2742 let xml = format!(
2743 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\" xmlns:dsig11=\"http://www.w3.org/2009/xmldsig11#\"><dsig11:DEREncodedKeyValue>{oversized}</dsig11:DEREncodedKeyValue></KeyInfo>"
2744 );
2745 let doc = Document::parse(&xml).unwrap();
2746
2747 let err = parse_key_info(doc.root_element()).unwrap_err();
2748 assert!(matches!(err, ParseError::InvalidStructure(_)));
2749 }
2750
2751 #[test]
2752 fn parse_key_info_der_encoded_key_value_rejects_empty_payload() {
2753 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
2754 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
2755 <dsig11:DEREncodedKeyValue>
2756
2757 </dsig11:DEREncodedKeyValue>
2758 </KeyInfo>"#;
2759 let doc = Document::parse(xml).unwrap();
2760
2761 let err = parse_key_info(doc.root_element()).unwrap_err();
2762 assert!(matches!(err, ParseError::InvalidStructure(_)));
2763 }
2764
2765 #[test]
2766 fn parse_key_info_der_encoded_key_value_non_xml_ascii_whitespace_is_not_parseable_xml() {
2767 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>";
2768 assert!(Document::parse(xml).is_err());
2769 }
2770
2771 #[test]
2774 fn parse_signed_info_rsa_sha256_with_reference() {
2775 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2776 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
2777 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
2778 <Reference URI="">
2779 <Transforms>
2780 <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
2781 <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
2782 </Transforms>
2783 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
2784 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
2785 </Reference>
2786 </SignedInfo>"#;
2787 let doc = Document::parse(xml).unwrap();
2788 let si = parse_signed_info(doc.root_element()).unwrap();
2789
2790 assert_eq!(si.signature_method, SignatureAlgorithm::RsaSha256);
2791 assert_eq!(si.references.len(), 1);
2792
2793 let r = &si.references[0];
2794 assert_eq!(r.uri.as_deref(), Some(""));
2795 assert_eq!(r.digest_method, DigestAlgorithm::Sha256);
2796 assert_eq!(r.digest_value, vec![0u8; 32]);
2797 assert_eq!(r.transforms.len(), 2);
2798 }
2799
2800 #[test]
2801 fn parse_signed_info_multiple_references() {
2802 let xml = r##"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2803 <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
2804 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"/>
2805 <Reference URI="#a">
2806 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
2807 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
2808 </Reference>
2809 <Reference URI="#b">
2810 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
2811 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
2812 </Reference>
2813 </SignedInfo>"##;
2814 let doc = Document::parse(xml).unwrap();
2815 let si = parse_signed_info(doc.root_element()).unwrap();
2816
2817 assert_eq!(si.signature_method, SignatureAlgorithm::EcdsaP256Sha256);
2818 assert_eq!(si.references.len(), 2);
2819 assert_eq!(si.references[0].uri.as_deref(), Some("#a"));
2820 assert_eq!(si.references[0].digest_method, DigestAlgorithm::Sha256);
2821 assert_eq!(si.references[1].uri.as_deref(), Some("#b"));
2822 assert_eq!(si.references[1].digest_method, DigestAlgorithm::Sha1);
2823 }
2824
2825 #[test]
2826 fn parse_signed_info_rejects_too_many_references() {
2827 let references = (0..=MAX_REFERENCES_PER_SIGNATURE)
2830 .map(|index| {
2831 format!(
2832 r##"<Reference URI="#item-{index}">
2833 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
2834 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
2835 </Reference>"##
2836 )
2837 })
2838 .collect::<String>();
2839 let xml = format!(
2840 r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2841 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
2842 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
2843 {references}
2844 </SignedInfo>"#
2845 );
2846 let document = Document::parse(&xml).expect("fixed oversized fixture must parse");
2847
2848 let error = parse_signed_info(document.root_element())
2849 .expect_err("the parser must reject the 65th Reference");
2850
2851 assert!(matches!(
2852 error,
2853 ParseError::TooManyReferences {
2854 max: MAX_REFERENCES_PER_SIGNATURE
2855 }
2856 ));
2857 }
2858
2859 #[test]
2860 fn parse_signed_info_bounds_xpath_expressions_across_references() {
2861 let filters = r#"<XPath xmlns="http://www.w3.org/2002/06/xmldsig-filter2" Filter="intersect">true()</XPath>"#
2864 .repeat(64);
2865 let filter_transform = format!(
2866 r#"<Transform Algorithm="http://www.w3.org/2002/06/xmldsig-filter2">{filters}</Transform>"#
2867 );
2868 let reference = |index, transforms: &str| {
2869 format!(
2870 r##"<Reference URI="#item-{index}">
2871 <Transforms>{transforms}</Transforms>
2872 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
2873 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
2874 </Reference>"##
2875 )
2876 };
2877 let signed_info = |references: &str| {
2878 format!(
2879 r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2880 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
2881 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
2882 {references}
2883 </SignedInfo>"#
2884 )
2885 };
2886
2887 let max_reference = reference(0, &filter_transform.repeat(64));
2888 let boundary_xml = signed_info(&max_reference);
2889 let boundary_document =
2890 Document::parse(&boundary_xml).expect("fixed boundary fixture must parse");
2891 parse_signed_info(boundary_document.root_element())
2892 .expect("one maximum-shaped Reference must remain accepted");
2893
2894 let extra_transform = r#"<Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116"><XPath>true()</XPath></Transform>"#;
2895 let xml = signed_info(&format!("{max_reference}{}", reference(1, extra_transform)));
2896 let document = Document::parse(&xml).expect("fixed aggregate fixture must parse");
2897
2898 let error = parse_signed_info(document.root_element())
2899 .expect_err("signature-wide XPath expression count must be bounded");
2900
2901 assert!(
2902 error
2903 .to_string()
2904 .contains("signature-wide XPath expression budget")
2905 );
2906 }
2907
2908 #[test]
2909 fn parse_reference_without_transforms() {
2910 let xml = r##"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2912 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
2913 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
2914 <Reference URI="#obj">
2915 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
2916 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
2917 </Reference>
2918 </SignedInfo>"##;
2919 let doc = Document::parse(xml).unwrap();
2920 let si = parse_signed_info(doc.root_element()).unwrap();
2921
2922 assert!(si.references[0].transforms.is_empty());
2923 }
2924
2925 #[test]
2926 fn parse_reference_with_all_attributes() {
2927 let xml = r##"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2928 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
2929 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
2930 <Reference URI="#data" Id="ref1" Type="http://www.w3.org/2000/09/xmldsig#Object">
2931 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
2932 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
2933 </Reference>
2934 </SignedInfo>"##;
2935 let doc = Document::parse(xml).unwrap();
2936 let si = parse_signed_info(doc.root_element()).unwrap();
2937 let r = &si.references[0];
2938
2939 assert_eq!(r.uri.as_deref(), Some("#data"));
2940 assert_eq!(r.id.as_deref(), Some("ref1"));
2941 assert_eq!(
2942 r.ref_type.as_deref(),
2943 Some("http://www.w3.org/2000/09/xmldsig#Object")
2944 );
2945 }
2946
2947 #[test]
2948 fn parse_reference_absent_uri() {
2949 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2951 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
2952 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
2953 <Reference>
2954 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
2955 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
2956 </Reference>
2957 </SignedInfo>"#;
2958 let doc = Document::parse(xml).unwrap();
2959 let si = parse_signed_info(doc.root_element()).unwrap();
2960 assert!(si.references[0].uri.is_none());
2961 }
2962
2963 #[test]
2964 fn parse_signed_info_preserves_inclusive_prefixes() {
2965 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
2966 xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#">
2967 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
2968 <ec:InclusiveNamespaces PrefixList="ds saml #default"/>
2969 </CanonicalizationMethod>
2970 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
2971 <Reference URI="">
2972 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
2973 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
2974 </Reference>
2975 </SignedInfo>"#;
2976 let doc = Document::parse(xml).unwrap();
2977
2978 let si = parse_signed_info(doc.root_element()).unwrap();
2979 assert!(si.c14n_method.inclusive_prefixes().contains("ds"));
2980 assert!(si.c14n_method.inclusive_prefixes().contains("saml"));
2981 assert!(si.c14n_method.inclusive_prefixes().contains(""));
2982 }
2983
2984 #[test]
2987 fn missing_canonicalization_method() {
2988 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2989 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
2990 <Reference URI="">
2991 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
2992 <DigestValue>dGVzdA==</DigestValue>
2993 </Reference>
2994 </SignedInfo>"#;
2995 let doc = Document::parse(xml).unwrap();
2996 let result = parse_signed_info(doc.root_element());
2997 assert!(result.is_err());
2998 assert!(matches!(
3000 result.unwrap_err(),
3001 ParseError::InvalidStructure(_)
3002 ));
3003 }
3004
3005 #[test]
3006 fn missing_signature_method() {
3007 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3008 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
3009 <Reference URI="">
3010 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
3011 <DigestValue>dGVzdA==</DigestValue>
3012 </Reference>
3013 </SignedInfo>"#;
3014 let doc = Document::parse(xml).unwrap();
3015 let result = parse_signed_info(doc.root_element());
3016 assert!(result.is_err());
3017 assert!(matches!(
3019 result.unwrap_err(),
3020 ParseError::InvalidStructure(_)
3021 ));
3022 }
3023
3024 #[test]
3025 fn no_references() {
3026 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3027 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
3028 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
3029 </SignedInfo>"#;
3030 let doc = Document::parse(xml).unwrap();
3031 let result = parse_signed_info(doc.root_element());
3032 assert!(matches!(
3033 result.unwrap_err(),
3034 ParseError::MissingElement {
3035 element: "Reference"
3036 }
3037 ));
3038 }
3039
3040 #[test]
3041 fn unsupported_c14n_algorithm() {
3042 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3043 <CanonicalizationMethod Algorithm="http://example.com/bogus-c14n"/>
3044 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
3045 <Reference URI="">
3046 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
3047 <DigestValue>dGVzdA==</DigestValue>
3048 </Reference>
3049 </SignedInfo>"#;
3050 let doc = Document::parse(xml).unwrap();
3051 let result = parse_signed_info(doc.root_element());
3052 assert!(matches!(
3053 result.unwrap_err(),
3054 ParseError::UnsupportedAlgorithm { .. }
3055 ));
3056 }
3057
3058 #[test]
3059 fn unsupported_signature_algorithm() {
3060 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3061 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
3062 <SignatureMethod Algorithm="http://example.com/bogus-sign"/>
3063 <Reference URI="">
3064 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
3065 <DigestValue>dGVzdA==</DigestValue>
3066 </Reference>
3067 </SignedInfo>"#;
3068 let doc = Document::parse(xml).unwrap();
3069 let result = parse_signed_info(doc.root_element());
3070 assert!(matches!(
3071 result.unwrap_err(),
3072 ParseError::UnsupportedAlgorithm { .. }
3073 ));
3074 }
3075
3076 #[test]
3077 fn unsupported_digest_algorithm() {
3078 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3079 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
3080 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
3081 <Reference URI="">
3082 <DigestMethod Algorithm="http://example.com/bogus-digest"/>
3083 <DigestValue>dGVzdA==</DigestValue>
3084 </Reference>
3085 </SignedInfo>"#;
3086 let doc = Document::parse(xml).unwrap();
3087 let result = parse_signed_info(doc.root_element());
3088 assert!(matches!(
3089 result.unwrap_err(),
3090 ParseError::UnsupportedAlgorithm { .. }
3091 ));
3092 }
3093
3094 #[test]
3095 fn missing_digest_method() {
3096 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3097 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
3098 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
3099 <Reference URI="">
3100 <DigestValue>dGVzdA==</DigestValue>
3101 </Reference>
3102 </SignedInfo>"#;
3103 let doc = Document::parse(xml).unwrap();
3104 let result = parse_signed_info(doc.root_element());
3105 assert!(result.is_err());
3107 }
3108
3109 #[test]
3110 fn missing_digest_value() {
3111 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3112 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
3113 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
3114 <Reference URI="">
3115 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
3116 </Reference>
3117 </SignedInfo>"#;
3118 let doc = Document::parse(xml).unwrap();
3119 let result = parse_signed_info(doc.root_element());
3120 assert!(matches!(
3121 result.unwrap_err(),
3122 ParseError::MissingElement {
3123 element: "DigestValue"
3124 }
3125 ));
3126 }
3127
3128 #[test]
3129 fn invalid_base64_digest_value() {
3130 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3131 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
3132 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
3133 <Reference URI="">
3134 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
3135 <DigestValue>!!!not-base64!!!</DigestValue>
3136 </Reference>
3137 </SignedInfo>"#;
3138 let doc = Document::parse(xml).unwrap();
3139 let result = parse_signed_info(doc.root_element());
3140 assert!(matches!(result.unwrap_err(), ParseError::Base64(_)));
3141 }
3142
3143 #[test]
3144 fn digest_value_length_must_match_digest_method() {
3145 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3146 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
3147 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
3148 <Reference URI="">
3149 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
3150 <DigestValue>dGVzdA==</DigestValue>
3151 </Reference>
3152 </SignedInfo>"#;
3153 let doc = Document::parse(xml).unwrap();
3154
3155 let result = parse_signed_info(doc.root_element());
3156 assert!(matches!(
3157 result.unwrap_err(),
3158 ParseError::DigestLengthMismatch {
3159 algorithm: "http://www.w3.org/2001/04/xmlenc#sha256",
3160 expected: 32,
3161 actual: 4,
3162 }
3163 ));
3164 }
3165
3166 #[test]
3167 fn inclusive_prefixes_on_inclusive_c14n_is_rejected() {
3168 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
3169 xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#">
3170 <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315">
3171 <ec:InclusiveNamespaces PrefixList="ds"/>
3172 </CanonicalizationMethod>
3173 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
3174 <Reference URI="">
3175 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
3176 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
3177 </Reference>
3178 </SignedInfo>"#;
3179 let doc = Document::parse(xml).unwrap();
3180
3181 let result = parse_signed_info(doc.root_element());
3182 assert!(matches!(
3183 result.unwrap_err(),
3184 ParseError::UnsupportedAlgorithm { .. }
3185 ));
3186 }
3187
3188 #[test]
3189 fn extra_element_after_digest_value() {
3190 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3191 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
3192 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
3193 <Reference URI="">
3194 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
3195 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
3196 <Unexpected/>
3197 </Reference>
3198 </SignedInfo>"#;
3199 let doc = Document::parse(xml).unwrap();
3200 let result = parse_signed_info(doc.root_element());
3201 assert!(matches!(
3202 result.unwrap_err(),
3203 ParseError::InvalidStructure(_)
3204 ));
3205 }
3206
3207 #[test]
3208 fn digest_value_with_element_child_is_rejected() {
3209 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3210 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
3211 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
3212 <Reference URI="">
3213 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
3214 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAA=<Junk/>AAAA</DigestValue>
3215 </Reference>
3216 </SignedInfo>"#;
3217 let doc = Document::parse(xml).unwrap();
3218
3219 let result = parse_signed_info(doc.root_element());
3220 assert!(matches!(
3221 result.unwrap_err(),
3222 ParseError::InvalidStructure(_)
3223 ));
3224 }
3225
3226 #[test]
3227 fn wrong_namespace_on_signed_info() {
3228 let xml = r#"<SignedInfo xmlns="http://example.com/fake">
3229 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
3230 </SignedInfo>"#;
3231 let doc = Document::parse(xml).unwrap();
3232 let result = parse_signed_info(doc.root_element());
3233 assert!(matches!(
3234 result.unwrap_err(),
3235 ParseError::InvalidStructure(_)
3236 ));
3237 }
3238
3239 #[test]
3242 fn base64_with_whitespace() {
3243 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3244 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
3245 <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
3246 <Reference URI="">
3247 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
3248 <DigestValue>
3249 AAAAAAAA
3250 AAAAAAAAAAAAAAAAAAA=
3251 </DigestValue>
3252 </Reference>
3253 </SignedInfo>"#;
3254 let doc = Document::parse(xml).unwrap();
3255 let si = parse_signed_info(doc.root_element()).unwrap();
3256 assert_eq!(si.references[0].digest_value, vec![0u8; 20]);
3257 }
3258
3259 #[test]
3260 fn base64_decode_digest_accepts_xml_whitespace_chars() {
3261 let digest =
3262 base64_decode_digest("AAAA\tAAAA\rAAAA\nAAAA AAAAAAAAAAA=", DigestAlgorithm::Sha1)
3263 .expect("XML whitespace in DigestValue must be accepted");
3264 assert_eq!(digest, vec![0u8; 20]);
3265 }
3266
3267 #[test]
3268 fn base64_decode_digest_rejects_non_xml_ascii_whitespace() {
3269 let err = base64_decode_digest(
3270 "AAAA\u{000C}AAAAAAAAAAAAAAAAAAAAAAA=",
3271 DigestAlgorithm::Sha1,
3272 )
3273 .expect_err("form-feed/vertical-tab in DigestValue must be rejected");
3274 assert!(matches!(err, ParseError::Base64(_)));
3275 }
3276
3277 #[test]
3278 fn base64_decode_digest_rejects_oversized_base64_before_decode() {
3279 let err = base64_decode_digest("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", DigestAlgorithm::Sha1)
3280 .expect_err("oversized DigestValue base64 must fail before decode");
3281 match err {
3282 ParseError::Base64(message) => {
3283 assert!(
3284 message.contains("DigestValue exceeds maximum allowed base64 length"),
3285 "unexpected message: {message}"
3286 );
3287 }
3288 other => panic!("expected ParseError::Base64, got {other:?}"),
3289 }
3290 }
3291
3292 #[test]
3295 fn saml_response_signed_info() {
3296 let xml = r##"<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
3297 <ds:SignedInfo>
3298 <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
3299 <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
3300 <ds:Reference URI="#_resp1">
3301 <ds:Transforms>
3302 <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
3303 <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
3304 </ds:Transforms>
3305 <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
3306 <ds:DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</ds:DigestValue>
3307 </ds:Reference>
3308 </ds:SignedInfo>
3309 <ds:SignatureValue>ZmFrZQ==</ds:SignatureValue>
3310 </ds:Signature>"##;
3311 let doc = Document::parse(xml).unwrap();
3312
3313 let sig_node = doc.root_element();
3315 let signed_info_node = sig_node
3316 .children()
3317 .find(|n| n.is_element() && n.tag_name().name() == "SignedInfo")
3318 .unwrap();
3319
3320 let si = parse_signed_info(signed_info_node).unwrap();
3321 assert_eq!(si.signature_method, SignatureAlgorithm::RsaSha256);
3322 assert_eq!(si.references.len(), 1);
3323 assert_eq!(si.references[0].uri.as_deref(), Some("#_resp1"));
3324 assert_eq!(si.references[0].transforms.len(), 2);
3325 assert_eq!(si.references[0].digest_value, vec![0u8; 32]);
3326 }
3327}