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;
34const MAX_X509_BASE64_TEXT_LEN: usize = 262_144;
35const MAX_X509_BASE64_NORMALIZED_LEN: usize = MAX_X509_BASE64_TEXT_LEN;
36const MAX_X509_DECODED_BINARY_LEN: usize = MAX_X509_BASE64_NORMALIZED_LEN.div_ceil(4) * 3;
37const MAX_X509_SUBJECT_NAME_TEXT_LEN: usize = 16_384;
38const MAX_X509_ISSUER_NAME_TEXT_LEN: usize = 16_384;
39const MAX_X509_SERIAL_NUMBER_TEXT_LEN: usize = 4096;
40const MAX_X509_DATA_ENTRY_COUNT: usize = 64;
41const MAX_X509_DATA_TOTAL_BINARY_LEN: usize = 1_048_576;
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
45pub enum SignatureAlgorithm {
46 RsaSha1,
48 RsaSha256,
50 RsaSha384,
52 RsaSha512,
54 EcdsaP256Sha256,
56 EcdsaP384Sha384,
63}
64
65impl SignatureAlgorithm {
66 #[must_use]
68 pub fn from_uri(uri: &str) -> Option<Self> {
69 match uri {
70 "http://www.w3.org/2000/09/xmldsig#rsa-sha1" => Some(Self::RsaSha1),
71 "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" => Some(Self::RsaSha256),
72 "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384" => Some(Self::RsaSha384),
73 "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512" => Some(Self::RsaSha512),
74 "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256" => Some(Self::EcdsaP256Sha256),
75 "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384" => Some(Self::EcdsaP384Sha384),
76 _ => None,
77 }
78 }
79
80 #[must_use]
82 pub fn uri(self) -> &'static str {
83 match self {
84 Self::RsaSha1 => "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
85 Self::RsaSha256 => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
86 Self::RsaSha384 => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384",
87 Self::RsaSha512 => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512",
88 Self::EcdsaP256Sha256 => "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256",
89 Self::EcdsaP384Sha384 => "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384",
90 }
91 }
92
93 #[must_use]
95 pub fn signing_allowed(self) -> bool {
96 !matches!(self, Self::RsaSha1)
97 }
98}
99
100#[derive(Debug)]
102pub struct SignedInfo {
103 pub c14n_method: C14nAlgorithm,
105 pub signature_method: SignatureAlgorithm,
107 pub references: Vec<Reference>,
109}
110
111#[derive(Debug)]
113pub struct Reference {
114 pub uri: Option<String>,
116 pub id: Option<String>,
118 pub ref_type: Option<String>,
120 pub transforms: Vec<Transform>,
122 pub digest_method: DigestAlgorithm,
124 pub digest_value: Vec<u8>,
126}
127
128#[derive(Debug, Default, Clone, PartialEq, Eq)]
130#[non_exhaustive]
131pub struct KeyInfo {
132 pub sources: Vec<KeyInfoSource>,
134}
135
136#[derive(Debug, Clone, PartialEq, Eq)]
138#[non_exhaustive]
139pub enum KeyInfoSource {
140 KeyName(String),
142 KeyValue(KeyValueInfo),
144 X509Data(X509DataInfo),
146 DerEncodedKeyValue(Vec<u8>),
148}
149
150#[derive(Debug, Clone, PartialEq, Eq)]
152#[non_exhaustive]
153pub enum KeyValueInfo {
154 RsaKeyValue,
156 EcKeyValue,
158 Unsupported {
160 namespace: Option<String>,
162 local_name: String,
164 },
165}
166
167#[derive(Debug, Default, Clone, PartialEq, Eq)]
169#[non_exhaustive]
170pub struct X509DataInfo {
171 pub certificates: Vec<Vec<u8>>,
173 pub subject_names: Vec<String>,
175 pub issuer_serials: Vec<(String, String)>,
177 pub skis: Vec<Vec<u8>>,
179 pub crls: Vec<Vec<u8>>,
181 pub digests: Vec<(String, Vec<u8>)>,
183}
184
185#[derive(Debug, thiserror::Error)]
187#[non_exhaustive]
188pub enum ParseError {
189 #[error("missing required element: <{element}>")]
191 MissingElement {
192 element: &'static str,
194 },
195
196 #[error("invalid structure: {0}")]
198 InvalidStructure(String),
199
200 #[error("unsupported algorithm: {uri}")]
202 UnsupportedAlgorithm {
203 uri: String,
205 },
206
207 #[error("base64 decode error: {0}")]
209 Base64(String),
210
211 #[error(
213 "digest length mismatch for {algorithm}: expected {expected} bytes, got {actual} bytes"
214 )]
215 DigestLengthMismatch {
216 algorithm: &'static str,
218 expected: usize,
220 actual: usize,
222 },
223
224 #[error("transform error: {0}")]
226 Transform(#[from] super::types::TransformError),
227}
228
229#[must_use]
231pub fn find_signature_node<'a>(doc: &'a Document<'a>) -> Option<Node<'a, 'a>> {
232 doc.descendants().find(|n| {
233 n.is_element()
234 && n.tag_name().name() == "Signature"
235 && n.tag_name().namespace() == Some(XMLDSIG_NS)
236 })
237}
238
239pub fn parse_signed_info(signed_info_node: Node) -> Result<SignedInfo, ParseError> {
244 verify_ds_element(signed_info_node, "SignedInfo")?;
245
246 let mut children = element_children(signed_info_node);
247
248 let c14n_node = children.next().ok_or(ParseError::MissingElement {
250 element: "CanonicalizationMethod",
251 })?;
252 verify_ds_element(c14n_node, "CanonicalizationMethod")?;
253 let c14n_uri = required_algorithm_attr(c14n_node, "CanonicalizationMethod")?;
254 let mut c14n_method =
255 C14nAlgorithm::from_uri(c14n_uri).ok_or_else(|| ParseError::UnsupportedAlgorithm {
256 uri: c14n_uri.to_string(),
257 })?;
258 if let Some(prefix_list) = parse_inclusive_prefixes(c14n_node)? {
259 if c14n_method.mode() == crate::c14n::C14nMode::Exclusive1_0 {
260 c14n_method = c14n_method.with_prefix_list(&prefix_list);
261 } else {
262 return Err(ParseError::UnsupportedAlgorithm {
263 uri: c14n_uri.to_string(),
264 });
265 }
266 }
267
268 let sig_method_node = children.next().ok_or(ParseError::MissingElement {
270 element: "SignatureMethod",
271 })?;
272 verify_ds_element(sig_method_node, "SignatureMethod")?;
273 let sig_uri = required_algorithm_attr(sig_method_node, "SignatureMethod")?;
274 let signature_method =
275 SignatureAlgorithm::from_uri(sig_uri).ok_or_else(|| ParseError::UnsupportedAlgorithm {
276 uri: sig_uri.to_string(),
277 })?;
278
279 let mut references = Vec::new();
281 for child in children {
282 verify_ds_element(child, "Reference")?;
283 references.push(parse_reference(child)?);
284 }
285 if references.is_empty() {
286 return Err(ParseError::MissingElement {
287 element: "Reference",
288 });
289 }
290
291 Ok(SignedInfo {
292 c14n_method,
293 signature_method,
294 references,
295 })
296}
297
298pub(crate) fn parse_reference(reference_node: Node) -> Result<Reference, ParseError> {
302 let uri = reference_node.attribute("URI").map(String::from);
303 let id = reference_node.attribute("Id").map(String::from);
304 let ref_type = reference_node.attribute("Type").map(String::from);
305
306 let mut children = element_children(reference_node);
307
308 let mut transforms = Vec::new();
310 let mut next = children.next().ok_or(ParseError::MissingElement {
311 element: "DigestMethod",
312 })?;
313
314 if next.tag_name().name() == "Transforms" && next.tag_name().namespace() == Some(XMLDSIG_NS) {
315 transforms = transforms::parse_transforms(next)?;
316 next = children.next().ok_or(ParseError::MissingElement {
317 element: "DigestMethod",
318 })?;
319 }
320
321 verify_ds_element(next, "DigestMethod")?;
323 let digest_uri = required_algorithm_attr(next, "DigestMethod")?;
324 let digest_method =
325 DigestAlgorithm::from_uri(digest_uri).ok_or_else(|| ParseError::UnsupportedAlgorithm {
326 uri: digest_uri.to_string(),
327 })?;
328
329 let digest_value_node = children.next().ok_or(ParseError::MissingElement {
331 element: "DigestValue",
332 })?;
333 verify_ds_element(digest_value_node, "DigestValue")?;
334 let digest_value = decode_digest_value_children(digest_value_node, digest_method)?;
335
336 if let Some(unexpected) = children.next() {
338 return Err(ParseError::InvalidStructure(format!(
339 "unexpected element <{}> after <DigestValue> in <Reference>",
340 unexpected.tag_name().name()
341 )));
342 }
343
344 Ok(Reference {
345 uri,
346 id,
347 ref_type,
348 transforms,
349 digest_method,
350 digest_value,
351 })
352}
353
354pub fn parse_key_info(key_info_node: Node) -> Result<KeyInfo, ParseError> {
367 verify_ds_element(key_info_node, "KeyInfo")?;
368 ensure_no_non_whitespace_text(key_info_node, "KeyInfo")?;
369
370 let mut sources = Vec::new();
371 for child in element_children(key_info_node) {
372 match (child.tag_name().namespace(), child.tag_name().name()) {
373 (Some(XMLDSIG_NS), "KeyName") => {
374 ensure_no_element_children(child, "KeyName")?;
375 let key_name =
376 collect_text_content_bounded(child, MAX_KEY_NAME_TEXT_LEN, "KeyName")?;
377 sources.push(KeyInfoSource::KeyName(key_name));
378 }
379 (Some(XMLDSIG_NS), "KeyValue") => {
380 let key_value = parse_key_value_dispatch(child)?;
381 sources.push(KeyInfoSource::KeyValue(key_value));
382 }
383 (Some(XMLDSIG_NS), "X509Data") => {
384 let x509 = parse_x509_data_dispatch(child)?;
385 sources.push(KeyInfoSource::X509Data(x509));
386 }
387 (Some(XMLDSIG11_NS), "DEREncodedKeyValue") => {
388 ensure_no_element_children(child, "DEREncodedKeyValue")?;
389 let der = decode_der_encoded_key_value_base64(child)?;
390 sources.push(KeyInfoSource::DerEncodedKeyValue(der));
391 }
392 _ => {}
393 }
394 }
395
396 Ok(KeyInfo { sources })
397}
398
399fn element_children<'a>(node: Node<'a, 'a>) -> impl Iterator<Item = Node<'a, 'a>> {
403 node.children().filter(|n| n.is_element())
404}
405
406fn verify_ds_element(node: Node, expected_name: &'static str) -> Result<(), ParseError> {
408 if !node.is_element() {
409 return Err(ParseError::InvalidStructure(format!(
410 "expected element <{expected_name}>, got non-element node"
411 )));
412 }
413 let tag = node.tag_name();
414 if tag.name() != expected_name || tag.namespace() != Some(XMLDSIG_NS) {
415 return Err(ParseError::InvalidStructure(format!(
416 "expected <ds:{expected_name}>, got <{}{}>",
417 tag.namespace()
418 .map(|ns| format!("{{{ns}}}"))
419 .unwrap_or_default(),
420 tag.name()
421 )));
422 }
423 Ok(())
424}
425
426fn required_algorithm_attr<'a>(
428 node: Node<'a, 'a>,
429 element_name: &'static str,
430) -> Result<&'a str, ParseError> {
431 node.attribute("Algorithm").ok_or_else(|| {
432 ParseError::InvalidStructure(format!("missing Algorithm attribute on <{element_name}>"))
433 })
434}
435
436fn parse_inclusive_prefixes(node: Node) -> Result<Option<String>, ParseError> {
442 const EXCLUSIVE_C14N_NS_URI: &str = "http://www.w3.org/2001/10/xml-exc-c14n#";
443
444 for child in node.children() {
445 if child.is_element() {
446 let tag = child.tag_name();
447 if tag.name() == "InclusiveNamespaces" && tag.namespace() == Some(EXCLUSIVE_C14N_NS_URI)
448 {
449 return child
450 .attribute("PrefixList")
451 .map(str::to_string)
452 .ok_or_else(|| {
453 ParseError::InvalidStructure(
454 "missing PrefixList attribute on <InclusiveNamespaces>".into(),
455 )
456 })
457 .map(Some);
458 }
459 }
460 }
461
462 Ok(None)
463}
464
465fn parse_key_value_dispatch(node: Node) -> Result<KeyValueInfo, ParseError> {
466 verify_ds_element(node, "KeyValue")?;
467 ensure_no_non_whitespace_text(node, "KeyValue")?;
468
469 let mut children = element_children(node);
470 let Some(first_child) = children.next() else {
471 return Err(ParseError::InvalidStructure(
472 "KeyValue must contain exactly one key-value child".into(),
473 ));
474 };
475 if children.next().is_some() {
476 return Err(ParseError::InvalidStructure(
477 "KeyValue must contain exactly one key-value child".into(),
478 ));
479 }
480
481 match (
482 first_child.tag_name().namespace(),
483 first_child.tag_name().name(),
484 ) {
485 (Some(XMLDSIG_NS), "RSAKeyValue") => Ok(KeyValueInfo::RsaKeyValue),
486 (Some(XMLDSIG11_NS), "ECKeyValue") => Ok(KeyValueInfo::EcKeyValue),
487 (namespace, child_name) => Ok(KeyValueInfo::Unsupported {
488 namespace: namespace.map(str::to_string),
489 local_name: child_name.to_string(),
490 }),
491 }
492}
493
494fn parse_x509_data_dispatch(node: Node) -> Result<X509DataInfo, ParseError> {
495 verify_ds_element(node, "X509Data")?;
496 ensure_no_non_whitespace_text(node, "X509Data")?;
497
498 let mut info = X509DataInfo::default();
499 let mut total_binary_len = 0usize;
500 for child in element_children(node) {
501 match (child.tag_name().namespace(), child.tag_name().name()) {
502 (Some(XMLDSIG_NS), "X509Certificate") => {
503 ensure_no_element_children(child, "X509Certificate")?;
504 ensure_x509_data_entry_budget(&info)?;
505 let cert = decode_x509_base64(child, "X509Certificate")?;
506 add_x509_data_usage(&mut total_binary_len, cert.len())?;
507 info.certificates.push(cert);
508 }
509 (Some(XMLDSIG_NS), "X509SubjectName") => {
510 ensure_no_element_children(child, "X509SubjectName")?;
511 ensure_x509_data_entry_budget(&info)?;
512 let subject_name = collect_text_content_bounded(
513 child,
514 MAX_X509_SUBJECT_NAME_TEXT_LEN,
515 "X509SubjectName",
516 )?;
517 info.subject_names.push(subject_name);
518 }
519 (Some(XMLDSIG_NS), "X509IssuerSerial") => {
520 ensure_x509_data_entry_budget(&info)?;
521 let issuer_serial = parse_x509_issuer_serial(child)?;
522 info.issuer_serials.push(issuer_serial);
523 }
524 (Some(XMLDSIG_NS), "X509SKI") => {
525 ensure_no_element_children(child, "X509SKI")?;
526 ensure_x509_data_entry_budget(&info)?;
527 let ski = decode_x509_base64(child, "X509SKI")?;
528 add_x509_data_usage(&mut total_binary_len, ski.len())?;
529 info.skis.push(ski);
530 }
531 (Some(XMLDSIG_NS), "X509CRL") => {
532 ensure_no_element_children(child, "X509CRL")?;
533 ensure_x509_data_entry_budget(&info)?;
534 let crl = decode_x509_base64(child, "X509CRL")?;
535 add_x509_data_usage(&mut total_binary_len, crl.len())?;
536 info.crls.push(crl);
537 }
538 (Some(XMLDSIG11_NS), "X509Digest") => {
539 ensure_no_element_children(child, "X509Digest")?;
540 ensure_x509_data_entry_budget(&info)?;
541 let algorithm = required_algorithm_attr(child, "X509Digest")?;
542 let digest = decode_x509_base64(child, "X509Digest")?;
543 add_x509_data_usage(&mut total_binary_len, digest.len())?;
544 info.digests.push((algorithm.to_string(), digest));
545 }
546 (Some(XMLDSIG_NS), child_name) | (Some(XMLDSIG11_NS), child_name) => {
547 return Err(ParseError::InvalidStructure(format!(
548 "X509Data contains unsupported XMLDSig child element <{child_name}>"
549 )));
550 }
551 _ => {}
552 }
553 }
554
555 Ok(info)
556}
557
558fn ensure_x509_data_entry_budget(info: &X509DataInfo) -> Result<(), ParseError> {
559 let total_entries = info.certificates.len()
560 + info.subject_names.len()
561 + info.issuer_serials.len()
562 + info.skis.len()
563 + info.crls.len()
564 + info.digests.len();
565 if total_entries >= MAX_X509_DATA_ENTRY_COUNT {
566 return Err(ParseError::InvalidStructure(
567 "X509Data contains too many entries".into(),
568 ));
569 }
570 Ok(())
571}
572
573fn add_x509_data_usage(total_binary_len: &mut usize, delta: usize) -> Result<(), ParseError> {
574 *total_binary_len = total_binary_len.checked_add(delta).ok_or_else(|| {
575 ParseError::InvalidStructure("X509Data exceeds maximum allowed total binary length".into())
576 })?;
577 if *total_binary_len > MAX_X509_DATA_TOTAL_BINARY_LEN {
578 return Err(ParseError::InvalidStructure(
579 "X509Data exceeds maximum allowed total binary length".into(),
580 ));
581 }
582 Ok(())
583}
584
585fn decode_x509_base64(
586 node: Node<'_, '_>,
587 element_name: &'static str,
588) -> Result<Vec<u8>, ParseError> {
589 use base64::Engine;
590 use base64::engine::general_purpose::STANDARD;
591
592 let mut cleaned = String::new();
593 let mut raw_text_len = 0usize;
594 for text in node
595 .children()
596 .filter(|child| child.is_text())
597 .filter_map(|child| child.text())
598 {
599 if raw_text_len.saturating_add(text.len()) > MAX_X509_BASE64_TEXT_LEN {
600 return Err(ParseError::InvalidStructure(format!(
601 "{element_name} exceeds maximum allowed text length"
602 )));
603 }
604 raw_text_len = raw_text_len.saturating_add(text.len());
605 normalize_xml_base64_text(text, &mut cleaned).map_err(|err| {
606 ParseError::Base64(format!(
607 "invalid XML whitespace U+{:04X} in {element_name}",
608 err.invalid_byte
609 ))
610 })?;
611 if cleaned.len() > MAX_X509_BASE64_NORMALIZED_LEN {
612 return Err(ParseError::InvalidStructure(format!(
613 "{element_name} exceeds maximum allowed base64 length"
614 )));
615 }
616 }
617
618 let decoded = STANDARD
619 .decode(&cleaned)
620 .map_err(|e| ParseError::Base64(format!("{element_name}: {e}")))?;
621 if decoded.is_empty() {
622 return Err(ParseError::InvalidStructure(format!(
623 "{element_name} must not be empty"
624 )));
625 }
626 if decoded.len() > MAX_X509_DECODED_BINARY_LEN {
627 return Err(ParseError::InvalidStructure(format!(
628 "{element_name} exceeds maximum allowed binary length"
629 )));
630 }
631 Ok(decoded)
632}
633
634fn parse_x509_issuer_serial(node: Node<'_, '_>) -> Result<(String, String), ParseError> {
635 verify_ds_element(node, "X509IssuerSerial")?;
636 ensure_no_non_whitespace_text(node, "X509IssuerSerial")?;
637
638 let children = element_children(node).collect::<Vec<_>>();
639 if children.len() != 2 {
640 return Err(ParseError::InvalidStructure(
641 "X509IssuerSerial must contain exactly X509IssuerName then X509SerialNumber".into(),
642 ));
643 }
644 if !matches!(
645 (
646 children[0].tag_name().namespace(),
647 children[0].tag_name().name()
648 ),
649 (Some(XMLDSIG_NS), "X509IssuerName")
650 ) {
651 return Err(ParseError::InvalidStructure(
652 "X509IssuerSerial must contain X509IssuerName as the first child element".into(),
653 ));
654 }
655 if !matches!(
656 (
657 children[1].tag_name().namespace(),
658 children[1].tag_name().name()
659 ),
660 (Some(XMLDSIG_NS), "X509SerialNumber")
661 ) {
662 return Err(ParseError::InvalidStructure(
663 "X509IssuerSerial must contain X509SerialNumber as the second child element".into(),
664 ));
665 }
666
667 let issuer_node = children[0];
668 ensure_no_element_children(issuer_node, "X509IssuerName")?;
669 let issuer_name =
670 collect_text_content_bounded(issuer_node, MAX_X509_ISSUER_NAME_TEXT_LEN, "X509IssuerName")?;
671
672 let serial_node = children[1];
673 ensure_no_element_children(serial_node, "X509SerialNumber")?;
674 let serial_number = collect_text_content_bounded(
675 serial_node,
676 MAX_X509_SERIAL_NUMBER_TEXT_LEN,
677 "X509SerialNumber",
678 )?;
679 if issuer_name.trim().is_empty() || serial_number.trim().is_empty() {
680 return Err(ParseError::InvalidStructure(
681 "X509IssuerSerial requires non-empty X509IssuerName and X509SerialNumber".into(),
682 ));
683 }
684
685 Ok((issuer_name, serial_number))
686}
687
688fn base64_decode_digest(b64: &str, digest_method: DigestAlgorithm) -> Result<Vec<u8>, ParseError> {
692 use base64::Engine;
693 use base64::engine::general_purpose::STANDARD;
694
695 let expected = digest_method.output_len();
696 let max_base64_len = expected.div_ceil(3) * 4;
697 let mut cleaned = String::with_capacity(b64.len().min(max_base64_len));
698 normalize_xml_base64_text(b64, &mut cleaned).map_err(|err| {
699 ParseError::Base64(format!(
700 "invalid XML whitespace U+{:04X} in DigestValue",
701 err.invalid_byte
702 ))
703 })?;
704 if cleaned.len() > max_base64_len {
705 return Err(ParseError::Base64(
706 "DigestValue exceeds maximum allowed base64 length".into(),
707 ));
708 }
709 let digest = STANDARD
710 .decode(&cleaned)
711 .map_err(|e| ParseError::Base64(e.to_string()))?;
712 let actual = digest.len();
713 if actual != expected {
714 return Err(ParseError::DigestLengthMismatch {
715 algorithm: digest_method.uri(),
716 expected,
717 actual,
718 });
719 }
720 Ok(digest)
721}
722
723fn decode_digest_value_children(
724 digest_value_node: Node<'_, '_>,
725 digest_method: DigestAlgorithm,
726) -> Result<Vec<u8>, ParseError> {
727 let max_base64_len = digest_method.output_len().div_ceil(3) * 4;
728 let mut cleaned = String::with_capacity(max_base64_len);
729
730 for child in digest_value_node.children() {
731 if child.is_element() {
732 return Err(ParseError::InvalidStructure(
733 "DigestValue must not contain element children".into(),
734 ));
735 }
736 if let Some(text) = child.text() {
737 normalize_xml_base64_text(text, &mut cleaned).map_err(|err| {
738 ParseError::Base64(format!(
739 "invalid XML whitespace U+{:04X} in DigestValue",
740 err.invalid_byte
741 ))
742 })?;
743 if cleaned.len() > max_base64_len {
744 return Err(ParseError::Base64(
745 "DigestValue exceeds maximum allowed base64 length".into(),
746 ));
747 }
748 }
749 }
750
751 base64_decode_digest(&cleaned, digest_method)
752}
753
754fn decode_der_encoded_key_value_base64(node: Node<'_, '_>) -> Result<Vec<u8>, ParseError> {
755 use base64::Engine;
756 use base64::engine::general_purpose::STANDARD;
757
758 let mut cleaned = String::new();
759 let mut raw_text_len = 0usize;
760 for text in node
761 .children()
762 .filter(|child| child.is_text())
763 .filter_map(|child| child.text())
764 {
765 if raw_text_len.saturating_add(text.len()) > MAX_DER_ENCODED_KEY_VALUE_TEXT_LEN {
766 return Err(ParseError::InvalidStructure(
767 "DEREncodedKeyValue exceeds maximum allowed text length".into(),
768 ));
769 }
770 raw_text_len = raw_text_len.saturating_add(text.len());
771 normalize_xml_base64_text(text, &mut cleaned).map_err(|err| {
772 ParseError::Base64(format!(
773 "invalid XML whitespace U+{:04X} in base64 text",
774 err.invalid_byte
775 ))
776 })?;
777 if cleaned.len() > MAX_DER_ENCODED_KEY_VALUE_BASE64_LEN {
778 return Err(ParseError::InvalidStructure(
779 "DEREncodedKeyValue exceeds maximum allowed length".into(),
780 ));
781 }
782 }
783
784 let der = STANDARD
785 .decode(&cleaned)
786 .map_err(|e| ParseError::Base64(e.to_string()))?;
787 if der.is_empty() {
788 return Err(ParseError::InvalidStructure(
789 "DEREncodedKeyValue must not be empty".into(),
790 ));
791 }
792 if der.len() > MAX_DER_ENCODED_KEY_VALUE_LEN {
793 return Err(ParseError::InvalidStructure(
794 "DEREncodedKeyValue exceeds maximum allowed length".into(),
795 ));
796 }
797 Ok(der)
798}
799
800fn collect_text_content_bounded(
801 node: Node<'_, '_>,
802 max_len: usize,
803 element_name: &'static str,
804) -> Result<String, ParseError> {
805 let mut text = String::new();
806 for chunk in node
807 .children()
808 .filter_map(|child| child.is_text().then(|| child.text()).flatten())
809 {
810 if text.len().saturating_add(chunk.len()) > max_len {
811 return Err(ParseError::InvalidStructure(format!(
812 "{element_name} exceeds maximum allowed text length"
813 )));
814 }
815 text.push_str(chunk);
816 }
817 Ok(text)
818}
819
820fn ensure_no_element_children(node: Node<'_, '_>, element_name: &str) -> Result<(), ParseError> {
821 if node.children().any(|child| child.is_element()) {
822 return Err(ParseError::InvalidStructure(format!(
823 "{element_name} must not contain child elements"
824 )));
825 }
826 Ok(())
827}
828
829fn ensure_no_non_whitespace_text(node: Node<'_, '_>, element_name: &str) -> Result<(), ParseError> {
830 for child in node.children().filter(|child| child.is_text()) {
831 if let Some(text) = child.text()
832 && !is_xml_whitespace_only(text)
833 {
834 return Err(ParseError::InvalidStructure(format!(
835 "{element_name} must not contain non-whitespace mixed content"
836 )));
837 }
838 }
839 Ok(())
840}
841
842#[cfg(test)]
843#[expect(clippy::unwrap_used, reason = "tests use trusted XML fixtures")]
844mod tests {
845 use super::*;
846 use base64::Engine;
847
848 #[test]
851 fn signature_algorithm_from_uri_rsa_sha256() {
852 assert_eq!(
853 SignatureAlgorithm::from_uri("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"),
854 Some(SignatureAlgorithm::RsaSha256)
855 );
856 }
857
858 #[test]
859 fn signature_algorithm_from_uri_rsa_sha1() {
860 assert_eq!(
861 SignatureAlgorithm::from_uri("http://www.w3.org/2000/09/xmldsig#rsa-sha1"),
862 Some(SignatureAlgorithm::RsaSha1)
863 );
864 }
865
866 #[test]
867 fn signature_algorithm_from_uri_ecdsa_sha256() {
868 assert_eq!(
869 SignatureAlgorithm::from_uri("http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"),
870 Some(SignatureAlgorithm::EcdsaP256Sha256)
871 );
872 }
873
874 #[test]
875 fn signature_algorithm_from_uri_unknown() {
876 assert_eq!(
877 SignatureAlgorithm::from_uri("http://example.com/unknown"),
878 None
879 );
880 }
881
882 #[test]
883 fn signature_algorithm_uri_round_trip() {
884 for algo in [
885 SignatureAlgorithm::RsaSha1,
886 SignatureAlgorithm::RsaSha256,
887 SignatureAlgorithm::RsaSha384,
888 SignatureAlgorithm::RsaSha512,
889 SignatureAlgorithm::EcdsaP256Sha256,
890 SignatureAlgorithm::EcdsaP384Sha384,
891 ] {
892 assert_eq!(
893 SignatureAlgorithm::from_uri(algo.uri()),
894 Some(algo),
895 "round-trip failed for {algo:?}"
896 );
897 }
898 }
899
900 #[test]
901 fn rsa_sha1_verify_only() {
902 assert!(!SignatureAlgorithm::RsaSha1.signing_allowed());
903 assert!(SignatureAlgorithm::RsaSha256.signing_allowed());
904 assert!(SignatureAlgorithm::EcdsaP256Sha256.signing_allowed());
905 }
906
907 #[test]
910 fn find_signature_in_saml() {
911 let xml = r#"<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
912 <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
913 <ds:SignedInfo/>
914 </ds:Signature>
915 </samlp:Response>"#;
916 let doc = Document::parse(xml).unwrap();
917 let sig = find_signature_node(&doc);
918 assert!(sig.is_some());
919 assert_eq!(sig.unwrap().tag_name().name(), "Signature");
920 }
921
922 #[test]
923 fn find_signature_missing() {
924 let xml = "<root><child/></root>";
925 let doc = Document::parse(xml).unwrap();
926 assert!(find_signature_node(&doc).is_none());
927 }
928
929 #[test]
930 fn find_signature_ignores_wrong_namespace() {
931 let xml = r#"<root><Signature xmlns="http://example.com/fake"/></root>"#;
932 let doc = Document::parse(xml).unwrap();
933 assert!(find_signature_node(&doc).is_none());
934 }
935
936 #[test]
939 fn parse_key_info_dispatches_supported_children() {
940 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
941 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
942 <KeyName>idp-signing-key</KeyName>
943 <KeyValue>
944 <RSAKeyValue>
945 <Modulus>AQAB</Modulus>
946 <Exponent>AQAB</Exponent>
947 </RSAKeyValue>
948 </KeyValue>
949 <X509Data>
950 <X509Certificate>AQID</X509Certificate>
951 <X509SubjectName>CN=Example</X509SubjectName>
952 <X509IssuerSerial>
953 <X509IssuerName>CN=CA</X509IssuerName>
954 <X509SerialNumber>42</X509SerialNumber>
955 </X509IssuerSerial>
956 <X509SKI>AQIDBA==</X509SKI>
957 <X509CRL>BAUGBw==</X509CRL>
958 <dsig11:X509Digest Algorithm="http://www.w3.org/2001/04/xmlenc#sha256">CAkK</dsig11:X509Digest>
959 </X509Data>
960 <dsig11:DEREncodedKeyValue>AQIDBA==</dsig11:DEREncodedKeyValue>
961 </KeyInfo>"#;
962 let doc = Document::parse(xml).unwrap();
963
964 let key_info = parse_key_info(doc.root_element()).unwrap();
965 assert_eq!(key_info.sources.len(), 4);
966
967 assert_eq!(
968 key_info.sources[0],
969 KeyInfoSource::KeyName("idp-signing-key".to_string())
970 );
971 assert_eq!(
972 key_info.sources[1],
973 KeyInfoSource::KeyValue(KeyValueInfo::RsaKeyValue)
974 );
975 assert_eq!(
976 key_info.sources[2],
977 KeyInfoSource::X509Data(X509DataInfo {
978 certificates: vec![vec![1, 2, 3]],
979 subject_names: vec!["CN=Example".into()],
980 issuer_serials: vec![("CN=CA".into(), "42".into())],
981 skis: vec![vec![1, 2, 3, 4]],
982 crls: vec![vec![4, 5, 6, 7]],
983 digests: vec![(
984 "http://www.w3.org/2001/04/xmlenc#sha256".into(),
985 vec![8, 9, 10]
986 )],
987 })
988 );
989 assert_eq!(
990 key_info.sources[3],
991 KeyInfoSource::DerEncodedKeyValue(vec![1, 2, 3, 4])
992 );
993 }
994
995 #[test]
996 fn parse_key_info_ignores_unknown_children() {
997 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
998 <Foo>bar</Foo>
999 <KeyName>ok</KeyName>
1000 </KeyInfo>"#;
1001 let doc = Document::parse(xml).unwrap();
1002
1003 let key_info = parse_key_info(doc.root_element()).unwrap();
1004 assert_eq!(key_info.sources, vec![KeyInfoSource::KeyName("ok".into())]);
1005 }
1006
1007 #[test]
1008 fn parse_key_info_keyvalue_requires_single_child() {
1009 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1010 <KeyValue/>
1011 </KeyInfo>"#;
1012 let doc = Document::parse(xml).unwrap();
1013
1014 let err = parse_key_info(doc.root_element()).unwrap_err();
1015 assert!(matches!(err, ParseError::InvalidStructure(_)));
1016 }
1017
1018 #[test]
1019 fn parse_key_info_accepts_empty_x509data() {
1020 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1021 <X509Data/>
1022 </KeyInfo>"#;
1023 let doc = Document::parse(xml).unwrap();
1024
1025 let key_info = parse_key_info(doc.root_element()).unwrap();
1026 assert_eq!(
1027 key_info.sources,
1028 vec![KeyInfoSource::X509Data(X509DataInfo::default())]
1029 );
1030 }
1031
1032 #[test]
1033 fn parse_key_info_rejects_unknown_xmlsig_child_in_x509data() {
1034 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1035 <X509Data>
1036 <Foo/>
1037 </X509Data>
1038 </KeyInfo>"#;
1039 let doc = Document::parse(xml).unwrap();
1040
1041 let err = parse_key_info(doc.root_element()).unwrap_err();
1042 assert!(matches!(err, ParseError::InvalidStructure(_)));
1043 }
1044
1045 #[test]
1046 fn parse_key_info_rejects_unknown_xmlsig11_child_in_x509data() {
1047 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
1048 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
1049 <X509Data>
1050 <dsig11:Foo/>
1051 </X509Data>
1052 </KeyInfo>"#;
1053 let doc = Document::parse(xml).unwrap();
1054
1055 let err = parse_key_info(doc.root_element()).unwrap_err();
1056 assert!(matches!(err, ParseError::InvalidStructure(_)));
1057 }
1058
1059 #[test]
1060 fn parse_key_info_rejects_x509_issuer_serial_without_required_children() {
1061 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1062 <X509Data>
1063 <X509IssuerSerial>
1064 <X509IssuerName>CN=CA</X509IssuerName>
1065 </X509IssuerSerial>
1066 </X509Data>
1067 </KeyInfo>"#;
1068 let doc = Document::parse(xml).unwrap();
1069
1070 let err = parse_key_info(doc.root_element()).unwrap_err();
1071 assert!(matches!(err, ParseError::InvalidStructure(_)));
1072 }
1073
1074 #[test]
1075 fn parse_key_info_rejects_x509_issuer_serial_with_duplicate_issuer_name() {
1076 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1077 <X509Data>
1078 <X509IssuerSerial>
1079 <X509IssuerName>CN=CA-1</X509IssuerName>
1080 <X509IssuerName>CN=CA-2</X509IssuerName>
1081 <X509SerialNumber>42</X509SerialNumber>
1082 </X509IssuerSerial>
1083 </X509Data>
1084 </KeyInfo>"#;
1085 let doc = Document::parse(xml).unwrap();
1086
1087 let err = parse_key_info(doc.root_element()).unwrap_err();
1088 assert!(matches!(err, ParseError::InvalidStructure(_)));
1089 }
1090
1091 #[test]
1092 fn parse_key_info_rejects_x509_issuer_serial_with_duplicate_serial_number() {
1093 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1094 <X509Data>
1095 <X509IssuerSerial>
1096 <X509IssuerName>CN=CA</X509IssuerName>
1097 <X509SerialNumber>1</X509SerialNumber>
1098 <X509SerialNumber>2</X509SerialNumber>
1099 </X509IssuerSerial>
1100 </X509Data>
1101 </KeyInfo>"#;
1102 let doc = Document::parse(xml).unwrap();
1103
1104 let err = parse_key_info(doc.root_element()).unwrap_err();
1105 assert!(matches!(err, ParseError::InvalidStructure(_)));
1106 }
1107
1108 #[test]
1109 fn parse_key_info_rejects_x509_issuer_serial_with_whitespace_only_values() {
1110 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1111 <X509Data>
1112 <X509IssuerSerial>
1113 <X509IssuerName> </X509IssuerName>
1114 <X509SerialNumber>
1115
1116 </X509SerialNumber>
1117 </X509IssuerSerial>
1118 </X509Data>
1119 </KeyInfo>"#;
1120 let doc = Document::parse(xml).unwrap();
1121
1122 let err = parse_key_info(doc.root_element()).unwrap_err();
1123 assert!(matches!(err, ParseError::InvalidStructure(_)));
1124 }
1125
1126 #[test]
1127 fn parse_key_info_rejects_x509_issuer_serial_with_wrong_child_order() {
1128 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1129 <X509Data>
1130 <X509IssuerSerial>
1131 <X509SerialNumber>42</X509SerialNumber>
1132 <X509IssuerName>CN=CA</X509IssuerName>
1133 </X509IssuerSerial>
1134 </X509Data>
1135 </KeyInfo>"#;
1136 let doc = Document::parse(xml).unwrap();
1137
1138 let err = parse_key_info(doc.root_element()).unwrap_err();
1139 assert!(matches!(err, ParseError::InvalidStructure(_)));
1140 }
1141
1142 #[test]
1143 fn parse_key_info_rejects_x509_issuer_serial_with_extra_child_element() {
1144 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
1145 xmlns:foo="urn:example:foo">
1146 <X509Data>
1147 <X509IssuerSerial>
1148 <X509IssuerName>CN=CA</X509IssuerName>
1149 <X509SerialNumber>42</X509SerialNumber>
1150 <foo:Extra/>
1151 </X509IssuerSerial>
1152 </X509Data>
1153 </KeyInfo>"#;
1154 let doc = Document::parse(xml).unwrap();
1155
1156 let err = parse_key_info(doc.root_element()).unwrap_err();
1157 assert!(matches!(err, ParseError::InvalidStructure(_)));
1158 }
1159
1160 #[test]
1161 fn parse_key_info_rejects_x509_digest_without_algorithm() {
1162 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
1163 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
1164 <X509Data>
1165 <dsig11:X509Digest>AQID</dsig11:X509Digest>
1166 </X509Data>
1167 </KeyInfo>"#;
1168 let doc = Document::parse(xml).unwrap();
1169
1170 let err = parse_key_info(doc.root_element()).unwrap_err();
1171 assert!(matches!(err, ParseError::InvalidStructure(_)));
1172 }
1173
1174 #[test]
1175 fn parse_key_info_rejects_invalid_x509_certificate_base64() {
1176 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1177 <X509Data>
1178 <X509Certificate>%%%invalid%%%</X509Certificate>
1179 </X509Data>
1180 </KeyInfo>"#;
1181 let doc = Document::parse(xml).unwrap();
1182
1183 let err = parse_key_info(doc.root_element()).unwrap_err();
1184 assert!(matches!(err, ParseError::Base64(_)));
1185 }
1186
1187 #[test]
1188 fn parse_key_info_rejects_x509_data_exceeding_entry_budget() {
1189 let subjects = (0..(MAX_X509_DATA_ENTRY_COUNT + 1))
1190 .map(|idx| format!("<X509SubjectName>CN={idx}</X509SubjectName>"))
1191 .collect::<Vec<_>>()
1192 .join("");
1193 let xml = format!(
1194 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><X509Data>{subjects}</X509Data></KeyInfo>"
1195 );
1196 let doc = Document::parse(&xml).unwrap();
1197
1198 let err = parse_key_info(doc.root_element()).unwrap_err();
1199 assert!(matches!(err, ParseError::InvalidStructure(_)));
1200 }
1201
1202 #[test]
1203 fn parse_key_info_rejects_x509_data_exceeding_total_binary_budget() {
1204 let payload = base64::engine::general_purpose::STANDARD.encode(vec![0u8; 190_000]);
1205 let certs = (0..6)
1206 .map(|_| format!("<X509Certificate>{payload}</X509Certificate>"))
1207 .collect::<Vec<_>>()
1208 .join("");
1209 let xml = format!(
1210 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><X509Data>{certs}</X509Data></KeyInfo>"
1211 );
1212 let doc = Document::parse(&xml).unwrap();
1213
1214 let err = parse_key_info(doc.root_element()).unwrap_err();
1215 assert!(matches!(err, ParseError::InvalidStructure(_)));
1216 }
1217
1218 #[test]
1219 fn parse_key_info_accepts_large_textual_x509_entries_within_entry_budget() {
1220 let issuer_name = "C".repeat(MAX_X509_ISSUER_NAME_TEXT_LEN);
1221 let serial_number = "7".repeat(MAX_X509_SERIAL_NUMBER_TEXT_LEN);
1222 let issuer_serials = (0..52)
1223 .map(|_| {
1224 format!(
1225 "<X509IssuerSerial><X509IssuerName>{issuer_name}</X509IssuerName><X509SerialNumber>{serial_number}</X509SerialNumber></X509IssuerSerial>"
1226 )
1227 })
1228 .collect::<Vec<_>>()
1229 .join("");
1230 let xml = format!(
1231 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><X509Data>{issuer_serials}</X509Data></KeyInfo>"
1232 );
1233 let doc = Document::parse(&xml).unwrap();
1234
1235 let key_info = parse_key_info(doc.root_element()).unwrap();
1236 let parsed = match &key_info.sources[0] {
1237 KeyInfoSource::X509Data(x509) => x509,
1238 _ => panic!("expected X509Data source"),
1239 };
1240 assert_eq!(parsed.issuer_serials.len(), 52);
1241 }
1242
1243 #[test]
1244 fn parse_key_info_accepts_x509data_with_only_foreign_namespace_children() {
1245 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
1246 xmlns:foo="urn:example:foo">
1247 <X509Data>
1248 <foo:Bar/>
1249 </X509Data>
1250 </KeyInfo>"#;
1251 let doc = Document::parse(xml).unwrap();
1252
1253 let key_info = parse_key_info(doc.root_element()).unwrap();
1254 assert_eq!(
1255 key_info.sources,
1256 vec![KeyInfoSource::X509Data(X509DataInfo::default())]
1257 );
1258 }
1259
1260 #[test]
1261 fn parse_key_info_der_encoded_key_value_rejects_invalid_base64() {
1262 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
1263 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
1264 <dsig11:DEREncodedKeyValue>%%%invalid%%%</dsig11:DEREncodedKeyValue>
1265 </KeyInfo>"#;
1266 let doc = Document::parse(xml).unwrap();
1267
1268 let err = parse_key_info(doc.root_element()).unwrap_err();
1269 assert!(matches!(err, ParseError::Base64(_)));
1270 }
1271
1272 #[test]
1273 fn parse_key_info_der_encoded_key_value_accepts_xml_whitespace() {
1274 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
1275 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
1276 <dsig11:DEREncodedKeyValue>
1277 AQID
1278 BA==
1279 </dsig11:DEREncodedKeyValue>
1280 </KeyInfo>"#;
1281 let doc = Document::parse(xml).unwrap();
1282
1283 let key_info = parse_key_info(doc.root_element()).unwrap();
1284 assert_eq!(
1285 key_info.sources,
1286 vec![KeyInfoSource::DerEncodedKeyValue(vec![1, 2, 3, 4])]
1287 );
1288 }
1289
1290 #[test]
1291 fn parse_key_info_dispatches_dsig11_ec_keyvalue() {
1292 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
1293 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
1294 <KeyValue>
1295 <dsig11:ECKeyValue/>
1296 </KeyValue>
1297 </KeyInfo>"#;
1298 let doc = Document::parse(xml).unwrap();
1299
1300 let key_info = parse_key_info(doc.root_element()).unwrap();
1301 assert_eq!(
1302 key_info.sources,
1303 vec![KeyInfoSource::KeyValue(KeyValueInfo::EcKeyValue)]
1304 );
1305 }
1306
1307 #[test]
1308 fn parse_key_info_marks_ds_namespace_ec_keyvalue_as_unsupported() {
1309 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1310 <KeyValue>
1311 <ECKeyValue/>
1312 </KeyValue>
1313 </KeyInfo>"#;
1314 let doc = Document::parse(xml).unwrap();
1315
1316 let key_info = parse_key_info(doc.root_element()).unwrap();
1317 assert_eq!(
1318 key_info.sources,
1319 vec![KeyInfoSource::KeyValue(KeyValueInfo::Unsupported {
1320 namespace: Some(XMLDSIG_NS.to_string()),
1321 local_name: "ECKeyValue".into(),
1322 })]
1323 );
1324 }
1325
1326 #[test]
1327 fn parse_key_info_keeps_unsupported_keyvalue_child_as_marker() {
1328 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1329 <KeyValue>
1330 <DSAKeyValue/>
1331 </KeyValue>
1332 </KeyInfo>"#;
1333 let doc = Document::parse(xml).unwrap();
1334
1335 let key_info = parse_key_info(doc.root_element()).unwrap();
1336 assert_eq!(
1337 key_info.sources,
1338 vec![KeyInfoSource::KeyValue(KeyValueInfo::Unsupported {
1339 namespace: Some(XMLDSIG_NS.to_string()),
1340 local_name: "DSAKeyValue".into(),
1341 })]
1342 );
1343 }
1344
1345 #[test]
1346 fn parse_key_info_rejects_keyname_with_child_elements() {
1347 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1348 <KeyName>ok<foo/></KeyName>
1349 </KeyInfo>"#;
1350 let doc = Document::parse(xml).unwrap();
1351
1352 let err = parse_key_info(doc.root_element()).unwrap_err();
1353 assert!(matches!(err, ParseError::InvalidStructure(_)));
1354 }
1355
1356 #[test]
1357 fn parse_key_info_preserves_keyname_text_without_trimming() {
1358 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1359 <KeyName> signing key </KeyName>
1360 </KeyInfo>"#;
1361 let doc = Document::parse(xml).unwrap();
1362
1363 let key_info = parse_key_info(doc.root_element()).unwrap();
1364 assert_eq!(
1365 key_info.sources,
1366 vec![KeyInfoSource::KeyName(" signing key ".into())]
1367 );
1368 }
1369
1370 #[test]
1371 fn parse_key_info_rejects_oversized_keyname_text() {
1372 let oversized = "A".repeat(4097);
1373 let xml = format!(
1374 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><KeyName>{oversized}</KeyName></KeyInfo>"
1375 );
1376 let doc = Document::parse(&xml).unwrap();
1377
1378 let err = parse_key_info(doc.root_element()).unwrap_err();
1379 assert!(matches!(err, ParseError::InvalidStructure(_)));
1380 }
1381
1382 #[test]
1383 fn parse_key_info_rejects_non_whitespace_mixed_content() {
1384 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">oops<KeyName>k</KeyName></KeyInfo>"#;
1385 let doc = Document::parse(xml).unwrap();
1386
1387 let err = parse_key_info(doc.root_element()).unwrap_err();
1388 assert!(matches!(err, ParseError::InvalidStructure(_)));
1389 }
1390
1391 #[test]
1392 fn parse_key_info_rejects_nbsp_as_non_xml_whitespace_mixed_content() {
1393 let xml = "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\u{00A0}<KeyName>k</KeyName></KeyInfo>";
1394 let doc = Document::parse(xml).unwrap();
1395
1396 let err = parse_key_info(doc.root_element()).unwrap_err();
1397 assert!(matches!(err, ParseError::InvalidStructure(_)));
1398 }
1399
1400 #[test]
1401 fn parse_key_info_der_encoded_key_value_rejects_oversized_payload() {
1402 let oversized =
1403 base64::engine::general_purpose::STANDARD
1404 .encode(vec![0u8; MAX_DER_ENCODED_KEY_VALUE_LEN + 1]);
1405 let xml = format!(
1406 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\" xmlns:dsig11=\"http://www.w3.org/2009/xmldsig11#\"><dsig11:DEREncodedKeyValue>{oversized}</dsig11:DEREncodedKeyValue></KeyInfo>"
1407 );
1408 let doc = Document::parse(&xml).unwrap();
1409
1410 let err = parse_key_info(doc.root_element()).unwrap_err();
1411 assert!(matches!(err, ParseError::InvalidStructure(_)));
1412 }
1413
1414 #[test]
1415 fn parse_key_info_der_encoded_key_value_rejects_empty_payload() {
1416 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
1417 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
1418 <dsig11:DEREncodedKeyValue>
1419
1420 </dsig11:DEREncodedKeyValue>
1421 </KeyInfo>"#;
1422 let doc = Document::parse(xml).unwrap();
1423
1424 let err = parse_key_info(doc.root_element()).unwrap_err();
1425 assert!(matches!(err, ParseError::InvalidStructure(_)));
1426 }
1427
1428 #[test]
1429 fn parse_key_info_der_encoded_key_value_non_xml_ascii_whitespace_is_not_parseable_xml() {
1430 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>";
1431 assert!(Document::parse(xml).is_err());
1432 }
1433
1434 #[test]
1437 fn parse_signed_info_rsa_sha256_with_reference() {
1438 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1439 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1440 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
1441 <Reference URI="">
1442 <Transforms>
1443 <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
1444 <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1445 </Transforms>
1446 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
1447 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
1448 </Reference>
1449 </SignedInfo>"#;
1450 let doc = Document::parse(xml).unwrap();
1451 let si = parse_signed_info(doc.root_element()).unwrap();
1452
1453 assert_eq!(si.signature_method, SignatureAlgorithm::RsaSha256);
1454 assert_eq!(si.references.len(), 1);
1455
1456 let r = &si.references[0];
1457 assert_eq!(r.uri.as_deref(), Some(""));
1458 assert_eq!(r.digest_method, DigestAlgorithm::Sha256);
1459 assert_eq!(r.digest_value, vec![0u8; 32]);
1460 assert_eq!(r.transforms.len(), 2);
1461 }
1462
1463 #[test]
1464 fn parse_signed_info_multiple_references() {
1465 let xml = r##"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1466 <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
1467 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"/>
1468 <Reference URI="#a">
1469 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
1470 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
1471 </Reference>
1472 <Reference URI="#b">
1473 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
1474 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
1475 </Reference>
1476 </SignedInfo>"##;
1477 let doc = Document::parse(xml).unwrap();
1478 let si = parse_signed_info(doc.root_element()).unwrap();
1479
1480 assert_eq!(si.signature_method, SignatureAlgorithm::EcdsaP256Sha256);
1481 assert_eq!(si.references.len(), 2);
1482 assert_eq!(si.references[0].uri.as_deref(), Some("#a"));
1483 assert_eq!(si.references[0].digest_method, DigestAlgorithm::Sha256);
1484 assert_eq!(si.references[1].uri.as_deref(), Some("#b"));
1485 assert_eq!(si.references[1].digest_method, DigestAlgorithm::Sha1);
1486 }
1487
1488 #[test]
1489 fn parse_reference_without_transforms() {
1490 let xml = r##"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1492 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1493 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
1494 <Reference URI="#obj">
1495 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
1496 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
1497 </Reference>
1498 </SignedInfo>"##;
1499 let doc = Document::parse(xml).unwrap();
1500 let si = parse_signed_info(doc.root_element()).unwrap();
1501
1502 assert!(si.references[0].transforms.is_empty());
1503 }
1504
1505 #[test]
1506 fn parse_reference_with_all_attributes() {
1507 let xml = r##"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1508 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1509 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
1510 <Reference URI="#data" Id="ref1" Type="http://www.w3.org/2000/09/xmldsig#Object">
1511 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
1512 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
1513 </Reference>
1514 </SignedInfo>"##;
1515 let doc = Document::parse(xml).unwrap();
1516 let si = parse_signed_info(doc.root_element()).unwrap();
1517 let r = &si.references[0];
1518
1519 assert_eq!(r.uri.as_deref(), Some("#data"));
1520 assert_eq!(r.id.as_deref(), Some("ref1"));
1521 assert_eq!(
1522 r.ref_type.as_deref(),
1523 Some("http://www.w3.org/2000/09/xmldsig#Object")
1524 );
1525 }
1526
1527 #[test]
1528 fn parse_reference_absent_uri() {
1529 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1531 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1532 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
1533 <Reference>
1534 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
1535 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
1536 </Reference>
1537 </SignedInfo>"#;
1538 let doc = Document::parse(xml).unwrap();
1539 let si = parse_signed_info(doc.root_element()).unwrap();
1540 assert!(si.references[0].uri.is_none());
1541 }
1542
1543 #[test]
1544 fn parse_signed_info_preserves_inclusive_prefixes() {
1545 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
1546 xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#">
1547 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
1548 <ec:InclusiveNamespaces PrefixList="ds saml #default"/>
1549 </CanonicalizationMethod>
1550 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
1551 <Reference URI="">
1552 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
1553 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
1554 </Reference>
1555 </SignedInfo>"#;
1556 let doc = Document::parse(xml).unwrap();
1557
1558 let si = parse_signed_info(doc.root_element()).unwrap();
1559 assert!(si.c14n_method.inclusive_prefixes().contains("ds"));
1560 assert!(si.c14n_method.inclusive_prefixes().contains("saml"));
1561 assert!(si.c14n_method.inclusive_prefixes().contains(""));
1562 }
1563
1564 #[test]
1567 fn missing_canonicalization_method() {
1568 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1569 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
1570 <Reference URI="">
1571 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
1572 <DigestValue>dGVzdA==</DigestValue>
1573 </Reference>
1574 </SignedInfo>"#;
1575 let doc = Document::parse(xml).unwrap();
1576 let result = parse_signed_info(doc.root_element());
1577 assert!(result.is_err());
1578 assert!(matches!(
1580 result.unwrap_err(),
1581 ParseError::InvalidStructure(_)
1582 ));
1583 }
1584
1585 #[test]
1586 fn missing_signature_method() {
1587 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1588 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1589 <Reference URI="">
1590 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
1591 <DigestValue>dGVzdA==</DigestValue>
1592 </Reference>
1593 </SignedInfo>"#;
1594 let doc = Document::parse(xml).unwrap();
1595 let result = parse_signed_info(doc.root_element());
1596 assert!(result.is_err());
1597 assert!(matches!(
1599 result.unwrap_err(),
1600 ParseError::InvalidStructure(_)
1601 ));
1602 }
1603
1604 #[test]
1605 fn no_references() {
1606 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1607 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1608 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
1609 </SignedInfo>"#;
1610 let doc = Document::parse(xml).unwrap();
1611 let result = parse_signed_info(doc.root_element());
1612 assert!(matches!(
1613 result.unwrap_err(),
1614 ParseError::MissingElement {
1615 element: "Reference"
1616 }
1617 ));
1618 }
1619
1620 #[test]
1621 fn unsupported_c14n_algorithm() {
1622 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1623 <CanonicalizationMethod Algorithm="http://example.com/bogus-c14n"/>
1624 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
1625 <Reference URI="">
1626 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
1627 <DigestValue>dGVzdA==</DigestValue>
1628 </Reference>
1629 </SignedInfo>"#;
1630 let doc = Document::parse(xml).unwrap();
1631 let result = parse_signed_info(doc.root_element());
1632 assert!(matches!(
1633 result.unwrap_err(),
1634 ParseError::UnsupportedAlgorithm { .. }
1635 ));
1636 }
1637
1638 #[test]
1639 fn unsupported_signature_algorithm() {
1640 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1641 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1642 <SignatureMethod Algorithm="http://example.com/bogus-sign"/>
1643 <Reference URI="">
1644 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
1645 <DigestValue>dGVzdA==</DigestValue>
1646 </Reference>
1647 </SignedInfo>"#;
1648 let doc = Document::parse(xml).unwrap();
1649 let result = parse_signed_info(doc.root_element());
1650 assert!(matches!(
1651 result.unwrap_err(),
1652 ParseError::UnsupportedAlgorithm { .. }
1653 ));
1654 }
1655
1656 #[test]
1657 fn unsupported_digest_algorithm() {
1658 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1659 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1660 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
1661 <Reference URI="">
1662 <DigestMethod Algorithm="http://example.com/bogus-digest"/>
1663 <DigestValue>dGVzdA==</DigestValue>
1664 </Reference>
1665 </SignedInfo>"#;
1666 let doc = Document::parse(xml).unwrap();
1667 let result = parse_signed_info(doc.root_element());
1668 assert!(matches!(
1669 result.unwrap_err(),
1670 ParseError::UnsupportedAlgorithm { .. }
1671 ));
1672 }
1673
1674 #[test]
1675 fn missing_digest_method() {
1676 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1677 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1678 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
1679 <Reference URI="">
1680 <DigestValue>dGVzdA==</DigestValue>
1681 </Reference>
1682 </SignedInfo>"#;
1683 let doc = Document::parse(xml).unwrap();
1684 let result = parse_signed_info(doc.root_element());
1685 assert!(result.is_err());
1687 }
1688
1689 #[test]
1690 fn missing_digest_value() {
1691 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1692 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1693 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
1694 <Reference URI="">
1695 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
1696 </Reference>
1697 </SignedInfo>"#;
1698 let doc = Document::parse(xml).unwrap();
1699 let result = parse_signed_info(doc.root_element());
1700 assert!(matches!(
1701 result.unwrap_err(),
1702 ParseError::MissingElement {
1703 element: "DigestValue"
1704 }
1705 ));
1706 }
1707
1708 #[test]
1709 fn invalid_base64_digest_value() {
1710 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1711 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1712 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
1713 <Reference URI="">
1714 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
1715 <DigestValue>!!!not-base64!!!</DigestValue>
1716 </Reference>
1717 </SignedInfo>"#;
1718 let doc = Document::parse(xml).unwrap();
1719 let result = parse_signed_info(doc.root_element());
1720 assert!(matches!(result.unwrap_err(), ParseError::Base64(_)));
1721 }
1722
1723 #[test]
1724 fn digest_value_length_must_match_digest_method() {
1725 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1726 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1727 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
1728 <Reference URI="">
1729 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
1730 <DigestValue>dGVzdA==</DigestValue>
1731 </Reference>
1732 </SignedInfo>"#;
1733 let doc = Document::parse(xml).unwrap();
1734
1735 let result = parse_signed_info(doc.root_element());
1736 assert!(matches!(
1737 result.unwrap_err(),
1738 ParseError::DigestLengthMismatch {
1739 algorithm: "http://www.w3.org/2001/04/xmlenc#sha256",
1740 expected: 32,
1741 actual: 4,
1742 }
1743 ));
1744 }
1745
1746 #[test]
1747 fn inclusive_prefixes_on_inclusive_c14n_is_rejected() {
1748 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
1749 xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#">
1750 <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315">
1751 <ec:InclusiveNamespaces PrefixList="ds"/>
1752 </CanonicalizationMethod>
1753 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
1754 <Reference URI="">
1755 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
1756 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
1757 </Reference>
1758 </SignedInfo>"#;
1759 let doc = Document::parse(xml).unwrap();
1760
1761 let result = parse_signed_info(doc.root_element());
1762 assert!(matches!(
1763 result.unwrap_err(),
1764 ParseError::UnsupportedAlgorithm { .. }
1765 ));
1766 }
1767
1768 #[test]
1769 fn extra_element_after_digest_value() {
1770 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1771 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1772 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
1773 <Reference URI="">
1774 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
1775 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
1776 <Unexpected/>
1777 </Reference>
1778 </SignedInfo>"#;
1779 let doc = Document::parse(xml).unwrap();
1780 let result = parse_signed_info(doc.root_element());
1781 assert!(matches!(
1782 result.unwrap_err(),
1783 ParseError::InvalidStructure(_)
1784 ));
1785 }
1786
1787 #[test]
1788 fn digest_value_with_element_child_is_rejected() {
1789 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1790 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1791 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
1792 <Reference URI="">
1793 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
1794 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAA=<Junk/>AAAA</DigestValue>
1795 </Reference>
1796 </SignedInfo>"#;
1797 let doc = Document::parse(xml).unwrap();
1798
1799 let result = parse_signed_info(doc.root_element());
1800 assert!(matches!(
1801 result.unwrap_err(),
1802 ParseError::InvalidStructure(_)
1803 ));
1804 }
1805
1806 #[test]
1807 fn wrong_namespace_on_signed_info() {
1808 let xml = r#"<SignedInfo xmlns="http://example.com/fake">
1809 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1810 </SignedInfo>"#;
1811 let doc = Document::parse(xml).unwrap();
1812 let result = parse_signed_info(doc.root_element());
1813 assert!(matches!(
1814 result.unwrap_err(),
1815 ParseError::InvalidStructure(_)
1816 ));
1817 }
1818
1819 #[test]
1822 fn base64_with_whitespace() {
1823 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1824 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1825 <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
1826 <Reference URI="">
1827 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
1828 <DigestValue>
1829 AAAAAAAA
1830 AAAAAAAAAAAAAAAAAAA=
1831 </DigestValue>
1832 </Reference>
1833 </SignedInfo>"#;
1834 let doc = Document::parse(xml).unwrap();
1835 let si = parse_signed_info(doc.root_element()).unwrap();
1836 assert_eq!(si.references[0].digest_value, vec![0u8; 20]);
1837 }
1838
1839 #[test]
1840 fn base64_decode_digest_accepts_xml_whitespace_chars() {
1841 let digest =
1842 base64_decode_digest("AAAA\tAAAA\rAAAA\nAAAA AAAAAAAAAAA=", DigestAlgorithm::Sha1)
1843 .expect("XML whitespace in DigestValue must be accepted");
1844 assert_eq!(digest, vec![0u8; 20]);
1845 }
1846
1847 #[test]
1848 fn base64_decode_digest_rejects_non_xml_ascii_whitespace() {
1849 let err = base64_decode_digest(
1850 "AAAA\u{000C}AAAAAAAAAAAAAAAAAAAAAAA=",
1851 DigestAlgorithm::Sha1,
1852 )
1853 .expect_err("form-feed/vertical-tab in DigestValue must be rejected");
1854 assert!(matches!(err, ParseError::Base64(_)));
1855 }
1856
1857 #[test]
1858 fn base64_decode_digest_rejects_oversized_base64_before_decode() {
1859 let err = base64_decode_digest("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", DigestAlgorithm::Sha1)
1860 .expect_err("oversized DigestValue base64 must fail before decode");
1861 match err {
1862 ParseError::Base64(message) => {
1863 assert!(
1864 message.contains("DigestValue exceeds maximum allowed base64 length"),
1865 "unexpected message: {message}"
1866 );
1867 }
1868 other => panic!("expected ParseError::Base64, got {other:?}"),
1869 }
1870 }
1871
1872 #[test]
1875 fn saml_response_signed_info() {
1876 let xml = r##"<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
1877 <ds:SignedInfo>
1878 <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1879 <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
1880 <ds:Reference URI="#_resp1">
1881 <ds:Transforms>
1882 <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
1883 <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1884 </ds:Transforms>
1885 <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
1886 <ds:DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</ds:DigestValue>
1887 </ds:Reference>
1888 </ds:SignedInfo>
1889 <ds:SignatureValue>ZmFrZQ==</ds:SignatureValue>
1890 </ds:Signature>"##;
1891 let doc = Document::parse(xml).unwrap();
1892
1893 let sig_node = doc.root_element();
1895 let signed_info_node = sig_node
1896 .children()
1897 .find(|n| n.is_element() && n.tag_name().name() == "SignedInfo")
1898 .unwrap();
1899
1900 let si = parse_signed_info(signed_info_node).unwrap();
1901 assert_eq!(si.signature_method, SignatureAlgorithm::RsaSha256);
1902 assert_eq!(si.references.len(), 1);
1903 assert_eq!(si.references[0].uri.as_deref(), Some("#_resp1"));
1904 assert_eq!(si.references[0].transforms.len(), 2);
1905 assert_eq!(si.references[0].digest_value, vec![0u8; 32]);
1906 }
1907}