1use roxmltree::{Document, Node};
20use x509_parser::extensions::ParsedExtension;
21use x509_parser::prelude::FromDer;
22use x509_parser::public_key::PublicKey;
23
24use super::digest::DigestAlgorithm;
25use super::transforms::{self, Transform};
26use super::whitespace::{is_xml_whitespace_only, normalize_xml_base64_text};
27use crate::c14n::C14nAlgorithm;
28
29pub(crate) const XMLDSIG_NS: &str = "http://www.w3.org/2000/09/xmldsig#";
31pub(crate) const XMLDSIG11_NS: &str = "http://www.w3.org/2009/xmldsig11#";
33const MAX_DER_ENCODED_KEY_VALUE_LEN: usize = 8192;
34const MAX_DER_ENCODED_KEY_VALUE_TEXT_LEN: usize = 65_536;
35const MAX_DER_ENCODED_KEY_VALUE_BASE64_LEN: usize = MAX_DER_ENCODED_KEY_VALUE_LEN.div_ceil(3) * 4;
36const MAX_KEY_NAME_TEXT_LEN: usize = 4096;
37const MAX_X509_BASE64_TEXT_LEN: usize = 262_144;
38const MAX_X509_BASE64_NORMALIZED_LEN: usize = MAX_X509_BASE64_TEXT_LEN;
39const MAX_X509_DECODED_BINARY_LEN: usize = MAX_X509_BASE64_NORMALIZED_LEN.div_ceil(4) * 3;
40const MAX_X509_SUBJECT_NAME_TEXT_LEN: usize = 16_384;
41const MAX_X509_ISSUER_NAME_TEXT_LEN: usize = 16_384;
42const MAX_X509_SERIAL_NUMBER_TEXT_LEN: usize = 4096;
43const MAX_X509_DATA_ENTRY_COUNT: usize = 64;
44const MAX_X509_DATA_TOTAL_BINARY_LEN: usize = 1_048_576;
45const MAX_X509_CHAIN_DEPTH: usize = 9;
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
49pub enum SignatureAlgorithm {
50 RsaSha1,
52 RsaSha256,
54 RsaSha384,
56 RsaSha512,
58 EcdsaP256Sha256,
60 EcdsaP384Sha384,
67}
68
69impl SignatureAlgorithm {
70 #[must_use]
72 pub fn from_uri(uri: &str) -> Option<Self> {
73 match uri {
74 "http://www.w3.org/2000/09/xmldsig#rsa-sha1" => Some(Self::RsaSha1),
75 "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" => Some(Self::RsaSha256),
76 "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384" => Some(Self::RsaSha384),
77 "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512" => Some(Self::RsaSha512),
78 "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256" => Some(Self::EcdsaP256Sha256),
79 "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384" => Some(Self::EcdsaP384Sha384),
80 _ => None,
81 }
82 }
83
84 #[must_use]
86 pub fn uri(self) -> &'static str {
87 match self {
88 Self::RsaSha1 => "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
89 Self::RsaSha256 => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
90 Self::RsaSha384 => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384",
91 Self::RsaSha512 => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512",
92 Self::EcdsaP256Sha256 => "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256",
93 Self::EcdsaP384Sha384 => "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384",
94 }
95 }
96
97 #[must_use]
99 pub fn signing_allowed(self) -> bool {
100 !matches!(self, Self::RsaSha1)
101 }
102}
103
104#[derive(Debug)]
106pub struct SignedInfo {
107 pub c14n_method: C14nAlgorithm,
109 pub signature_method: SignatureAlgorithm,
111 pub references: Vec<Reference>,
113}
114
115#[derive(Debug)]
117pub struct Reference {
118 pub uri: Option<String>,
120 pub id: Option<String>,
122 pub ref_type: Option<String>,
124 pub transforms: Vec<Transform>,
126 pub digest_method: DigestAlgorithm,
128 pub digest_value: Vec<u8>,
130}
131
132#[derive(Debug, Default, Clone, PartialEq, Eq)]
134#[non_exhaustive]
135pub struct KeyInfo {
136 pub sources: Vec<KeyInfoSource>,
138}
139
140#[derive(Debug, Clone, PartialEq, Eq)]
142#[non_exhaustive]
143pub enum KeyInfoSource {
144 KeyName(String),
146 KeyValue(KeyValueInfo),
148 X509Data(X509DataInfo),
150 DerEncodedKeyValue(Vec<u8>),
152}
153
154#[derive(Debug, Clone, PartialEq, Eq)]
156#[non_exhaustive]
157pub enum KeyValueInfo {
158 RsaKeyValue,
160 EcKeyValue,
162 Unsupported {
164 namespace: Option<String>,
166 local_name: String,
168 },
169}
170
171#[derive(Debug, Default, Clone, PartialEq, Eq)]
173#[non_exhaustive]
174pub struct X509DataInfo {
175 pub certificates: Vec<Vec<u8>>,
179 pub subject_names: Vec<String>,
181 pub issuer_serials: Vec<(String, String)>,
183 pub skis: Vec<Vec<u8>>,
185 pub crls: Vec<Vec<u8>>,
187 pub digests: Vec<(String, Vec<u8>)>,
189 pub parsed_certificates: Vec<ParsedX509Certificate>,
193 pub certificate_chain: Vec<usize>,
195}
196
197#[derive(Debug, Clone, PartialEq, Eq)]
199#[non_exhaustive]
200pub struct ParsedX509Certificate {
201 pub subject_dn: String,
203 pub issuer_dn: String,
205 pub serial_number: Vec<u8>,
207 pub serial_number_hex: String,
209 pub subject_key_identifier: Option<Vec<u8>>,
211 pub public_key: X509PublicKeyInfo,
213}
214
215#[derive(Debug, Clone, PartialEq, Eq)]
217#[non_exhaustive]
218pub enum X509PublicKeyInfo {
219 Rsa {
221 modulus: Vec<u8>,
223 exponent: Vec<u8>,
225 },
226 Ec {
228 curve_oid: String,
230 public_key: Vec<u8>,
232 },
233 Unsupported {
235 algorithm_oid: String,
237 },
238}
239
240#[derive(Debug, thiserror::Error)]
242#[non_exhaustive]
243pub enum ParseError {
244 #[error("missing required element: <{element}>")]
246 MissingElement {
247 element: &'static str,
249 },
250
251 #[error("invalid structure: {0}")]
253 InvalidStructure(String),
254
255 #[error("unsupported algorithm: {uri}")]
257 UnsupportedAlgorithm {
258 uri: String,
260 },
261
262 #[error("base64 decode error: {0}")]
264 Base64(String),
265
266 #[error(
268 "digest length mismatch for {algorithm}: expected {expected} bytes, got {actual} bytes"
269 )]
270 DigestLengthMismatch {
271 algorithm: &'static str,
273 expected: usize,
275 actual: usize,
277 },
278
279 #[error("transform error: {0}")]
281 Transform(#[from] super::types::TransformError),
282}
283
284#[must_use]
286pub fn find_signature_node<'a>(doc: &'a Document<'a>) -> Option<Node<'a, 'a>> {
287 doc.descendants().find(|n| {
288 n.is_element()
289 && n.tag_name().name() == "Signature"
290 && n.tag_name().namespace() == Some(XMLDSIG_NS)
291 })
292}
293
294pub fn parse_signed_info(signed_info_node: Node) -> Result<SignedInfo, ParseError> {
299 verify_ds_element(signed_info_node, "SignedInfo")?;
300
301 let mut children = element_children(signed_info_node);
302
303 let c14n_node = children.next().ok_or(ParseError::MissingElement {
305 element: "CanonicalizationMethod",
306 })?;
307 verify_ds_element(c14n_node, "CanonicalizationMethod")?;
308 let c14n_uri = required_algorithm_attr(c14n_node, "CanonicalizationMethod")?;
309 let mut c14n_method =
310 C14nAlgorithm::from_uri(c14n_uri).ok_or_else(|| ParseError::UnsupportedAlgorithm {
311 uri: c14n_uri.to_string(),
312 })?;
313 if let Some(prefix_list) = parse_inclusive_prefixes(c14n_node)? {
314 if c14n_method.mode() == crate::c14n::C14nMode::Exclusive1_0 {
315 c14n_method = c14n_method.with_prefix_list(&prefix_list);
316 } else {
317 return Err(ParseError::UnsupportedAlgorithm {
318 uri: c14n_uri.to_string(),
319 });
320 }
321 }
322
323 let sig_method_node = children.next().ok_or(ParseError::MissingElement {
325 element: "SignatureMethod",
326 })?;
327 verify_ds_element(sig_method_node, "SignatureMethod")?;
328 let sig_uri = required_algorithm_attr(sig_method_node, "SignatureMethod")?;
329 let signature_method =
330 SignatureAlgorithm::from_uri(sig_uri).ok_or_else(|| ParseError::UnsupportedAlgorithm {
331 uri: sig_uri.to_string(),
332 })?;
333
334 let mut references = Vec::new();
336 for child in children {
337 verify_ds_element(child, "Reference")?;
338 references.push(parse_reference(child)?);
339 }
340 if references.is_empty() {
341 return Err(ParseError::MissingElement {
342 element: "Reference",
343 });
344 }
345
346 Ok(SignedInfo {
347 c14n_method,
348 signature_method,
349 references,
350 })
351}
352
353pub(crate) fn parse_reference(reference_node: Node) -> Result<Reference, ParseError> {
357 let uri = reference_node.attribute("URI").map(String::from);
358 let id = reference_node.attribute("Id").map(String::from);
359 let ref_type = reference_node.attribute("Type").map(String::from);
360
361 let mut children = element_children(reference_node);
362
363 let mut transforms = Vec::new();
365 let mut next = children.next().ok_or(ParseError::MissingElement {
366 element: "DigestMethod",
367 })?;
368
369 if next.tag_name().name() == "Transforms" && next.tag_name().namespace() == Some(XMLDSIG_NS) {
370 transforms = transforms::parse_transforms(next)?;
371 next = children.next().ok_or(ParseError::MissingElement {
372 element: "DigestMethod",
373 })?;
374 }
375
376 verify_ds_element(next, "DigestMethod")?;
378 let digest_uri = required_algorithm_attr(next, "DigestMethod")?;
379 let digest_method =
380 DigestAlgorithm::from_uri(digest_uri).ok_or_else(|| ParseError::UnsupportedAlgorithm {
381 uri: digest_uri.to_string(),
382 })?;
383
384 let digest_value_node = children.next().ok_or(ParseError::MissingElement {
386 element: "DigestValue",
387 })?;
388 verify_ds_element(digest_value_node, "DigestValue")?;
389 let digest_value = decode_digest_value_children(digest_value_node, digest_method)?;
390
391 if let Some(unexpected) = children.next() {
393 return Err(ParseError::InvalidStructure(format!(
394 "unexpected element <{}> after <DigestValue> in <Reference>",
395 unexpected.tag_name().name()
396 )));
397 }
398
399 Ok(Reference {
400 uri,
401 id,
402 ref_type,
403 transforms,
404 digest_method,
405 digest_value,
406 })
407}
408
409pub fn parse_key_info(key_info_node: Node) -> Result<KeyInfo, ParseError> {
422 verify_ds_element(key_info_node, "KeyInfo")?;
423 ensure_no_non_whitespace_text(key_info_node, "KeyInfo")?;
424
425 let mut sources = Vec::new();
426 for child in element_children(key_info_node) {
427 match (child.tag_name().namespace(), child.tag_name().name()) {
428 (Some(XMLDSIG_NS), "KeyName") => {
429 ensure_no_element_children(child, "KeyName")?;
430 let key_name =
431 collect_text_content_bounded(child, MAX_KEY_NAME_TEXT_LEN, "KeyName")?;
432 sources.push(KeyInfoSource::KeyName(key_name));
433 }
434 (Some(XMLDSIG_NS), "KeyValue") => {
435 let key_value = parse_key_value_dispatch(child)?;
436 sources.push(KeyInfoSource::KeyValue(key_value));
437 }
438 (Some(XMLDSIG_NS), "X509Data") => {
439 let x509 = parse_x509_data_dispatch(child)?;
440 sources.push(KeyInfoSource::X509Data(x509));
441 }
442 (Some(XMLDSIG11_NS), "DEREncodedKeyValue") => {
443 ensure_no_element_children(child, "DEREncodedKeyValue")?;
444 let der = decode_der_encoded_key_value_base64(child)?;
445 sources.push(KeyInfoSource::DerEncodedKeyValue(der));
446 }
447 _ => {}
448 }
449 }
450
451 Ok(KeyInfo { sources })
452}
453
454fn element_children<'a>(node: Node<'a, 'a>) -> impl Iterator<Item = Node<'a, 'a>> {
458 node.children().filter(|n| n.is_element())
459}
460
461fn verify_ds_element(node: Node, expected_name: &'static str) -> Result<(), ParseError> {
463 if !node.is_element() {
464 return Err(ParseError::InvalidStructure(format!(
465 "expected element <{expected_name}>, got non-element node"
466 )));
467 }
468 let tag = node.tag_name();
469 if tag.name() != expected_name || tag.namespace() != Some(XMLDSIG_NS) {
470 return Err(ParseError::InvalidStructure(format!(
471 "expected <ds:{expected_name}>, got <{}{}>",
472 tag.namespace()
473 .map(|ns| format!("{{{ns}}}"))
474 .unwrap_or_default(),
475 tag.name()
476 )));
477 }
478 Ok(())
479}
480
481fn required_algorithm_attr<'a>(
483 node: Node<'a, 'a>,
484 element_name: &'static str,
485) -> Result<&'a str, ParseError> {
486 node.attribute("Algorithm").ok_or_else(|| {
487 ParseError::InvalidStructure(format!("missing Algorithm attribute on <{element_name}>"))
488 })
489}
490
491fn parse_inclusive_prefixes(node: Node) -> Result<Option<String>, ParseError> {
497 const EXCLUSIVE_C14N_NS_URI: &str = "http://www.w3.org/2001/10/xml-exc-c14n#";
498
499 for child in node.children() {
500 if child.is_element() {
501 let tag = child.tag_name();
502 if tag.name() == "InclusiveNamespaces" && tag.namespace() == Some(EXCLUSIVE_C14N_NS_URI)
503 {
504 return child
505 .attribute("PrefixList")
506 .map(str::to_string)
507 .ok_or_else(|| {
508 ParseError::InvalidStructure(
509 "missing PrefixList attribute on <InclusiveNamespaces>".into(),
510 )
511 })
512 .map(Some);
513 }
514 }
515 }
516
517 Ok(None)
518}
519
520fn parse_key_value_dispatch(node: Node) -> Result<KeyValueInfo, ParseError> {
521 verify_ds_element(node, "KeyValue")?;
522 ensure_no_non_whitespace_text(node, "KeyValue")?;
523
524 let mut children = element_children(node);
525 let Some(first_child) = children.next() else {
526 return Err(ParseError::InvalidStructure(
527 "KeyValue must contain exactly one key-value child".into(),
528 ));
529 };
530 if children.next().is_some() {
531 return Err(ParseError::InvalidStructure(
532 "KeyValue must contain exactly one key-value child".into(),
533 ));
534 }
535
536 match (
537 first_child.tag_name().namespace(),
538 first_child.tag_name().name(),
539 ) {
540 (Some(XMLDSIG_NS), "RSAKeyValue") => Ok(KeyValueInfo::RsaKeyValue),
541 (Some(XMLDSIG11_NS), "ECKeyValue") => Ok(KeyValueInfo::EcKeyValue),
542 (namespace, child_name) => Ok(KeyValueInfo::Unsupported {
543 namespace: namespace.map(str::to_string),
544 local_name: child_name.to_string(),
545 }),
546 }
547}
548
549fn parse_x509_data_dispatch(node: Node) -> Result<X509DataInfo, ParseError> {
550 verify_ds_element(node, "X509Data")?;
551 ensure_no_non_whitespace_text(node, "X509Data")?;
552
553 let mut info = X509DataInfo::default();
554 let mut total_binary_len = 0usize;
555 for child in element_children(node) {
556 match (child.tag_name().namespace(), child.tag_name().name()) {
557 (Some(XMLDSIG_NS), "X509Certificate") => {
558 ensure_no_element_children(child, "X509Certificate")?;
559 ensure_x509_data_entry_budget(&info)?;
560 let cert = decode_x509_base64(child, "X509Certificate")?;
561 add_x509_data_usage(&mut total_binary_len, cert.len())?;
562 let parsed_cert = parse_x509_certificate(cert.as_slice())?;
563 info.parsed_certificates.push(parsed_cert);
564 info.certificates.push(cert);
565 }
566 (Some(XMLDSIG_NS), "X509SubjectName") => {
567 ensure_no_element_children(child, "X509SubjectName")?;
568 ensure_x509_data_entry_budget(&info)?;
569 let subject_name = collect_text_content_bounded(
570 child,
571 MAX_X509_SUBJECT_NAME_TEXT_LEN,
572 "X509SubjectName",
573 )?;
574 info.subject_names.push(subject_name);
575 }
576 (Some(XMLDSIG_NS), "X509IssuerSerial") => {
577 ensure_x509_data_entry_budget(&info)?;
578 let issuer_serial = parse_x509_issuer_serial(child)?;
579 info.issuer_serials.push(issuer_serial);
580 }
581 (Some(XMLDSIG_NS), "X509SKI") => {
582 ensure_no_element_children(child, "X509SKI")?;
583 ensure_x509_data_entry_budget(&info)?;
584 let ski = decode_x509_base64(child, "X509SKI")?;
585 add_x509_data_usage(&mut total_binary_len, ski.len())?;
586 info.skis.push(ski);
587 }
588 (Some(XMLDSIG_NS), "X509CRL") => {
589 ensure_no_element_children(child, "X509CRL")?;
590 ensure_x509_data_entry_budget(&info)?;
591 let crl = decode_x509_base64(child, "X509CRL")?;
592 add_x509_data_usage(&mut total_binary_len, crl.len())?;
593 info.crls.push(crl);
594 }
595 (Some(XMLDSIG11_NS), "X509Digest") => {
596 ensure_no_element_children(child, "X509Digest")?;
597 ensure_x509_data_entry_budget(&info)?;
598 let algorithm = required_algorithm_attr(child, "X509Digest")?;
599 let digest = decode_x509_base64(child, "X509Digest")?;
600 add_x509_data_usage(&mut total_binary_len, digest.len())?;
601 info.digests.push((algorithm.to_string(), digest));
602 }
603 (Some(XMLDSIG_NS), child_name) | (Some(XMLDSIG11_NS), child_name) => {
604 return Err(ParseError::InvalidStructure(format!(
605 "X509Data contains unsupported XMLDSig child element <{child_name}>"
606 )));
607 }
608 _ => {}
609 }
610 }
611
612 info.certificate_chain = build_x509_certificate_chain(&info)?;
613 Ok(info)
614}
615
616fn build_x509_certificate_chain(info: &X509DataInfo) -> Result<Vec<usize>, ParseError> {
617 if info.parsed_certificates.is_empty() {
618 return Ok(Vec::new());
619 }
620
621 let signing_idx = select_x509_signing_certificate(info)?;
622 let mut chain = vec![signing_idx];
623
624 loop {
625 if chain.len() > MAX_X509_CHAIN_DEPTH {
626 return Err(ParseError::InvalidStructure(
627 "X509Data certificate chain exceeds maximum depth".into(),
628 ));
629 }
630
631 let current_idx = *chain
632 .last()
633 .expect("chain starts with signing certificate index");
634 let current = &info.parsed_certificates[current_idx];
635 if current.subject_dn == current.issuer_dn {
636 break;
637 }
638
639 let candidates = info
640 .parsed_certificates
641 .iter()
642 .enumerate()
643 .filter(|(idx, cert)| *idx != current_idx && cert.subject_dn == current.issuer_dn)
644 .map(|(idx, _)| idx)
645 .collect::<Vec<_>>();
646
647 match candidates.as_slice() {
648 [] => break,
649 [issuer_idx] => {
650 if chain.contains(issuer_idx) {
651 return Err(ParseError::InvalidStructure(
652 "X509Data certificate chain contains a cycle".into(),
653 ));
654 }
655 if chain.len() == MAX_X509_CHAIN_DEPTH {
656 return Err(ParseError::InvalidStructure(
657 "X509Data certificate chain exceeds maximum depth".into(),
658 ));
659 }
660 chain.push(*issuer_idx);
661 }
662 _ => {
663 return Err(ParseError::InvalidStructure(
664 "X509Data certificate chain contains ambiguous issuer certificates".into(),
665 ));
666 }
667 }
668 }
669
670 Ok(chain)
671}
672
673fn select_x509_signing_certificate(info: &X509DataInfo) -> Result<usize, ParseError> {
674 let mut candidates = Vec::new();
675 let has_lookup_identifiers =
676 !info.subject_names.is_empty() || !info.issuer_serials.is_empty() || !info.skis.is_empty();
677
678 for subject_name in &info.subject_names {
679 let subject_name = subject_name.trim();
680 let matches = info
681 .parsed_certificates
682 .iter()
683 .enumerate()
684 .filter(|(_, cert)| subject_name == cert.subject_dn)
685 .map(|(idx, _)| idx)
686 .collect::<Vec<_>>();
687 if matches.is_empty() {
688 return Err(ParseError::InvalidStructure(
689 "X509Data lookup identifiers do not match any embedded certificate".into(),
690 ));
691 }
692 candidates.extend(matches);
693 }
694
695 for (issuer_name, serial) in &info.issuer_serials {
696 let serial_hex = x509_serial_decimal_to_hex(serial).ok_or_else(|| {
697 ParseError::InvalidStructure(
698 "X509Data lookup identifiers do not match any embedded certificate".into(),
699 )
700 })?;
701 let issuer_name = issuer_name.trim();
702 let matches = info
703 .parsed_certificates
704 .iter()
705 .enumerate()
706 .filter(|(_, cert)| {
707 issuer_name == cert.issuer_dn && serial_hex == cert.serial_number_hex
708 })
709 .map(|(idx, _)| idx)
710 .collect::<Vec<_>>();
711 if matches.is_empty() {
712 return Err(ParseError::InvalidStructure(
713 "X509Data lookup identifiers do not match any embedded certificate".into(),
714 ));
715 }
716 candidates.extend(matches);
717 }
718
719 for ski in &info.skis {
720 let matches = info
721 .parsed_certificates
722 .iter()
723 .enumerate()
724 .filter(|(_, cert)| {
725 cert.subject_key_identifier
726 .as_ref()
727 .is_some_and(|subject_key_identifier| subject_key_identifier == ski)
728 })
729 .map(|(idx, _)| idx)
730 .collect::<Vec<_>>();
731 if matches.is_empty() {
732 return Err(ParseError::InvalidStructure(
733 "X509Data lookup identifiers do not match any embedded certificate".into(),
734 ));
735 }
736 candidates.extend(matches);
737 }
738
739 if has_lookup_identifiers {
740 candidates.sort_unstable();
741 candidates.dedup();
742 }
743
744 match candidates.as_slice() {
745 [idx] => return Ok(*idx),
746 [] if has_lookup_identifiers => {
747 return Err(ParseError::InvalidStructure(
748 "X509Data lookup identifiers do not match any embedded certificate".into(),
749 ));
750 }
751 [] => {}
752 _ => {
753 return Err(ParseError::InvalidStructure(
754 "X509Data lookup identifiers match multiple certificates".into(),
755 ));
756 }
757 }
758
759 let leaf_candidates = info
760 .parsed_certificates
761 .iter()
762 .enumerate()
763 .filter(|(_, cert)| {
764 cert.subject_dn != cert.issuer_dn
765 && !info
766 .parsed_certificates
767 .iter()
768 .any(|other| other.issuer_dn == cert.subject_dn)
769 })
770 .map(|(idx, _)| idx)
771 .collect::<Vec<_>>();
772
773 match leaf_candidates.as_slice() {
774 [idx] => Ok(*idx),
775 [] => Ok(0),
776 _ => Err(ParseError::InvalidStructure(
777 "X509Data contains multiple possible signing certificates".into(),
778 )),
779 }
780}
781
782fn ensure_x509_data_entry_budget(info: &X509DataInfo) -> Result<(), ParseError> {
783 let total_entries = info.certificates.len()
784 + info.subject_names.len()
785 + info.issuer_serials.len()
786 + info.skis.len()
787 + info.crls.len()
788 + info.digests.len();
789 if total_entries >= MAX_X509_DATA_ENTRY_COUNT {
790 return Err(ParseError::InvalidStructure(
791 "X509Data contains too many entries".into(),
792 ));
793 }
794 Ok(())
795}
796
797fn add_x509_data_usage(total_binary_len: &mut usize, delta: usize) -> Result<(), ParseError> {
798 *total_binary_len = total_binary_len.checked_add(delta).ok_or_else(|| {
799 ParseError::InvalidStructure("X509Data exceeds maximum allowed total binary length".into())
800 })?;
801 if *total_binary_len > MAX_X509_DATA_TOTAL_BINARY_LEN {
802 return Err(ParseError::InvalidStructure(
803 "X509Data exceeds maximum allowed total binary length".into(),
804 ));
805 }
806 Ok(())
807}
808
809fn decode_x509_base64(
810 node: Node<'_, '_>,
811 element_name: &'static str,
812) -> Result<Vec<u8>, ParseError> {
813 use base64::Engine;
814 use base64::engine::general_purpose::STANDARD;
815
816 let mut cleaned = String::new();
817 let mut raw_text_len = 0usize;
818 for text in node
819 .children()
820 .filter(|child| child.is_text())
821 .filter_map(|child| child.text())
822 {
823 if raw_text_len.saturating_add(text.len()) > MAX_X509_BASE64_TEXT_LEN {
824 return Err(ParseError::InvalidStructure(format!(
825 "{element_name} exceeds maximum allowed text length"
826 )));
827 }
828 raw_text_len = raw_text_len.saturating_add(text.len());
829 normalize_xml_base64_text(text, &mut cleaned).map_err(|err| {
830 ParseError::Base64(format!(
831 "invalid XML whitespace U+{:04X} in {element_name}",
832 err.invalid_byte
833 ))
834 })?;
835 if cleaned.len() > MAX_X509_BASE64_NORMALIZED_LEN {
836 return Err(ParseError::InvalidStructure(format!(
837 "{element_name} exceeds maximum allowed base64 length"
838 )));
839 }
840 }
841
842 let decoded = STANDARD
843 .decode(&cleaned)
844 .map_err(|e| ParseError::Base64(format!("{element_name}: {e}")))?;
845 if decoded.is_empty() {
846 return Err(ParseError::InvalidStructure(format!(
847 "{element_name} must not be empty"
848 )));
849 }
850 if decoded.len() > MAX_X509_DECODED_BINARY_LEN {
851 return Err(ParseError::InvalidStructure(format!(
852 "{element_name} exceeds maximum allowed binary length"
853 )));
854 }
855 Ok(decoded)
856}
857
858fn parse_x509_certificate(cert_der: &[u8]) -> Result<ParsedX509Certificate, ParseError> {
859 let (rest, cert) =
860 x509_parser::certificate::X509Certificate::from_der(cert_der).map_err(|err| {
861 ParseError::InvalidStructure(format!("X509Certificate is not valid DER X.509: {err}"))
862 })?;
863 if !rest.is_empty() {
864 return Err(ParseError::InvalidStructure(
865 "X509Certificate contains trailing bytes after DER certificate".into(),
866 ));
867 }
868
869 let subject_dn = cert.subject().to_string();
870 let issuer_dn = cert.issuer().to_string();
871 let serial_number = cert.tbs_certificate.raw_serial().to_vec();
872 let serial_number_hex = format_x509_serial_value_hex(&serial_number);
873
874 let subject_key_identifier = cert.extensions().iter().find_map(|ext| {
875 if let ParsedExtension::SubjectKeyIdentifier(ski) = ext.parsed_extension() {
876 Some(ski.0.to_vec())
877 } else {
878 None
879 }
880 });
881
882 let spki = cert.public_key();
883 let public_key = match spki.parsed().map_err(|err| {
884 ParseError::InvalidStructure(format!("X509Certificate public key parse error: {err}"))
885 })? {
886 PublicKey::RSA(rsa) => {
887 let modulus = trim_leading_zeroes(rsa.modulus);
888 let exponent = trim_leading_zeroes(rsa.exponent);
889 if modulus.is_empty() || exponent.is_empty() {
890 return Err(ParseError::InvalidStructure(
891 "X509Certificate RSA key contains empty modulus or exponent".into(),
892 ));
893 }
894 X509PublicKeyInfo::Rsa { modulus, exponent }
895 }
896 PublicKey::EC(ec_point) => {
897 let Some(params) = spki.algorithm.parameters.as_ref() else {
898 return Err(ParseError::InvalidStructure(
899 "X509Certificate EC key is missing curve parameters".into(),
900 ));
901 };
902
903 match params.as_oid() {
904 Ok(oid) => X509PublicKeyInfo::Ec {
905 curve_oid: oid.to_id_string(),
906 public_key: ec_point.data().to_vec(),
907 },
908 Err(_) => X509PublicKeyInfo::Unsupported {
909 algorithm_oid: spki.algorithm.algorithm.to_id_string(),
910 },
911 }
912 }
913 _ => X509PublicKeyInfo::Unsupported {
914 algorithm_oid: spki.algorithm.algorithm.to_id_string(),
915 },
916 };
917
918 Ok(ParsedX509Certificate {
919 subject_dn,
920 issuer_dn,
921 serial_number,
922 serial_number_hex,
923 subject_key_identifier,
924 public_key,
925 })
926}
927
928fn format_x509_serial_hex(serial: &[u8]) -> String {
929 serial
930 .iter()
931 .map(|byte| format!("{byte:02X}"))
932 .collect::<String>()
933}
934
935fn format_x509_serial_value_hex(serial: &[u8]) -> String {
936 let first_non_zero = serial
937 .iter()
938 .position(|byte| *byte != 0)
939 .unwrap_or(serial.len());
940 let canonical = if first_non_zero == serial.len() {
941 &[0]
942 } else {
943 &serial[first_non_zero..]
944 };
945 format_x509_serial_hex(canonical)
946}
947
948fn x509_serial_decimal_to_hex(serial: &str) -> Option<String> {
949 let serial = serial.trim();
950 let serial = serial.strip_prefix('+').unwrap_or(serial);
951 if serial.is_empty() || !serial.bytes().all(|byte| byte.is_ascii_digit()) {
952 return None;
953 }
954
955 let mut bytes = Vec::<u8>::new();
956 for digit in serial.bytes().map(|byte| byte - b'0') {
957 let mut carry = u16::from(digit);
958 for byte in bytes.iter_mut().rev() {
959 let value = u16::from(*byte) * 10 + carry;
960 *byte = value as u8;
961 carry = value >> 8;
962 }
963 while carry > 0 {
964 bytes.insert(0, carry as u8);
965 carry >>= 8;
966 }
967 }
968
969 Some(format_x509_serial_value_hex(&bytes))
970}
971
972fn trim_leading_zeroes(bytes: &[u8]) -> Vec<u8> {
973 let first_non_zero = bytes
974 .iter()
975 .position(|byte| *byte != 0)
976 .unwrap_or(bytes.len());
977 bytes[first_non_zero..].to_vec()
978}
979
980fn parse_x509_issuer_serial(node: Node<'_, '_>) -> Result<(String, String), ParseError> {
981 verify_ds_element(node, "X509IssuerSerial")?;
982 ensure_no_non_whitespace_text(node, "X509IssuerSerial")?;
983
984 let children = element_children(node).collect::<Vec<_>>();
985 if children.len() != 2 {
986 return Err(ParseError::InvalidStructure(
987 "X509IssuerSerial must contain exactly X509IssuerName then X509SerialNumber".into(),
988 ));
989 }
990 if !matches!(
991 (
992 children[0].tag_name().namespace(),
993 children[0].tag_name().name()
994 ),
995 (Some(XMLDSIG_NS), "X509IssuerName")
996 ) {
997 return Err(ParseError::InvalidStructure(
998 "X509IssuerSerial must contain X509IssuerName as the first child element".into(),
999 ));
1000 }
1001 if !matches!(
1002 (
1003 children[1].tag_name().namespace(),
1004 children[1].tag_name().name()
1005 ),
1006 (Some(XMLDSIG_NS), "X509SerialNumber")
1007 ) {
1008 return Err(ParseError::InvalidStructure(
1009 "X509IssuerSerial must contain X509SerialNumber as the second child element".into(),
1010 ));
1011 }
1012
1013 let issuer_node = children[0];
1014 ensure_no_element_children(issuer_node, "X509IssuerName")?;
1015 let issuer_name =
1016 collect_text_content_bounded(issuer_node, MAX_X509_ISSUER_NAME_TEXT_LEN, "X509IssuerName")?;
1017
1018 let serial_node = children[1];
1019 ensure_no_element_children(serial_node, "X509SerialNumber")?;
1020 let serial_number = collect_text_content_bounded(
1021 serial_node,
1022 MAX_X509_SERIAL_NUMBER_TEXT_LEN,
1023 "X509SerialNumber",
1024 )?;
1025 if issuer_name.trim().is_empty() || serial_number.trim().is_empty() {
1026 return Err(ParseError::InvalidStructure(
1027 "X509IssuerSerial requires non-empty X509IssuerName and X509SerialNumber".into(),
1028 ));
1029 }
1030
1031 Ok((issuer_name, serial_number))
1032}
1033
1034fn base64_decode_digest(b64: &str, digest_method: DigestAlgorithm) -> Result<Vec<u8>, ParseError> {
1038 use base64::Engine;
1039 use base64::engine::general_purpose::STANDARD;
1040
1041 let expected = digest_method.output_len();
1042 let max_base64_len = expected.div_ceil(3) * 4;
1043 let mut cleaned = String::with_capacity(b64.len().min(max_base64_len));
1044 normalize_xml_base64_text(b64, &mut cleaned).map_err(|err| {
1045 ParseError::Base64(format!(
1046 "invalid XML whitespace U+{:04X} in DigestValue",
1047 err.invalid_byte
1048 ))
1049 })?;
1050 if cleaned.len() > max_base64_len {
1051 return Err(ParseError::Base64(
1052 "DigestValue exceeds maximum allowed base64 length".into(),
1053 ));
1054 }
1055 let digest = STANDARD
1056 .decode(&cleaned)
1057 .map_err(|e| ParseError::Base64(e.to_string()))?;
1058 let actual = digest.len();
1059 if actual != expected {
1060 return Err(ParseError::DigestLengthMismatch {
1061 algorithm: digest_method.uri(),
1062 expected,
1063 actual,
1064 });
1065 }
1066 Ok(digest)
1067}
1068
1069fn decode_digest_value_children(
1070 digest_value_node: Node<'_, '_>,
1071 digest_method: DigestAlgorithm,
1072) -> Result<Vec<u8>, ParseError> {
1073 let max_base64_len = digest_method.output_len().div_ceil(3) * 4;
1074 let mut cleaned = String::with_capacity(max_base64_len);
1075
1076 for child in digest_value_node.children() {
1077 if child.is_element() {
1078 return Err(ParseError::InvalidStructure(
1079 "DigestValue must not contain element children".into(),
1080 ));
1081 }
1082 if let Some(text) = child.text() {
1083 normalize_xml_base64_text(text, &mut cleaned).map_err(|err| {
1084 ParseError::Base64(format!(
1085 "invalid XML whitespace U+{:04X} in DigestValue",
1086 err.invalid_byte
1087 ))
1088 })?;
1089 if cleaned.len() > max_base64_len {
1090 return Err(ParseError::Base64(
1091 "DigestValue exceeds maximum allowed base64 length".into(),
1092 ));
1093 }
1094 }
1095 }
1096
1097 base64_decode_digest(&cleaned, digest_method)
1098}
1099
1100fn decode_der_encoded_key_value_base64(node: Node<'_, '_>) -> Result<Vec<u8>, ParseError> {
1101 use base64::Engine;
1102 use base64::engine::general_purpose::STANDARD;
1103
1104 let mut cleaned = String::new();
1105 let mut raw_text_len = 0usize;
1106 for text in node
1107 .children()
1108 .filter(|child| child.is_text())
1109 .filter_map(|child| child.text())
1110 {
1111 if raw_text_len.saturating_add(text.len()) > MAX_DER_ENCODED_KEY_VALUE_TEXT_LEN {
1112 return Err(ParseError::InvalidStructure(
1113 "DEREncodedKeyValue exceeds maximum allowed text length".into(),
1114 ));
1115 }
1116 raw_text_len = raw_text_len.saturating_add(text.len());
1117 normalize_xml_base64_text(text, &mut cleaned).map_err(|err| {
1118 ParseError::Base64(format!(
1119 "invalid XML whitespace U+{:04X} in base64 text",
1120 err.invalid_byte
1121 ))
1122 })?;
1123 if cleaned.len() > MAX_DER_ENCODED_KEY_VALUE_BASE64_LEN {
1124 return Err(ParseError::InvalidStructure(
1125 "DEREncodedKeyValue exceeds maximum allowed length".into(),
1126 ));
1127 }
1128 }
1129
1130 let der = STANDARD
1131 .decode(&cleaned)
1132 .map_err(|e| ParseError::Base64(e.to_string()))?;
1133 if der.is_empty() {
1134 return Err(ParseError::InvalidStructure(
1135 "DEREncodedKeyValue must not be empty".into(),
1136 ));
1137 }
1138 if der.len() > MAX_DER_ENCODED_KEY_VALUE_LEN {
1139 return Err(ParseError::InvalidStructure(
1140 "DEREncodedKeyValue exceeds maximum allowed length".into(),
1141 ));
1142 }
1143 Ok(der)
1144}
1145
1146fn collect_text_content_bounded(
1147 node: Node<'_, '_>,
1148 max_len: usize,
1149 element_name: &'static str,
1150) -> Result<String, ParseError> {
1151 let mut text = String::new();
1152 for chunk in node
1153 .children()
1154 .filter_map(|child| child.is_text().then(|| child.text()).flatten())
1155 {
1156 if text.len().saturating_add(chunk.len()) > max_len {
1157 return Err(ParseError::InvalidStructure(format!(
1158 "{element_name} exceeds maximum allowed text length"
1159 )));
1160 }
1161 text.push_str(chunk);
1162 }
1163 Ok(text)
1164}
1165
1166fn ensure_no_element_children(node: Node<'_, '_>, element_name: &str) -> Result<(), ParseError> {
1167 if node.children().any(|child| child.is_element()) {
1168 return Err(ParseError::InvalidStructure(format!(
1169 "{element_name} must not contain child elements"
1170 )));
1171 }
1172 Ok(())
1173}
1174
1175fn ensure_no_non_whitespace_text(node: Node<'_, '_>, element_name: &str) -> Result<(), ParseError> {
1176 for child in node.children().filter(|child| child.is_text()) {
1177 if let Some(text) = child.text()
1178 && !is_xml_whitespace_only(text)
1179 {
1180 return Err(ParseError::InvalidStructure(format!(
1181 "{element_name} must not contain non-whitespace mixed content"
1182 )));
1183 }
1184 }
1185 Ok(())
1186}
1187
1188#[cfg(test)]
1189#[expect(clippy::unwrap_used, reason = "tests use trusted XML fixtures")]
1190mod tests {
1191 use super::*;
1192 use base64::Engine;
1193
1194 fn fixture_rsa_cert_base64() -> String {
1195 fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem")
1196 }
1197
1198 fn fixture_cert_base64(path: &str) -> String {
1199 match path {
1200 "../../tests/fixtures/keys/rsa/rsa-2048-cert.pem" => {
1201 include_str!("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem")
1202 }
1203 "../../tests/fixtures/keys/rsa/rsa-4096-cert.pem" => {
1204 include_str!("../../tests/fixtures/keys/rsa/rsa-4096-cert.pem")
1205 }
1206 "../../tests/fixtures/keys/ca2cert.pem" => {
1207 include_str!("../../tests/fixtures/keys/ca2cert.pem")
1208 }
1209 "../../tests/fixtures/keys/cacert.pem" => {
1210 include_str!("../../tests/fixtures/keys/cacert.pem")
1211 }
1212 _ => unreachable!("unknown certificate fixture"),
1213 }
1214 .lines()
1215 .skip_while(|line| *line != "-----BEGIN CERTIFICATE-----")
1216 .skip(1)
1217 .take_while(|line| *line != "-----END CERTIFICATE-----")
1218 .collect::<String>()
1219 }
1220
1221 #[test]
1224 fn signature_algorithm_from_uri_rsa_sha256() {
1225 assert_eq!(
1226 SignatureAlgorithm::from_uri("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"),
1227 Some(SignatureAlgorithm::RsaSha256)
1228 );
1229 }
1230
1231 #[test]
1232 fn signature_algorithm_from_uri_rsa_sha1() {
1233 assert_eq!(
1234 SignatureAlgorithm::from_uri("http://www.w3.org/2000/09/xmldsig#rsa-sha1"),
1235 Some(SignatureAlgorithm::RsaSha1)
1236 );
1237 }
1238
1239 #[test]
1240 fn signature_algorithm_from_uri_ecdsa_sha256() {
1241 assert_eq!(
1242 SignatureAlgorithm::from_uri("http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"),
1243 Some(SignatureAlgorithm::EcdsaP256Sha256)
1244 );
1245 }
1246
1247 #[test]
1248 fn signature_algorithm_from_uri_unknown() {
1249 assert_eq!(
1250 SignatureAlgorithm::from_uri("http://example.com/unknown"),
1251 None
1252 );
1253 }
1254
1255 #[test]
1256 fn signature_algorithm_uri_round_trip() {
1257 for algo in [
1258 SignatureAlgorithm::RsaSha1,
1259 SignatureAlgorithm::RsaSha256,
1260 SignatureAlgorithm::RsaSha384,
1261 SignatureAlgorithm::RsaSha512,
1262 SignatureAlgorithm::EcdsaP256Sha256,
1263 SignatureAlgorithm::EcdsaP384Sha384,
1264 ] {
1265 assert_eq!(
1266 SignatureAlgorithm::from_uri(algo.uri()),
1267 Some(algo),
1268 "round-trip failed for {algo:?}"
1269 );
1270 }
1271 }
1272
1273 #[test]
1274 fn rsa_sha1_verify_only() {
1275 assert!(!SignatureAlgorithm::RsaSha1.signing_allowed());
1276 assert!(SignatureAlgorithm::RsaSha256.signing_allowed());
1277 assert!(SignatureAlgorithm::EcdsaP256Sha256.signing_allowed());
1278 }
1279
1280 #[test]
1283 fn find_signature_in_saml() {
1284 let xml = r#"<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
1285 <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
1286 <ds:SignedInfo/>
1287 </ds:Signature>
1288 </samlp:Response>"#;
1289 let doc = Document::parse(xml).unwrap();
1290 let sig = find_signature_node(&doc);
1291 assert!(sig.is_some());
1292 assert_eq!(sig.unwrap().tag_name().name(), "Signature");
1293 }
1294
1295 #[test]
1296 fn find_signature_missing() {
1297 let xml = "<root><child/></root>";
1298 let doc = Document::parse(xml).unwrap();
1299 assert!(find_signature_node(&doc).is_none());
1300 }
1301
1302 #[test]
1303 fn find_signature_ignores_wrong_namespace() {
1304 let xml = r#"<root><Signature xmlns="http://example.com/fake"/></root>"#;
1305 let doc = Document::parse(xml).unwrap();
1306 assert!(find_signature_node(&doc).is_none());
1307 }
1308
1309 #[test]
1312 fn parse_key_info_dispatches_supported_children() {
1313 let cert_base64 = fixture_rsa_cert_base64();
1314 let xml = format!(
1315 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
1316 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
1317 <KeyName>idp-signing-key</KeyName>
1318 <KeyValue>
1319 <RSAKeyValue>
1320 <Modulus>AQAB</Modulus>
1321 <Exponent>AQAB</Exponent>
1322 </RSAKeyValue>
1323 </KeyValue>
1324 <X509Data>
1325 <X509Certificate>{cert_base64}</X509Certificate>
1326 <X509SubjectName>C=US, ST=California, O=XML Security Library (http://www.aleksey.com/xmlsec), CN=Test Key rsa-2048</X509SubjectName>
1327 <X509IssuerSerial>
1328 <X509IssuerName>C=US, ST=California, O=XML Security Library (http://www.aleksey.com/xmlsec), OU=Second level CA, CN=Aleksey Sanin, Email=xmlsec@aleksey.com</X509IssuerName>
1329 <X509SerialNumber>680572598617295163017172295025714171905498632019</X509SerialNumber>
1330 </X509IssuerSerial>
1331 <X509SKI>bcOXN/nsVl8GatRbcKrPbzIbw0Y=</X509SKI>
1332 <X509CRL>BAUGBw==</X509CRL>
1333 <dsig11:X509Digest Algorithm="http://www.w3.org/2001/04/xmlenc#sha256">CAkK</dsig11:X509Digest>
1334 </X509Data>
1335 <dsig11:DEREncodedKeyValue>AQIDBA==</dsig11:DEREncodedKeyValue>
1336 </KeyInfo>"#
1337 );
1338 let doc = Document::parse(&xml).unwrap();
1339
1340 let key_info = parse_key_info(doc.root_element()).unwrap();
1341 assert_eq!(key_info.sources.len(), 4);
1342
1343 assert_eq!(
1344 key_info.sources[0],
1345 KeyInfoSource::KeyName("idp-signing-key".to_string())
1346 );
1347 assert_eq!(
1348 key_info.sources[1],
1349 KeyInfoSource::KeyValue(KeyValueInfo::RsaKeyValue)
1350 );
1351 let x509_info = match &key_info.sources[2] {
1352 KeyInfoSource::X509Data(x509) => x509,
1353 other => panic!("expected X509Data source, got {other:?}"),
1354 };
1355 let expected_cert = base64::engine::general_purpose::STANDARD
1356 .decode(&cert_base64)
1357 .expect("fixture PEM must contain valid base64");
1358 assert_eq!(x509_info.certificates, vec![expected_cert]);
1359 assert_eq!(
1360 x509_info.subject_names,
1361 vec![
1362 "C=US, ST=California, O=XML Security Library (http://www.aleksey.com/xmlsec), CN=Test Key rsa-2048"
1363 .to_string()
1364 ]
1365 );
1366 assert_eq!(
1367 x509_info.issuer_serials,
1368 vec![(
1369 "C=US, ST=California, O=XML Security Library (http://www.aleksey.com/xmlsec), OU=Second level CA, CN=Aleksey Sanin, Email=xmlsec@aleksey.com".to_string(),
1370 "680572598617295163017172295025714171905498632019".to_string()
1371 )]
1372 );
1373 assert_eq!(
1374 x509_info.skis,
1375 vec![vec![
1376 109, 195, 151, 55, 249, 236, 86, 95, 6, 106, 212, 91, 112, 170, 207, 111, 50, 27,
1377 195, 70
1378 ]]
1379 );
1380 assert_eq!(x509_info.crls, vec![vec![4, 5, 6, 7]]);
1381 assert_eq!(
1382 x509_info.digests,
1383 vec![(
1384 "http://www.w3.org/2001/04/xmlenc#sha256".to_string(),
1385 vec![8, 9, 10]
1386 )]
1387 );
1388 assert_eq!(x509_info.parsed_certificates.len(), 1);
1389 assert_eq!(x509_info.certificate_chain, vec![0]);
1390 let parsed_cert = &x509_info.parsed_certificates[0];
1391 assert!(!parsed_cert.subject_dn.is_empty());
1392 assert!(!parsed_cert.issuer_dn.is_empty());
1393 assert_eq!(
1394 parsed_cert.serial_number_hex,
1395 "7735EE487F6862DAF1B3956D961CCB0FA6F34F53"
1396 );
1397 assert!(parsed_cert.subject_key_identifier.is_some());
1398 assert!(matches!(
1399 parsed_cert.public_key,
1400 X509PublicKeyInfo::Rsa { .. }
1401 ));
1402
1403 assert_eq!(
1404 key_info.sources[3],
1405 KeyInfoSource::DerEncodedKeyValue(vec![1, 2, 3, 4])
1406 );
1407 }
1408
1409 #[test]
1410 fn parse_key_info_ignores_unknown_children() {
1411 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1412 <Foo>bar</Foo>
1413 <KeyName>ok</KeyName>
1414 </KeyInfo>"#;
1415 let doc = Document::parse(xml).unwrap();
1416
1417 let key_info = parse_key_info(doc.root_element()).unwrap();
1418 assert_eq!(key_info.sources, vec![KeyInfoSource::KeyName("ok".into())]);
1419 }
1420
1421 #[test]
1422 fn parse_key_info_keyvalue_requires_single_child() {
1423 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1424 <KeyValue/>
1425 </KeyInfo>"#;
1426 let doc = Document::parse(xml).unwrap();
1427
1428 let err = parse_key_info(doc.root_element()).unwrap_err();
1429 assert!(matches!(err, ParseError::InvalidStructure(_)));
1430 }
1431
1432 #[test]
1433 fn parse_key_info_accepts_empty_x509data() {
1434 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1435 <X509Data/>
1436 </KeyInfo>"#;
1437 let doc = Document::parse(xml).unwrap();
1438
1439 let key_info = parse_key_info(doc.root_element()).unwrap();
1440 assert_eq!(
1441 key_info.sources,
1442 vec![KeyInfoSource::X509Data(X509DataInfo::default())]
1443 );
1444 }
1445
1446 #[test]
1447 fn parse_key_info_rejects_unknown_xmlsig_child_in_x509data() {
1448 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1449 <X509Data>
1450 <Foo/>
1451 </X509Data>
1452 </KeyInfo>"#;
1453 let doc = Document::parse(xml).unwrap();
1454
1455 let err = parse_key_info(doc.root_element()).unwrap_err();
1456 assert!(matches!(err, ParseError::InvalidStructure(_)));
1457 }
1458
1459 #[test]
1460 fn parse_key_info_rejects_unknown_xmlsig11_child_in_x509data() {
1461 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
1462 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
1463 <X509Data>
1464 <dsig11:Foo/>
1465 </X509Data>
1466 </KeyInfo>"#;
1467 let doc = Document::parse(xml).unwrap();
1468
1469 let err = parse_key_info(doc.root_element()).unwrap_err();
1470 assert!(matches!(err, ParseError::InvalidStructure(_)));
1471 }
1472
1473 #[test]
1474 fn parse_key_info_rejects_x509_issuer_serial_without_required_children() {
1475 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1476 <X509Data>
1477 <X509IssuerSerial>
1478 <X509IssuerName>CN=CA</X509IssuerName>
1479 </X509IssuerSerial>
1480 </X509Data>
1481 </KeyInfo>"#;
1482 let doc = Document::parse(xml).unwrap();
1483
1484 let err = parse_key_info(doc.root_element()).unwrap_err();
1485 assert!(matches!(err, ParseError::InvalidStructure(_)));
1486 }
1487
1488 #[test]
1489 fn parse_key_info_rejects_x509_issuer_serial_with_duplicate_issuer_name() {
1490 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1491 <X509Data>
1492 <X509IssuerSerial>
1493 <X509IssuerName>CN=CA-1</X509IssuerName>
1494 <X509IssuerName>CN=CA-2</X509IssuerName>
1495 <X509SerialNumber>42</X509SerialNumber>
1496 </X509IssuerSerial>
1497 </X509Data>
1498 </KeyInfo>"#;
1499 let doc = Document::parse(xml).unwrap();
1500
1501 let err = parse_key_info(doc.root_element()).unwrap_err();
1502 assert!(matches!(err, ParseError::InvalidStructure(_)));
1503 }
1504
1505 #[test]
1506 fn parse_key_info_rejects_x509_issuer_serial_with_duplicate_serial_number() {
1507 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1508 <X509Data>
1509 <X509IssuerSerial>
1510 <X509IssuerName>CN=CA</X509IssuerName>
1511 <X509SerialNumber>1</X509SerialNumber>
1512 <X509SerialNumber>2</X509SerialNumber>
1513 </X509IssuerSerial>
1514 </X509Data>
1515 </KeyInfo>"#;
1516 let doc = Document::parse(xml).unwrap();
1517
1518 let err = parse_key_info(doc.root_element()).unwrap_err();
1519 assert!(matches!(err, ParseError::InvalidStructure(_)));
1520 }
1521
1522 #[test]
1523 fn parse_key_info_rejects_x509_issuer_serial_with_whitespace_only_values() {
1524 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1525 <X509Data>
1526 <X509IssuerSerial>
1527 <X509IssuerName> </X509IssuerName>
1528 <X509SerialNumber>
1529
1530 </X509SerialNumber>
1531 </X509IssuerSerial>
1532 </X509Data>
1533 </KeyInfo>"#;
1534 let doc = Document::parse(xml).unwrap();
1535
1536 let err = parse_key_info(doc.root_element()).unwrap_err();
1537 assert!(matches!(err, ParseError::InvalidStructure(_)));
1538 }
1539
1540 #[test]
1541 fn parse_key_info_rejects_x509_issuer_serial_with_wrong_child_order() {
1542 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1543 <X509Data>
1544 <X509IssuerSerial>
1545 <X509SerialNumber>42</X509SerialNumber>
1546 <X509IssuerName>CN=CA</X509IssuerName>
1547 </X509IssuerSerial>
1548 </X509Data>
1549 </KeyInfo>"#;
1550 let doc = Document::parse(xml).unwrap();
1551
1552 let err = parse_key_info(doc.root_element()).unwrap_err();
1553 assert!(matches!(err, ParseError::InvalidStructure(_)));
1554 }
1555
1556 #[test]
1557 fn parse_key_info_rejects_x509_issuer_serial_with_extra_child_element() {
1558 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
1559 xmlns:foo="urn:example:foo">
1560 <X509Data>
1561 <X509IssuerSerial>
1562 <X509IssuerName>CN=CA</X509IssuerName>
1563 <X509SerialNumber>42</X509SerialNumber>
1564 <foo:Extra/>
1565 </X509IssuerSerial>
1566 </X509Data>
1567 </KeyInfo>"#;
1568 let doc = Document::parse(xml).unwrap();
1569
1570 let err = parse_key_info(doc.root_element()).unwrap_err();
1571 assert!(matches!(err, ParseError::InvalidStructure(_)));
1572 }
1573
1574 #[test]
1575 fn parse_key_info_rejects_x509_digest_without_algorithm() {
1576 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
1577 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
1578 <X509Data>
1579 <dsig11:X509Digest>AQID</dsig11:X509Digest>
1580 </X509Data>
1581 </KeyInfo>"#;
1582 let doc = Document::parse(xml).unwrap();
1583
1584 let err = parse_key_info(doc.root_element()).unwrap_err();
1585 assert!(matches!(err, ParseError::InvalidStructure(_)));
1586 }
1587
1588 #[test]
1589 fn parse_key_info_rejects_invalid_x509_certificate_base64() {
1590 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1591 <X509Data>
1592 <X509Certificate>%%%invalid%%%</X509Certificate>
1593 </X509Data>
1594 </KeyInfo>"#;
1595 let doc = Document::parse(xml).unwrap();
1596
1597 let err = parse_key_info(doc.root_element()).unwrap_err();
1598 assert!(matches!(err, ParseError::Base64(_)));
1599 }
1600
1601 #[test]
1602 fn parse_key_info_rejects_x509_data_exceeding_entry_budget() {
1603 let subjects = (0..(MAX_X509_DATA_ENTRY_COUNT + 1))
1604 .map(|idx| format!("<X509SubjectName>CN={idx}</X509SubjectName>"))
1605 .collect::<Vec<_>>()
1606 .join("");
1607 let xml = format!(
1608 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><X509Data>{subjects}</X509Data></KeyInfo>"
1609 );
1610 let doc = Document::parse(&xml).unwrap();
1611
1612 let err = parse_key_info(doc.root_element()).unwrap_err();
1613 assert!(matches!(err, ParseError::InvalidStructure(_)));
1614 }
1615
1616 #[test]
1617 fn parse_key_info_rejects_x509_data_exceeding_total_binary_budget() {
1618 let payload = base64::engine::general_purpose::STANDARD.encode(vec![0u8; 190_000]);
1619 let entries = (0..6)
1620 .map(|_| format!("<X509SKI>{payload}</X509SKI>"))
1621 .collect::<Vec<_>>()
1622 .join("");
1623 let xml = format!(
1624 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><X509Data>{entries}</X509Data></KeyInfo>"
1625 );
1626 let doc = Document::parse(&xml).unwrap();
1627
1628 let err = parse_key_info(doc.root_element()).unwrap_err();
1629 assert!(matches!(err, ParseError::InvalidStructure(_)));
1630 }
1631
1632 #[test]
1633 fn parse_key_info_rejects_x509_certificate_with_invalid_der() {
1634 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1635 <X509Data>
1636 <X509Certificate>AQID</X509Certificate>
1637 </X509Data>
1638 </KeyInfo>"#;
1639 let doc = Document::parse(xml).unwrap();
1640
1641 let err = parse_key_info(doc.root_element()).unwrap_err();
1642 assert!(matches!(err, ParseError::InvalidStructure(_)));
1643 }
1644
1645 #[test]
1646 fn parse_key_info_rejects_x509_certificate_with_trailing_der_bytes() {
1647 let mut cert = base64::engine::general_purpose::STANDARD
1648 .decode(fixture_rsa_cert_base64())
1649 .unwrap();
1650 cert.extend_from_slice(&[0x00, 0x01]);
1651 let cert_base64 = base64::engine::general_purpose::STANDARD.encode(cert);
1652 let xml = format!(
1653 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1654 <X509Data>
1655 <X509Certificate>{cert_base64}</X509Certificate>
1656 </X509Data>
1657 </KeyInfo>"#
1658 );
1659 let doc = Document::parse(&xml).unwrap();
1660
1661 let err = parse_key_info(doc.root_element()).unwrap_err();
1662 assert!(matches!(err, ParseError::InvalidStructure(_)));
1663 }
1664
1665 #[test]
1666 fn parse_key_info_marks_unsupported_spki_algorithm_as_unsupported() {
1667 let xml = include_str!(
1668 "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt.xml"
1669 );
1670 let doc = Document::parse(xml).unwrap();
1671 let key_info_node = doc
1672 .descendants()
1673 .find(|node| {
1674 node.is_element()
1675 && node.tag_name().namespace() == Some(XMLDSIG_NS)
1676 && node.tag_name().name() == "KeyInfo"
1677 })
1678 .expect("fixture must contain ds:KeyInfo");
1679
1680 let key_info = parse_key_info(key_info_node).expect("KeyInfo parse should succeed");
1681 let x509_info = match &key_info.sources[0] {
1682 KeyInfoSource::X509Data(x509) => x509,
1683 other => panic!("expected X509Data source, got {other:?}"),
1684 };
1685 assert_eq!(x509_info.certificates.len(), 1);
1686 assert_eq!(x509_info.parsed_certificates.len(), 1);
1687 assert_eq!(x509_info.certificate_chain, vec![0]);
1688 let parsed_cert = &x509_info.parsed_certificates[0];
1689 assert!(!parsed_cert.subject_dn.is_empty());
1690 assert!(!parsed_cert.issuer_dn.is_empty());
1691 assert!(parsed_cert.subject_key_identifier.is_some());
1692 assert!(matches!(
1693 parsed_cert.public_key,
1694 X509PublicKeyInfo::Unsupported { .. }
1695 ));
1696 }
1697
1698 #[test]
1699 fn parse_key_info_orders_x509_certificate_chain_from_signing_cert() {
1700 let root = fixture_cert_base64("../../tests/fixtures/keys/cacert.pem");
1701 let intermediate = fixture_cert_base64("../../tests/fixtures/keys/ca2cert.pem");
1702 let leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
1703 let xml = format!(
1704 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1705 <X509Data>
1706 <X509Certificate>{root}</X509Certificate>
1707 <X509Certificate>{intermediate}</X509Certificate>
1708 <X509Certificate>{leaf}</X509Certificate>
1709 </X509Data>
1710 </KeyInfo>"#
1711 );
1712 let doc = Document::parse(&xml).unwrap();
1713
1714 let key_info = parse_key_info(doc.root_element()).unwrap();
1715 let x509_info = match &key_info.sources[0] {
1716 KeyInfoSource::X509Data(x509) => x509,
1717 other => panic!("expected X509Data source, got {other:?}"),
1718 };
1719
1720 assert_eq!(x509_info.certificate_chain, vec![2, 1, 0]);
1721 }
1722
1723 #[test]
1724 fn parse_key_info_uses_issuer_serial_to_select_x509_signing_certificate() {
1725 let root = fixture_cert_base64("../../tests/fixtures/keys/cacert.pem");
1726 let intermediate = fixture_cert_base64("../../tests/fixtures/keys/ca2cert.pem");
1727 let leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
1728 let xml = format!(
1729 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1730 <X509Data>
1731 <X509IssuerSerial>
1732 <X509IssuerName>C=US, ST=California, O=XML Security Library (http://www.aleksey.com/xmlsec), OU=Second level CA, CN=Aleksey Sanin, Email=xmlsec@aleksey.com</X509IssuerName>
1733 <X509SerialNumber>680572598617295163017172295025714171905498632019</X509SerialNumber>
1734 </X509IssuerSerial>
1735 <X509Certificate>{root}</X509Certificate>
1736 <X509Certificate>{intermediate}</X509Certificate>
1737 <X509Certificate>{leaf}</X509Certificate>
1738 </X509Data>
1739 </KeyInfo>"#
1740 );
1741 let doc = Document::parse(&xml).unwrap();
1742
1743 let key_info = parse_key_info(doc.root_element()).unwrap();
1744 let x509_info = match &key_info.sources[0] {
1745 KeyInfoSource::X509Data(x509) => x509,
1746 other => panic!("expected X509Data source, got {other:?}"),
1747 };
1748
1749 assert_eq!(x509_info.certificate_chain, vec![2, 1, 0]);
1750 }
1751
1752 #[test]
1753 fn parse_key_info_uses_decimal_issuer_serial_to_select_x509_signing_certificate() {
1754 assert_eq!(
1755 x509_serial_decimal_to_hex("680572598617295163017172295025714171905498632019")
1756 .as_deref(),
1757 Some("7735EE487F6862DAF1B3956D961CCB0FA6F34F53")
1758 );
1759 let root = fixture_cert_base64("../../tests/fixtures/keys/cacert.pem");
1760 let intermediate = fixture_cert_base64("../../tests/fixtures/keys/ca2cert.pem");
1761 let leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
1762 let other_leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-4096-cert.pem");
1763 let xml = format!(
1764 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1765 <X509Data>
1766 <X509IssuerSerial>
1767 <X509IssuerName>C=US, ST=California, O=XML Security Library (http://www.aleksey.com/xmlsec), OU=Second level CA, CN=Aleksey Sanin, Email=xmlsec@aleksey.com</X509IssuerName>
1768 <X509SerialNumber>680572598617295163017172295025714171905498632019</X509SerialNumber>
1769 </X509IssuerSerial>
1770 <X509Certificate>{root}</X509Certificate>
1771 <X509Certificate>{intermediate}</X509Certificate>
1772 <X509Certificate>{leaf}</X509Certificate>
1773 <X509Certificate>{other_leaf}</X509Certificate>
1774 </X509Data>
1775 </KeyInfo>"#
1776 );
1777 let doc = Document::parse(&xml).unwrap();
1778
1779 let key_info = parse_key_info(doc.root_element()).unwrap();
1780 let x509_info = match &key_info.sources[0] {
1781 KeyInfoSource::X509Data(x509) => x509,
1782 other => panic!("expected X509Data source, got {other:?}"),
1783 };
1784
1785 assert_eq!(x509_info.certificate_chain, vec![2, 1, 0]);
1786 }
1787
1788 #[test]
1789 fn parse_key_info_rejects_ambiguous_x509_signing_certificate_candidates() {
1790 let first_leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
1791 let second_leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-4096-cert.pem");
1792 let xml = format!(
1793 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1794 <X509Data>
1795 <X509Certificate>{first_leaf}</X509Certificate>
1796 <X509Certificate>{second_leaf}</X509Certificate>
1797 </X509Data>
1798 </KeyInfo>"#
1799 );
1800 let doc = Document::parse(&xml).unwrap();
1801
1802 let err = parse_key_info(doc.root_element()).unwrap_err();
1803 assert!(matches!(err, ParseError::InvalidStructure(_)));
1804 }
1805
1806 #[test]
1807 fn parse_key_info_rejects_unmatched_x509_lookup_identifier() {
1808 let cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
1809 let xml = format!(
1810 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1811 <X509Data>
1812 <X509SubjectName>CN=Not The Embedded Certificate</X509SubjectName>
1813 <X509Certificate>{cert}</X509Certificate>
1814 </X509Data>
1815 </KeyInfo>"#
1816 );
1817 let doc = Document::parse(&xml).unwrap();
1818
1819 let err = parse_key_info(doc.root_element()).unwrap_err();
1820 assert!(
1821 matches!(err, ParseError::InvalidStructure(message) if message.contains("lookup identifiers"))
1822 );
1823 }
1824
1825 #[test]
1826 fn parse_key_info_rejects_malformed_issuer_serial_even_with_matching_subject() {
1827 let cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
1828 let xml = format!(
1829 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1830 <X509Data>
1831 <X509SubjectName>C=US, ST=California, O=XML Security Library (http://www.aleksey.com/xmlsec), CN=Test Key rsa-2048</X509SubjectName>
1832 <X509IssuerSerial>
1833 <X509IssuerName>C=US, ST=California, O=XML Security Library (http://www.aleksey.com/xmlsec), OU=Second level CA, CN=Aleksey Sanin, Email=xmlsec@aleksey.com</X509IssuerName>
1834 <X509SerialNumber>not-a-decimal-serial</X509SerialNumber>
1835 </X509IssuerSerial>
1836 <X509Certificate>{cert}</X509Certificate>
1837 </X509Data>
1838 </KeyInfo>"#
1839 );
1840 let doc = Document::parse(&xml).unwrap();
1841
1842 let err = parse_key_info(doc.root_element()).unwrap_err();
1843 assert!(
1844 matches!(err, ParseError::InvalidStructure(message) if message.contains("lookup identifiers"))
1845 );
1846 }
1847
1848 #[test]
1849 fn parse_key_info_rejects_unmatched_ski_even_with_matching_subject() {
1850 let cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
1851 let xml = format!(
1852 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1853 <X509Data>
1854 <X509SubjectName>C=US, ST=California, O=XML Security Library (http://www.aleksey.com/xmlsec), CN=Test Key rsa-2048</X509SubjectName>
1855 <X509SKI>AQIDBA==</X509SKI>
1856 <X509Certificate>{cert}</X509Certificate>
1857 </X509Data>
1858 </KeyInfo>"#
1859 );
1860 let doc = Document::parse(&xml).unwrap();
1861
1862 let err = parse_key_info(doc.root_element()).unwrap_err();
1863 assert!(
1864 matches!(err, ParseError::InvalidStructure(message) if message.contains("lookup identifiers"))
1865 );
1866 }
1867
1868 #[test]
1869 fn parse_key_info_rejects_lookup_hints_for_different_certificates() {
1870 let first_cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
1871 let second_cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-4096-cert.pem");
1872 let xml = format!(
1873 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
1874 <X509Data>
1875 <X509SubjectName>C=US, ST=California, O=XML Security Library (http://www.aleksey.com/xmlsec), CN=Test Key rsa-2048</X509SubjectName>
1876 <X509SKI>60zMLKCfzQ3qnXAzABzRNpdgQ8Q=</X509SKI>
1877 <X509Certificate>{first_cert}</X509Certificate>
1878 <X509Certificate>{second_cert}</X509Certificate>
1879 </X509Data>
1880 </KeyInfo>"#
1881 );
1882 let doc = Document::parse(&xml).unwrap();
1883
1884 let err = parse_key_info(doc.root_element()).unwrap_err();
1885 assert!(
1886 matches!(err, ParseError::InvalidStructure(message) if message.contains("lookup identifiers match multiple certificates"))
1887 );
1888 }
1889
1890 #[test]
1891 fn build_x509_certificate_chain_rejects_chain_exceeding_max_depth() {
1892 let parsed_certificates = (0..=MAX_X509_CHAIN_DEPTH)
1893 .map(|idx| ParsedX509Certificate {
1894 subject_dn: format!("CN=cert-{idx}"),
1895 issuer_dn: if idx == MAX_X509_CHAIN_DEPTH {
1896 format!("CN=cert-{idx}")
1897 } else {
1898 format!("CN=cert-{}", idx + 1)
1899 },
1900 serial_number: vec![u8::try_from(idx).unwrap()],
1901 serial_number_hex: format!("{idx:02X}"),
1902 subject_key_identifier: None,
1903 public_key: X509PublicKeyInfo::Unsupported {
1904 algorithm_oid: "1.2.3.4".into(),
1905 },
1906 })
1907 .collect();
1908 let info = X509DataInfo {
1909 parsed_certificates,
1910 ..X509DataInfo::default()
1911 };
1912
1913 let err = build_x509_certificate_chain(&info).unwrap_err();
1914 assert!(
1915 matches!(err, ParseError::InvalidStructure(message) if message.contains("maximum depth"))
1916 );
1917 }
1918
1919 #[test]
1920 fn x509_serial_hex_strips_der_sign_extension_zeroes() {
1921 assert_eq!(format_x509_serial_value_hex(&[0x00, 0xFF]), "FF");
1922 assert_eq!(format_x509_serial_value_hex(&[0x00, 0x7F]), "7F");
1923 assert_eq!(format_x509_serial_value_hex(&[0x00, 0x00]), "00");
1924 }
1925
1926 #[test]
1927 fn parse_key_info_accepts_large_textual_x509_entries_within_entry_budget() {
1928 let issuer_name = "C".repeat(MAX_X509_ISSUER_NAME_TEXT_LEN);
1929 let serial_number = "7".repeat(MAX_X509_SERIAL_NUMBER_TEXT_LEN);
1930 let issuer_serials = (0..52)
1931 .map(|_| {
1932 format!(
1933 "<X509IssuerSerial><X509IssuerName>{issuer_name}</X509IssuerName><X509SerialNumber>{serial_number}</X509SerialNumber></X509IssuerSerial>"
1934 )
1935 })
1936 .collect::<Vec<_>>()
1937 .join("");
1938 let xml = format!(
1939 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><X509Data>{issuer_serials}</X509Data></KeyInfo>"
1940 );
1941 let doc = Document::parse(&xml).unwrap();
1942
1943 let key_info = parse_key_info(doc.root_element()).unwrap();
1944 let parsed = match &key_info.sources[0] {
1945 KeyInfoSource::X509Data(x509) => x509,
1946 _ => panic!("expected X509Data source"),
1947 };
1948 assert_eq!(parsed.issuer_serials.len(), 52);
1949 }
1950
1951 #[test]
1952 fn parse_key_info_accepts_x509data_with_only_foreign_namespace_children() {
1953 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
1954 xmlns:foo="urn:example:foo">
1955 <X509Data>
1956 <foo:Bar/>
1957 </X509Data>
1958 </KeyInfo>"#;
1959 let doc = Document::parse(xml).unwrap();
1960
1961 let key_info = parse_key_info(doc.root_element()).unwrap();
1962 assert_eq!(
1963 key_info.sources,
1964 vec![KeyInfoSource::X509Data(X509DataInfo::default())]
1965 );
1966 }
1967
1968 #[test]
1969 fn parse_key_info_der_encoded_key_value_rejects_invalid_base64() {
1970 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
1971 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
1972 <dsig11:DEREncodedKeyValue>%%%invalid%%%</dsig11:DEREncodedKeyValue>
1973 </KeyInfo>"#;
1974 let doc = Document::parse(xml).unwrap();
1975
1976 let err = parse_key_info(doc.root_element()).unwrap_err();
1977 assert!(matches!(err, ParseError::Base64(_)));
1978 }
1979
1980 #[test]
1981 fn parse_key_info_der_encoded_key_value_accepts_xml_whitespace() {
1982 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
1983 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
1984 <dsig11:DEREncodedKeyValue>
1985 AQID
1986 BA==
1987 </dsig11:DEREncodedKeyValue>
1988 </KeyInfo>"#;
1989 let doc = Document::parse(xml).unwrap();
1990
1991 let key_info = parse_key_info(doc.root_element()).unwrap();
1992 assert_eq!(
1993 key_info.sources,
1994 vec![KeyInfoSource::DerEncodedKeyValue(vec![1, 2, 3, 4])]
1995 );
1996 }
1997
1998 #[test]
1999 fn parse_key_info_dispatches_dsig11_ec_keyvalue() {
2000 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
2001 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
2002 <KeyValue>
2003 <dsig11:ECKeyValue/>
2004 </KeyValue>
2005 </KeyInfo>"#;
2006 let doc = Document::parse(xml).unwrap();
2007
2008 let key_info = parse_key_info(doc.root_element()).unwrap();
2009 assert_eq!(
2010 key_info.sources,
2011 vec![KeyInfoSource::KeyValue(KeyValueInfo::EcKeyValue)]
2012 );
2013 }
2014
2015 #[test]
2016 fn parse_key_info_marks_ds_namespace_ec_keyvalue_as_unsupported() {
2017 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2018 <KeyValue>
2019 <ECKeyValue/>
2020 </KeyValue>
2021 </KeyInfo>"#;
2022 let doc = Document::parse(xml).unwrap();
2023
2024 let key_info = parse_key_info(doc.root_element()).unwrap();
2025 assert_eq!(
2026 key_info.sources,
2027 vec![KeyInfoSource::KeyValue(KeyValueInfo::Unsupported {
2028 namespace: Some(XMLDSIG_NS.to_string()),
2029 local_name: "ECKeyValue".into(),
2030 })]
2031 );
2032 }
2033
2034 #[test]
2035 fn parse_key_info_keeps_unsupported_keyvalue_child_as_marker() {
2036 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2037 <KeyValue>
2038 <DSAKeyValue/>
2039 </KeyValue>
2040 </KeyInfo>"#;
2041 let doc = Document::parse(xml).unwrap();
2042
2043 let key_info = parse_key_info(doc.root_element()).unwrap();
2044 assert_eq!(
2045 key_info.sources,
2046 vec![KeyInfoSource::KeyValue(KeyValueInfo::Unsupported {
2047 namespace: Some(XMLDSIG_NS.to_string()),
2048 local_name: "DSAKeyValue".into(),
2049 })]
2050 );
2051 }
2052
2053 #[test]
2054 fn parse_key_info_rejects_keyname_with_child_elements() {
2055 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2056 <KeyName>ok<foo/></KeyName>
2057 </KeyInfo>"#;
2058 let doc = Document::parse(xml).unwrap();
2059
2060 let err = parse_key_info(doc.root_element()).unwrap_err();
2061 assert!(matches!(err, ParseError::InvalidStructure(_)));
2062 }
2063
2064 #[test]
2065 fn parse_key_info_preserves_keyname_text_without_trimming() {
2066 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2067 <KeyName> signing key </KeyName>
2068 </KeyInfo>"#;
2069 let doc = Document::parse(xml).unwrap();
2070
2071 let key_info = parse_key_info(doc.root_element()).unwrap();
2072 assert_eq!(
2073 key_info.sources,
2074 vec![KeyInfoSource::KeyName(" signing key ".into())]
2075 );
2076 }
2077
2078 #[test]
2079 fn parse_key_info_rejects_oversized_keyname_text() {
2080 let oversized = "A".repeat(4097);
2081 let xml = format!(
2082 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><KeyName>{oversized}</KeyName></KeyInfo>"
2083 );
2084 let doc = Document::parse(&xml).unwrap();
2085
2086 let err = parse_key_info(doc.root_element()).unwrap_err();
2087 assert!(matches!(err, ParseError::InvalidStructure(_)));
2088 }
2089
2090 #[test]
2091 fn parse_key_info_rejects_non_whitespace_mixed_content() {
2092 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">oops<KeyName>k</KeyName></KeyInfo>"#;
2093 let doc = Document::parse(xml).unwrap();
2094
2095 let err = parse_key_info(doc.root_element()).unwrap_err();
2096 assert!(matches!(err, ParseError::InvalidStructure(_)));
2097 }
2098
2099 #[test]
2100 fn parse_key_info_rejects_nbsp_as_non_xml_whitespace_mixed_content() {
2101 let xml = "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\u{00A0}<KeyName>k</KeyName></KeyInfo>";
2102 let doc = Document::parse(xml).unwrap();
2103
2104 let err = parse_key_info(doc.root_element()).unwrap_err();
2105 assert!(matches!(err, ParseError::InvalidStructure(_)));
2106 }
2107
2108 #[test]
2109 fn parse_key_info_der_encoded_key_value_rejects_oversized_payload() {
2110 let oversized =
2111 base64::engine::general_purpose::STANDARD
2112 .encode(vec![0u8; MAX_DER_ENCODED_KEY_VALUE_LEN + 1]);
2113 let xml = format!(
2114 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\" xmlns:dsig11=\"http://www.w3.org/2009/xmldsig11#\"><dsig11:DEREncodedKeyValue>{oversized}</dsig11:DEREncodedKeyValue></KeyInfo>"
2115 );
2116 let doc = Document::parse(&xml).unwrap();
2117
2118 let err = parse_key_info(doc.root_element()).unwrap_err();
2119 assert!(matches!(err, ParseError::InvalidStructure(_)));
2120 }
2121
2122 #[test]
2123 fn parse_key_info_der_encoded_key_value_rejects_empty_payload() {
2124 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
2125 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
2126 <dsig11:DEREncodedKeyValue>
2127
2128 </dsig11:DEREncodedKeyValue>
2129 </KeyInfo>"#;
2130 let doc = Document::parse(xml).unwrap();
2131
2132 let err = parse_key_info(doc.root_element()).unwrap_err();
2133 assert!(matches!(err, ParseError::InvalidStructure(_)));
2134 }
2135
2136 #[test]
2137 fn parse_key_info_der_encoded_key_value_non_xml_ascii_whitespace_is_not_parseable_xml() {
2138 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>";
2139 assert!(Document::parse(xml).is_err());
2140 }
2141
2142 #[test]
2145 fn parse_signed_info_rsa_sha256_with_reference() {
2146 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2147 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
2148 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
2149 <Reference URI="">
2150 <Transforms>
2151 <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
2152 <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
2153 </Transforms>
2154 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
2155 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
2156 </Reference>
2157 </SignedInfo>"#;
2158 let doc = Document::parse(xml).unwrap();
2159 let si = parse_signed_info(doc.root_element()).unwrap();
2160
2161 assert_eq!(si.signature_method, SignatureAlgorithm::RsaSha256);
2162 assert_eq!(si.references.len(), 1);
2163
2164 let r = &si.references[0];
2165 assert_eq!(r.uri.as_deref(), Some(""));
2166 assert_eq!(r.digest_method, DigestAlgorithm::Sha256);
2167 assert_eq!(r.digest_value, vec![0u8; 32]);
2168 assert_eq!(r.transforms.len(), 2);
2169 }
2170
2171 #[test]
2172 fn parse_signed_info_multiple_references() {
2173 let xml = r##"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2174 <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
2175 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"/>
2176 <Reference URI="#a">
2177 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
2178 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
2179 </Reference>
2180 <Reference URI="#b">
2181 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
2182 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
2183 </Reference>
2184 </SignedInfo>"##;
2185 let doc = Document::parse(xml).unwrap();
2186 let si = parse_signed_info(doc.root_element()).unwrap();
2187
2188 assert_eq!(si.signature_method, SignatureAlgorithm::EcdsaP256Sha256);
2189 assert_eq!(si.references.len(), 2);
2190 assert_eq!(si.references[0].uri.as_deref(), Some("#a"));
2191 assert_eq!(si.references[0].digest_method, DigestAlgorithm::Sha256);
2192 assert_eq!(si.references[1].uri.as_deref(), Some("#b"));
2193 assert_eq!(si.references[1].digest_method, DigestAlgorithm::Sha1);
2194 }
2195
2196 #[test]
2197 fn parse_reference_without_transforms() {
2198 let xml = r##"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2200 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
2201 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
2202 <Reference URI="#obj">
2203 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
2204 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
2205 </Reference>
2206 </SignedInfo>"##;
2207 let doc = Document::parse(xml).unwrap();
2208 let si = parse_signed_info(doc.root_element()).unwrap();
2209
2210 assert!(si.references[0].transforms.is_empty());
2211 }
2212
2213 #[test]
2214 fn parse_reference_with_all_attributes() {
2215 let xml = r##"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2216 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
2217 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
2218 <Reference URI="#data" Id="ref1" Type="http://www.w3.org/2000/09/xmldsig#Object">
2219 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
2220 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
2221 </Reference>
2222 </SignedInfo>"##;
2223 let doc = Document::parse(xml).unwrap();
2224 let si = parse_signed_info(doc.root_element()).unwrap();
2225 let r = &si.references[0];
2226
2227 assert_eq!(r.uri.as_deref(), Some("#data"));
2228 assert_eq!(r.id.as_deref(), Some("ref1"));
2229 assert_eq!(
2230 r.ref_type.as_deref(),
2231 Some("http://www.w3.org/2000/09/xmldsig#Object")
2232 );
2233 }
2234
2235 #[test]
2236 fn parse_reference_absent_uri() {
2237 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2239 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
2240 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
2241 <Reference>
2242 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
2243 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
2244 </Reference>
2245 </SignedInfo>"#;
2246 let doc = Document::parse(xml).unwrap();
2247 let si = parse_signed_info(doc.root_element()).unwrap();
2248 assert!(si.references[0].uri.is_none());
2249 }
2250
2251 #[test]
2252 fn parse_signed_info_preserves_inclusive_prefixes() {
2253 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
2254 xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#">
2255 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
2256 <ec:InclusiveNamespaces PrefixList="ds saml #default"/>
2257 </CanonicalizationMethod>
2258 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
2259 <Reference URI="">
2260 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
2261 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
2262 </Reference>
2263 </SignedInfo>"#;
2264 let doc = Document::parse(xml).unwrap();
2265
2266 let si = parse_signed_info(doc.root_element()).unwrap();
2267 assert!(si.c14n_method.inclusive_prefixes().contains("ds"));
2268 assert!(si.c14n_method.inclusive_prefixes().contains("saml"));
2269 assert!(si.c14n_method.inclusive_prefixes().contains(""));
2270 }
2271
2272 #[test]
2275 fn missing_canonicalization_method() {
2276 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2277 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
2278 <Reference URI="">
2279 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
2280 <DigestValue>dGVzdA==</DigestValue>
2281 </Reference>
2282 </SignedInfo>"#;
2283 let doc = Document::parse(xml).unwrap();
2284 let result = parse_signed_info(doc.root_element());
2285 assert!(result.is_err());
2286 assert!(matches!(
2288 result.unwrap_err(),
2289 ParseError::InvalidStructure(_)
2290 ));
2291 }
2292
2293 #[test]
2294 fn missing_signature_method() {
2295 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2296 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
2297 <Reference URI="">
2298 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
2299 <DigestValue>dGVzdA==</DigestValue>
2300 </Reference>
2301 </SignedInfo>"#;
2302 let doc = Document::parse(xml).unwrap();
2303 let result = parse_signed_info(doc.root_element());
2304 assert!(result.is_err());
2305 assert!(matches!(
2307 result.unwrap_err(),
2308 ParseError::InvalidStructure(_)
2309 ));
2310 }
2311
2312 #[test]
2313 fn no_references() {
2314 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2315 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
2316 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
2317 </SignedInfo>"#;
2318 let doc = Document::parse(xml).unwrap();
2319 let result = parse_signed_info(doc.root_element());
2320 assert!(matches!(
2321 result.unwrap_err(),
2322 ParseError::MissingElement {
2323 element: "Reference"
2324 }
2325 ));
2326 }
2327
2328 #[test]
2329 fn unsupported_c14n_algorithm() {
2330 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2331 <CanonicalizationMethod Algorithm="http://example.com/bogus-c14n"/>
2332 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
2333 <Reference URI="">
2334 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
2335 <DigestValue>dGVzdA==</DigestValue>
2336 </Reference>
2337 </SignedInfo>"#;
2338 let doc = Document::parse(xml).unwrap();
2339 let result = parse_signed_info(doc.root_element());
2340 assert!(matches!(
2341 result.unwrap_err(),
2342 ParseError::UnsupportedAlgorithm { .. }
2343 ));
2344 }
2345
2346 #[test]
2347 fn unsupported_signature_algorithm() {
2348 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2349 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
2350 <SignatureMethod Algorithm="http://example.com/bogus-sign"/>
2351 <Reference URI="">
2352 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
2353 <DigestValue>dGVzdA==</DigestValue>
2354 </Reference>
2355 </SignedInfo>"#;
2356 let doc = Document::parse(xml).unwrap();
2357 let result = parse_signed_info(doc.root_element());
2358 assert!(matches!(
2359 result.unwrap_err(),
2360 ParseError::UnsupportedAlgorithm { .. }
2361 ));
2362 }
2363
2364 #[test]
2365 fn unsupported_digest_algorithm() {
2366 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2367 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
2368 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
2369 <Reference URI="">
2370 <DigestMethod Algorithm="http://example.com/bogus-digest"/>
2371 <DigestValue>dGVzdA==</DigestValue>
2372 </Reference>
2373 </SignedInfo>"#;
2374 let doc = Document::parse(xml).unwrap();
2375 let result = parse_signed_info(doc.root_element());
2376 assert!(matches!(
2377 result.unwrap_err(),
2378 ParseError::UnsupportedAlgorithm { .. }
2379 ));
2380 }
2381
2382 #[test]
2383 fn missing_digest_method() {
2384 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2385 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
2386 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
2387 <Reference URI="">
2388 <DigestValue>dGVzdA==</DigestValue>
2389 </Reference>
2390 </SignedInfo>"#;
2391 let doc = Document::parse(xml).unwrap();
2392 let result = parse_signed_info(doc.root_element());
2393 assert!(result.is_err());
2395 }
2396
2397 #[test]
2398 fn missing_digest_value() {
2399 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2400 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
2401 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
2402 <Reference URI="">
2403 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
2404 </Reference>
2405 </SignedInfo>"#;
2406 let doc = Document::parse(xml).unwrap();
2407 let result = parse_signed_info(doc.root_element());
2408 assert!(matches!(
2409 result.unwrap_err(),
2410 ParseError::MissingElement {
2411 element: "DigestValue"
2412 }
2413 ));
2414 }
2415
2416 #[test]
2417 fn invalid_base64_digest_value() {
2418 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2419 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
2420 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
2421 <Reference URI="">
2422 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
2423 <DigestValue>!!!not-base64!!!</DigestValue>
2424 </Reference>
2425 </SignedInfo>"#;
2426 let doc = Document::parse(xml).unwrap();
2427 let result = parse_signed_info(doc.root_element());
2428 assert!(matches!(result.unwrap_err(), ParseError::Base64(_)));
2429 }
2430
2431 #[test]
2432 fn digest_value_length_must_match_digest_method() {
2433 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2434 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
2435 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
2436 <Reference URI="">
2437 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
2438 <DigestValue>dGVzdA==</DigestValue>
2439 </Reference>
2440 </SignedInfo>"#;
2441 let doc = Document::parse(xml).unwrap();
2442
2443 let result = parse_signed_info(doc.root_element());
2444 assert!(matches!(
2445 result.unwrap_err(),
2446 ParseError::DigestLengthMismatch {
2447 algorithm: "http://www.w3.org/2001/04/xmlenc#sha256",
2448 expected: 32,
2449 actual: 4,
2450 }
2451 ));
2452 }
2453
2454 #[test]
2455 fn inclusive_prefixes_on_inclusive_c14n_is_rejected() {
2456 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
2457 xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#">
2458 <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315">
2459 <ec:InclusiveNamespaces PrefixList="ds"/>
2460 </CanonicalizationMethod>
2461 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
2462 <Reference URI="">
2463 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
2464 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
2465 </Reference>
2466 </SignedInfo>"#;
2467 let doc = Document::parse(xml).unwrap();
2468
2469 let result = parse_signed_info(doc.root_element());
2470 assert!(matches!(
2471 result.unwrap_err(),
2472 ParseError::UnsupportedAlgorithm { .. }
2473 ));
2474 }
2475
2476 #[test]
2477 fn extra_element_after_digest_value() {
2478 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2479 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
2480 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
2481 <Reference URI="">
2482 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
2483 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
2484 <Unexpected/>
2485 </Reference>
2486 </SignedInfo>"#;
2487 let doc = Document::parse(xml).unwrap();
2488 let result = parse_signed_info(doc.root_element());
2489 assert!(matches!(
2490 result.unwrap_err(),
2491 ParseError::InvalidStructure(_)
2492 ));
2493 }
2494
2495 #[test]
2496 fn digest_value_with_element_child_is_rejected() {
2497 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2498 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
2499 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
2500 <Reference URI="">
2501 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
2502 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAA=<Junk/>AAAA</DigestValue>
2503 </Reference>
2504 </SignedInfo>"#;
2505 let doc = Document::parse(xml).unwrap();
2506
2507 let result = parse_signed_info(doc.root_element());
2508 assert!(matches!(
2509 result.unwrap_err(),
2510 ParseError::InvalidStructure(_)
2511 ));
2512 }
2513
2514 #[test]
2515 fn wrong_namespace_on_signed_info() {
2516 let xml = r#"<SignedInfo xmlns="http://example.com/fake">
2517 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
2518 </SignedInfo>"#;
2519 let doc = Document::parse(xml).unwrap();
2520 let result = parse_signed_info(doc.root_element());
2521 assert!(matches!(
2522 result.unwrap_err(),
2523 ParseError::InvalidStructure(_)
2524 ));
2525 }
2526
2527 #[test]
2530 fn base64_with_whitespace() {
2531 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
2532 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
2533 <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
2534 <Reference URI="">
2535 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
2536 <DigestValue>
2537 AAAAAAAA
2538 AAAAAAAAAAAAAAAAAAA=
2539 </DigestValue>
2540 </Reference>
2541 </SignedInfo>"#;
2542 let doc = Document::parse(xml).unwrap();
2543 let si = parse_signed_info(doc.root_element()).unwrap();
2544 assert_eq!(si.references[0].digest_value, vec![0u8; 20]);
2545 }
2546
2547 #[test]
2548 fn base64_decode_digest_accepts_xml_whitespace_chars() {
2549 let digest =
2550 base64_decode_digest("AAAA\tAAAA\rAAAA\nAAAA AAAAAAAAAAA=", DigestAlgorithm::Sha1)
2551 .expect("XML whitespace in DigestValue must be accepted");
2552 assert_eq!(digest, vec![0u8; 20]);
2553 }
2554
2555 #[test]
2556 fn base64_decode_digest_rejects_non_xml_ascii_whitespace() {
2557 let err = base64_decode_digest(
2558 "AAAA\u{000C}AAAAAAAAAAAAAAAAAAAAAAA=",
2559 DigestAlgorithm::Sha1,
2560 )
2561 .expect_err("form-feed/vertical-tab in DigestValue must be rejected");
2562 assert!(matches!(err, ParseError::Base64(_)));
2563 }
2564
2565 #[test]
2566 fn base64_decode_digest_rejects_oversized_base64_before_decode() {
2567 let err = base64_decode_digest("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", DigestAlgorithm::Sha1)
2568 .expect_err("oversized DigestValue base64 must fail before decode");
2569 match err {
2570 ParseError::Base64(message) => {
2571 assert!(
2572 message.contains("DigestValue exceeds maximum allowed base64 length"),
2573 "unexpected message: {message}"
2574 );
2575 }
2576 other => panic!("expected ParseError::Base64, got {other:?}"),
2577 }
2578 }
2579
2580 #[test]
2583 fn saml_response_signed_info() {
2584 let xml = r##"<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
2585 <ds:SignedInfo>
2586 <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
2587 <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
2588 <ds:Reference URI="#_resp1">
2589 <ds:Transforms>
2590 <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
2591 <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
2592 </ds:Transforms>
2593 <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
2594 <ds:DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</ds:DigestValue>
2595 </ds:Reference>
2596 </ds:SignedInfo>
2597 <ds:SignatureValue>ZmFrZQ==</ds:SignatureValue>
2598 </ds:Signature>"##;
2599 let doc = Document::parse(xml).unwrap();
2600
2601 let sig_node = doc.root_element();
2603 let signed_info_node = sig_node
2604 .children()
2605 .find(|n| n.is_element() && n.tag_name().name() == "SignedInfo")
2606 .unwrap();
2607
2608 let si = parse_signed_info(signed_info_node).unwrap();
2609 assert_eq!(si.signature_method, SignatureAlgorithm::RsaSha256);
2610 assert_eq!(si.references.len(), 1);
2611 assert_eq!(si.references[0].uri.as_deref(), Some("#_resp1"));
2612 assert_eq!(si.references[0].transforms.len(), 2);
2613 assert_eq!(si.references[0].digest_value, vec![0u8; 32]);
2614 }
2615}