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