1use roxmltree::{Document, Node};
20
21use super::digest::DigestAlgorithm;
22use super::transforms::{self, Transform};
23use super::whitespace::{is_xml_whitespace_only, normalize_xml_base64_text};
24use crate::c14n::C14nAlgorithm;
25
26pub(crate) const XMLDSIG_NS: &str = "http://www.w3.org/2000/09/xmldsig#";
28pub(crate) const XMLDSIG11_NS: &str = "http://www.w3.org/2009/xmldsig11#";
30const MAX_DER_ENCODED_KEY_VALUE_LEN: usize = 8192;
31const MAX_DER_ENCODED_KEY_VALUE_TEXT_LEN: usize = 65_536;
32const MAX_DER_ENCODED_KEY_VALUE_BASE64_LEN: usize = MAX_DER_ENCODED_KEY_VALUE_LEN.div_ceil(3) * 4;
33const MAX_KEY_NAME_TEXT_LEN: usize = 4096;
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
37pub enum SignatureAlgorithm {
38 RsaSha1,
40 RsaSha256,
42 RsaSha384,
44 RsaSha512,
46 EcdsaP256Sha256,
48 EcdsaP384Sha384,
55}
56
57impl SignatureAlgorithm {
58 #[must_use]
60 pub fn from_uri(uri: &str) -> Option<Self> {
61 match uri {
62 "http://www.w3.org/2000/09/xmldsig#rsa-sha1" => Some(Self::RsaSha1),
63 "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" => Some(Self::RsaSha256),
64 "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384" => Some(Self::RsaSha384),
65 "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512" => Some(Self::RsaSha512),
66 "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256" => Some(Self::EcdsaP256Sha256),
67 "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384" => Some(Self::EcdsaP384Sha384),
68 _ => None,
69 }
70 }
71
72 #[must_use]
74 pub fn uri(self) -> &'static str {
75 match self {
76 Self::RsaSha1 => "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
77 Self::RsaSha256 => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
78 Self::RsaSha384 => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384",
79 Self::RsaSha512 => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512",
80 Self::EcdsaP256Sha256 => "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256",
81 Self::EcdsaP384Sha384 => "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384",
82 }
83 }
84
85 #[must_use]
87 pub fn signing_allowed(self) -> bool {
88 !matches!(self, Self::RsaSha1)
89 }
90}
91
92#[derive(Debug)]
94pub struct SignedInfo {
95 pub c14n_method: C14nAlgorithm,
97 pub signature_method: SignatureAlgorithm,
99 pub references: Vec<Reference>,
101}
102
103#[derive(Debug)]
105pub struct Reference {
106 pub uri: Option<String>,
108 pub id: Option<String>,
110 pub ref_type: Option<String>,
112 pub transforms: Vec<Transform>,
114 pub digest_method: DigestAlgorithm,
116 pub digest_value: Vec<u8>,
118}
119
120#[derive(Debug, Default, Clone, PartialEq, Eq)]
122#[non_exhaustive]
123pub struct KeyInfo {
124 pub sources: Vec<KeyInfoSource>,
126}
127
128#[derive(Debug, Clone, PartialEq, Eq)]
130#[non_exhaustive]
131pub enum KeyInfoSource {
132 KeyName(String),
134 KeyValue(KeyValueInfo),
136 X509Data(X509DataInfo),
138 DerEncodedKeyValue(Vec<u8>),
140}
141
142#[derive(Debug, Clone, PartialEq, Eq)]
144#[non_exhaustive]
145pub enum KeyValueInfo {
146 RsaKeyValue,
148 EcKeyValue,
150 Unsupported {
152 namespace: Option<String>,
154 local_name: String,
156 },
157}
158
159#[derive(Debug, Default, Clone, PartialEq, Eq)]
161#[non_exhaustive]
162pub struct X509DataInfo {
163 pub certificate_count: usize,
165 pub subject_name_count: usize,
167 pub issuer_serial_count: usize,
169 pub ski_count: usize,
171 pub crl_count: usize,
173 pub digest_count: usize,
175}
176
177#[derive(Debug, thiserror::Error)]
179#[non_exhaustive]
180pub enum ParseError {
181 #[error("missing required element: <{element}>")]
183 MissingElement {
184 element: &'static str,
186 },
187
188 #[error("invalid structure: {0}")]
190 InvalidStructure(String),
191
192 #[error("unsupported algorithm: {uri}")]
194 UnsupportedAlgorithm {
195 uri: String,
197 },
198
199 #[error("base64 decode error: {0}")]
201 Base64(String),
202
203 #[error(
205 "digest length mismatch for {algorithm}: expected {expected} bytes, got {actual} bytes"
206 )]
207 DigestLengthMismatch {
208 algorithm: &'static str,
210 expected: usize,
212 actual: usize,
214 },
215
216 #[error("transform error: {0}")]
218 Transform(#[from] super::types::TransformError),
219}
220
221#[must_use]
223pub fn find_signature_node<'a>(doc: &'a Document<'a>) -> Option<Node<'a, 'a>> {
224 doc.descendants().find(|n| {
225 n.is_element()
226 && n.tag_name().name() == "Signature"
227 && n.tag_name().namespace() == Some(XMLDSIG_NS)
228 })
229}
230
231pub fn parse_signed_info(signed_info_node: Node) -> Result<SignedInfo, ParseError> {
236 verify_ds_element(signed_info_node, "SignedInfo")?;
237
238 let mut children = element_children(signed_info_node);
239
240 let c14n_node = children.next().ok_or(ParseError::MissingElement {
242 element: "CanonicalizationMethod",
243 })?;
244 verify_ds_element(c14n_node, "CanonicalizationMethod")?;
245 let c14n_uri = required_algorithm_attr(c14n_node, "CanonicalizationMethod")?;
246 let mut c14n_method =
247 C14nAlgorithm::from_uri(c14n_uri).ok_or_else(|| ParseError::UnsupportedAlgorithm {
248 uri: c14n_uri.to_string(),
249 })?;
250 if let Some(prefix_list) = parse_inclusive_prefixes(c14n_node)? {
251 if c14n_method.mode() == crate::c14n::C14nMode::Exclusive1_0 {
252 c14n_method = c14n_method.with_prefix_list(&prefix_list);
253 } else {
254 return Err(ParseError::UnsupportedAlgorithm {
255 uri: c14n_uri.to_string(),
256 });
257 }
258 }
259
260 let sig_method_node = children.next().ok_or(ParseError::MissingElement {
262 element: "SignatureMethod",
263 })?;
264 verify_ds_element(sig_method_node, "SignatureMethod")?;
265 let sig_uri = required_algorithm_attr(sig_method_node, "SignatureMethod")?;
266 let signature_method =
267 SignatureAlgorithm::from_uri(sig_uri).ok_or_else(|| ParseError::UnsupportedAlgorithm {
268 uri: sig_uri.to_string(),
269 })?;
270
271 let mut references = Vec::new();
273 for child in children {
274 verify_ds_element(child, "Reference")?;
275 references.push(parse_reference(child)?);
276 }
277 if references.is_empty() {
278 return Err(ParseError::MissingElement {
279 element: "Reference",
280 });
281 }
282
283 Ok(SignedInfo {
284 c14n_method,
285 signature_method,
286 references,
287 })
288}
289
290pub(crate) fn parse_reference(reference_node: Node) -> Result<Reference, ParseError> {
294 let uri = reference_node.attribute("URI").map(String::from);
295 let id = reference_node.attribute("Id").map(String::from);
296 let ref_type = reference_node.attribute("Type").map(String::from);
297
298 let mut children = element_children(reference_node);
299
300 let mut transforms = Vec::new();
302 let mut next = children.next().ok_or(ParseError::MissingElement {
303 element: "DigestMethod",
304 })?;
305
306 if next.tag_name().name() == "Transforms" && next.tag_name().namespace() == Some(XMLDSIG_NS) {
307 transforms = transforms::parse_transforms(next)?;
308 next = children.next().ok_or(ParseError::MissingElement {
309 element: "DigestMethod",
310 })?;
311 }
312
313 verify_ds_element(next, "DigestMethod")?;
315 let digest_uri = required_algorithm_attr(next, "DigestMethod")?;
316 let digest_method =
317 DigestAlgorithm::from_uri(digest_uri).ok_or_else(|| ParseError::UnsupportedAlgorithm {
318 uri: digest_uri.to_string(),
319 })?;
320
321 let digest_value_node = children.next().ok_or(ParseError::MissingElement {
323 element: "DigestValue",
324 })?;
325 verify_ds_element(digest_value_node, "DigestValue")?;
326 let digest_value = decode_digest_value_children(digest_value_node, digest_method)?;
327
328 if let Some(unexpected) = children.next() {
330 return Err(ParseError::InvalidStructure(format!(
331 "unexpected element <{}> after <DigestValue> in <Reference>",
332 unexpected.tag_name().name()
333 )));
334 }
335
336 Ok(Reference {
337 uri,
338 id,
339 ref_type,
340 transforms,
341 digest_method,
342 digest_value,
343 })
344}
345
346pub fn parse_key_info(key_info_node: Node) -> Result<KeyInfo, ParseError> {
359 verify_ds_element(key_info_node, "KeyInfo")?;
360 ensure_no_non_whitespace_text(key_info_node, "KeyInfo")?;
361
362 let mut sources = Vec::new();
363 for child in element_children(key_info_node) {
364 match (child.tag_name().namespace(), child.tag_name().name()) {
365 (Some(XMLDSIG_NS), "KeyName") => {
366 ensure_no_element_children(child, "KeyName")?;
367 let key_name =
368 collect_text_content_bounded(child, MAX_KEY_NAME_TEXT_LEN, "KeyName")?;
369 sources.push(KeyInfoSource::KeyName(key_name));
370 }
371 (Some(XMLDSIG_NS), "KeyValue") => {
372 let key_value = parse_key_value_dispatch(child)?;
373 sources.push(KeyInfoSource::KeyValue(key_value));
374 }
375 (Some(XMLDSIG_NS), "X509Data") => {
376 let x509 = parse_x509_data_dispatch(child)?;
377 sources.push(KeyInfoSource::X509Data(x509));
378 }
379 (Some(XMLDSIG11_NS), "DEREncodedKeyValue") => {
380 ensure_no_element_children(child, "DEREncodedKeyValue")?;
381 let der = decode_der_encoded_key_value_base64(child)?;
382 sources.push(KeyInfoSource::DerEncodedKeyValue(der));
383 }
384 _ => {}
385 }
386 }
387
388 Ok(KeyInfo { sources })
389}
390
391fn element_children<'a>(node: Node<'a, 'a>) -> impl Iterator<Item = Node<'a, 'a>> {
395 node.children().filter(|n| n.is_element())
396}
397
398fn verify_ds_element(node: Node, expected_name: &'static str) -> Result<(), ParseError> {
400 if !node.is_element() {
401 return Err(ParseError::InvalidStructure(format!(
402 "expected element <{expected_name}>, got non-element node"
403 )));
404 }
405 let tag = node.tag_name();
406 if tag.name() != expected_name || tag.namespace() != Some(XMLDSIG_NS) {
407 return Err(ParseError::InvalidStructure(format!(
408 "expected <ds:{expected_name}>, got <{}{}>",
409 tag.namespace()
410 .map(|ns| format!("{{{ns}}}"))
411 .unwrap_or_default(),
412 tag.name()
413 )));
414 }
415 Ok(())
416}
417
418fn required_algorithm_attr<'a>(
420 node: Node<'a, 'a>,
421 element_name: &'static str,
422) -> Result<&'a str, ParseError> {
423 node.attribute("Algorithm").ok_or_else(|| {
424 ParseError::InvalidStructure(format!("missing Algorithm attribute on <{element_name}>"))
425 })
426}
427
428fn parse_inclusive_prefixes(node: Node) -> Result<Option<String>, ParseError> {
434 const EXCLUSIVE_C14N_NS_URI: &str = "http://www.w3.org/2001/10/xml-exc-c14n#";
435
436 for child in node.children() {
437 if child.is_element() {
438 let tag = child.tag_name();
439 if tag.name() == "InclusiveNamespaces" && tag.namespace() == Some(EXCLUSIVE_C14N_NS_URI)
440 {
441 return child
442 .attribute("PrefixList")
443 .map(str::to_string)
444 .ok_or_else(|| {
445 ParseError::InvalidStructure(
446 "missing PrefixList attribute on <InclusiveNamespaces>".into(),
447 )
448 })
449 .map(Some);
450 }
451 }
452 }
453
454 Ok(None)
455}
456
457fn parse_key_value_dispatch(node: Node) -> Result<KeyValueInfo, ParseError> {
458 verify_ds_element(node, "KeyValue")?;
459 ensure_no_non_whitespace_text(node, "KeyValue")?;
460
461 let mut children = element_children(node);
462 let Some(first_child) = children.next() else {
463 return Err(ParseError::InvalidStructure(
464 "KeyValue must contain exactly one key-value child".into(),
465 ));
466 };
467 if children.next().is_some() {
468 return Err(ParseError::InvalidStructure(
469 "KeyValue must contain exactly one key-value child".into(),
470 ));
471 }
472
473 match (
474 first_child.tag_name().namespace(),
475 first_child.tag_name().name(),
476 ) {
477 (Some(XMLDSIG_NS), "RSAKeyValue") => Ok(KeyValueInfo::RsaKeyValue),
478 (Some(XMLDSIG11_NS), "ECKeyValue") => Ok(KeyValueInfo::EcKeyValue),
479 (namespace, child_name) => Ok(KeyValueInfo::Unsupported {
480 namespace: namespace.map(str::to_string),
481 local_name: child_name.to_string(),
482 }),
483 }
484}
485
486fn parse_x509_data_dispatch(node: Node) -> Result<X509DataInfo, ParseError> {
487 verify_ds_element(node, "X509Data")?;
488 ensure_no_non_whitespace_text(node, "X509Data")?;
489
490 let mut info = X509DataInfo::default();
491 for child in element_children(node) {
492 match (child.tag_name().namespace(), child.tag_name().name()) {
493 (Some(XMLDSIG_NS), "X509Certificate") => {
494 info.certificate_count += 1;
495 }
496 (Some(XMLDSIG_NS), "X509SubjectName") => {
497 info.subject_name_count += 1;
498 }
499 (Some(XMLDSIG_NS), "X509IssuerSerial") => {
500 info.issuer_serial_count += 1;
501 }
502 (Some(XMLDSIG_NS), "X509SKI") => {
503 info.ski_count += 1;
504 }
505 (Some(XMLDSIG_NS), "X509CRL") => {
506 info.crl_count += 1;
507 }
508 (Some(XMLDSIG11_NS), "X509Digest") => {
509 info.digest_count += 1;
510 }
511 (Some(XMLDSIG_NS), child_name) | (Some(XMLDSIG11_NS), child_name) => {
512 return Err(ParseError::InvalidStructure(format!(
513 "X509Data contains unsupported XMLDSig child element <{child_name}>"
514 )));
515 }
516 _ => {}
517 }
518 }
519
520 Ok(info)
521}
522
523fn base64_decode_digest(b64: &str, digest_method: DigestAlgorithm) -> Result<Vec<u8>, ParseError> {
527 use base64::Engine;
528 use base64::engine::general_purpose::STANDARD;
529
530 let expected = digest_method.output_len();
531 let max_base64_len = expected.div_ceil(3) * 4;
532 let mut cleaned = String::with_capacity(b64.len().min(max_base64_len));
533 normalize_xml_base64_text(b64, &mut cleaned).map_err(|err| {
534 ParseError::Base64(format!(
535 "invalid XML whitespace U+{:04X} in DigestValue",
536 err.invalid_byte
537 ))
538 })?;
539 if cleaned.len() > max_base64_len {
540 return Err(ParseError::Base64(
541 "DigestValue exceeds maximum allowed base64 length".into(),
542 ));
543 }
544 let digest = STANDARD
545 .decode(&cleaned)
546 .map_err(|e| ParseError::Base64(e.to_string()))?;
547 let actual = digest.len();
548 if actual != expected {
549 return Err(ParseError::DigestLengthMismatch {
550 algorithm: digest_method.uri(),
551 expected,
552 actual,
553 });
554 }
555 Ok(digest)
556}
557
558fn decode_digest_value_children(
559 digest_value_node: Node<'_, '_>,
560 digest_method: DigestAlgorithm,
561) -> Result<Vec<u8>, ParseError> {
562 let max_base64_len = digest_method.output_len().div_ceil(3) * 4;
563 let mut cleaned = String::with_capacity(max_base64_len);
564
565 for child in digest_value_node.children() {
566 if child.is_element() {
567 return Err(ParseError::InvalidStructure(
568 "DigestValue must not contain element children".into(),
569 ));
570 }
571 if let Some(text) = child.text() {
572 normalize_xml_base64_text(text, &mut cleaned).map_err(|err| {
573 ParseError::Base64(format!(
574 "invalid XML whitespace U+{:04X} in DigestValue",
575 err.invalid_byte
576 ))
577 })?;
578 if cleaned.len() > max_base64_len {
579 return Err(ParseError::Base64(
580 "DigestValue exceeds maximum allowed base64 length".into(),
581 ));
582 }
583 }
584 }
585
586 base64_decode_digest(&cleaned, digest_method)
587}
588
589fn decode_der_encoded_key_value_base64(node: Node<'_, '_>) -> Result<Vec<u8>, ParseError> {
590 use base64::Engine;
591 use base64::engine::general_purpose::STANDARD;
592
593 let mut cleaned = String::new();
594 let mut raw_text_len = 0usize;
595 for text in node
596 .children()
597 .filter(|child| child.is_text())
598 .filter_map(|child| child.text())
599 {
600 if raw_text_len.saturating_add(text.len()) > MAX_DER_ENCODED_KEY_VALUE_TEXT_LEN {
601 return Err(ParseError::InvalidStructure(
602 "DEREncodedKeyValue exceeds maximum allowed text length".into(),
603 ));
604 }
605 raw_text_len = raw_text_len.saturating_add(text.len());
606 normalize_xml_base64_text(text, &mut cleaned).map_err(|err| {
607 ParseError::Base64(format!(
608 "invalid XML whitespace U+{:04X} in base64 text",
609 err.invalid_byte
610 ))
611 })?;
612 if cleaned.len() > MAX_DER_ENCODED_KEY_VALUE_BASE64_LEN {
613 return Err(ParseError::InvalidStructure(
614 "DEREncodedKeyValue exceeds maximum allowed length".into(),
615 ));
616 }
617 }
618
619 let der = STANDARD
620 .decode(&cleaned)
621 .map_err(|e| ParseError::Base64(e.to_string()))?;
622 if der.is_empty() {
623 return Err(ParseError::InvalidStructure(
624 "DEREncodedKeyValue must not be empty".into(),
625 ));
626 }
627 if der.len() > MAX_DER_ENCODED_KEY_VALUE_LEN {
628 return Err(ParseError::InvalidStructure(
629 "DEREncodedKeyValue exceeds maximum allowed length".into(),
630 ));
631 }
632 Ok(der)
633}
634
635fn collect_text_content_bounded(
636 node: Node<'_, '_>,
637 max_len: usize,
638 element_name: &'static str,
639) -> Result<String, ParseError> {
640 let mut text = String::new();
641 for chunk in node
642 .children()
643 .filter_map(|child| child.is_text().then(|| child.text()).flatten())
644 {
645 if text.len().saturating_add(chunk.len()) > max_len {
646 return Err(ParseError::InvalidStructure(format!(
647 "{element_name} exceeds maximum allowed text length"
648 )));
649 }
650 text.push_str(chunk);
651 }
652 Ok(text)
653}
654
655fn ensure_no_element_children(node: Node<'_, '_>, element_name: &str) -> Result<(), ParseError> {
656 if node.children().any(|child| child.is_element()) {
657 return Err(ParseError::InvalidStructure(format!(
658 "{element_name} must not contain child elements"
659 )));
660 }
661 Ok(())
662}
663
664fn ensure_no_non_whitespace_text(node: Node<'_, '_>, element_name: &str) -> Result<(), ParseError> {
665 for child in node.children().filter(|child| child.is_text()) {
666 if let Some(text) = child.text()
667 && !is_xml_whitespace_only(text)
668 {
669 return Err(ParseError::InvalidStructure(format!(
670 "{element_name} must not contain non-whitespace mixed content"
671 )));
672 }
673 }
674 Ok(())
675}
676
677#[cfg(test)]
678#[expect(clippy::unwrap_used, reason = "tests use trusted XML fixtures")]
679mod tests {
680 use super::*;
681 use base64::Engine;
682
683 #[test]
686 fn signature_algorithm_from_uri_rsa_sha256() {
687 assert_eq!(
688 SignatureAlgorithm::from_uri("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"),
689 Some(SignatureAlgorithm::RsaSha256)
690 );
691 }
692
693 #[test]
694 fn signature_algorithm_from_uri_rsa_sha1() {
695 assert_eq!(
696 SignatureAlgorithm::from_uri("http://www.w3.org/2000/09/xmldsig#rsa-sha1"),
697 Some(SignatureAlgorithm::RsaSha1)
698 );
699 }
700
701 #[test]
702 fn signature_algorithm_from_uri_ecdsa_sha256() {
703 assert_eq!(
704 SignatureAlgorithm::from_uri("http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"),
705 Some(SignatureAlgorithm::EcdsaP256Sha256)
706 );
707 }
708
709 #[test]
710 fn signature_algorithm_from_uri_unknown() {
711 assert_eq!(
712 SignatureAlgorithm::from_uri("http://example.com/unknown"),
713 None
714 );
715 }
716
717 #[test]
718 fn signature_algorithm_uri_round_trip() {
719 for algo in [
720 SignatureAlgorithm::RsaSha1,
721 SignatureAlgorithm::RsaSha256,
722 SignatureAlgorithm::RsaSha384,
723 SignatureAlgorithm::RsaSha512,
724 SignatureAlgorithm::EcdsaP256Sha256,
725 SignatureAlgorithm::EcdsaP384Sha384,
726 ] {
727 assert_eq!(
728 SignatureAlgorithm::from_uri(algo.uri()),
729 Some(algo),
730 "round-trip failed for {algo:?}"
731 );
732 }
733 }
734
735 #[test]
736 fn rsa_sha1_verify_only() {
737 assert!(!SignatureAlgorithm::RsaSha1.signing_allowed());
738 assert!(SignatureAlgorithm::RsaSha256.signing_allowed());
739 assert!(SignatureAlgorithm::EcdsaP256Sha256.signing_allowed());
740 }
741
742 #[test]
745 fn find_signature_in_saml() {
746 let xml = r#"<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
747 <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
748 <ds:SignedInfo/>
749 </ds:Signature>
750 </samlp:Response>"#;
751 let doc = Document::parse(xml).unwrap();
752 let sig = find_signature_node(&doc);
753 assert!(sig.is_some());
754 assert_eq!(sig.unwrap().tag_name().name(), "Signature");
755 }
756
757 #[test]
758 fn find_signature_missing() {
759 let xml = "<root><child/></root>";
760 let doc = Document::parse(xml).unwrap();
761 assert!(find_signature_node(&doc).is_none());
762 }
763
764 #[test]
765 fn find_signature_ignores_wrong_namespace() {
766 let xml = r#"<root><Signature xmlns="http://example.com/fake"/></root>"#;
767 let doc = Document::parse(xml).unwrap();
768 assert!(find_signature_node(&doc).is_none());
769 }
770
771 #[test]
774 fn parse_key_info_dispatches_supported_children() {
775 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
776 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
777 <KeyName>idp-signing-key</KeyName>
778 <KeyValue>
779 <RSAKeyValue>
780 <Modulus>AQAB</Modulus>
781 <Exponent>AQAB</Exponent>
782 </RSAKeyValue>
783 </KeyValue>
784 <X509Data>
785 <X509Certificate>MIIB</X509Certificate>
786 <X509SubjectName>CN=Example</X509SubjectName>
787 </X509Data>
788 <dsig11:DEREncodedKeyValue>AQIDBA==</dsig11:DEREncodedKeyValue>
789 </KeyInfo>"#;
790 let doc = Document::parse(xml).unwrap();
791
792 let key_info = parse_key_info(doc.root_element()).unwrap();
793 assert_eq!(key_info.sources.len(), 4);
794
795 assert_eq!(
796 key_info.sources[0],
797 KeyInfoSource::KeyName("idp-signing-key".to_string())
798 );
799 assert_eq!(
800 key_info.sources[1],
801 KeyInfoSource::KeyValue(KeyValueInfo::RsaKeyValue)
802 );
803 assert_eq!(
804 key_info.sources[2],
805 KeyInfoSource::X509Data(X509DataInfo {
806 certificate_count: 1,
807 subject_name_count: 1,
808 issuer_serial_count: 0,
809 ski_count: 0,
810 crl_count: 0,
811 digest_count: 0,
812 })
813 );
814 assert_eq!(
815 key_info.sources[3],
816 KeyInfoSource::DerEncodedKeyValue(vec![1, 2, 3, 4])
817 );
818 }
819
820 #[test]
821 fn parse_key_info_ignores_unknown_children() {
822 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
823 <Foo>bar</Foo>
824 <KeyName>ok</KeyName>
825 </KeyInfo>"#;
826 let doc = Document::parse(xml).unwrap();
827
828 let key_info = parse_key_info(doc.root_element()).unwrap();
829 assert_eq!(key_info.sources, vec![KeyInfoSource::KeyName("ok".into())]);
830 }
831
832 #[test]
833 fn parse_key_info_keyvalue_requires_single_child() {
834 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
835 <KeyValue/>
836 </KeyInfo>"#;
837 let doc = Document::parse(xml).unwrap();
838
839 let err = parse_key_info(doc.root_element()).unwrap_err();
840 assert!(matches!(err, ParseError::InvalidStructure(_)));
841 }
842
843 #[test]
844 fn parse_key_info_accepts_empty_x509data() {
845 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
846 <X509Data/>
847 </KeyInfo>"#;
848 let doc = Document::parse(xml).unwrap();
849
850 let key_info = parse_key_info(doc.root_element()).unwrap();
851 assert_eq!(
852 key_info.sources,
853 vec![KeyInfoSource::X509Data(X509DataInfo::default())]
854 );
855 }
856
857 #[test]
858 fn parse_key_info_rejects_unknown_xmlsig_child_in_x509data() {
859 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
860 <X509Data>
861 <Foo/>
862 </X509Data>
863 </KeyInfo>"#;
864 let doc = Document::parse(xml).unwrap();
865
866 let err = parse_key_info(doc.root_element()).unwrap_err();
867 assert!(matches!(err, ParseError::InvalidStructure(_)));
868 }
869
870 #[test]
871 fn parse_key_info_rejects_unknown_xmlsig11_child_in_x509data() {
872 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
873 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
874 <X509Data>
875 <dsig11:Foo/>
876 </X509Data>
877 </KeyInfo>"#;
878 let doc = Document::parse(xml).unwrap();
879
880 let err = parse_key_info(doc.root_element()).unwrap_err();
881 assert!(matches!(err, ParseError::InvalidStructure(_)));
882 }
883
884 #[test]
885 fn parse_key_info_accepts_x509data_with_only_foreign_namespace_children() {
886 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
887 xmlns:foo="urn:example:foo">
888 <X509Data>
889 <foo:Bar/>
890 </X509Data>
891 </KeyInfo>"#;
892 let doc = Document::parse(xml).unwrap();
893
894 let key_info = parse_key_info(doc.root_element()).unwrap();
895 assert_eq!(
896 key_info.sources,
897 vec![KeyInfoSource::X509Data(X509DataInfo::default())]
898 );
899 }
900
901 #[test]
902 fn parse_key_info_der_encoded_key_value_rejects_invalid_base64() {
903 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
904 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
905 <dsig11:DEREncodedKeyValue>%%%invalid%%%</dsig11:DEREncodedKeyValue>
906 </KeyInfo>"#;
907 let doc = Document::parse(xml).unwrap();
908
909 let err = parse_key_info(doc.root_element()).unwrap_err();
910 assert!(matches!(err, ParseError::Base64(_)));
911 }
912
913 #[test]
914 fn parse_key_info_der_encoded_key_value_accepts_xml_whitespace() {
915 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
916 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
917 <dsig11:DEREncodedKeyValue>
918 AQID
919 BA==
920 </dsig11:DEREncodedKeyValue>
921 </KeyInfo>"#;
922 let doc = Document::parse(xml).unwrap();
923
924 let key_info = parse_key_info(doc.root_element()).unwrap();
925 assert_eq!(
926 key_info.sources,
927 vec![KeyInfoSource::DerEncodedKeyValue(vec![1, 2, 3, 4])]
928 );
929 }
930
931 #[test]
932 fn parse_key_info_dispatches_dsig11_ec_keyvalue() {
933 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
934 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
935 <KeyValue>
936 <dsig11:ECKeyValue/>
937 </KeyValue>
938 </KeyInfo>"#;
939 let doc = Document::parse(xml).unwrap();
940
941 let key_info = parse_key_info(doc.root_element()).unwrap();
942 assert_eq!(
943 key_info.sources,
944 vec![KeyInfoSource::KeyValue(KeyValueInfo::EcKeyValue)]
945 );
946 }
947
948 #[test]
949 fn parse_key_info_marks_ds_namespace_ec_keyvalue_as_unsupported() {
950 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
951 <KeyValue>
952 <ECKeyValue/>
953 </KeyValue>
954 </KeyInfo>"#;
955 let doc = Document::parse(xml).unwrap();
956
957 let key_info = parse_key_info(doc.root_element()).unwrap();
958 assert_eq!(
959 key_info.sources,
960 vec![KeyInfoSource::KeyValue(KeyValueInfo::Unsupported {
961 namespace: Some(XMLDSIG_NS.to_string()),
962 local_name: "ECKeyValue".into(),
963 })]
964 );
965 }
966
967 #[test]
968 fn parse_key_info_keeps_unsupported_keyvalue_child_as_marker() {
969 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
970 <KeyValue>
971 <DSAKeyValue/>
972 </KeyValue>
973 </KeyInfo>"#;
974 let doc = Document::parse(xml).unwrap();
975
976 let key_info = parse_key_info(doc.root_element()).unwrap();
977 assert_eq!(
978 key_info.sources,
979 vec![KeyInfoSource::KeyValue(KeyValueInfo::Unsupported {
980 namespace: Some(XMLDSIG_NS.to_string()),
981 local_name: "DSAKeyValue".into(),
982 })]
983 );
984 }
985
986 #[test]
987 fn parse_key_info_rejects_keyname_with_child_elements() {
988 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
989 <KeyName>ok<foo/></KeyName>
990 </KeyInfo>"#;
991 let doc = Document::parse(xml).unwrap();
992
993 let err = parse_key_info(doc.root_element()).unwrap_err();
994 assert!(matches!(err, ParseError::InvalidStructure(_)));
995 }
996
997 #[test]
998 fn parse_key_info_preserves_keyname_text_without_trimming() {
999 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1000 <KeyName> signing key </KeyName>
1001 </KeyInfo>"#;
1002 let doc = Document::parse(xml).unwrap();
1003
1004 let key_info = parse_key_info(doc.root_element()).unwrap();
1005 assert_eq!(
1006 key_info.sources,
1007 vec![KeyInfoSource::KeyName(" signing key ".into())]
1008 );
1009 }
1010
1011 #[test]
1012 fn parse_key_info_rejects_oversized_keyname_text() {
1013 let oversized = "A".repeat(4097);
1014 let xml = format!(
1015 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><KeyName>{oversized}</KeyName></KeyInfo>"
1016 );
1017 let doc = Document::parse(&xml).unwrap();
1018
1019 let err = parse_key_info(doc.root_element()).unwrap_err();
1020 assert!(matches!(err, ParseError::InvalidStructure(_)));
1021 }
1022
1023 #[test]
1024 fn parse_key_info_rejects_non_whitespace_mixed_content() {
1025 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">oops<KeyName>k</KeyName></KeyInfo>"#;
1026 let doc = Document::parse(xml).unwrap();
1027
1028 let err = parse_key_info(doc.root_element()).unwrap_err();
1029 assert!(matches!(err, ParseError::InvalidStructure(_)));
1030 }
1031
1032 #[test]
1033 fn parse_key_info_rejects_nbsp_as_non_xml_whitespace_mixed_content() {
1034 let xml = "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\u{00A0}<KeyName>k</KeyName></KeyInfo>";
1035 let doc = Document::parse(xml).unwrap();
1036
1037 let err = parse_key_info(doc.root_element()).unwrap_err();
1038 assert!(matches!(err, ParseError::InvalidStructure(_)));
1039 }
1040
1041 #[test]
1042 fn parse_key_info_der_encoded_key_value_rejects_oversized_payload() {
1043 let oversized =
1044 base64::engine::general_purpose::STANDARD
1045 .encode(vec![0u8; MAX_DER_ENCODED_KEY_VALUE_LEN + 1]);
1046 let xml = format!(
1047 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\" xmlns:dsig11=\"http://www.w3.org/2009/xmldsig11#\"><dsig11:DEREncodedKeyValue>{oversized}</dsig11:DEREncodedKeyValue></KeyInfo>"
1048 );
1049 let doc = Document::parse(&xml).unwrap();
1050
1051 let err = parse_key_info(doc.root_element()).unwrap_err();
1052 assert!(matches!(err, ParseError::InvalidStructure(_)));
1053 }
1054
1055 #[test]
1056 fn parse_key_info_der_encoded_key_value_rejects_empty_payload() {
1057 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
1058 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
1059 <dsig11:DEREncodedKeyValue>
1060
1061 </dsig11:DEREncodedKeyValue>
1062 </KeyInfo>"#;
1063 let doc = Document::parse(xml).unwrap();
1064
1065 let err = parse_key_info(doc.root_element()).unwrap_err();
1066 assert!(matches!(err, ParseError::InvalidStructure(_)));
1067 }
1068
1069 #[test]
1070 fn parse_key_info_der_encoded_key_value_non_xml_ascii_whitespace_is_not_parseable_xml() {
1071 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>";
1072 assert!(Document::parse(xml).is_err());
1073 }
1074
1075 #[test]
1078 fn parse_signed_info_rsa_sha256_with_reference() {
1079 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1080 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1081 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
1082 <Reference URI="">
1083 <Transforms>
1084 <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
1085 <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1086 </Transforms>
1087 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
1088 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
1089 </Reference>
1090 </SignedInfo>"#;
1091 let doc = Document::parse(xml).unwrap();
1092 let si = parse_signed_info(doc.root_element()).unwrap();
1093
1094 assert_eq!(si.signature_method, SignatureAlgorithm::RsaSha256);
1095 assert_eq!(si.references.len(), 1);
1096
1097 let r = &si.references[0];
1098 assert_eq!(r.uri.as_deref(), Some(""));
1099 assert_eq!(r.digest_method, DigestAlgorithm::Sha256);
1100 assert_eq!(r.digest_value, vec![0u8; 32]);
1101 assert_eq!(r.transforms.len(), 2);
1102 }
1103
1104 #[test]
1105 fn parse_signed_info_multiple_references() {
1106 let xml = r##"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1107 <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
1108 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"/>
1109 <Reference URI="#a">
1110 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
1111 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
1112 </Reference>
1113 <Reference URI="#b">
1114 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
1115 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
1116 </Reference>
1117 </SignedInfo>"##;
1118 let doc = Document::parse(xml).unwrap();
1119 let si = parse_signed_info(doc.root_element()).unwrap();
1120
1121 assert_eq!(si.signature_method, SignatureAlgorithm::EcdsaP256Sha256);
1122 assert_eq!(si.references.len(), 2);
1123 assert_eq!(si.references[0].uri.as_deref(), Some("#a"));
1124 assert_eq!(si.references[0].digest_method, DigestAlgorithm::Sha256);
1125 assert_eq!(si.references[1].uri.as_deref(), Some("#b"));
1126 assert_eq!(si.references[1].digest_method, DigestAlgorithm::Sha1);
1127 }
1128
1129 #[test]
1130 fn parse_reference_without_transforms() {
1131 let xml = r##"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1133 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1134 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
1135 <Reference URI="#obj">
1136 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
1137 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
1138 </Reference>
1139 </SignedInfo>"##;
1140 let doc = Document::parse(xml).unwrap();
1141 let si = parse_signed_info(doc.root_element()).unwrap();
1142
1143 assert!(si.references[0].transforms.is_empty());
1144 }
1145
1146 #[test]
1147 fn parse_reference_with_all_attributes() {
1148 let xml = r##"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1149 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1150 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
1151 <Reference URI="#data" Id="ref1" Type="http://www.w3.org/2000/09/xmldsig#Object">
1152 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
1153 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
1154 </Reference>
1155 </SignedInfo>"##;
1156 let doc = Document::parse(xml).unwrap();
1157 let si = parse_signed_info(doc.root_element()).unwrap();
1158 let r = &si.references[0];
1159
1160 assert_eq!(r.uri.as_deref(), Some("#data"));
1161 assert_eq!(r.id.as_deref(), Some("ref1"));
1162 assert_eq!(
1163 r.ref_type.as_deref(),
1164 Some("http://www.w3.org/2000/09/xmldsig#Object")
1165 );
1166 }
1167
1168 #[test]
1169 fn parse_reference_absent_uri() {
1170 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1172 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1173 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
1174 <Reference>
1175 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
1176 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
1177 </Reference>
1178 </SignedInfo>"#;
1179 let doc = Document::parse(xml).unwrap();
1180 let si = parse_signed_info(doc.root_element()).unwrap();
1181 assert!(si.references[0].uri.is_none());
1182 }
1183
1184 #[test]
1185 fn parse_signed_info_preserves_inclusive_prefixes() {
1186 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
1187 xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#">
1188 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
1189 <ec:InclusiveNamespaces PrefixList="ds saml #default"/>
1190 </CanonicalizationMethod>
1191 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
1192 <Reference URI="">
1193 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
1194 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
1195 </Reference>
1196 </SignedInfo>"#;
1197 let doc = Document::parse(xml).unwrap();
1198
1199 let si = parse_signed_info(doc.root_element()).unwrap();
1200 assert!(si.c14n_method.inclusive_prefixes().contains("ds"));
1201 assert!(si.c14n_method.inclusive_prefixes().contains("saml"));
1202 assert!(si.c14n_method.inclusive_prefixes().contains(""));
1203 }
1204
1205 #[test]
1208 fn missing_canonicalization_method() {
1209 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1210 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
1211 <Reference URI="">
1212 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
1213 <DigestValue>dGVzdA==</DigestValue>
1214 </Reference>
1215 </SignedInfo>"#;
1216 let doc = Document::parse(xml).unwrap();
1217 let result = parse_signed_info(doc.root_element());
1218 assert!(result.is_err());
1219 assert!(matches!(
1221 result.unwrap_err(),
1222 ParseError::InvalidStructure(_)
1223 ));
1224 }
1225
1226 #[test]
1227 fn missing_signature_method() {
1228 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1229 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1230 <Reference URI="">
1231 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
1232 <DigestValue>dGVzdA==</DigestValue>
1233 </Reference>
1234 </SignedInfo>"#;
1235 let doc = Document::parse(xml).unwrap();
1236 let result = parse_signed_info(doc.root_element());
1237 assert!(result.is_err());
1238 assert!(matches!(
1240 result.unwrap_err(),
1241 ParseError::InvalidStructure(_)
1242 ));
1243 }
1244
1245 #[test]
1246 fn no_references() {
1247 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1248 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1249 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
1250 </SignedInfo>"#;
1251 let doc = Document::parse(xml).unwrap();
1252 let result = parse_signed_info(doc.root_element());
1253 assert!(matches!(
1254 result.unwrap_err(),
1255 ParseError::MissingElement {
1256 element: "Reference"
1257 }
1258 ));
1259 }
1260
1261 #[test]
1262 fn unsupported_c14n_algorithm() {
1263 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1264 <CanonicalizationMethod Algorithm="http://example.com/bogus-c14n"/>
1265 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
1266 <Reference URI="">
1267 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
1268 <DigestValue>dGVzdA==</DigestValue>
1269 </Reference>
1270 </SignedInfo>"#;
1271 let doc = Document::parse(xml).unwrap();
1272 let result = parse_signed_info(doc.root_element());
1273 assert!(matches!(
1274 result.unwrap_err(),
1275 ParseError::UnsupportedAlgorithm { .. }
1276 ));
1277 }
1278
1279 #[test]
1280 fn unsupported_signature_algorithm() {
1281 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1282 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1283 <SignatureMethod Algorithm="http://example.com/bogus-sign"/>
1284 <Reference URI="">
1285 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
1286 <DigestValue>dGVzdA==</DigestValue>
1287 </Reference>
1288 </SignedInfo>"#;
1289 let doc = Document::parse(xml).unwrap();
1290 let result = parse_signed_info(doc.root_element());
1291 assert!(matches!(
1292 result.unwrap_err(),
1293 ParseError::UnsupportedAlgorithm { .. }
1294 ));
1295 }
1296
1297 #[test]
1298 fn unsupported_digest_algorithm() {
1299 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1300 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1301 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
1302 <Reference URI="">
1303 <DigestMethod Algorithm="http://example.com/bogus-digest"/>
1304 <DigestValue>dGVzdA==</DigestValue>
1305 </Reference>
1306 </SignedInfo>"#;
1307 let doc = Document::parse(xml).unwrap();
1308 let result = parse_signed_info(doc.root_element());
1309 assert!(matches!(
1310 result.unwrap_err(),
1311 ParseError::UnsupportedAlgorithm { .. }
1312 ));
1313 }
1314
1315 #[test]
1316 fn missing_digest_method() {
1317 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1318 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1319 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
1320 <Reference URI="">
1321 <DigestValue>dGVzdA==</DigestValue>
1322 </Reference>
1323 </SignedInfo>"#;
1324 let doc = Document::parse(xml).unwrap();
1325 let result = parse_signed_info(doc.root_element());
1326 assert!(result.is_err());
1328 }
1329
1330 #[test]
1331 fn missing_digest_value() {
1332 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1333 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1334 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
1335 <Reference URI="">
1336 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
1337 </Reference>
1338 </SignedInfo>"#;
1339 let doc = Document::parse(xml).unwrap();
1340 let result = parse_signed_info(doc.root_element());
1341 assert!(matches!(
1342 result.unwrap_err(),
1343 ParseError::MissingElement {
1344 element: "DigestValue"
1345 }
1346 ));
1347 }
1348
1349 #[test]
1350 fn invalid_base64_digest_value() {
1351 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1352 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1353 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
1354 <Reference URI="">
1355 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
1356 <DigestValue>!!!not-base64!!!</DigestValue>
1357 </Reference>
1358 </SignedInfo>"#;
1359 let doc = Document::parse(xml).unwrap();
1360 let result = parse_signed_info(doc.root_element());
1361 assert!(matches!(result.unwrap_err(), ParseError::Base64(_)));
1362 }
1363
1364 #[test]
1365 fn digest_value_length_must_match_digest_method() {
1366 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1367 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1368 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
1369 <Reference URI="">
1370 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
1371 <DigestValue>dGVzdA==</DigestValue>
1372 </Reference>
1373 </SignedInfo>"#;
1374 let doc = Document::parse(xml).unwrap();
1375
1376 let result = parse_signed_info(doc.root_element());
1377 assert!(matches!(
1378 result.unwrap_err(),
1379 ParseError::DigestLengthMismatch {
1380 algorithm: "http://www.w3.org/2001/04/xmlenc#sha256",
1381 expected: 32,
1382 actual: 4,
1383 }
1384 ));
1385 }
1386
1387 #[test]
1388 fn inclusive_prefixes_on_inclusive_c14n_is_rejected() {
1389 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
1390 xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#">
1391 <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315">
1392 <ec:InclusiveNamespaces PrefixList="ds"/>
1393 </CanonicalizationMethod>
1394 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
1395 <Reference URI="">
1396 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
1397 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
1398 </Reference>
1399 </SignedInfo>"#;
1400 let doc = Document::parse(xml).unwrap();
1401
1402 let result = parse_signed_info(doc.root_element());
1403 assert!(matches!(
1404 result.unwrap_err(),
1405 ParseError::UnsupportedAlgorithm { .. }
1406 ));
1407 }
1408
1409 #[test]
1410 fn extra_element_after_digest_value() {
1411 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1412 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1413 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
1414 <Reference URI="">
1415 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
1416 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
1417 <Unexpected/>
1418 </Reference>
1419 </SignedInfo>"#;
1420 let doc = Document::parse(xml).unwrap();
1421 let result = parse_signed_info(doc.root_element());
1422 assert!(matches!(
1423 result.unwrap_err(),
1424 ParseError::InvalidStructure(_)
1425 ));
1426 }
1427
1428 #[test]
1429 fn digest_value_with_element_child_is_rejected() {
1430 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1431 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1432 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
1433 <Reference URI="">
1434 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
1435 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAA=<Junk/>AAAA</DigestValue>
1436 </Reference>
1437 </SignedInfo>"#;
1438 let doc = Document::parse(xml).unwrap();
1439
1440 let result = parse_signed_info(doc.root_element());
1441 assert!(matches!(
1442 result.unwrap_err(),
1443 ParseError::InvalidStructure(_)
1444 ));
1445 }
1446
1447 #[test]
1448 fn wrong_namespace_on_signed_info() {
1449 let xml = r#"<SignedInfo xmlns="http://example.com/fake">
1450 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1451 </SignedInfo>"#;
1452 let doc = Document::parse(xml).unwrap();
1453 let result = parse_signed_info(doc.root_element());
1454 assert!(matches!(
1455 result.unwrap_err(),
1456 ParseError::InvalidStructure(_)
1457 ));
1458 }
1459
1460 #[test]
1463 fn base64_with_whitespace() {
1464 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1465 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1466 <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
1467 <Reference URI="">
1468 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
1469 <DigestValue>
1470 AAAAAAAA
1471 AAAAAAAAAAAAAAAAAAA=
1472 </DigestValue>
1473 </Reference>
1474 </SignedInfo>"#;
1475 let doc = Document::parse(xml).unwrap();
1476 let si = parse_signed_info(doc.root_element()).unwrap();
1477 assert_eq!(si.references[0].digest_value, vec![0u8; 20]);
1478 }
1479
1480 #[test]
1481 fn base64_decode_digest_accepts_xml_whitespace_chars() {
1482 let digest =
1483 base64_decode_digest("AAAA\tAAAA\rAAAA\nAAAA AAAAAAAAAAA=", DigestAlgorithm::Sha1)
1484 .expect("XML whitespace in DigestValue must be accepted");
1485 assert_eq!(digest, vec![0u8; 20]);
1486 }
1487
1488 #[test]
1489 fn base64_decode_digest_rejects_non_xml_ascii_whitespace() {
1490 let err = base64_decode_digest(
1491 "AAAA\u{000C}AAAAAAAAAAAAAAAAAAAAAAA=",
1492 DigestAlgorithm::Sha1,
1493 )
1494 .expect_err("form-feed/vertical-tab in DigestValue must be rejected");
1495 assert!(matches!(err, ParseError::Base64(_)));
1496 }
1497
1498 #[test]
1499 fn base64_decode_digest_rejects_oversized_base64_before_decode() {
1500 let err = base64_decode_digest("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", DigestAlgorithm::Sha1)
1501 .expect_err("oversized DigestValue base64 must fail before decode");
1502 match err {
1503 ParseError::Base64(message) => {
1504 assert!(
1505 message.contains("DigestValue exceeds maximum allowed base64 length"),
1506 "unexpected message: {message}"
1507 );
1508 }
1509 other => panic!("expected ParseError::Base64, got {other:?}"),
1510 }
1511 }
1512
1513 #[test]
1516 fn saml_response_signed_info() {
1517 let xml = r##"<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
1518 <ds:SignedInfo>
1519 <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1520 <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
1521 <ds:Reference URI="#_resp1">
1522 <ds:Transforms>
1523 <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
1524 <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1525 </ds:Transforms>
1526 <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
1527 <ds:DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</ds:DigestValue>
1528 </ds:Reference>
1529 </ds:SignedInfo>
1530 <ds:SignatureValue>ZmFrZQ==</ds:SignatureValue>
1531 </ds:Signature>"##;
1532 let doc = Document::parse(xml).unwrap();
1533
1534 let sig_node = doc.root_element();
1536 let signed_info_node = sig_node
1537 .children()
1538 .find(|n| n.is_element() && n.tag_name().name() == "SignedInfo")
1539 .unwrap();
1540
1541 let si = parse_signed_info(signed_info_node).unwrap();
1542 assert_eq!(si.signature_method, SignatureAlgorithm::RsaSha256);
1543 assert_eq!(si.references.len(), 1);
1544 assert_eq!(si.references[0].uri.as_deref(), Some("#_resp1"));
1545 assert_eq!(si.references[0].transforms.len(), 2);
1546 assert_eq!(si.references[0].digest_value, vec![0u8; 32]);
1547 }
1548}