1use crate::xml::dom::{Document, Node};
20use der::{
21 Decode,
22 asn1::{Ia5StringRef, ObjectIdentifier},
23};
24use std::collections::BTreeMap;
25use x509_cert::ext::pkix::name::DirectoryString;
26use x509_cert::name::Name;
27use x509_parser::extensions::ParsedExtension;
28use x509_parser::prelude::FromDer;
29use x509_parser::public_key::PublicKey;
30use x509_parser::x509::X509Name;
31
32#[cfg(test)]
33use super::digest::compute_digest;
34use super::digest::{DigestAlgorithm, compute_digest_with_provider, constant_time_eq};
35use super::transforms::{self, Transform};
36use super::whitespace::{is_xml_whitespace_only, normalize_xml_base64_text};
37use super::x509::certificate_signature_matches_with_provider;
38use crate::c14n::C14nAlgorithm;
39use crate::c14n::xml_base::{
40 XmlBaseResolutionBudget, resolve_uri_from_node_with_document_base_with_budget,
41};
42
43pub(crate) use crate::hard_limits::SIGNATURE_REFERENCE_CEILING as MAX_REFERENCES_PER_SIGNATURE;
44#[cfg(test)]
45use crate::hard_limits::X509_CHAIN_DEPTH_CEILING as MAX_X509_CHAIN_DEPTH;
46
47pub(crate) const XMLDSIG_NS: &str = "http://www.w3.org/2000/09/xmldsig#";
49pub(crate) const XMLDSIG11_NS: &str = "http://www.w3.org/2009/xmldsig11#";
51const MAX_DER_ENCODED_KEY_VALUE_LEN: usize = 8192;
52const MAX_DER_ENCODED_KEY_VALUE_TEXT_LEN: usize = 65_536;
53const MAX_DER_ENCODED_KEY_VALUE_BASE64_LEN: usize = MAX_DER_ENCODED_KEY_VALUE_LEN.div_ceil(3) * 4;
54pub(crate) const MAX_KEY_NAME_TEXT_LEN: usize = 4096;
55const MAX_KEY_INFO_CHILD_COUNT: usize = 64;
56const MAX_HMAC_OUTPUT_LENGTH_TEXT_LEN: usize = 32;
57pub(crate) const EXPERIMENTAL_SIGNATURE_NS: &str =
58 "http://www.aleksey.com/xmlsec/2025/12/xmldsig-more#";
59
60#[derive(Debug, Clone, Default, PartialEq, Eq)]
62pub struct SignatureContext(Vec<u8>);
63
64impl SignatureContext {
65 pub fn new(bytes: &[u8]) -> Result<Self, ParseError> {
68 if bytes.len() > 255 {
69 return Err(ParseError::InvalidStructure(
70 "signature context exceeds 255 octets".into(),
71 ));
72 }
73 Ok(Self(bytes.to_vec()))
74 }
75
76 pub fn as_bytes(&self) -> &[u8] {
78 &self.0
79 }
80}
81const MAX_RETRIEVAL_XPATH_TEXT_LEN: usize = 256;
82const MAX_RSA_MODULUS_LEN: usize = 1024;
83const MAX_RSA_EXPONENT_LEN: usize = 8;
84pub(crate) const EC_P256_OID: &str = "1.2.840.10045.3.1.7";
85pub(crate) const EC_P384_OID: &str = "1.3.132.0.34";
86pub(crate) const EC_P521_OID: &str = "1.3.132.0.35";
87const MAX_EC_PUBLIC_KEY_LEN: usize = 133;
88const MAX_X509_BASE64_TEXT_LEN: usize = 262_144;
89const MAX_X509_BASE64_NORMALIZED_LEN: usize = MAX_X509_BASE64_TEXT_LEN;
90pub(crate) const MAX_X509_DECODED_BINARY_LEN: usize =
91 MAX_X509_BASE64_NORMALIZED_LEN.div_ceil(4) * 3;
92const MAX_X509_SUBJECT_NAME_TEXT_LEN: usize = 16_384;
93const MAX_X509_ISSUER_NAME_TEXT_LEN: usize = 16_384;
94const MAX_X509_SERIAL_NUMBER_RAW_TEXT_LEN: usize = 16_384;
95const MAX_X509_SERIAL_NUMBER_VALUE_DIGITS: usize = 49;
99const MAX_X509_SERIAL_NUMBER_BYTES: usize = 20;
100const MAX_X509_DATA_ENTRY_COUNT: usize = 64;
101pub(crate) const MAX_X509_DATA_TOTAL_BINARY_LEN: usize = 1_048_576;
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
105#[non_exhaustive]
106pub enum SignatureAlgorithm {
107 DsaSha1,
109 DsaSha256,
111 HmacSha1,
113 HmacSha224,
115 HmacSha256,
117 HmacSha384,
119 HmacSha512,
121 RsaSha1,
123 RsaSha224,
125 RsaSha256,
127 RsaSha384,
129 RsaSha512,
131 EcdsaSha1,
133 EcdsaSha224,
135 EcdsaSha256,
137 EcdsaSha384,
139 EcdsaSha512,
141 EcdsaSha3_224,
143 EcdsaSha3_256,
145 EcdsaSha3_384,
147 EcdsaSha3_512,
149 Ed25519,
151 Ed25519Ctx,
153 Ed25519Ph,
155 Ed448,
157 Ed448Ph,
159 PostQuantum(super::PqAlgorithm),
161}
162
163impl SignatureAlgorithm {
164 pub const ALL: [Self; 35] = [
166 Self::DsaSha1,
167 Self::DsaSha256,
168 Self::HmacSha1,
169 Self::HmacSha224,
170 Self::HmacSha256,
171 Self::HmacSha384,
172 Self::HmacSha512,
173 Self::RsaSha1,
174 Self::RsaSha224,
175 Self::RsaSha256,
176 Self::RsaSha384,
177 Self::RsaSha512,
178 Self::EcdsaSha1,
179 Self::EcdsaSha224,
180 Self::EcdsaSha256,
181 Self::EcdsaSha384,
182 Self::EcdsaSha512,
183 Self::EcdsaSha3_224,
184 Self::EcdsaSha3_256,
185 Self::EcdsaSha3_384,
186 Self::EcdsaSha3_512,
187 Self::Ed25519,
188 Self::Ed25519Ctx,
189 Self::Ed25519Ph,
190 Self::Ed448,
191 Self::Ed448Ph,
192 Self::PostQuantum(super::PqAlgorithm::MlDsa44),
193 Self::PostQuantum(super::PqAlgorithm::MlDsa65),
194 Self::PostQuantum(super::PqAlgorithm::MlDsa87),
195 Self::PostQuantum(super::PqAlgorithm::SlhDsaSha2_128f),
196 Self::PostQuantum(super::PqAlgorithm::SlhDsaSha2_128s),
197 Self::PostQuantum(super::PqAlgorithm::SlhDsaSha2_192f),
198 Self::PostQuantum(super::PqAlgorithm::SlhDsaSha2_192s),
199 Self::PostQuantum(super::PqAlgorithm::SlhDsaSha2_256f),
200 Self::PostQuantum(super::PqAlgorithm::SlhDsaSha2_256s),
201 ];
202
203 pub(crate) const fn ecdsa_digest(self) -> Option<DigestAlgorithm> {
205 match self {
206 Self::EcdsaSha1 => Some(DigestAlgorithm::Sha1),
207 Self::EcdsaSha224 => Some(DigestAlgorithm::Sha224),
208 Self::EcdsaSha256 => Some(DigestAlgorithm::Sha256),
209 Self::EcdsaSha384 => Some(DigestAlgorithm::Sha384),
210 Self::EcdsaSha512 => Some(DigestAlgorithm::Sha512),
211 Self::EcdsaSha3_224 => Some(DigestAlgorithm::Sha3_224),
212 Self::EcdsaSha3_256 => Some(DigestAlgorithm::Sha3_256),
213 Self::EcdsaSha3_384 => Some(DigestAlgorithm::Sha3_384),
214 Self::EcdsaSha3_512 => Some(DigestAlgorithm::Sha3_512),
215 _ => None,
216 }
217 }
218
219 pub(crate) const fn eddsa_signature_len(self) -> Option<usize> {
221 match self {
222 Self::Ed25519 | Self::Ed25519Ctx | Self::Ed25519Ph => Some(64),
223 Self::Ed448 | Self::Ed448Ph => Some(114),
224 _ => None,
225 }
226 }
227
228 pub(crate) const fn eddsa_key_algorithm(self) -> Option<Self> {
229 match self {
230 Self::Ed25519 | Self::Ed25519Ctx | Self::Ed25519Ph => Some(Self::Ed25519),
231 Self::Ed448 | Self::Ed448Ph => Some(Self::Ed448),
232 _ => None,
233 }
234 }
235
236 pub(crate) const fn context_element(self) -> Option<&'static str> {
237 match self {
238 Self::Ed25519Ctx | Self::Ed25519Ph | Self::Ed448 | Self::Ed448Ph => {
239 Some("EdDSAContextString")
240 }
241 Self::PostQuantum(algorithm) => Some(algorithm.context_element()),
242 _ => None,
243 }
244 }
245
246 pub(crate) const fn dsa_component_len(self) -> Option<usize> {
248 match self {
249 Self::DsaSha1 => Some(20),
250 Self::DsaSha256 => Some(32),
251 _ => None,
252 }
253 }
254
255 #[must_use]
257 pub const fn hmac_output_bits(self) -> Option<usize> {
258 match self {
259 Self::HmacSha1 => Some(160),
260 Self::HmacSha224 => Some(224),
261 Self::HmacSha256 => Some(256),
262 Self::HmacSha384 => Some(384),
263 Self::HmacSha512 => Some(512),
264 _ => None,
265 }
266 }
267
268 #[must_use]
270 pub fn from_uri(uri: &str) -> Option<Self> {
271 match uri {
272 "http://www.w3.org/2000/09/xmldsig#dsa-sha1" => Some(Self::DsaSha1),
273 "http://www.w3.org/2009/xmldsig11#dsa-sha256" => Some(Self::DsaSha256),
274 "http://www.w3.org/2000/09/xmldsig#hmac-sha1" => Some(Self::HmacSha1),
275 "http://www.w3.org/2001/04/xmldsig-more#hmac-sha224" => Some(Self::HmacSha224),
276 "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256" => Some(Self::HmacSha256),
277 "http://www.w3.org/2001/04/xmldsig-more#hmac-sha384" => Some(Self::HmacSha384),
278 "http://www.w3.org/2001/04/xmldsig-more#hmac-sha512" => Some(Self::HmacSha512),
279 "http://www.w3.org/2000/09/xmldsig#rsa-sha1" => Some(Self::RsaSha1),
280 "http://www.w3.org/2001/04/xmldsig-more#rsa-sha224" => Some(Self::RsaSha224),
281 "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" => Some(Self::RsaSha256),
282 "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384" => Some(Self::RsaSha384),
283 "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512" => Some(Self::RsaSha512),
284 "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1" => Some(Self::EcdsaSha1),
285 "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha224" => Some(Self::EcdsaSha224),
286 "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256" => Some(Self::EcdsaSha256),
287 "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384" => Some(Self::EcdsaSha384),
288 "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512" => Some(Self::EcdsaSha512),
289 "http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-224" => Some(Self::EcdsaSha3_224),
292 "http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-256" => Some(Self::EcdsaSha3_256),
293 "http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-384" => Some(Self::EcdsaSha3_384),
294 "http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-512" => Some(Self::EcdsaSha3_512),
295 "http://www.w3.org/2021/04/xmldsig-more#eddsa-ed25519" => Some(Self::Ed25519),
298 "http://www.w3.org/2021/04/xmldsig-more#eddsa-ed25519ctx" => Some(Self::Ed25519Ctx),
299 "http://www.w3.org/2021/04/xmldsig-more#eddsa-ed25519ph" => Some(Self::Ed25519Ph),
300 "http://www.w3.org/2021/04/xmldsig-more#eddsa-ed448" => Some(Self::Ed448),
301 "http://www.w3.org/2021/04/xmldsig-more#eddsa-ed448ph" => Some(Self::Ed448Ph),
302 _ => super::PqAlgorithm::ALL
303 .into_iter()
304 .find(|algorithm| algorithm.uri() == uri)
305 .map(Self::PostQuantum),
306 }
307 }
308
309 #[must_use]
311 pub fn uri(self) -> &'static str {
312 match self {
313 Self::DsaSha1 => "http://www.w3.org/2000/09/xmldsig#dsa-sha1",
314 Self::DsaSha256 => "http://www.w3.org/2009/xmldsig11#dsa-sha256",
315 Self::HmacSha1 => "http://www.w3.org/2000/09/xmldsig#hmac-sha1",
316 Self::HmacSha224 => "http://www.w3.org/2001/04/xmldsig-more#hmac-sha224",
317 Self::HmacSha256 => "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256",
318 Self::HmacSha384 => "http://www.w3.org/2001/04/xmldsig-more#hmac-sha384",
319 Self::HmacSha512 => "http://www.w3.org/2001/04/xmldsig-more#hmac-sha512",
320 Self::RsaSha1 => "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
321 Self::RsaSha224 => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha224",
322 Self::RsaSha256 => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
323 Self::RsaSha384 => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384",
324 Self::RsaSha512 => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512",
325 Self::EcdsaSha1 => "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1",
326 Self::EcdsaSha224 => "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha224",
327 Self::EcdsaSha256 => "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256",
328 Self::EcdsaSha384 => "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384",
329 Self::EcdsaSha512 => "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512",
330 Self::EcdsaSha3_224 => "http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-224",
331 Self::EcdsaSha3_256 => "http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-256",
332 Self::EcdsaSha3_384 => "http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-384",
333 Self::EcdsaSha3_512 => "http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-512",
334 Self::Ed25519 => "http://www.w3.org/2021/04/xmldsig-more#eddsa-ed25519",
335 Self::Ed25519Ctx => "http://www.w3.org/2021/04/xmldsig-more#eddsa-ed25519ctx",
336 Self::Ed25519Ph => "http://www.w3.org/2021/04/xmldsig-more#eddsa-ed25519ph",
337 Self::Ed448 => "http://www.w3.org/2021/04/xmldsig-more#eddsa-ed448",
338 Self::Ed448Ph => "http://www.w3.org/2021/04/xmldsig-more#eddsa-ed448ph",
339 Self::PostQuantum(algorithm) => algorithm.uri(),
340 }
341 }
342
343 #[must_use]
345 pub fn signing_allowed(self) -> bool {
346 !matches!(
347 self,
348 Self::RsaSha1 | Self::DsaSha1 | Self::HmacSha1 | Self::EcdsaSha1 | Self::PostQuantum(_)
349 )
350 }
351}
352
353#[derive(Debug)]
355#[non_exhaustive]
356pub struct SignedInfo {
357 pub c14n_method: C14nAlgorithm,
359 pub signature_method: SignatureAlgorithm,
361 pub hmac_output_length_bits: Option<usize>,
363 pub signature_context: SignatureContext,
365 pub references: Vec<Reference>,
367}
368
369#[derive(Debug)]
371pub struct Reference {
372 pub uri: Option<String>,
374 pub id: Option<String>,
376 pub ref_type: Option<String>,
378 pub transforms: Vec<Transform>,
380 pub digest_method: DigestAlgorithm,
382 pub digest_value: Vec<u8>,
384}
385
386#[derive(Debug, Default, Clone, PartialEq, Eq)]
388#[non_exhaustive]
389pub struct KeyInfo {
390 pub sources: Vec<KeyInfoSource>,
392}
393
394impl KeyInfo {
395 pub(crate) fn embedded_candidate_count(&self) -> usize {
401 self.sources.iter().fold(0usize, |count, source| {
402 count.saturating_add(match source {
403 KeyInfoSource::KeyValue(_) | KeyInfoSource::DerEncodedKeyValue(_) => 1,
404 KeyInfoSource::X509Data(info) => info.certificates.len(),
405 KeyInfoSource::KeyName(_)
406 | KeyInfoSource::RetrievalMethod { .. }
407 | KeyInfoSource::KeyInfoReference { .. } => 0,
408 })
409 })
410 }
411}
412
413#[derive(Debug, Clone, PartialEq, Eq)]
415#[non_exhaustive]
416pub enum KeyInfoSource {
417 KeyName(String),
419 KeyValue(KeyValueInfo),
421 X509Data(X509DataInfo),
423 DerEncodedKeyValue(Vec<u8>),
425 RetrievalMethod {
427 uri: String,
429 resource_type: Option<String>,
431 transforms: RetrievalMethodTransforms,
433 },
434 KeyInfoReference {
436 uri: String,
438 },
439}
440
441#[derive(Debug, Clone, PartialEq, Eq, Hash)]
443#[non_exhaustive]
444pub enum RetrievalMethodTransforms {
445 None,
447 X509DataNodeSetFilter {
449 expression: String,
451 namespaces: BTreeMap<String, String>,
453 },
454 Unsupported,
458}
459
460#[derive(Debug, Clone, PartialEq, Eq)]
462#[non_exhaustive]
463pub enum KeyValueInfo {
464 Dsa {
466 p: Option<Vec<u8>>,
468 q: Option<Vec<u8>>,
470 g: Option<Vec<u8>>,
472 y: Vec<u8>,
474 },
475 Rsa {
477 modulus: Vec<u8>,
479 exponent: Vec<u8>,
481 },
482 Ec {
484 curve_oid: String,
486 public_key: Vec<u8>,
488 },
489 InvalidEcKeyValue,
491 Unsupported {
493 namespace: Option<String>,
495 local_name: String,
497 },
498}
499
500#[derive(Debug, Default, Clone, PartialEq, Eq)]
502#[non_exhaustive]
503pub struct X509DataInfo {
504 pub certificates: Vec<Vec<u8>>,
508 pub subject_names: Vec<String>,
510 pub issuer_serials: Vec<(String, String)>,
512 pub skis: Vec<Vec<u8>>,
514 pub crls: Vec<Vec<u8>>,
516 pub digests: Vec<(String, Vec<u8>)>,
518 pub parsed_certificates: Vec<ParsedX509Certificate>,
522 pub certificate_chain: Vec<usize>,
524}
525
526#[derive(Debug, Clone, PartialEq, Eq)]
528#[non_exhaustive]
529pub struct ParsedX509Certificate {
530 pub subject_dn: String,
532 pub issuer_dn: String,
534 pub serial_number: Vec<u8>,
536 pub serial_number_hex: String,
538 pub subject_key_identifier: Option<Vec<u8>>,
540 pub public_key: X509PublicKeyInfo,
542}
543
544#[derive(Debug, Clone, PartialEq, Eq)]
546#[non_exhaustive]
547pub enum X509PublicKeyInfo {
548 Rsa {
550 modulus: Vec<u8>,
552 exponent: Vec<u8>,
554 },
555 Ec {
557 curve_oid: String,
559 public_key: Vec<u8>,
561 },
562 Unsupported {
564 algorithm_oid: String,
566 },
567}
568
569#[derive(Debug, thiserror::Error)]
571#[non_exhaustive]
572pub enum ParseError {
573 #[error("XMLDSig policy violation: {0}")]
575 Policy(#[from] crate::policy::PolicyViolation),
576
577 #[error("cryptographic provider error: {0}")]
579 Provider(#[from] crate::provider::ProviderError),
580
581 #[error("missing required element: <{element}>")]
583 MissingElement {
584 element: &'static str,
586 },
587
588 #[error("invalid structure: {0}")]
590 InvalidStructure(String),
591
592 #[error("unsupported algorithm: {uri}")]
594 UnsupportedAlgorithm {
595 uri: String,
597 },
598
599 #[error("base64 decode error: {0}")]
601 Base64(String),
602
603 #[error(
605 "digest length mismatch for {algorithm}: expected {expected} bytes, got {actual} bytes"
606 )]
607 DigestLengthMismatch {
608 algorithm: &'static str,
610 expected: usize,
612 actual: usize,
614 },
615
616 #[error("transform error: {0}")]
618 Transform(#[from] super::types::TransformError),
619}
620
621#[must_use]
623pub fn find_signature_node<'a>(doc: &'a Document<'a>) -> Option<Node<'a, 'a>> {
624 doc.descendants().find(|n| {
625 n.is_element()
626 && n.tag_name().name() == "Signature"
627 && n.tag_name().namespace() == Some(XMLDSIG_NS)
628 })
629}
630
631pub fn parse_signed_info(signed_info_node: Node) -> Result<SignedInfo, ParseError> {
636 parse_signed_info_with_xpath_budget(
637 signed_info_node,
638 &mut transforms::XPathSignatureParseBudget::default(),
639 &crate::policy::ResourcePolicy::default(),
640 )
641}
642
643pub(crate) fn parse_signed_info_with_xpath_budget(
644 signed_info_node: Node,
645 xpath_budget: &mut transforms::XPathSignatureParseBudget,
646 resources: &crate::policy::ResourcePolicy,
647) -> Result<SignedInfo, ParseError> {
648 verify_ds_element(signed_info_node, "SignedInfo")?;
649
650 let mut children = element_children(signed_info_node);
651
652 let c14n_node = children.next().ok_or(ParseError::MissingElement {
654 element: "CanonicalizationMethod",
655 })?;
656 verify_ds_element(c14n_node, "CanonicalizationMethod")?;
657 let c14n_uri = required_algorithm_attr(c14n_node, "CanonicalizationMethod")?;
658 let mut c14n_method =
659 C14nAlgorithm::from_uri(c14n_uri).ok_or_else(|| ParseError::UnsupportedAlgorithm {
660 uri: c14n_uri.to_string(),
661 })?;
662 if let Some(prefix_list) = parse_inclusive_prefixes(c14n_node)? {
663 if c14n_method.mode() == crate::c14n::C14nMode::Exclusive1_0 {
664 c14n_method = c14n_method.with_prefix_list(&prefix_list);
665 } else {
666 return Err(ParseError::UnsupportedAlgorithm {
667 uri: c14n_uri.to_string(),
668 });
669 }
670 }
671
672 let sig_method_node = children.next().ok_or(ParseError::MissingElement {
674 element: "SignatureMethod",
675 })?;
676 let (signature_method, hmac_output_length_bits, signature_context) =
677 parse_signature_method(sig_method_node, resources)?;
678
679 let mut references = Vec::new();
681 for child in children {
682 verify_ds_element(child, "Reference")?;
683 if references.len() == crate::hard_limits::SIGNATURE_REFERENCE_CEILING {
684 return Err(crate::policy::PolicyViolation::ResourceLimit {
685 resource: crate::policy::resource_name::SIGNATURE_REFERENCES,
686 maximum: crate::hard_limits::SIGNATURE_REFERENCE_CEILING,
687 actual: references.len().saturating_add(1),
688 }
689 .into());
690 }
691 references.push(parse_reference_with_xpath_budget(child, xpath_budget)?);
692 }
693 if references.is_empty() {
694 return Err(ParseError::MissingElement {
695 element: "Reference",
696 });
697 }
698
699 Ok(SignedInfo {
700 c14n_method,
701 signature_method,
702 hmac_output_length_bits,
703 signature_context,
704 references,
705 })
706}
707
708#[derive(Clone, Copy)]
709struct ByteAlignedHmacOutputLength(usize);
710
711pub(crate) fn parse_signature_method(
712 node: Node<'_, '_>,
713 resources: &crate::policy::ResourcePolicy,
714) -> Result<(SignatureAlgorithm, Option<usize>, SignatureContext), ParseError> {
715 verify_ds_element(node, "SignatureMethod")?;
716 let uri = required_algorithm_attr(node, "SignatureMethod")?;
717 let algorithm =
718 SignatureAlgorithm::from_uri(uri).ok_or_else(|| ParseError::UnsupportedAlgorithm {
719 uri: uri.to_string(),
720 })?;
721 ensure_no_non_whitespace_text(node, "SignatureMethod")?;
722 let (hmac, context) = if algorithm.context_element().is_some() {
723 (None, parse_signature_context(node, algorithm, resources)?)
724 } else {
725 (
726 parse_hmac_output_length(node, algorithm)?,
727 SignatureContext::default(),
728 )
729 };
730 Ok((algorithm, hmac, context))
731}
732
733impl ByteAlignedHmacOutputLength {
734 fn parse(text: &str, maximum_bits: usize) -> Result<Self, ParseError> {
735 let bits = text
739 .trim_matches([' ', '\t', '\r', '\n'])
740 .parse::<usize>()
741 .map_err(|_| ParseError::InvalidStructure("invalid HMACOutputLength".into()))?;
742 if bits == 0 || bits > maximum_bits || !bits.is_multiple_of(8) {
746 return Err(ParseError::InvalidStructure(format!(
747 "HMACOutputLength must be a positive byte-aligned value no greater than {maximum_bits}"
748 )));
749 }
750 Ok(Self(bits))
751 }
752
753 const fn bits(self) -> usize {
754 self.0
755 }
756}
757
758fn parse_hmac_output_length(
759 node: Node<'_, '_>,
760 algorithm: SignatureAlgorithm,
761) -> Result<Option<usize>, ParseError> {
762 ensure_no_non_whitespace_text(node, "SignatureMethod")?;
763 let mut children = element_children(node);
764 let Some(child) = children.next() else {
765 return Ok(None);
766 };
767 let Some(maximum_bits) = algorithm.hmac_output_bits() else {
768 return Err(ParseError::InvalidStructure(
769 "SignatureMethod parameters do not match the selected algorithm".into(),
770 ));
771 };
772 if child.tag_name().namespace() != Some(XMLDSIG_NS)
773 || child.tag_name().name() != "HMACOutputLength"
774 || children.next().is_some()
775 {
776 return Err(ParseError::InvalidStructure(
777 "SignatureMethod parameters do not match the selected algorithm".into(),
778 ));
779 }
780 ensure_no_element_children(child, "HMACOutputLength")?;
781 let text =
782 collect_text_content_bounded(child, MAX_HMAC_OUTPUT_LENGTH_TEXT_LEN, "HMACOutputLength")?;
783 Ok(Some(
784 ByteAlignedHmacOutputLength::parse(&text, maximum_bits)?.bits(),
785 ))
786}
787
788fn parse_signature_context(
789 node: Node<'_, '_>,
790 algorithm: SignatureAlgorithm,
791 resources: &crate::policy::ResourcePolicy,
792) -> Result<SignatureContext, ParseError> {
793 let Some(element_name) = algorithm.context_element() else {
794 return Ok(SignatureContext::default());
795 };
796 let mut children = element_children(node);
797 let Some(child) = children.next() else {
798 return Ok(SignatureContext::default());
799 };
800 if child.tag_name().namespace() != Some(EXPERIMENTAL_SIGNATURE_NS)
803 || child.tag_name().name() != element_name
804 || children.next().is_some()
805 {
806 return Err(ParseError::InvalidStructure(
807 "SignatureMethod parameters do not match the selected algorithm".into(),
808 ));
809 }
810 let payload =
816 super::whitespace::XmlBase64Payload::bounded(child, resources.max_xml_document_bytes, 340)
817 .map_err(|reason| {
818 ParseError::InvalidStructure(format!("signature context: {reason}"))
819 })?;
820 if payload.decoded_len > 255 {
821 return Err(ParseError::InvalidStructure(
822 "signature context exceeds 255 octets".into(),
823 ));
824 }
825 let bytes = payload
826 .decode()
827 .map_err(|reason| ParseError::InvalidStructure(format!("signature context: {reason}")))?;
828 Ok(SignatureContext(bytes))
829}
830
831pub fn parse_reference(reference_node: Node) -> Result<Reference, ParseError> {
835 parse_reference_with_xpath_budget(
836 reference_node,
837 &mut transforms::XPathSignatureParseBudget::default(),
838 )
839}
840
841pub(crate) fn parse_reference_with_xpath_budget(
842 reference_node: Node,
843 xpath_budget: &mut transforms::XPathSignatureParseBudget,
844) -> Result<Reference, ParseError> {
845 verify_ds_element(reference_node, "Reference")?;
846 ensure_no_non_whitespace_text(reference_node, "Reference")?;
847 let uri = reference_node.attribute("URI").map(String::from);
848 let id = reference_node.attribute("Id").map(String::from);
849 let ref_type = reference_node.attribute("Type").map(String::from);
850
851 let mut children = element_children(reference_node);
852
853 let mut transforms = Vec::new();
855 let mut transform_error = None;
856 let (transforms_node, digest_method_node) =
857 reference_transforms_and_digest_method(&mut children)?;
858
859 if let Some(transforms_node) = transforms_node {
860 match transforms::parse_transforms_with_budget(transforms_node, xpath_budget) {
861 Ok(parsed) => transforms = parsed,
862 Err(error) => transform_error = Some(error),
863 }
864 }
865
866 let digest_uri = required_algorithm_attr(digest_method_node, "DigestMethod")?;
868 let digest_method =
869 DigestAlgorithm::from_uri(digest_uri).ok_or_else(|| ParseError::UnsupportedAlgorithm {
870 uri: digest_uri.to_string(),
871 })?;
872
873 let digest_value_node = children.next().ok_or(ParseError::MissingElement {
875 element: "DigestValue",
876 })?;
877 verify_ds_element(digest_value_node, "DigestValue")?;
878 let digest_value = decode_digest_value_children(digest_value_node, digest_method)?;
879
880 if let Some(unexpected) = children.next() {
882 return Err(ParseError::InvalidStructure(format!(
883 "unexpected element <{}> after <DigestValue> in <Reference>",
884 unexpected.tag_name().name()
885 )));
886 }
887
888 if let Some(error) = transform_error {
892 return Err(ParseError::Transform(error));
893 }
894
895 Ok(Reference {
896 uri,
897 id,
898 ref_type,
899 transforms,
900 digest_method,
901 digest_value,
902 })
903}
904
905pub(crate) fn reference_digest_method(
906 reference_node: Node<'_, '_>,
907) -> Result<DigestAlgorithm, ParseError> {
908 verify_ds_element(reference_node, "Reference")?;
909 let mut children = element_children(reference_node);
910 let (_, digest_method_node) = reference_transforms_and_digest_method(&mut children)?;
911 let uri = required_algorithm_attr(digest_method_node, "DigestMethod")?;
912 DigestAlgorithm::from_uri(uri).ok_or_else(|| ParseError::UnsupportedAlgorithm {
913 uri: uri.to_owned(),
914 })
915}
916
917fn reference_transforms_and_digest_method<'a, 'input>(
918 children: &mut impl Iterator<Item = Node<'a, 'input>>,
919) -> Result<(Option<Node<'a, 'input>>, Node<'a, 'input>), ParseError> {
920 let first = children.next().ok_or(ParseError::MissingElement {
921 element: "DigestMethod",
922 })?;
923 let transforms_node = is_ds_element(first, "Transforms").then_some(first);
924 let digest_method_node = if transforms_node.is_some() {
925 children.next().ok_or(ParseError::MissingElement {
926 element: "DigestMethod",
927 })?
928 } else {
929 first
930 };
931 verify_ds_element(digest_method_node, "DigestMethod")?;
932 Ok((transforms_node, digest_method_node))
933}
934
935pub fn parse_key_info(key_info_node: Node) -> Result<KeyInfo, ParseError> {
948 parse_key_info_with_provider(key_info_node, crate::provider::default_provider())
949}
950
951pub struct KeyInfoParsingSession<'a> {
957 resources: &'a crate::policy::ResourcePolicy,
958 xml_base: XmlBaseResolutionBudget,
959 usage: KeyInfoParseUsage,
960}
961
962#[derive(Default)]
963struct KeyInfoParseUsage {
964 embedded_candidates: usize,
965 x509_binary_bytes: usize,
966}
967
968impl<'a> KeyInfoParsingSession<'a> {
969 pub fn new(resources: &'a crate::policy::ResourcePolicy) -> Result<Self, ParseError> {
971 resources.validate()?;
972 Ok(Self {
973 resources,
974 xml_base: XmlBaseResolutionBudget::with_limits(
975 resources.effective_xml_base_components(),
976 resources.effective_xml_base_resolution_bytes(),
977 ),
978 usage: KeyInfoParseUsage::default(),
979 })
980 }
981
982 pub fn parse(&mut self, node: Node) -> Result<KeyInfo, ParseError> {
984 self.parse_with_provider(node, crate::provider::default_provider())
985 }
986
987 pub fn parse_with_provider(
989 &mut self,
990 node: Node,
991 provider: &dyn crate::provider::CryptoProvider,
992 ) -> Result<KeyInfo, ParseError> {
993 parse_key_info_in_session(
994 node,
995 provider,
996 &self.xml_base,
997 self.resources,
998 None,
999 None,
1000 &mut self.usage,
1001 )
1002 }
1003}
1004
1005pub(crate) fn parse_key_info_with_provider(
1006 key_info_node: Node,
1007 provider: &dyn crate::provider::CryptoProvider,
1008) -> Result<KeyInfo, ParseError> {
1009 let xml_base_budget = XmlBaseResolutionBudget::default();
1010 parse_key_info_with_policy_budgets(
1011 key_info_node,
1012 provider,
1013 &xml_base_budget,
1014 &crate::policy::ResourcePolicy::default(),
1015 None,
1016 )
1017}
1018
1019pub(crate) fn parse_key_info_with_policy_budgets(
1020 key_info_node: Node,
1021 provider: &dyn crate::provider::CryptoProvider,
1022 xml_base_budget: &XmlBaseResolutionBudget,
1023 resources: &crate::policy::ResourcePolicy,
1024 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
1025) -> Result<KeyInfo, ParseError> {
1026 parse_key_info_with_policy_budgets_and_document_base(
1027 key_info_node,
1028 provider,
1029 xml_base_budget,
1030 resources,
1031 None,
1032 allowed,
1033 )
1034}
1035
1036pub(crate) fn parse_key_info_with_policy_budgets_and_document_base(
1037 key_info_node: Node,
1038 provider: &dyn crate::provider::CryptoProvider,
1039 xml_base_budget: &XmlBaseResolutionBudget,
1040 resources: &crate::policy::ResourcePolicy,
1041 document_base: Option<&str>,
1042 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
1043) -> Result<KeyInfo, ParseError> {
1044 parse_key_info_in_session(
1045 key_info_node,
1046 provider,
1047 xml_base_budget,
1048 resources,
1049 document_base,
1050 allowed,
1051 &mut KeyInfoParseUsage::default(),
1052 )
1053}
1054
1055fn parse_key_info_in_session(
1056 key_info_node: Node,
1057 provider: &dyn crate::provider::CryptoProvider,
1058 xml_base_budget: &XmlBaseResolutionBudget,
1059 resources: &crate::policy::ResourcePolicy,
1060 document_base: Option<&str>,
1061 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
1062 usage: &mut KeyInfoParseUsage,
1063) -> Result<KeyInfo, ParseError> {
1064 validate_key_info_container(key_info_node)?;
1065
1066 let mut sources = Vec::new();
1067 for (index, child) in element_children(key_info_node).enumerate() {
1071 if index >= MAX_KEY_INFO_CHILD_COUNT {
1072 return Err(ParseError::InvalidStructure(
1073 "KeyInfo contains too many child elements".into(),
1074 ));
1075 }
1076 match (child.tag_name().namespace(), child.tag_name().name()) {
1077 (Some(XMLDSIG_NS), "KeyName") => {
1078 ensure_no_element_children(child, "KeyName")?;
1079 let key_name =
1080 collect_text_content_bounded(child, MAX_KEY_NAME_TEXT_LEN, "KeyName")?;
1081 sources.push(KeyInfoSource::KeyName(key_name));
1082 }
1083 (Some(XMLDSIG_NS), "KeyValue") => {
1084 charge_embedded_key_candidate(&mut usage.embedded_candidates, resources)?;
1085 let key_value = parse_key_value_dispatch(child)?;
1086 sources.push(KeyInfoSource::KeyValue(key_value));
1087 }
1088 (Some(XMLDSIG_NS), "X509Data") => {
1089 let x509 = parse_x509_data_dispatch_with_budget_and_provider(
1090 child,
1091 &mut usage.x509_binary_bytes,
1092 &mut usage.embedded_candidates,
1093 provider,
1094 resources,
1095 allowed,
1096 )?;
1097 sources.push(KeyInfoSource::X509Data(x509));
1098 }
1099 (Some(XMLDSIG_NS), "RetrievalMethod") => {
1100 ensure_no_non_whitespace_text(child, "RetrievalMethod")?;
1101 let lexical_uri = child.attribute("URI").ok_or_else(|| {
1102 ParseError::InvalidStructure("RetrievalMethod requires URI".into())
1103 })?;
1104 if lexical_uri.len() > MAX_KEY_NAME_TEXT_LEN {
1105 return Err(ParseError::InvalidStructure(
1106 "RetrievalMethod URI exceeds maximum length".into(),
1107 ));
1108 }
1109 let uri = if lexical_uri.is_empty() || lexical_uri.starts_with('#') {
1110 lexical_uri.to_owned()
1111 } else {
1112 resolve_uri_from_node_with_document_base_with_budget(
1115 child,
1116 lexical_uri,
1117 document_base,
1118 xml_base_budget,
1119 )
1120 .map_err(|error| ParseError::InvalidStructure(error.to_string()))?
1121 };
1122 let resource_type = child.attribute("Type");
1123 if resource_type.is_some_and(|value| value.len() > MAX_KEY_NAME_TEXT_LEN) {
1124 return Err(ParseError::InvalidStructure(
1125 "RetrievalMethod Type exceeds maximum length".into(),
1126 ));
1127 }
1128 let resource_type = resource_type.map(str::to_owned);
1129 let transforms = if resource_type.as_deref()
1130 == Some("http://www.w3.org/2000/09/xmldsig#X509Data")
1131 {
1132 parse_retrieval_method_transforms(child, resources)?
1133 } else if element_children(child).next().is_some() {
1134 RetrievalMethodTransforms::Unsupported
1135 } else {
1136 RetrievalMethodTransforms::None
1137 };
1138 sources.push(KeyInfoSource::RetrievalMethod {
1139 uri,
1140 resource_type,
1141 transforms,
1142 });
1143 }
1144 (Some(XMLDSIG11_NS), "DEREncodedKeyValue") => {
1145 charge_embedded_key_candidate(&mut usage.embedded_candidates, resources)?;
1146 ensure_no_element_children(child, "DEREncodedKeyValue")?;
1147 let der = decode_der_encoded_key_value_base64(child)?;
1148 sources.push(KeyInfoSource::DerEncodedKeyValue(der));
1149 }
1150 (Some(XMLDSIG11_NS), "KeyInfoReference") => {
1151 ensure_no_element_children(child, "KeyInfoReference")?;
1152 ensure_no_non_whitespace_text(child, "KeyInfoReference")?;
1153 let lexical_uri = child.attribute("URI").ok_or_else(|| {
1154 ParseError::InvalidStructure("KeyInfoReference requires URI".into())
1155 })?;
1156 if lexical_uri.len() > MAX_KEY_NAME_TEXT_LEN {
1157 return Err(ParseError::InvalidStructure(
1158 "KeyInfoReference URI exceeds maximum length".into(),
1159 ));
1160 }
1161 let uri = if lexical_uri.is_empty() || lexical_uri.starts_with('#') {
1162 lexical_uri.to_owned()
1163 } else {
1164 resolve_uri_from_node_with_document_base_with_budget(
1165 child,
1166 lexical_uri,
1167 document_base,
1168 xml_base_budget,
1169 )
1170 .map_err(|error| ParseError::InvalidStructure(error.to_string()))?
1171 };
1172 sources.push(KeyInfoSource::KeyInfoReference { uri });
1173 }
1174 _ => {}
1175 }
1176 }
1177
1178 Ok(KeyInfo { sources })
1179}
1180
1181fn charge_embedded_key_candidate(
1182 embedded_key_candidates: &mut usize,
1183 resources: &crate::policy::ResourcePolicy,
1184) -> Result<(), ParseError> {
1185 *embedded_key_candidates = embedded_key_candidates.saturating_add(1);
1186 resources.validate_key_candidates(*embedded_key_candidates)?;
1187 Ok(())
1188}
1189
1190fn parse_retrieval_method_transforms(
1191 node: Node<'_, '_>,
1192 resources: &crate::policy::ResourcePolicy,
1193) -> Result<RetrievalMethodTransforms, ParseError> {
1194 let mut children = element_children(node);
1195 let Some(transforms) = children.next() else {
1196 return Ok(RetrievalMethodTransforms::None);
1197 };
1198 if children.next().is_some()
1199 || transforms.tag_name().namespace() != Some(XMLDSIG_NS)
1200 || transforms.tag_name().name() != "Transforms"
1201 {
1202 return Err(ParseError::InvalidStructure(
1203 "RetrievalMethod accepts only one optional ds:Transforms child".into(),
1204 ));
1205 }
1206 ensure_no_non_whitespace_text(transforms, "Transforms")?;
1207 let mut transform_children = element_children(transforms);
1208 let transform = transform_children.next().ok_or_else(|| {
1209 ParseError::InvalidStructure("RetrievalMethod Transforms must not be empty".into())
1210 })?;
1211 if transform_children.next().is_some()
1212 || transform.tag_name().namespace() != Some(XMLDSIG_NS)
1213 || transform.tag_name().name() != "Transform"
1214 || transform.attribute("Algorithm") != Some(transforms::XPATH_TRANSFORM_URI)
1215 {
1216 return Err(ParseError::InvalidStructure(
1217 "unsupported RetrievalMethod transform chain".into(),
1218 ));
1219 }
1220 ensure_no_non_whitespace_text(transform, "Transform")?;
1221 let mut parameters = element_children(transform);
1222 let xpath = parameters.next().ok_or_else(|| {
1223 ParseError::InvalidStructure("RetrievalMethod XPath parameter is missing".into())
1224 })?;
1225 if parameters.next().is_some()
1226 || xpath.tag_name().namespace() != Some(XMLDSIG_NS)
1227 || xpath.tag_name().name() != "XPath"
1228 {
1229 return Err(ParseError::InvalidStructure(
1230 "unsupported RetrievalMethod transform chain".into(),
1231 ));
1232 }
1233 ensure_no_element_children(xpath, "XPath")?;
1234 let expression =
1235 collect_text_content_bounded(xpath, MAX_RETRIEVAL_XPATH_TEXT_LEN, "RetrievalMethod XPath")?;
1236 let normalized_expression = expression.trim();
1237 let selects_x509_data = normalized_expression
1238 .strip_prefix("ancestor-or-self::")
1239 .and_then(|step| step.split_once(':'))
1240 .is_some_and(|(prefix, local)| {
1241 local == "X509Data" && xpath.lookup_namespace_uri(Some(prefix)) == Some(XMLDSIG_NS)
1242 });
1243 if !selects_x509_data {
1244 return Err(ParseError::InvalidStructure(
1245 "unsupported RetrievalMethod XPath selection".into(),
1246 ));
1247 }
1248 let namespaces = transforms::collect_xpath_namespaces_with_resources(xpath, resources)?;
1249 Ok(RetrievalMethodTransforms::X509DataNodeSetFilter {
1250 expression,
1251 namespaces,
1252 })
1253}
1254
1255fn element_children<'a>(node: Node<'a, 'a>) -> impl Iterator<Item = Node<'a, 'a>> {
1259 node.children().filter(|n| n.is_element())
1260}
1261
1262fn verify_ds_element(node: Node, expected_name: &'static str) -> Result<(), ParseError> {
1264 verify_namespaced_element(node, expected_name, XMLDSIG_NS, "ds")
1265}
1266
1267fn verify_dsig11_element(node: Node, expected_name: &'static str) -> Result<(), ParseError> {
1269 verify_namespaced_element(node, expected_name, XMLDSIG11_NS, "dsig11")
1270}
1271
1272fn verify_namespaced_element(
1273 node: Node,
1274 expected_name: &'static str,
1275 expected_namespace: &'static str,
1276 diagnostic_prefix: &'static str,
1277) -> Result<(), ParseError> {
1278 if !node.is_element() {
1279 return Err(ParseError::InvalidStructure(format!(
1280 "expected element <{expected_name}>, got non-element node"
1281 )));
1282 }
1283 let tag = node.tag_name();
1284 if tag.name() != expected_name || tag.namespace() != Some(expected_namespace) {
1285 return Err(ParseError::InvalidStructure(format!(
1286 "expected <{diagnostic_prefix}:{expected_name}>, got <{}{}>",
1287 tag.namespace()
1288 .map(|ns| format!("{{{ns}}}"))
1289 .unwrap_or_default(),
1290 tag.name()
1291 )));
1292 }
1293 Ok(())
1294}
1295
1296fn required_algorithm_attr<'a>(
1298 node: Node<'a, 'a>,
1299 element_name: &'static str,
1300) -> Result<&'a str, ParseError> {
1301 node.attribute("Algorithm").ok_or_else(|| {
1302 ParseError::InvalidStructure(format!("missing Algorithm attribute on <{element_name}>"))
1303 })
1304}
1305
1306fn parse_inclusive_prefixes(node: Node) -> Result<Option<String>, ParseError> {
1312 const EXCLUSIVE_C14N_NS_URI: &str = "http://www.w3.org/2001/10/xml-exc-c14n#";
1313
1314 for child in node.children() {
1315 if child.is_element() {
1316 let tag = child.tag_name();
1317 if tag.name() == "InclusiveNamespaces" && tag.namespace() == Some(EXCLUSIVE_C14N_NS_URI)
1318 {
1319 return child
1320 .attribute("PrefixList")
1321 .map(str::to_string)
1322 .ok_or_else(|| {
1323 ParseError::InvalidStructure(
1324 "missing PrefixList attribute on <InclusiveNamespaces>".into(),
1325 )
1326 })
1327 .map(Some);
1328 }
1329 }
1330 }
1331
1332 Ok(None)
1333}
1334
1335pub(crate) fn parse_key_value_dispatch(node: Node) -> Result<KeyValueInfo, ParseError> {
1336 verify_ds_element(node, "KeyValue")?;
1337 ensure_no_non_whitespace_text(node, "KeyValue")?;
1338
1339 let mut children = element_children(node);
1340 let Some(first_child) = children.next() else {
1341 return Err(ParseError::InvalidStructure(
1342 "KeyValue must contain exactly one key-value child".into(),
1343 ));
1344 };
1345 if children.next().is_some() {
1346 return Err(ParseError::InvalidStructure(
1347 "KeyValue must contain exactly one key-value child".into(),
1348 ));
1349 }
1350
1351 match (
1352 first_child.tag_name().namespace(),
1353 first_child.tag_name().name(),
1354 ) {
1355 (Some(XMLDSIG_NS), "RSAKeyValue") => parse_rsa_key_value(first_child),
1356 (Some(XMLDSIG_NS), "DSAKeyValue") => parse_dsa_key_value(first_child),
1357 (Some(XMLDSIG11_NS), "ECKeyValue") => parse_ec_key_value(first_child),
1358 (namespace, child_name) => Ok(KeyValueInfo::Unsupported {
1359 namespace: namespace.map(str::to_string),
1360 local_name: child_name.to_string(),
1361 }),
1362 }
1363}
1364
1365pub(crate) fn validate_key_info_container(node: Node) -> Result<(), ParseError> {
1366 verify_ds_element(node, "KeyInfo")?;
1367 ensure_no_non_whitespace_text(node, "KeyInfo")
1368}
1369
1370fn parse_dsa_key_value(node: Node<'_, '_>) -> Result<KeyValueInfo, ParseError> {
1371 verify_ds_element(node, "DSAKeyValue")?;
1372 ensure_no_non_whitespace_text(node, "DSAKeyValue")?;
1373 let children = element_children(node).collect::<Vec<_>>();
1374 let mut index = 0;
1375 let p = take_dsa_crypto_binary(&children, &mut index, "P")?;
1376 let q = take_dsa_crypto_binary(&children, &mut index, "Q")?;
1377 if p.is_some() != q.is_some() {
1378 return Err(ParseError::InvalidStructure(
1379 "DSAKeyValue P and Q must be present together".into(),
1380 ));
1381 }
1382 let g = take_dsa_crypto_binary(&children, &mut index, "G")?;
1383 let y = take_dsa_crypto_binary(&children, &mut index, "Y")?
1384 .ok_or_else(|| ParseError::InvalidStructure("DSAKeyValue requires Y".into()))?;
1385 let _j = take_dsa_crypto_binary(&children, &mut index, "J")?;
1386 let seed = take_dsa_crypto_binary(&children, &mut index, "Seed")?;
1387 let counter = take_dsa_crypto_binary(&children, &mut index, "PgenCounter")?;
1388 if seed.is_some() != counter.is_some() {
1389 return Err(ParseError::InvalidStructure(
1390 "DSAKeyValue Seed and PgenCounter must be present together".into(),
1391 ));
1392 }
1393 if index != children.len() {
1394 return Err(ParseError::InvalidStructure(
1395 "DSAKeyValue children do not match the XMLDSig schema order".into(),
1396 ));
1397 }
1398 Ok(KeyValueInfo::Dsa { p, q, g, y })
1399}
1400
1401fn take_dsa_crypto_binary(
1402 children: &[Node<'_, '_>],
1403 index: &mut usize,
1404 name: &'static str,
1405) -> Result<Option<Vec<u8>>, ParseError> {
1406 let Some(&child) = children.get(*index) else {
1407 return Ok(None);
1408 };
1409 if !is_ds_element(child, name) {
1410 return Ok(None);
1411 }
1412 *index += 1;
1413 ensure_no_element_children(child, name)?;
1414 decode_crypto_binary(child, name, MAX_RSA_MODULUS_LEN).map(Some)
1415}
1416
1417fn is_ds_element(node: Node<'_, '_>, name: &str) -> bool {
1418 node.tag_name().namespace() == Some(XMLDSIG_NS) && node.tag_name().name() == name
1419}
1420
1421fn parse_ec_key_value(node: Node<'_, '_>) -> Result<KeyValueInfo, ParseError> {
1422 verify_dsig11_element(node, "ECKeyValue")?;
1423 ensure_no_non_whitespace_text(node, "ECKeyValue")?;
1424
1425 let mut children = element_children(node);
1426 let Some(named_curve_node) = children.next() else {
1427 return Ok(KeyValueInfo::InvalidEcKeyValue);
1428 };
1429 if named_curve_node.tag_name().namespace() == Some(XMLDSIG11_NS)
1430 && named_curve_node.tag_name().name() == "ECParameters"
1431 {
1432 return Ok(KeyValueInfo::Unsupported {
1433 namespace: Some(XMLDSIG11_NS.to_string()),
1434 local_name: "ECKeyValue".into(),
1435 });
1436 }
1437 if named_curve_node.tag_name().namespace() != Some(XMLDSIG11_NS)
1438 || named_curve_node.tag_name().name() != "NamedCurve"
1439 {
1440 return Ok(KeyValueInfo::InvalidEcKeyValue);
1441 }
1442 ensure_no_element_children(named_curve_node, "NamedCurve")?;
1443 ensure_no_non_whitespace_text(named_curve_node, "NamedCurve")?;
1444 let Some((curve_oid, expected_public_key_len)) =
1445 (match parse_ec_named_curve_oid(named_curve_node) {
1446 Ok(curve) => curve,
1447 Err(_) => return Ok(KeyValueInfo::InvalidEcKeyValue),
1448 })
1449 else {
1450 return Ok(KeyValueInfo::Unsupported {
1451 namespace: Some(XMLDSIG11_NS.to_string()),
1452 local_name: "ECKeyValue".into(),
1453 });
1454 };
1455
1456 let Some(public_key_node) = children.next() else {
1457 return Ok(KeyValueInfo::InvalidEcKeyValue);
1458 };
1459 if public_key_node.tag_name().namespace() != Some(XMLDSIG11_NS)
1460 || public_key_node.tag_name().name() != "PublicKey"
1461 {
1462 return Ok(KeyValueInfo::InvalidEcKeyValue);
1463 }
1464 ensure_no_element_children(public_key_node, "PublicKey")?;
1465 if children.next().is_some() {
1466 return Ok(KeyValueInfo::InvalidEcKeyValue);
1467 }
1468
1469 let public_key = match decode_crypto_binary(public_key_node, "PublicKey", MAX_EC_PUBLIC_KEY_LEN)
1470 {
1471 Ok(public_key) => public_key,
1472 Err(_) => return Ok(KeyValueInfo::InvalidEcKeyValue),
1473 };
1474 if validate_ec_public_key_point(&public_key, expected_public_key_len).is_err() {
1475 return Ok(KeyValueInfo::InvalidEcKeyValue);
1476 }
1477
1478 Ok(KeyValueInfo::Ec {
1479 curve_oid,
1480 public_key,
1481 })
1482}
1483
1484fn parse_ec_named_curve_oid(node: Node<'_, '_>) -> Result<Option<(String, usize)>, ParseError> {
1485 let uri = node.attribute("URI").ok_or_else(|| {
1486 ParseError::InvalidStructure("ECKeyValue NamedCurve must include URI attribute".into())
1487 })?;
1488 let curve_oid = uri.strip_prefix("urn:oid:").unwrap_or(uri);
1489 if curve_oid.is_empty() {
1490 return Err(ParseError::InvalidStructure(
1491 "ECKeyValue NamedCurve URI must not be empty".into(),
1492 ));
1493 }
1494 let Some(public_key_len) = ec_public_key_len(curve_oid) else {
1495 return Ok(None);
1496 };
1497 Ok(Some((curve_oid.to_string(), public_key_len)))
1498}
1499
1500fn ec_public_key_len(curve_oid: &str) -> Option<usize> {
1501 match curve_oid {
1502 EC_P256_OID => Some(65),
1503 EC_P384_OID => Some(97),
1504 EC_P521_OID => Some(133),
1505 _ => None,
1506 }
1507}
1508
1509fn validate_ec_public_key_point(public_key: &[u8], expected_len: usize) -> Result<(), ParseError> {
1510 if public_key.len() != expected_len {
1511 return Err(ParseError::InvalidStructure(
1512 "ECKeyValue PublicKey length does not match NamedCurve".into(),
1513 ));
1514 }
1515 if public_key.first().copied() != Some(0x04) {
1516 return Err(ParseError::InvalidStructure(
1517 "ECKeyValue PublicKey must be an uncompressed SEC1 point".into(),
1518 ));
1519 }
1520 Ok(())
1521}
1522
1523fn parse_rsa_key_value(node: Node<'_, '_>) -> Result<KeyValueInfo, ParseError> {
1524 verify_ds_element(node, "RSAKeyValue")?;
1525 ensure_no_non_whitespace_text(node, "RSAKeyValue")?;
1526
1527 let mut children = element_children(node);
1528 let modulus_node = children.next().ok_or_else(|| {
1529 ParseError::InvalidStructure("RSAKeyValue requires Modulus and Exponent".into())
1530 })?;
1531 verify_ds_element(modulus_node, "Modulus")?;
1532 ensure_no_element_children(modulus_node, "Modulus")?;
1533
1534 let exponent_node = children.next().ok_or_else(|| {
1535 ParseError::InvalidStructure("RSAKeyValue requires Modulus and Exponent".into())
1536 })?;
1537 verify_ds_element(exponent_node, "Exponent")?;
1538 ensure_no_element_children(exponent_node, "Exponent")?;
1539 if children.next().is_some() {
1540 return Err(ParseError::InvalidStructure(
1541 "RSAKeyValue must contain exactly Modulus followed by Exponent".into(),
1542 ));
1543 }
1544
1545 Ok(KeyValueInfo::Rsa {
1546 modulus: decode_crypto_binary(modulus_node, "Modulus", MAX_RSA_MODULUS_LEN)?,
1547 exponent: decode_crypto_binary(exponent_node, "Exponent", MAX_RSA_EXPONENT_LEN)?,
1548 })
1549}
1550
1551fn decode_crypto_binary(
1552 node: Node<'_, '_>,
1553 element_name: &'static str,
1554 max_decoded_len: usize,
1555) -> Result<Vec<u8>, ParseError> {
1556 let max_base64_len = max_decoded_len.div_ceil(3) * 4;
1557 let payload = super::whitespace::XmlBase64Payload::bounded(node, usize::MAX, max_base64_len)
1558 .map_err(|reason| xml_base64_payload_error(element_name, reason))?;
1559 if payload.normalized_len > max_base64_len {
1560 return Err(ParseError::InvalidStructure(format!(
1561 "{element_name} exceeds maximum allowed base64 length"
1562 )));
1563 }
1564 if payload.decoded_len == 0 {
1565 return Err(ParseError::InvalidStructure(format!(
1566 "{element_name} must not be empty"
1567 )));
1568 }
1569 if payload.decoded_len > max_decoded_len {
1570 return Err(ParseError::InvalidStructure(format!(
1571 "{element_name} exceeds maximum allowed binary length"
1572 )));
1573 }
1574 payload
1575 .decode()
1576 .map_err(|reason| ParseError::Base64(format!("{element_name}: {reason}")))
1577}
1578
1579pub(crate) fn parse_x509_data_dispatch_with_budget_and_provider(
1580 node: Node,
1581 total_binary_len: &mut usize,
1582 embedded_key_candidates: &mut usize,
1583 provider: &dyn crate::provider::CryptoProvider,
1584 resources: &crate::policy::ResourcePolicy,
1585 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
1586) -> Result<X509DataInfo, ParseError> {
1587 verify_ds_element(node, "X509Data")?;
1588 ensure_no_non_whitespace_text(node, "X509Data")?;
1589
1590 let mut info = X509DataInfo::default();
1591 for child in element_children(node) {
1592 match (child.tag_name().namespace(), child.tag_name().name()) {
1593 (Some(XMLDSIG_NS), "X509Certificate") => {
1594 charge_embedded_key_candidate(embedded_key_candidates, resources)?;
1595 ensure_no_element_children(child, "X509Certificate")?;
1596 ensure_x509_data_entry_budget(&info)?;
1597 let cert =
1598 decode_x509_base64(child, "X509Certificate", total_binary_len, resources)?;
1599 let parsed_cert = parse_x509_certificate(cert.as_slice())?;
1600 info.parsed_certificates.push(parsed_cert);
1601 info.certificates.push(cert);
1602 }
1603 (Some(XMLDSIG_NS), "X509SubjectName") => {
1604 ensure_no_element_children(child, "X509SubjectName")?;
1605 ensure_x509_data_entry_budget(&info)?;
1606 let subject_name = collect_text_content_bounded(
1607 child,
1608 MAX_X509_SUBJECT_NAME_TEXT_LEN,
1609 "X509SubjectName",
1610 )?;
1611 info.subject_names.push(subject_name);
1612 }
1613 (Some(XMLDSIG_NS), "X509IssuerSerial") => {
1614 ensure_x509_data_entry_budget(&info)?;
1615 let issuer_serial = parse_x509_issuer_serial(child)?;
1616 info.issuer_serials.push(issuer_serial);
1617 }
1618 (Some(XMLDSIG_NS), "X509SKI") => {
1619 ensure_no_element_children(child, "X509SKI")?;
1620 ensure_x509_data_entry_budget(&info)?;
1621 let ski = decode_x509_base64(child, "X509SKI", total_binary_len, resources)?;
1622 info.skis.push(ski);
1623 }
1624 (Some(XMLDSIG_NS), "X509CRL") => {
1625 ensure_no_element_children(child, "X509CRL")?;
1626 ensure_x509_data_entry_budget(&info)?;
1627 let crl = decode_x509_base64(child, "X509CRL", total_binary_len, resources)?;
1628 info.crls.push(crl);
1629 }
1630 (Some(XMLDSIG11_NS), "X509Digest") => {
1631 ensure_no_element_children(child, "X509Digest")?;
1632 ensure_x509_data_entry_budget(&info)?;
1633 let algorithm = required_algorithm_attr(child, "X509Digest")?;
1634 let digest = decode_x509_base64(child, "X509Digest", total_binary_len, resources)?;
1635 info.digests.push((algorithm.to_string(), digest));
1636 }
1637 (Some(XMLDSIG_NS), child_name) | (Some(XMLDSIG11_NS), child_name) => {
1638 return Err(ParseError::InvalidStructure(format!(
1639 "X509Data contains unsupported XMLDSig child element <{child_name}>"
1640 )));
1641 }
1642 _ => {}
1643 }
1644 }
1645
1646 info.certificate_chain = build_x509_certificate_chain(&info, provider, allowed)?;
1647 Ok(info)
1648}
1649
1650fn build_x509_certificate_chain(
1651 info: &X509DataInfo,
1652 provider: &dyn crate::provider::CryptoProvider,
1653 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
1654) -> Result<Vec<usize>, ParseError> {
1655 if info.parsed_certificates.is_empty() {
1656 return Ok(Vec::new());
1657 }
1658
1659 let signing_idx = select_x509_signing_certificate(info, provider)?;
1660 build_x509_certificate_chain_from(info, signing_idx, provider, allowed)
1661 .map_err(ParseError::from)
1662}
1663
1664#[derive(Debug, Clone, PartialEq, Eq)]
1665pub(crate) enum X509ChainBuildError {
1666 Policy(crate::policy::PolicyViolation),
1667 InconsistentMetadata,
1668 DepthExceeded,
1669 Cycle,
1670 IssuerSignatureMismatch,
1671 AmbiguousIssuer,
1672 UnsupportedSignatureAlgorithm { oid: String },
1673 Provider(crate::provider::ProviderError),
1674}
1675
1676impl From<X509ChainBuildError> for ParseError {
1677 fn from(error: X509ChainBuildError) -> Self {
1678 let reason = match error {
1679 X509ChainBuildError::InconsistentMetadata => {
1680 "X509Data certificate metadata is inconsistent"
1681 }
1682 X509ChainBuildError::DepthExceeded => {
1683 "X509Data certificate chain exceeds maximum depth"
1684 }
1685 X509ChainBuildError::Cycle => "X509Data certificate chain contains a cycle",
1686 X509ChainBuildError::IssuerSignatureMismatch => {
1687 "X509Data issuer candidates do not verify the certificate signature"
1688 }
1689 X509ChainBuildError::AmbiguousIssuer => {
1690 "X509Data certificate chain contains ambiguous issuer certificates"
1691 }
1692 X509ChainBuildError::UnsupportedSignatureAlgorithm { oid } => {
1693 return Self::InvalidStructure(format!(
1694 "X509Data certificate chain uses unsupported signature algorithm {oid}"
1695 ));
1696 }
1697 X509ChainBuildError::Provider(error) => return Self::Provider(error),
1698 X509ChainBuildError::Policy(error) => return Self::Policy(error),
1699 };
1700 Self::InvalidStructure(reason.into())
1701 }
1702}
1703
1704pub(crate) fn build_x509_certificate_chain_from(
1706 info: &X509DataInfo,
1707 signing_idx: usize,
1708 provider: &dyn crate::provider::CryptoProvider,
1709 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
1710) -> Result<Vec<usize>, X509ChainBuildError> {
1711 if signing_idx >= info.parsed_certificates.len()
1712 || info.parsed_certificates.len() != info.certificates.len()
1713 {
1714 return Err(X509ChainBuildError::InconsistentMetadata);
1715 }
1716 let mut chain = vec![signing_idx];
1717
1718 loop {
1719 let current_idx = *chain
1720 .last()
1721 .expect("chain starts with signing certificate index");
1722 let current = &info.parsed_certificates[current_idx];
1723 if distinguished_names_equal(¤t.subject_dn, ¤t.issuer_dn) {
1724 break;
1725 }
1726
1727 let candidates = info
1728 .parsed_certificates
1729 .iter()
1730 .enumerate()
1731 .filter(|(idx, cert)| {
1732 *idx != current_idx
1733 && distinguished_names_equal(&cert.subject_dn, ¤t.issuer_dn)
1734 })
1735 .map(|(idx, _)| idx)
1736 .collect::<Vec<_>>();
1737
1738 let issuer_idx = match candidates.as_slice() {
1739 [] => break,
1740 [issuer_idx] => *issuer_idx,
1741 _ => {
1742 let mut verified = Vec::new();
1743 let mut unsupported_oid = None;
1744 for issuer_idx in candidates {
1745 match certificate_signature_matches_with_provider(
1746 &info.certificates[current_idx],
1747 &info.certificates[issuer_idx],
1748 provider,
1749 allowed,
1750 ) {
1751 Ok(true) => verified.push(issuer_idx),
1752 Ok(false) => {}
1753 Err(super::X509ChainError::Provider(
1754 crate::provider::ProviderError::Unsupported {
1755 operation: crate::provider::ProviderOperation::VerifyCertificate,
1756 algorithm: Some(oid),
1757 },
1758 )) => {
1759 unsupported_oid.get_or_insert(oid);
1760 }
1761 Err(super::X509ChainError::Provider(error)) => {
1762 return Err(X509ChainBuildError::Provider(error));
1763 }
1764 Err(super::X509ChainError::UnsupportedSignatureAlgorithm { oid }) => {
1765 unsupported_oid.get_or_insert(oid);
1766 }
1767 Err(super::X509ChainError::Policy(error)) => {
1768 return Err(X509ChainBuildError::Policy(error));
1769 }
1770 Err(_) => return Err(X509ChainBuildError::IssuerSignatureMismatch),
1771 }
1772 }
1773 match verified.as_slice() {
1774 [issuer_idx] => *issuer_idx,
1775 [] => {
1776 if let Some(oid) = unsupported_oid {
1777 return Err(X509ChainBuildError::UnsupportedSignatureAlgorithm { oid });
1778 }
1779 return Err(X509ChainBuildError::IssuerSignatureMismatch);
1780 }
1781 _ => return Err(X509ChainBuildError::AmbiguousIssuer),
1782 }
1783 }
1784 };
1785 if chain.contains(&issuer_idx) {
1786 return Err(X509ChainBuildError::Cycle);
1787 }
1788 if chain.len() == crate::hard_limits::X509_CHAIN_DEPTH_CEILING {
1789 return Err(X509ChainBuildError::DepthExceeded);
1790 }
1791 chain.push(issuer_idx);
1792 }
1793
1794 Ok(chain)
1795}
1796
1797pub(crate) fn build_x509_certificate_paths_to_trusted_prefix(
1801 info: &X509DataInfo,
1802 signing_idx: usize,
1803 trusted_prefix_len: usize,
1804 max_depth: usize,
1805 max_candidate_paths: usize,
1806 provider: &dyn crate::provider::CryptoProvider,
1807 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
1808) -> Result<Vec<Vec<usize>>, X509ChainBuildError> {
1809 if trusted_prefix_len > info.certificates.len() {
1810 return Err(X509ChainBuildError::InconsistentMetadata);
1811 }
1812 build_x509_certificate_paths(
1813 info,
1814 signing_idx,
1815 |index| index < trusted_prefix_len,
1816 false,
1817 X509PathBounds {
1818 max_depth,
1819 max_candidate_paths,
1820 },
1821 provider,
1822 allowed,
1823 )
1824}
1825
1826pub(crate) fn build_x509_certificate_paths_to_selector_targets(
1830 info: &X509DataInfo,
1831 signing_idx: usize,
1832 targets: &[usize],
1833 max_depth: usize,
1834 max_candidate_paths: usize,
1835 provider: &dyn crate::provider::CryptoProvider,
1836 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
1837) -> Result<Vec<Vec<usize>>, X509ChainBuildError> {
1838 if targets
1839 .iter()
1840 .any(|index| *index >= info.certificates.len())
1841 {
1842 return Err(X509ChainBuildError::InconsistentMetadata);
1843 }
1844 build_x509_certificate_paths(
1845 info,
1846 signing_idx,
1847 |index| targets.contains(&index),
1848 true,
1849 X509PathBounds {
1850 max_depth,
1851 max_candidate_paths,
1852 },
1853 provider,
1854 allowed,
1855 )
1856}
1857
1858struct X509PathBounds {
1859 max_depth: usize,
1860 max_candidate_paths: usize,
1861}
1862
1863fn build_x509_certificate_paths(
1864 info: &X509DataInfo,
1865 signing_idx: usize,
1866 is_terminal: impl Fn(usize) -> bool,
1867 continue_after_terminal: bool,
1868 bounds: X509PathBounds,
1869 provider: &dyn crate::provider::CryptoProvider,
1870 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
1871) -> Result<Vec<Vec<usize>>, X509ChainBuildError> {
1872 let X509PathBounds {
1873 max_depth,
1874 max_candidate_paths,
1875 } = bounds;
1876 if signing_idx >= info.parsed_certificates.len()
1877 || info.parsed_certificates.len() != info.certificates.len()
1878 {
1879 return Err(X509ChainBuildError::InconsistentMetadata);
1880 }
1881 if max_candidate_paths == 0 {
1882 return Err(X509ChainBuildError::AmbiguousIssuer);
1883 }
1884
1885 let mut pending = vec![vec![signing_idx]];
1886 let mut completed = Vec::new();
1887 let mut generated_paths = 1usize;
1888 let mut depth_exceeded = false;
1889 let mut unsupported_oid = None;
1890 let mut policy_rejection = None;
1891 let mut issuer_cache = vec![None; info.parsed_certificates.len()];
1892 while let Some(path) = pending.pop() {
1893 let current_idx = *path
1894 .last()
1895 .expect("candidate path starts with signing certificate index");
1896 if is_terminal(current_idx) {
1897 completed.push(path.clone());
1898 if !continue_after_terminal {
1899 continue;
1900 }
1901 }
1902 if path.len() == max_depth {
1903 depth_exceeded = true;
1904 continue;
1905 }
1906
1907 let current = &info.parsed_certificates[current_idx];
1908 if issuer_cache[current_idx].is_none() {
1909 let mut verified = Vec::new();
1910 for (issuer_idx, issuer) in info.parsed_certificates.iter().enumerate() {
1911 if !distinguished_names_equal(&issuer.subject_dn, ¤t.issuer_dn) {
1912 continue;
1913 }
1914 match certificate_signature_matches_with_provider(
1915 &info.certificates[current_idx],
1916 &info.certificates[issuer_idx],
1917 provider,
1918 allowed,
1919 ) {
1920 Ok(true) => verified.push(issuer_idx),
1921 Ok(false) => {}
1922 Err(super::X509ChainError::Provider(
1923 crate::provider::ProviderError::Unsupported {
1924 operation: crate::provider::ProviderOperation::VerifyCertificate,
1925 algorithm: Some(oid),
1926 },
1927 )) => {
1928 unsupported_oid.get_or_insert(oid);
1931 continue;
1932 }
1933 Err(super::X509ChainError::Provider(error)) => {
1934 return Err(X509ChainBuildError::Provider(error));
1935 }
1936 Err(super::X509ChainError::UnsupportedSignatureAlgorithm { oid }) => {
1937 unsupported_oid.get_or_insert(oid);
1940 break;
1941 }
1942 Err(super::X509ChainError::Policy(error)) => {
1943 policy_rejection.get_or_insert(error);
1949 break;
1950 }
1951 Err(_) => return Err(X509ChainBuildError::IssuerSignatureMismatch),
1952 }
1953 }
1954 issuer_cache[current_idx] = Some(verified);
1955 }
1956 let issuers = issuer_cache[current_idx]
1957 .as_ref()
1958 .expect("issuer cache entry was initialized");
1959 let issuers = issuers
1960 .iter()
1961 .copied()
1962 .filter(|issuer_idx| !path.contains(issuer_idx))
1963 .collect::<Vec<_>>();
1964 if generated_paths.saturating_add(issuers.len()) > max_candidate_paths {
1965 return Err(X509ChainBuildError::AmbiguousIssuer);
1966 }
1967 generated_paths += issuers.len();
1968 for issuer_idx in issuers {
1969 let mut candidate = path.clone();
1970 candidate.push(issuer_idx);
1971 pending.push(candidate);
1972 }
1973 }
1974
1975 if completed.is_empty() {
1976 if let Some(error) = policy_rejection {
1977 return Err(X509ChainBuildError::Policy(error));
1978 }
1979 if let Some(oid) = unsupported_oid {
1980 return Err(X509ChainBuildError::UnsupportedSignatureAlgorithm { oid });
1981 }
1982 if depth_exceeded {
1983 return Err(X509ChainBuildError::DepthExceeded);
1984 }
1985 }
1986 Ok(completed)
1987}
1988
1989fn select_x509_signing_certificate(
1990 info: &X509DataInfo,
1991 provider: &dyn crate::provider::CryptoProvider,
1992) -> Result<usize, ParseError> {
1993 let has_lookup_identifiers = x509_data_has_lookup_identifiers(info);
1994 let mut candidates = Vec::new();
1995 if has_lookup_identifiers {
1996 for (idx, (parsed, der)) in info
1997 .parsed_certificates
1998 .iter()
1999 .zip(&info.certificates)
2000 .enumerate()
2001 {
2002 if x509_certificate_matches_any_selector(info, parsed, der, provider)? {
2003 candidates.push(idx);
2004 }
2005 }
2006 if !x509_selector_categories_match_chain(info, provider)? {
2007 return Err(ParseError::InvalidStructure(
2008 "X509Data lookup identifiers do not match the embedded certificate chain".into(),
2009 ));
2010 }
2011 }
2012
2013 match candidates.as_slice() {
2014 [idx] => return Ok(*idx),
2015 [] if has_lookup_identifiers => {
2016 return Err(ParseError::InvalidStructure(
2017 "X509Data lookup identifiers do not match any embedded certificate".into(),
2018 ));
2019 }
2020 [] => {}
2021 _ => {}
2022 }
2023
2024 let leaf_candidates = info
2025 .parsed_certificates
2026 .iter()
2027 .enumerate()
2028 .filter(|(_, cert)| {
2029 !distinguished_names_equal(&cert.subject_dn, &cert.issuer_dn)
2030 && !info
2031 .parsed_certificates
2032 .iter()
2033 .any(|other| distinguished_names_equal(&other.issuer_dn, &cert.subject_dn))
2034 })
2035 .map(|(idx, _)| idx)
2036 .collect::<Vec<_>>();
2037
2038 let selected_leaves = leaf_candidates
2039 .iter()
2040 .filter(|idx| !has_lookup_identifiers || candidates.contains(idx))
2041 .copied()
2042 .collect::<Vec<_>>();
2043
2044 match selected_leaves.as_slice() {
2045 [idx] => Ok(*idx),
2046 [] if !has_lookup_identifiers => Ok(0),
2047 [] => Err(ParseError::InvalidStructure(
2048 "X509Data lookup identifiers match multiple certificates without a unique signing certificate"
2049 .into(),
2050 )),
2051 _ => Err(ParseError::InvalidStructure(
2052 if has_lookup_identifiers {
2053 "X509Data lookup identifiers match multiple certificates"
2054 } else {
2055 "X509Data contains multiple possible signing certificates"
2056 }
2057 .into(),
2058 )),
2059 }
2060}
2061
2062pub(crate) fn x509_data_has_lookup_identifiers(info: &X509DataInfo) -> bool {
2063 !info.subject_names.is_empty()
2064 || !info.issuer_serials.is_empty()
2065 || !info.skis.is_empty()
2066 || !info.digests.is_empty()
2067}
2068
2069pub(crate) fn x509_certificate_matches_any_selector(
2070 info: &X509DataInfo,
2071 certificate: &ParsedX509Certificate,
2072 certificate_der: &[u8],
2073 provider: &dyn crate::provider::CryptoProvider,
2074) -> Result<bool, ParseError> {
2075 let subject_match = info
2076 .subject_names
2077 .iter()
2078 .any(|subject| distinguished_names_equal(subject, &certificate.subject_dn));
2079 let mut issuer_serial_match = false;
2080 for (issuer, serial) in &info.issuer_serials {
2081 let serial_hex = x509_serial_decimal_to_hex(serial).ok_or_else(|| {
2082 ParseError::InvalidStructure(
2083 "X509Data lookup identifiers contain an invalid serial number".into(),
2084 )
2085 })?;
2086 issuer_serial_match |= distinguished_names_equal(issuer, &certificate.issuer_dn)
2087 && serial_hex == certificate.serial_number_hex;
2088 }
2089 let ski_match = certificate
2090 .subject_key_identifier
2091 .as_ref()
2092 .is_some_and(|certificate_ski| info.skis.iter().any(|ski| ski == certificate_ski));
2093 let mut digest_match = false;
2094 for (algorithm_uri, expected) in &info.digests {
2095 let algorithm = DigestAlgorithm::from_uri(algorithm_uri).ok_or_else(|| {
2096 ParseError::UnsupportedAlgorithm {
2097 uri: algorithm_uri.clone(),
2098 }
2099 })?;
2100 digest_match |= constant_time_eq(
2101 &compute_digest_with_provider(provider, algorithm, certificate_der)?,
2102 expected,
2103 );
2104 }
2105 Ok(subject_match || issuer_serial_match || ski_match || digest_match)
2106}
2107
2108pub fn x509_certificate_matches_selectors(
2114 info: &X509DataInfo,
2115 certificate_der: &[u8],
2116 provider: &dyn crate::provider::CryptoProvider,
2117) -> Result<bool, ParseError> {
2118 let mut candidate = info.clone();
2119 candidate.certificates = vec![certificate_der.to_vec()];
2120 candidate.parsed_certificates = vec![parse_x509_certificate(certificate_der)?];
2121 candidate.certificate_chain = vec![0];
2122 x509_selector_categories_match_chain(&candidate, provider)
2123}
2124
2125pub(crate) fn x509_selector_categories_match_chain(
2126 info: &X509DataInfo,
2127 provider: &dyn crate::provider::CryptoProvider,
2128) -> Result<bool, ParseError> {
2129 let subject_match = info.subject_names.iter().all(|subject| {
2130 info.parsed_certificates
2131 .iter()
2132 .any(|certificate| distinguished_names_equal(subject, &certificate.subject_dn))
2133 });
2134
2135 let mut issuer_serial_match = true;
2136 for (issuer, serial) in &info.issuer_serials {
2137 let serial_hex = x509_serial_decimal_to_hex(serial).ok_or_else(|| {
2138 ParseError::InvalidStructure(
2139 "X509Data lookup identifiers contain an invalid serial number".into(),
2140 )
2141 })?;
2142 issuer_serial_match &= info.parsed_certificates.iter().any(|certificate| {
2143 distinguished_names_equal(issuer, &certificate.issuer_dn)
2144 && serial_hex == certificate.serial_number_hex
2145 });
2146 }
2147
2148 let ski_match = info.skis.iter().all(|ski| {
2149 info.parsed_certificates.iter().any(|certificate| {
2150 certificate
2151 .subject_key_identifier
2152 .as_ref()
2153 .is_some_and(|certificate_ski| ski == certificate_ski)
2154 })
2155 });
2156
2157 let mut digest_match = true;
2158 for (algorithm_uri, expected) in &info.digests {
2159 let algorithm = DigestAlgorithm::from_uri(algorithm_uri).ok_or_else(|| {
2160 ParseError::UnsupportedAlgorithm {
2161 uri: algorithm_uri.clone(),
2162 }
2163 })?;
2164 let mut category_match = false;
2165 for certificate in &info.certificates {
2166 category_match |= constant_time_eq(
2167 &compute_digest_with_provider(provider, algorithm, certificate)?,
2168 expected,
2169 );
2170 }
2171 digest_match &= category_match;
2172 }
2173
2174 Ok(subject_match && issuer_serial_match && ski_match && digest_match)
2175}
2176
2177fn x509_attribute_values_equal(
2178 left: &x509_cert::attr::AttributeTypeAndValue,
2179 right: &x509_cert::attr::AttributeTypeAndValue,
2180) -> bool {
2181 if left.oid != right.oid {
2182 return false;
2183 }
2184 const EMAIL_ADDRESS: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.9.1");
2185 const DOMAIN_COMPONENT: ObjectIdentifier =
2186 ObjectIdentifier::new_unwrap("0.9.2342.19200300.100.1.25");
2187 if left.oid == EMAIL_ADDRESS {
2188 let (Ok(left), Ok(right)) = (
2189 Ia5StringRef::try_from(&left.value),
2190 Ia5StringRef::try_from(&right.value),
2191 ) else {
2192 return false;
2193 };
2194 let (Some((left_local, left_domain)), Some((right_local, right_domain))) = (
2195 left.as_str().rsplit_once('@'),
2196 right.as_str().rsplit_once('@'),
2197 ) else {
2198 return false;
2199 };
2200 return left_local == right_local && left_domain.eq_ignore_ascii_case(right_domain);
2201 }
2202 if left.oid == DOMAIN_COMPONENT {
2203 let (Ok(left), Ok(right)) = (
2204 Ia5StringRef::try_from(&left.value),
2205 Ia5StringRef::try_from(&right.value),
2206 ) else {
2207 return false;
2208 };
2209 return left.as_str().eq_ignore_ascii_case(right.as_str());
2210 }
2211 match (
2212 DirectoryString::try_from(&left.value),
2213 DirectoryString::try_from(&right.value),
2214 ) {
2215 (Ok(left), Ok(right)) => {
2216 let Ok(left) =
2219 x520_stringprep::x520_stringprep_to_case_ignore_string(left.value().as_ref())
2220 else {
2221 return false;
2222 };
2223 let Ok(right) =
2224 x520_stringprep::x520_stringprep_to_case_ignore_string(right.value().as_ref())
2225 else {
2226 return false;
2227 };
2228 left.trim_matches(' ') == right.trim_matches(' ')
2229 }
2230 _ => left.value == right.value,
2231 }
2232}
2233
2234fn x509_rdns_equal(
2235 left: &x509_cert::name::RelativeDistinguishedName,
2236 right: &x509_cert::name::RelativeDistinguishedName,
2237) -> bool {
2238 if left.len() != right.len() {
2239 return false;
2240 }
2241 let right = right.iter().collect::<Vec<_>>();
2243 let mut matched = vec![false; right.len()];
2244 left.iter().all(|left_attribute| {
2245 right
2246 .iter()
2247 .enumerate()
2248 .find(|(index, right_attribute)| {
2249 !matched[*index] && x509_attribute_values_equal(left_attribute, right_attribute)
2250 })
2251 .is_some_and(|(index, _)| {
2252 matched[index] = true;
2253 true
2254 })
2255 })
2256}
2257
2258fn trailing_whitespace_is_escaped(value: &str) -> bool {
2259 let Some((&last, prefix)) = value.as_bytes().split_last() else {
2260 return false;
2261 };
2262 if !matches!(last, b' ' | b'\t' | b'\r' | b'\n') {
2263 return false;
2264 }
2265 prefix
2266 .iter()
2267 .rev()
2268 .take_while(|byte| **byte == b'\\')
2269 .count()
2270 % 2
2271 == 1
2272}
2273
2274fn parse_distinguished_name(value: &str) -> Option<Name> {
2275 let mut normalized = String::with_capacity(value.len());
2276 let mut chars = value
2277 .trim_start_matches([' ', '\t', '\r', '\n'])
2278 .chars()
2279 .peekable();
2280 let mut escaped = false;
2281
2282 while let Some(ch) = chars.next() {
2283 if escaped {
2284 normalized.push(ch);
2285 escaped = false;
2286 continue;
2287 }
2288 if ch == '\\' {
2289 normalized.push(ch);
2290 escaped = true;
2291 continue;
2292 }
2293 if matches!(ch, ',' | '+') {
2294 while normalized
2295 .chars()
2296 .next_back()
2297 .is_some_and(|last| matches!(last, ' ' | '\t' | '\r' | '\n'))
2298 && !trailing_whitespace_is_escaped(&normalized)
2299 {
2300 normalized.pop();
2301 }
2302 normalized.push(ch);
2303 while chars
2304 .next_if(|next| matches!(next, ' ' | '\t' | '\r' | '\n'))
2305 .is_some()
2306 {}
2307 continue;
2308 }
2309 normalized.push(ch);
2310 }
2311
2312 while normalized
2313 .chars()
2314 .next_back()
2315 .is_some_and(|last| matches!(last, ' ' | '\t' | '\r' | '\n'))
2316 && !trailing_whitespace_is_escaped(&normalized)
2317 {
2318 normalized.pop();
2319 }
2320 normalized.parse().ok()
2321}
2322
2323pub(crate) fn distinguished_names_equal(left: &str, right: &str) -> bool {
2324 parse_distinguished_name(left)
2325 .zip(parse_distinguished_name(right))
2326 .is_some_and(|(left, right)| {
2327 left.len() == right.len()
2328 && left
2329 .iter_rdn()
2330 .zip(right.iter_rdn())
2331 .all(|(left, right)| x509_rdns_equal(left, right))
2332 })
2333}
2334
2335pub(crate) fn distinguished_name_within_subtree(name: &str, subtree: &str) -> bool {
2336 parse_distinguished_name(name)
2337 .zip(parse_distinguished_name(subtree))
2338 .is_some_and(|(name, subtree)| {
2339 subtree.len() <= name.len()
2340 && name
2341 .iter_rdn()
2342 .zip(subtree.iter_rdn())
2343 .all(|(name, subtree)| x509_rdns_equal(name, subtree))
2344 })
2345}
2346
2347fn ensure_x509_data_entry_budget(info: &X509DataInfo) -> Result<(), ParseError> {
2348 let total_entries = info.certificates.len()
2349 + info.subject_names.len()
2350 + info.issuer_serials.len()
2351 + info.skis.len()
2352 + info.crls.len()
2353 + info.digests.len();
2354 if total_entries >= MAX_X509_DATA_ENTRY_COUNT {
2355 return Err(ParseError::InvalidStructure(
2356 "X509Data contains too many entries".into(),
2357 ));
2358 }
2359 Ok(())
2360}
2361
2362fn add_x509_data_usage(total_binary_len: &mut usize, delta: usize) -> Result<(), ParseError> {
2363 *total_binary_len = total_binary_len.checked_add(delta).ok_or_else(|| {
2364 ParseError::InvalidStructure("X509Data exceeds maximum allowed total binary length".into())
2365 })?;
2366 if *total_binary_len > MAX_X509_DATA_TOTAL_BINARY_LEN {
2367 return Err(ParseError::InvalidStructure(
2368 "X509Data exceeds maximum allowed total binary length".into(),
2369 ));
2370 }
2371 Ok(())
2372}
2373
2374fn decode_x509_base64(
2375 node: Node<'_, '_>,
2376 element_name: &'static str,
2377 total_binary_len: &mut usize,
2378 resources: &crate::policy::ResourcePolicy,
2379) -> Result<Vec<u8>, ParseError> {
2380 let payload = super::whitespace::XmlBase64Payload::bounded(
2381 node,
2382 MAX_X509_BASE64_TEXT_LEN,
2383 MAX_X509_BASE64_NORMALIZED_LEN,
2384 )
2385 .map_err(|reason| xml_base64_payload_error(element_name, reason))?;
2386 if payload.text_len > MAX_X509_BASE64_TEXT_LEN {
2387 return Err(ParseError::InvalidStructure(format!(
2388 "{element_name} exceeds maximum allowed text length"
2389 )));
2390 }
2391 if payload.normalized_len > MAX_X509_BASE64_NORMALIZED_LEN {
2392 return Err(ParseError::InvalidStructure(format!(
2393 "{element_name} exceeds maximum allowed base64 length"
2394 )));
2395 }
2396 if payload.decoded_len == 0 {
2397 return Err(ParseError::InvalidStructure(format!(
2398 "{element_name} must not be empty"
2399 )));
2400 }
2401 if payload.decoded_len > MAX_X509_DECODED_BINARY_LEN {
2402 return Err(ParseError::InvalidStructure(format!(
2403 "{element_name} exceeds maximum allowed binary length"
2404 )));
2405 }
2406 if payload.decoded_len > resources.max_external_resource_bytes {
2407 return Err(crate::policy::PolicyViolation::ResourceLimitExceeded {
2408 resource: crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES,
2409 maximum: resources.max_external_resource_bytes,
2410 }
2411 .into());
2412 }
2413 if *total_binary_len > resources.max_external_resource_total_bytes
2414 || payload.decoded_len > resources.max_external_resource_total_bytes - *total_binary_len
2415 {
2416 return Err(crate::policy::PolicyViolation::ResourceLimitExceeded {
2417 resource: crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
2418 maximum: resources.max_external_resource_total_bytes,
2419 }
2420 .into());
2421 }
2422 add_x509_data_usage(total_binary_len, payload.decoded_len)?;
2425 payload
2426 .decode()
2427 .map_err(|reason| ParseError::Base64(format!("{element_name}: {reason}")))
2428}
2429
2430fn xml_base64_payload_error(element: &str, reason: &'static str) -> ParseError {
2431 match reason {
2432 "maximum allowed text length" | "maximum allowed base64 length" => {
2433 ParseError::InvalidStructure(format!("{element} exceeds {reason}"))
2434 }
2435 _ => ParseError::Base64(format!("{element}: {reason}")),
2436 }
2437}
2438
2439pub(crate) fn parse_x509_certificate(cert_der: &[u8]) -> Result<ParsedX509Certificate, ParseError> {
2440 let (rest, cert) =
2441 x509_parser::certificate::X509Certificate::from_der(cert_der).map_err(|err| {
2442 ParseError::InvalidStructure(format!("X509Certificate is not valid DER X.509: {err}"))
2443 })?;
2444 if !rest.is_empty() {
2445 return Err(ParseError::InvalidStructure(
2446 "X509Certificate contains trailing bytes after DER certificate".into(),
2447 ));
2448 }
2449
2450 let subject_dn = x509_name_to_rfc4514(cert.subject())?;
2455 let issuer_dn = x509_name_to_rfc4514(cert.issuer())?;
2456 let serial_number = cert.tbs_certificate.raw_serial().to_vec();
2457 let serial_number_hex = format_x509_serial_value_hex(&serial_number);
2458
2459 let subject_key_identifier = cert.extensions().iter().find_map(|ext| {
2460 if let ParsedExtension::SubjectKeyIdentifier(ski) = ext.parsed_extension() {
2461 Some(ski.0.to_vec())
2462 } else {
2463 None
2464 }
2465 });
2466
2467 let spki = cert.public_key();
2468 let public_key = match spki.parsed().map_err(|err| {
2469 ParseError::InvalidStructure(format!("X509Certificate public key parse error: {err}"))
2470 })? {
2471 PublicKey::RSA(rsa) => {
2472 let modulus = trim_leading_zeroes(rsa.modulus);
2473 let exponent = trim_leading_zeroes(rsa.exponent);
2474 if modulus.is_empty() || exponent.is_empty() {
2475 return Err(ParseError::InvalidStructure(
2476 "X509Certificate RSA key contains empty modulus or exponent".into(),
2477 ));
2478 }
2479 X509PublicKeyInfo::Rsa { modulus, exponent }
2480 }
2481 PublicKey::EC(ec_point) => {
2482 let Some(params) = spki.algorithm.parameters.as_ref() else {
2483 return Err(ParseError::InvalidStructure(
2484 "X509Certificate EC key is missing curve parameters".into(),
2485 ));
2486 };
2487
2488 match params.as_oid() {
2489 Ok(oid) => X509PublicKeyInfo::Ec {
2490 curve_oid: oid.to_id_string(),
2491 public_key: ec_point.data().to_vec(),
2492 },
2493 Err(_) => X509PublicKeyInfo::Unsupported {
2494 algorithm_oid: spki.algorithm.algorithm.to_id_string(),
2495 },
2496 }
2497 }
2498 _ => X509PublicKeyInfo::Unsupported {
2499 algorithm_oid: spki.algorithm.algorithm.to_id_string(),
2500 },
2501 };
2502
2503 Ok(ParsedX509Certificate {
2504 subject_dn,
2505 issuer_dn,
2506 serial_number,
2507 serial_number_hex,
2508 subject_key_identifier,
2509 public_key,
2510 })
2511}
2512
2513pub(crate) fn x509_name_to_rfc4514(name: &X509Name<'_>) -> Result<String, ParseError> {
2514 let name = Name::from_der(name.as_raw()).map_err(|error| {
2515 ParseError::InvalidStructure(format!(
2516 "X509Certificate distinguished name is invalid DER: {error}"
2517 ))
2518 })?;
2519 Ok(name.to_string())
2520}
2521
2522fn format_x509_serial_hex(serial: &[u8]) -> String {
2523 serial
2524 .iter()
2525 .map(|byte| format!("{byte:02X}"))
2526 .collect::<String>()
2527}
2528
2529fn format_x509_serial_value_hex(serial: &[u8]) -> String {
2530 let first_non_zero = serial
2531 .iter()
2532 .position(|byte| *byte != 0)
2533 .unwrap_or(serial.len());
2534 let canonical = if first_non_zero == serial.len() {
2535 &[0]
2536 } else {
2537 &serial[first_non_zero..]
2538 };
2539 format_x509_serial_hex(canonical)
2540}
2541
2542fn x509_serial_decimal_to_hex(serial: &str) -> Option<String> {
2543 let serial = serial.trim();
2544 let serial = serial.strip_prefix('+').unwrap_or(serial);
2545 let serial = serial.trim_start_matches('0');
2546 let serial = if serial.is_empty() { "0" } else { serial };
2547 if serial.len() > MAX_X509_SERIAL_NUMBER_VALUE_DIGITS
2548 || !serial.bytes().all(|byte| byte.is_ascii_digit())
2549 {
2550 return None;
2551 }
2552
2553 let mut bytes = [0_u8; MAX_X509_SERIAL_NUMBER_BYTES];
2554 for digit in serial.bytes().map(|byte| byte - b'0') {
2555 let mut carry = u16::from(digit);
2556 for byte in bytes.iter_mut().rev() {
2557 let value = u16::from(*byte) * 10 + carry;
2558 *byte = value as u8;
2559 carry = value >> 8;
2560 }
2561 if carry != 0 {
2562 return None;
2563 }
2564 }
2565
2566 if bytes.iter().all(|byte| *byte == 0) {
2567 return None;
2568 }
2569
2570 Some(format_x509_serial_value_hex(&bytes))
2571}
2572
2573fn trim_leading_zeroes(bytes: &[u8]) -> Vec<u8> {
2574 let first_non_zero = bytes
2575 .iter()
2576 .position(|byte| *byte != 0)
2577 .unwrap_or(bytes.len());
2578 bytes[first_non_zero..].to_vec()
2579}
2580
2581fn parse_x509_issuer_serial(node: Node<'_, '_>) -> Result<(String, String), ParseError> {
2582 verify_ds_element(node, "X509IssuerSerial")?;
2583 ensure_no_non_whitespace_text(node, "X509IssuerSerial")?;
2584
2585 let children = element_children(node).collect::<Vec<_>>();
2586 if children.len() != 2 {
2587 return Err(ParseError::InvalidStructure(
2588 "X509IssuerSerial must contain exactly X509IssuerName then X509SerialNumber".into(),
2589 ));
2590 }
2591 if !matches!(
2592 (
2593 children[0].tag_name().namespace(),
2594 children[0].tag_name().name()
2595 ),
2596 (Some(XMLDSIG_NS), "X509IssuerName")
2597 ) {
2598 return Err(ParseError::InvalidStructure(
2599 "X509IssuerSerial must contain X509IssuerName as the first child element".into(),
2600 ));
2601 }
2602 if !matches!(
2603 (
2604 children[1].tag_name().namespace(),
2605 children[1].tag_name().name()
2606 ),
2607 (Some(XMLDSIG_NS), "X509SerialNumber")
2608 ) {
2609 return Err(ParseError::InvalidStructure(
2610 "X509IssuerSerial must contain X509SerialNumber as the second child element".into(),
2611 ));
2612 }
2613
2614 let issuer_node = children[0];
2615 ensure_no_element_children(issuer_node, "X509IssuerName")?;
2616 let issuer_name =
2617 collect_text_content_bounded(issuer_node, MAX_X509_ISSUER_NAME_TEXT_LEN, "X509IssuerName")?;
2618
2619 let serial_node = children[1];
2620 ensure_no_element_children(serial_node, "X509SerialNumber")?;
2621 let serial_number = collect_x509_serial_number(serial_node)?;
2622 if issuer_name.trim().is_empty() {
2623 return Err(ParseError::InvalidStructure(
2624 "X509IssuerSerial requires non-empty X509IssuerName and X509SerialNumber".into(),
2625 ));
2626 }
2627
2628 Ok((issuer_name, serial_number))
2629}
2630
2631fn base64_decode_digest(b64: &str, digest_method: DigestAlgorithm) -> Result<Vec<u8>, ParseError> {
2635 use base64::Engine;
2636 use base64::engine::general_purpose::STANDARD;
2637
2638 let expected = digest_method.output_len();
2639 let max_base64_len = expected.div_ceil(3) * 4;
2640 let mut cleaned = String::with_capacity(b64.len().min(max_base64_len));
2641 normalize_xml_base64_text(b64, &mut cleaned).map_err(|err| {
2642 ParseError::Base64(format!(
2643 "invalid XML whitespace U+{:04X} in DigestValue",
2644 err.invalid_byte
2645 ))
2646 })?;
2647 if cleaned.len() > max_base64_len {
2648 return Err(ParseError::Base64(
2649 "DigestValue exceeds maximum allowed base64 length".into(),
2650 ));
2651 }
2652 let digest = STANDARD
2653 .decode(&cleaned)
2654 .map_err(|e| ParseError::Base64(e.to_string()))?;
2655 let actual = digest.len();
2656 if actual != expected {
2657 return Err(ParseError::DigestLengthMismatch {
2658 algorithm: digest_method.uri(),
2659 expected,
2660 actual,
2661 });
2662 }
2663 Ok(digest)
2664}
2665
2666fn decode_digest_value_children(
2667 digest_value_node: Node<'_, '_>,
2668 digest_method: DigestAlgorithm,
2669) -> Result<Vec<u8>, ParseError> {
2670 let max_base64_len = digest_method.output_len().div_ceil(3) * 4;
2671 let mut cleaned = String::with_capacity(max_base64_len);
2672
2673 for child in digest_value_node.children() {
2674 if child.is_element() {
2675 return Err(ParseError::InvalidStructure(
2676 "DigestValue must not contain element children".into(),
2677 ));
2678 }
2679 if let Some(text) = child.text() {
2680 normalize_xml_base64_text(text, &mut cleaned).map_err(|err| {
2681 ParseError::Base64(format!(
2682 "invalid XML whitespace U+{:04X} in DigestValue",
2683 err.invalid_byte
2684 ))
2685 })?;
2686 if cleaned.len() > max_base64_len {
2687 return Err(ParseError::Base64(
2688 "DigestValue exceeds maximum allowed base64 length".into(),
2689 ));
2690 }
2691 }
2692 }
2693
2694 base64_decode_digest(&cleaned, digest_method)
2695}
2696
2697fn decode_der_encoded_key_value_base64(node: Node<'_, '_>) -> Result<Vec<u8>, ParseError> {
2698 use base64::Engine;
2699 use base64::engine::general_purpose::STANDARD;
2700
2701 let mut cleaned = String::new();
2702 let mut raw_text_len = 0usize;
2703 for text in node
2704 .children()
2705 .filter(|child| child.is_text())
2706 .filter_map(|child| child.text())
2707 {
2708 if raw_text_len.saturating_add(text.len()) > MAX_DER_ENCODED_KEY_VALUE_TEXT_LEN {
2709 return Err(ParseError::InvalidStructure(
2710 "DEREncodedKeyValue exceeds maximum allowed text length".into(),
2711 ));
2712 }
2713 raw_text_len = raw_text_len.saturating_add(text.len());
2714 normalize_xml_base64_text(text, &mut cleaned).map_err(|err| {
2715 ParseError::Base64(format!(
2716 "invalid XML whitespace U+{:04X} in base64 text",
2717 err.invalid_byte
2718 ))
2719 })?;
2720 if cleaned.len() > MAX_DER_ENCODED_KEY_VALUE_BASE64_LEN {
2721 return Err(ParseError::InvalidStructure(
2722 "DEREncodedKeyValue exceeds maximum allowed length".into(),
2723 ));
2724 }
2725 }
2726
2727 let der = STANDARD
2728 .decode(&cleaned)
2729 .map_err(|e| ParseError::Base64(e.to_string()))?;
2730 if der.is_empty() {
2731 return Err(ParseError::InvalidStructure(
2732 "DEREncodedKeyValue must not be empty".into(),
2733 ));
2734 }
2735 if der.len() > MAX_DER_ENCODED_KEY_VALUE_LEN {
2736 return Err(ParseError::InvalidStructure(
2737 "DEREncodedKeyValue exceeds maximum allowed length".into(),
2738 ));
2739 }
2740 Ok(der)
2741}
2742
2743fn collect_text_content_bounded(
2744 node: Node<'_, '_>,
2745 max_len: usize,
2746 element_name: &'static str,
2747) -> Result<String, ParseError> {
2748 let mut text = String::new();
2749 for chunk in node
2750 .children()
2751 .filter_map(|child| child.is_text().then(|| child.text()).flatten())
2752 {
2753 if text.len().saturating_add(chunk.len()) > max_len {
2754 return Err(ParseError::InvalidStructure(format!(
2755 "{element_name} exceeds maximum allowed text length"
2756 )));
2757 }
2758 text.push_str(chunk);
2759 }
2760 Ok(text)
2761}
2762
2763fn collect_x509_serial_number(node: Node<'_, '_>) -> Result<String, ParseError> {
2764 let mut serial = String::with_capacity(MAX_X509_SERIAL_NUMBER_VALUE_DIGITS);
2765 let mut raw_text_len = 0usize;
2766 let mut trailing_whitespace = false;
2767 let mut explicit_positive = false;
2768 let mut saw_digit = false;
2769
2770 for chunk in node
2771 .children()
2772 .filter_map(|child| child.is_text().then(|| child.text()).flatten())
2773 {
2774 raw_text_len = raw_text_len.saturating_add(chunk.len());
2775 if raw_text_len > MAX_X509_SERIAL_NUMBER_RAW_TEXT_LEN {
2776 return Err(ParseError::InvalidStructure(
2777 "X509SerialNumber exceeds maximum allowed text length".into(),
2778 ));
2779 }
2780 for byte in chunk.bytes() {
2781 if matches!(byte, b' ' | b'\t' | b'\r' | b'\n') {
2782 trailing_whitespace |= explicit_positive || saw_digit;
2783 continue;
2784 }
2785 if byte == b'+' && !saw_digit && !explicit_positive && !trailing_whitespace {
2786 explicit_positive = true;
2787 continue;
2788 }
2789 if trailing_whitespace || !byte.is_ascii_digit() {
2790 return Err(ParseError::InvalidStructure(
2791 "invalid X509SerialNumber decimal value".into(),
2792 ));
2793 }
2794 saw_digit = true;
2795 if byte == b'0' && serial.is_empty() {
2796 continue;
2797 }
2798 if serial.len() == MAX_X509_SERIAL_NUMBER_VALUE_DIGITS {
2799 return Err(ParseError::InvalidStructure(
2800 "X509SerialNumber exceeds maximum allowed decimal value".into(),
2801 ));
2802 }
2803 serial.push(char::from(byte));
2804 }
2805 }
2806
2807 if !saw_digit {
2808 return Err(ParseError::InvalidStructure(
2809 "X509IssuerSerial requires non-empty X509IssuerName and X509SerialNumber".into(),
2810 ));
2811 }
2812 if serial.is_empty() {
2813 serial.push('0');
2814 }
2815 if x509_serial_decimal_to_hex(&serial).is_none() {
2816 return Err(ParseError::InvalidStructure(
2817 "invalid X509SerialNumber decimal value or RFC 5280 range".into(),
2818 ));
2819 }
2820
2821 Ok(serial)
2822}
2823
2824fn ensure_no_element_children(node: Node<'_, '_>, element_name: &str) -> Result<(), ParseError> {
2825 if node.children().any(|child| child.is_element()) {
2826 return Err(ParseError::InvalidStructure(format!(
2827 "{element_name} must not contain child elements"
2828 )));
2829 }
2830 Ok(())
2831}
2832
2833fn ensure_no_non_whitespace_text(node: Node<'_, '_>, element_name: &str) -> Result<(), ParseError> {
2834 for child in node.children().filter(|child| child.is_text()) {
2835 if let Some(text) = child.text()
2836 && !is_xml_whitespace_only(text)
2837 {
2838 return Err(ParseError::InvalidStructure(format!(
2839 "{element_name} must not contain non-whitespace mixed content"
2840 )));
2841 }
2842 }
2843 Ok(())
2844}
2845
2846#[cfg(test)]
2847#[expect(clippy::unwrap_used, reason = "tests use trusted XML fixtures")]
2848mod tests {
2849 use super::*;
2850 use crate::xmldsig::TransformError;
2851 use base64::Engine;
2852
2853 fn fixture_rsa_cert_base64() -> String {
2854 fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem")
2855 }
2856
2857 fn fixture_cert_base64(path: &str) -> String {
2858 match path {
2859 "../../tests/fixtures/keys/rsa/rsa-2048-cert.pem" => {
2860 include_str!("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem")
2861 }
2862 "../../tests/fixtures/keys/rsa/rsa-4096-cert.pem" => {
2863 include_str!("../../tests/fixtures/keys/rsa/rsa-4096-cert.pem")
2864 }
2865 "../../tests/fixtures/keys/ca2cert.pem" => {
2866 include_str!("../../tests/fixtures/keys/ca2cert.pem")
2867 }
2868 "../../tests/fixtures/keys/cacert.pem" => {
2869 include_str!("../../tests/fixtures/keys/cacert.pem")
2870 }
2871 _ => unreachable!("unknown certificate fixture"),
2872 }
2873 .lines()
2874 .skip_while(|line| *line != "-----BEGIN CERTIFICATE-----")
2875 .skip(1)
2876 .take_while(|line| *line != "-----END CERTIFICATE-----")
2877 .collect::<String>()
2878 }
2879
2880 #[test]
2883 fn signature_algorithm_from_uri_rsa_sha256() {
2884 assert_eq!(
2885 SignatureAlgorithm::from_uri("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"),
2886 Some(SignatureAlgorithm::RsaSha256)
2887 );
2888 }
2889
2890 #[test]
2891 fn signature_algorithm_from_uri_rsa_sha1() {
2892 assert_eq!(
2893 SignatureAlgorithm::from_uri("http://www.w3.org/2000/09/xmldsig#rsa-sha1"),
2894 Some(SignatureAlgorithm::RsaSha1)
2895 );
2896 }
2897
2898 #[test]
2899 fn signature_algorithm_from_uri_ecdsa_sha256() {
2900 assert_eq!(
2901 SignatureAlgorithm::from_uri("http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"),
2902 Some(SignatureAlgorithm::EcdsaSha256)
2903 );
2904 }
2905
2906 #[test]
2907 fn signature_algorithm_from_uri_unknown() {
2908 assert_eq!(
2909 SignatureAlgorithm::from_uri("http://example.com/unknown"),
2910 None
2911 );
2912 }
2913
2914 #[test]
2915 fn signature_algorithm_uri_round_trip() {
2916 for algo in SignatureAlgorithm::ALL {
2917 assert_eq!(
2918 SignatureAlgorithm::from_uri(algo.uri()),
2919 Some(algo),
2920 "round-trip failed for {algo:?}"
2921 );
2922 }
2923 }
2924
2925 #[test]
2926 fn legacy_algorithms_are_verify_only() {
2927 assert!(!SignatureAlgorithm::DsaSha1.signing_allowed());
2928 assert!(!SignatureAlgorithm::HmacSha1.signing_allowed());
2929 assert!(!SignatureAlgorithm::RsaSha1.signing_allowed());
2930 assert!(SignatureAlgorithm::RsaSha256.signing_allowed());
2931 assert!(SignatureAlgorithm::EcdsaSha256.signing_allowed());
2932 }
2933
2934 #[test]
2937 fn find_signature_in_saml() {
2938 let xml = r#"<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
2939 <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
2940 <ds:SignedInfo/>
2941 </ds:Signature>
2942 </samlp:Response>"#;
2943 let doc = Document::parse(xml).unwrap();
2944 let sig = find_signature_node(&doc);
2945 assert!(sig.is_some());
2946 assert_eq!(sig.unwrap().tag_name().name(), "Signature");
2947 }
2948
2949 #[test]
2950 fn find_signature_missing() {
2951 let xml = "<root><child/></root>";
2952 let doc = Document::parse(xml).unwrap();
2953 assert!(find_signature_node(&doc).is_none());
2954 }
2955
2956 #[test]
2957 fn find_signature_ignores_wrong_namespace() {
2958 let xml = r#"<root><Signature xmlns="http://example.com/fake"/></root>"#;
2959 let doc = Document::parse(xml).unwrap();
2960 assert!(find_signature_node(&doc).is_none());
2961 }
2962
2963 #[test]
2966 fn key_info_session_applies_x509_byte_limits_before_decode() {
2967 let document = Document::parse("<KeyInfo xmlns='http://www.w3.org/2000/09/xmldsig#'><X509Data><X509SKI>AQID</X509SKI></X509Data></KeyInfo>").unwrap();
2969 for individual in [true, false] {
2970 let resources = crate::policy::ResourcePolicy {
2971 max_external_resource_bytes: if individual { 2 } else { 3 },
2972 max_external_resource_total_bytes: if individual { 100 } else { 5 },
2973 ..Default::default()
2974 };
2975 let mut session = KeyInfoParsingSession::new(&resources).unwrap();
2976 if !individual {
2977 session.parse(document.root_element()).unwrap();
2978 }
2979 assert!(matches!(
2980 session.parse(document.root_element()),
2981 Err(ParseError::Policy(_))
2982 ));
2983 }
2984 }
2985
2986 #[test]
2987 fn all_x509_binary_fields_preflight_active_byte_policy() {
2988 for element in ["X509Certificate", "X509SKI", "X509CRL", "X509Digest"] {
2991 let namespace = if element == "X509Digest" {
2992 XMLDSIG11_NS
2993 } else {
2994 XMLDSIG_NS
2995 };
2996 let xml = format!(
2997 "<KeyInfo xmlns='{XMLDSIG_NS}'><X509Data><v:{element} xmlns:v='{namespace}' Algorithm='test'>AQID</v:{element}></X509Data></KeyInfo>"
2998 );
2999 let document = Document::parse(&xml).unwrap();
3000 let resources = crate::policy::ResourcePolicy {
3001 max_external_resource_bytes: 2,
3002 ..Default::default()
3003 };
3004 let mut session = KeyInfoParsingSession::new(&resources).unwrap();
3005 assert!(
3006 matches!(
3007 session.parse(document.root_element()),
3008 Err(ParseError::Policy(_))
3009 ),
3010 "{element}"
3011 );
3012 }
3013 }
3014
3015 #[test]
3016 fn failed_x509_der_parse_keeps_session_byte_charge() {
3017 let invalid = Document::parse("<KeyInfo xmlns='http://www.w3.org/2000/09/xmldsig#'><X509Data><X509Certificate>AQID</X509Certificate></X509Data></KeyInfo>").unwrap();
3020 let valid = Document::parse("<KeyInfo xmlns='http://www.w3.org/2000/09/xmldsig#'><X509Data><X509SKI>AQID</X509SKI></X509Data></KeyInfo>").unwrap();
3021 let resources = crate::policy::ResourcePolicy {
3022 max_external_resource_bytes: 3,
3023 max_external_resource_total_bytes: 5,
3024 ..Default::default()
3025 };
3026 let mut session = KeyInfoParsingSession::new(&resources).unwrap();
3027 assert!(matches!(
3028 session.parse(invalid.root_element()),
3029 Err(ParseError::InvalidStructure(_))
3030 ));
3031 assert!(matches!(
3032 session.parse(valid.root_element()),
3033 Err(ParseError::Policy(_))
3034 ));
3035 assert!(
3036 KeyInfoParsingSession::new(&resources)
3037 .unwrap()
3038 .parse(valid.root_element())
3039 .is_ok()
3040 );
3041 }
3042
3043 #[test]
3044 fn key_info_session_retains_failed_work_and_applies_active_policy() {
3045 let resources = crate::policy::ResourcePolicy {
3048 max_key_candidates: 1,
3049 ..Default::default()
3050 };
3051 let xml = "<KeyInfo xmlns='http://www.w3.org/2000/09/xmldsig#'><KeyValue/></KeyInfo>";
3052 let document = Document::parse(xml).unwrap();
3053 let mut session = KeyInfoParsingSession::new(&resources).unwrap();
3054 assert!(matches!(
3055 session.parse(document.root_element()),
3056 Err(ParseError::InvalidStructure(_))
3057 ));
3058 assert!(matches!(
3059 session.parse(document.root_element()),
3060 Err(ParseError::Policy(_))
3061 ));
3062 let zero = crate::policy::ResourcePolicy {
3063 max_key_candidates: 0,
3064 ..resources
3065 };
3066 assert!(matches!(
3067 KeyInfoParsingSession::new(&zero)
3068 .unwrap()
3069 .parse(document.root_element()),
3070 Err(ParseError::Policy(_))
3071 ));
3072 }
3073
3074 #[test]
3075 fn key_info_candidate_budget_precedes_key_value_parsing() {
3076 let document = Document::parse(
3079 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3080 <KeyValue><RSAKeyValue><Modulus>AQAB</Modulus></RSAKeyValue></KeyValue>
3081 </KeyInfo>"#,
3082 )
3083 .expect("fixed KeyInfo fixture must parse as XML");
3084 let resources = crate::policy::ResourcePolicy {
3085 max_key_candidates: 0,
3086 ..crate::policy::ResourcePolicy::default()
3087 };
3088
3089 let error = parse_key_info_with_policy_budgets(
3090 document.root_element(),
3091 crate::provider::default_provider(),
3092 &XmlBaseResolutionBudget::default(),
3093 &resources,
3094 None,
3095 )
3096 .expect_err("candidate policy must reject KeyValue before RSA parsing");
3097
3098 assert!(matches!(
3099 error,
3100 ParseError::Policy(crate::policy::PolicyViolation::ResourceLimit {
3101 resource: crate::policy::resource_name::KEY_CANDIDATES,
3102 maximum: 0,
3103 actual: 1,
3104 })
3105 ));
3106 }
3107
3108 #[test]
3109 fn key_info_candidate_budget_precedes_der_key_decoding() {
3110 let document = Document::parse(
3113 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
3114 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
3115 <dsig11:DEREncodedKeyValue>%%%invalid%%%</dsig11:DEREncodedKeyValue>
3116 </KeyInfo>"#,
3117 )
3118 .expect("fixed KeyInfo fixture must parse as XML");
3119 let resources = crate::policy::ResourcePolicy {
3120 max_key_candidates: 0,
3121 ..crate::policy::ResourcePolicy::default()
3122 };
3123
3124 let error = parse_key_info_with_policy_budgets(
3125 document.root_element(),
3126 crate::provider::default_provider(),
3127 &XmlBaseResolutionBudget::default(),
3128 &resources,
3129 None,
3130 )
3131 .expect_err("candidate policy must reject DEREncodedKeyValue before decoding");
3132
3133 assert!(matches!(
3134 error,
3135 ParseError::Policy(crate::policy::PolicyViolation::ResourceLimit {
3136 resource: crate::policy::resource_name::KEY_CANDIDATES,
3137 maximum: 0,
3138 actual: 1,
3139 })
3140 ));
3141 }
3142
3143 #[test]
3144 fn key_info_embedded_candidate_count_matches_materialized_key_kinds() {
3145 let key_info = KeyInfo {
3148 sources: vec![
3149 KeyInfoSource::KeyName("configured-key".into()),
3150 KeyInfoSource::KeyValue(KeyValueInfo::Unsupported {
3151 namespace: Some(XMLDSIG_NS.into()),
3152 local_name: "FutureKeyValue".into(),
3153 }),
3154 KeyInfoSource::DerEncodedKeyValue(vec![1]),
3155 KeyInfoSource::X509Data(X509DataInfo {
3156 certificates: vec![vec![2], vec![3]],
3157 ..X509DataInfo::default()
3158 }),
3159 KeyInfoSource::RetrievalMethod {
3160 uri: "urn:certificate".into(),
3161 resource_type: None,
3162 transforms: RetrievalMethodTransforms::None,
3163 },
3164 ],
3165 };
3166
3167 assert_eq!(key_info.embedded_candidate_count(), 4);
3168 }
3169
3170 #[test]
3171 fn parse_key_info_dispatches_supported_children() {
3172 let cert_base64 = fixture_rsa_cert_base64();
3173 let expected_cert = base64::engine::general_purpose::STANDARD
3174 .decode(&cert_base64)
3175 .expect("fixture PEM must contain valid base64");
3176 let cert_digest = base64::engine::general_purpose::STANDARD
3177 .encode(compute_digest(DigestAlgorithm::Sha256, &expected_cert));
3178 let xml = format!(
3179 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
3180 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
3181 <KeyName>idp-signing-key</KeyName>
3182 <KeyValue>
3183 <RSAKeyValue>
3184 <Modulus>AQAB</Modulus>
3185 <Exponent>AQAB</Exponent>
3186 </RSAKeyValue>
3187 </KeyValue>
3188 <X509Data>
3189 <X509Certificate>{cert_base64}</X509Certificate>
3190 <X509SubjectName>CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509SubjectName>
3191 <X509IssuerSerial>
3192 <X509IssuerName>Email=xmlsec@aleksey.com,CN=Aleksey Sanin,OU=Second level CA,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509IssuerName>
3193 <X509SerialNumber>680572598617295163017172295025714171905498632019</X509SerialNumber>
3194 </X509IssuerSerial>
3195 <X509SKI>bcOXN/nsVl8GatRbcKrPbzIbw0Y=</X509SKI>
3196 <X509CRL>BAUGBw==</X509CRL>
3197 <dsig11:X509Digest Algorithm="http://www.w3.org/2001/04/xmlenc#sha256">{cert_digest}</dsig11:X509Digest>
3198 </X509Data>
3199 <dsig11:DEREncodedKeyValue>AQIDBA==</dsig11:DEREncodedKeyValue>
3200 </KeyInfo>"#
3201 );
3202 let doc = Document::parse(&xml).unwrap();
3203
3204 let key_info = parse_key_info(doc.root_element()).unwrap();
3205 assert_eq!(key_info.sources.len(), 4);
3206
3207 assert_eq!(
3208 key_info.sources[0],
3209 KeyInfoSource::KeyName("idp-signing-key".to_string())
3210 );
3211 assert_eq!(
3212 key_info.sources[1],
3213 KeyInfoSource::KeyValue(KeyValueInfo::Rsa {
3214 modulus: vec![1, 0, 1],
3215 exponent: vec![1, 0, 1],
3216 })
3217 );
3218 let x509_info = match &key_info.sources[2] {
3219 KeyInfoSource::X509Data(x509) => x509,
3220 other => panic!("expected X509Data source, got {other:?}"),
3221 };
3222 assert_eq!(x509_info.certificates, vec![expected_cert]);
3223 assert_eq!(
3224 x509_info.subject_names,
3225 vec![
3226 "CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US"
3227 .to_string()
3228 ]
3229 );
3230 assert_eq!(
3231 x509_info.issuer_serials,
3232 vec![(
3233 "Email=xmlsec@aleksey.com,CN=Aleksey Sanin,OU=Second level CA,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US".to_string(),
3234 "680572598617295163017172295025714171905498632019".to_string()
3235 )]
3236 );
3237 assert_eq!(
3238 x509_info.skis,
3239 vec![vec![
3240 109, 195, 151, 55, 249, 236, 86, 95, 6, 106, 212, 91, 112, 170, 207, 111, 50, 27,
3241 195, 70
3242 ]]
3243 );
3244 assert_eq!(x509_info.crls, vec![vec![4, 5, 6, 7]]);
3245 assert_eq!(
3246 x509_info.digests,
3247 vec![(
3248 "http://www.w3.org/2001/04/xmlenc#sha256".to_string(),
3249 compute_digest(DigestAlgorithm::Sha256, &x509_info.certificates[0])
3250 )]
3251 );
3252 assert_eq!(x509_info.parsed_certificates.len(), 1);
3253 assert_eq!(x509_info.certificate_chain, vec![0]);
3254 let parsed_cert = &x509_info.parsed_certificates[0];
3255 assert!(!parsed_cert.subject_dn.is_empty());
3256 assert!(!parsed_cert.issuer_dn.is_empty());
3257 assert_eq!(
3258 parsed_cert.serial_number_hex,
3259 "7735EE487F6862DAF1B3956D961CCB0FA6F34F53"
3260 );
3261 assert!(parsed_cert.subject_key_identifier.is_some());
3262 assert!(matches!(
3263 parsed_cert.public_key,
3264 X509PublicKeyInfo::Rsa { .. }
3265 ));
3266
3267 assert_eq!(
3268 key_info.sources[3],
3269 KeyInfoSource::DerEncodedKeyValue(vec![1, 2, 3, 4])
3270 );
3271 }
3272
3273 #[test]
3274 fn parse_rsa_key_value_preserves_wrapped_crypto_binary() {
3275 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3277 <KeyValue><RSAKeyValue>
3278 <Modulus> AQID
3279BA== </Modulus>
3280 <Exponent> AQAB </Exponent>
3281 </RSAKeyValue></KeyValue>
3282 </KeyInfo>"##;
3283 let doc = Document::parse(xml).unwrap();
3284
3285 assert_eq!(
3286 parse_key_info(doc.root_element()).unwrap().sources,
3287 vec![KeyInfoSource::KeyValue(KeyValueInfo::Rsa {
3288 modulus: vec![1, 2, 3, 4],
3289 exponent: vec![1, 0, 1],
3290 })]
3291 );
3292 }
3293
3294 #[test]
3295 fn parse_rsa_key_value_rejects_reordered_parameters() {
3296 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3298 <KeyValue><RSAKeyValue>
3299 <Exponent>AQAB</Exponent><Modulus>AQID</Modulus>
3300 </RSAKeyValue></KeyValue>
3301 </KeyInfo>"##;
3302 let doc = Document::parse(xml).unwrap();
3303
3304 assert!(matches!(
3305 parse_key_info(doc.root_element()),
3306 Err(ParseError::InvalidStructure(_))
3307 ));
3308 }
3309
3310 #[test]
3311 fn parse_rsa_key_value_rejects_missing_exponent() {
3312 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3314 <KeyValue><RSAKeyValue><Modulus>AQID</Modulus></RSAKeyValue></KeyValue>
3315 </KeyInfo>"##;
3316 let doc = Document::parse(xml).unwrap();
3317
3318 assert!(matches!(
3319 parse_key_info(doc.root_element()),
3320 Err(ParseError::InvalidStructure(_))
3321 ));
3322 }
3323
3324 #[test]
3325 fn parse_rsa_key_value_rejects_duplicate_exponent() {
3326 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3328 <KeyValue><RSAKeyValue>
3329 <Modulus>AQID</Modulus><Exponent>AQAB</Exponent><Exponent>AQAB</Exponent>
3330 </RSAKeyValue></KeyValue>
3331 </KeyInfo>"##;
3332 let doc = Document::parse(xml).unwrap();
3333
3334 assert!(matches!(
3335 parse_key_info(doc.root_element()),
3336 Err(ParseError::InvalidStructure(_))
3337 ));
3338 }
3339
3340 #[test]
3341 fn parse_rsa_key_value_rejects_wrong_parameter_namespace() {
3342 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#" xmlns:bad="urn:bad">
3344 <KeyValue><RSAKeyValue>
3345 <bad:Modulus>AQID</bad:Modulus><Exponent>AQAB</Exponent>
3346 </RSAKeyValue></KeyValue>
3347 </KeyInfo>"#;
3348 let doc = Document::parse(xml).unwrap();
3349
3350 assert!(matches!(
3351 parse_key_info(doc.root_element()),
3352 Err(ParseError::InvalidStructure(_))
3353 ));
3354 }
3355
3356 #[test]
3357 fn parse_rsa_key_value_rejects_nested_crypto_binary() {
3358 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3360 <KeyValue><RSAKeyValue>
3361 <Modulus><chunk>AQID</chunk></Modulus><Exponent>AQAB</Exponent>
3362 </RSAKeyValue></KeyValue>
3363 </KeyInfo>"#;
3364 let doc = Document::parse(xml).unwrap();
3365
3366 assert!(matches!(
3367 parse_key_info(doc.root_element()),
3368 Err(ParseError::InvalidStructure(_))
3369 ));
3370 }
3371
3372 #[test]
3373 fn parse_rsa_key_value_rejects_malformed_base64() {
3374 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3376 <KeyValue><RSAKeyValue>
3377 <Modulus>%%%%</Modulus><Exponent>AQAB</Exponent>
3378 </RSAKeyValue></KeyValue>
3379 </KeyInfo>"#;
3380 let doc = Document::parse(xml).unwrap();
3381
3382 assert!(matches!(
3383 parse_key_info(doc.root_element()),
3384 Err(ParseError::Base64(_))
3385 ));
3386 }
3387
3388 #[test]
3389 fn parse_rsa_key_value_rejects_oversized_exponent_before_decode() {
3390 let exponent = "A".repeat(MAX_RSA_EXPONENT_LEN.div_ceil(3) * 4 + 1);
3392 let xml = format!(
3393 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3394 <KeyValue><RSAKeyValue>
3395 <Modulus>AQID</Modulus><Exponent>{exponent}</Exponent>
3396 </RSAKeyValue></KeyValue>
3397 </KeyInfo>"#
3398 );
3399 let doc = Document::parse(&xml).unwrap();
3400
3401 assert!(matches!(
3402 parse_key_info(doc.root_element()),
3403 Err(ParseError::InvalidStructure(_))
3404 ));
3405 }
3406
3407 #[test]
3408 fn parse_key_info_ignores_unknown_children() {
3409 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3410 <Foo>bar</Foo>
3411 <KeyName>ok</KeyName>
3412 </KeyInfo>"#;
3413 let doc = Document::parse(xml).unwrap();
3414
3415 let key_info = parse_key_info(doc.root_element()).unwrap();
3416 assert_eq!(key_info.sources, vec![KeyInfoSource::KeyName("ok".into())]);
3417 }
3418
3419 #[test]
3420 fn parse_key_info_keyvalue_requires_single_child() {
3421 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3422 <KeyValue/>
3423 </KeyInfo>"#;
3424 let doc = Document::parse(xml).unwrap();
3425
3426 let err = parse_key_info(doc.root_element()).unwrap_err();
3427 assert!(matches!(err, ParseError::InvalidStructure(_)));
3428 }
3429
3430 #[test]
3431 fn parse_key_info_accepts_empty_x509data() {
3432 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3433 <X509Data/>
3434 </KeyInfo>"#;
3435 let doc = Document::parse(xml).unwrap();
3436
3437 let key_info = parse_key_info(doc.root_element()).unwrap();
3438 assert_eq!(
3439 key_info.sources,
3440 vec![KeyInfoSource::X509Data(X509DataInfo::default())]
3441 );
3442 }
3443
3444 #[test]
3445 fn parse_key_info_rejects_unknown_xmlsig_child_in_x509data() {
3446 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3447 <X509Data>
3448 <Foo/>
3449 </X509Data>
3450 </KeyInfo>"#;
3451 let doc = Document::parse(xml).unwrap();
3452
3453 let err = parse_key_info(doc.root_element()).unwrap_err();
3454 assert!(matches!(err, ParseError::InvalidStructure(_)));
3455 }
3456
3457 #[test]
3458 fn parse_key_info_rejects_unknown_xmlsig11_child_in_x509data() {
3459 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
3460 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
3461 <X509Data>
3462 <dsig11:Foo/>
3463 </X509Data>
3464 </KeyInfo>"#;
3465 let doc = Document::parse(xml).unwrap();
3466
3467 let err = parse_key_info(doc.root_element()).unwrap_err();
3468 assert!(matches!(err, ParseError::InvalidStructure(_)));
3469 }
3470
3471 #[test]
3472 fn parse_key_info_rejects_x509_issuer_serial_without_required_children() {
3473 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3474 <X509Data>
3475 <X509IssuerSerial>
3476 <X509IssuerName>CN=CA</X509IssuerName>
3477 </X509IssuerSerial>
3478 </X509Data>
3479 </KeyInfo>"#;
3480 let doc = Document::parse(xml).unwrap();
3481
3482 let err = parse_key_info(doc.root_element()).unwrap_err();
3483 assert!(matches!(err, ParseError::InvalidStructure(_)));
3484 }
3485
3486 #[test]
3487 fn parse_key_info_rejects_x509_issuer_serial_with_duplicate_issuer_name() {
3488 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3489 <X509Data>
3490 <X509IssuerSerial>
3491 <X509IssuerName>CN=CA-1</X509IssuerName>
3492 <X509IssuerName>CN=CA-2</X509IssuerName>
3493 <X509SerialNumber>42</X509SerialNumber>
3494 </X509IssuerSerial>
3495 </X509Data>
3496 </KeyInfo>"#;
3497 let doc = Document::parse(xml).unwrap();
3498
3499 let err = parse_key_info(doc.root_element()).unwrap_err();
3500 assert!(matches!(err, ParseError::InvalidStructure(_)));
3501 }
3502
3503 #[test]
3504 fn parse_key_info_rejects_x509_issuer_serial_with_duplicate_serial_number() {
3505 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3506 <X509Data>
3507 <X509IssuerSerial>
3508 <X509IssuerName>CN=CA</X509IssuerName>
3509 <X509SerialNumber>1</X509SerialNumber>
3510 <X509SerialNumber>2</X509SerialNumber>
3511 </X509IssuerSerial>
3512 </X509Data>
3513 </KeyInfo>"#;
3514 let doc = Document::parse(xml).unwrap();
3515
3516 let err = parse_key_info(doc.root_element()).unwrap_err();
3517 assert!(matches!(err, ParseError::InvalidStructure(_)));
3518 }
3519
3520 #[test]
3521 fn parse_key_info_rejects_x509_issuer_serial_with_whitespace_only_values() {
3522 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3523 <X509Data>
3524 <X509IssuerSerial>
3525 <X509IssuerName> </X509IssuerName>
3526 <X509SerialNumber>
3527
3528 </X509SerialNumber>
3529 </X509IssuerSerial>
3530 </X509Data>
3531 </KeyInfo>"#;
3532 let doc = Document::parse(xml).unwrap();
3533
3534 let err = parse_key_info(doc.root_element()).unwrap_err();
3535 assert!(matches!(err, ParseError::InvalidStructure(_)));
3536 }
3537
3538 #[test]
3539 fn parse_key_info_rejects_x509_issuer_serial_with_wrong_child_order() {
3540 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3541 <X509Data>
3542 <X509IssuerSerial>
3543 <X509SerialNumber>42</X509SerialNumber>
3544 <X509IssuerName>CN=CA</X509IssuerName>
3545 </X509IssuerSerial>
3546 </X509Data>
3547 </KeyInfo>"#;
3548 let doc = Document::parse(xml).unwrap();
3549
3550 let err = parse_key_info(doc.root_element()).unwrap_err();
3551 assert!(matches!(err, ParseError::InvalidStructure(_)));
3552 }
3553
3554 #[test]
3555 fn parse_key_info_rejects_x509_issuer_serial_with_extra_child_element() {
3556 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
3557 xmlns:foo="urn:example:foo">
3558 <X509Data>
3559 <X509IssuerSerial>
3560 <X509IssuerName>CN=CA</X509IssuerName>
3561 <X509SerialNumber>42</X509SerialNumber>
3562 <foo:Extra/>
3563 </X509IssuerSerial>
3564 </X509Data>
3565 </KeyInfo>"#;
3566 let doc = Document::parse(xml).unwrap();
3567
3568 let err = parse_key_info(doc.root_element()).unwrap_err();
3569 assert!(matches!(err, ParseError::InvalidStructure(_)));
3570 }
3571
3572 #[test]
3573 fn parse_key_info_rejects_x509_digest_without_algorithm() {
3574 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
3575 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
3576 <X509Data>
3577 <dsig11:X509Digest>AQID</dsig11:X509Digest>
3578 </X509Data>
3579 </KeyInfo>"#;
3580 let doc = Document::parse(xml).unwrap();
3581
3582 let err = parse_key_info(doc.root_element()).unwrap_err();
3583 assert!(matches!(err, ParseError::InvalidStructure(_)));
3584 }
3585
3586 #[test]
3587 fn parse_key_info_rejects_invalid_x509_certificate_base64() {
3588 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3589 <X509Data>
3590 <X509Certificate>%%%invalid%%%</X509Certificate>
3591 </X509Data>
3592 </KeyInfo>"#;
3593 let doc = Document::parse(xml).unwrap();
3594
3595 let err = parse_key_info(doc.root_element()).unwrap_err();
3596 assert!(matches!(err, ParseError::Base64(_)));
3597 }
3598
3599 #[test]
3600 fn parse_key_info_rejects_x509_data_exceeding_entry_budget() {
3601 let subjects = (0..(MAX_X509_DATA_ENTRY_COUNT + 1))
3602 .map(|idx| format!("<X509SubjectName>CN={idx}</X509SubjectName>"))
3603 .collect::<Vec<_>>()
3604 .join("");
3605 let xml = format!(
3606 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><X509Data>{subjects}</X509Data></KeyInfo>"
3607 );
3608 let doc = Document::parse(&xml).unwrap();
3609
3610 let err = parse_key_info(doc.root_element()).unwrap_err();
3611 assert!(matches!(err, ParseError::InvalidStructure(_)));
3612 }
3613
3614 #[test]
3615 fn parse_key_info_rejects_x509_data_exceeding_total_binary_budget() {
3616 let payload = base64::engine::general_purpose::STANDARD.encode(vec![0u8; 190_000]);
3617 let entries = (0..6)
3618 .map(|_| format!("<X509SKI>{payload}</X509SKI>"))
3619 .collect::<Vec<_>>()
3620 .join("");
3621 let xml = format!(
3622 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><X509Data>{entries}</X509Data></KeyInfo>"
3623 );
3624 let doc = Document::parse(&xml).unwrap();
3625
3626 let err = parse_key_info(doc.root_element()).unwrap_err();
3627 assert!(matches!(err, ParseError::InvalidStructure(_)));
3628 }
3629
3630 #[test]
3631 fn parse_key_info_rejects_x509_certificate_with_invalid_der() {
3632 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3633 <X509Data>
3634 <X509Certificate>AQID</X509Certificate>
3635 </X509Data>
3636 </KeyInfo>"#;
3637 let doc = Document::parse(xml).unwrap();
3638
3639 let err = parse_key_info(doc.root_element()).unwrap_err();
3640 assert!(matches!(err, ParseError::InvalidStructure(_)));
3641 }
3642
3643 #[test]
3644 fn parse_key_info_rejects_x509_certificate_with_trailing_der_bytes() {
3645 let mut cert = base64::engine::general_purpose::STANDARD
3646 .decode(fixture_rsa_cert_base64())
3647 .unwrap();
3648 cert.extend_from_slice(&[0x00, 0x01]);
3649 let cert_base64 = base64::engine::general_purpose::STANDARD.encode(cert);
3650 let xml = format!(
3651 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3652 <X509Data>
3653 <X509Certificate>{cert_base64}</X509Certificate>
3654 </X509Data>
3655 </KeyInfo>"#
3656 );
3657 let doc = Document::parse(&xml).unwrap();
3658
3659 let err = parse_key_info(doc.root_element()).unwrap_err();
3660 assert!(matches!(err, ParseError::InvalidStructure(_)));
3661 }
3662
3663 #[test]
3664 fn parse_key_info_marks_unsupported_spki_algorithm_as_unsupported() {
3665 let xml = include_str!(
3666 "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt.xml"
3667 );
3668 let doc = Document::parse(xml).unwrap();
3669 let key_info_node = doc
3670 .descendants()
3671 .find(|node| {
3672 node.is_element()
3673 && node.tag_name().namespace() == Some(XMLDSIG_NS)
3674 && node.tag_name().name() == "KeyInfo"
3675 })
3676 .expect("fixture must contain ds:KeyInfo");
3677
3678 let key_info = parse_key_info(key_info_node).expect("KeyInfo parse should succeed");
3679 let x509_info = match &key_info.sources[0] {
3680 KeyInfoSource::X509Data(x509) => x509,
3681 other => panic!("expected X509Data source, got {other:?}"),
3682 };
3683 assert_eq!(x509_info.certificates.len(), 1);
3684 assert_eq!(x509_info.parsed_certificates.len(), 1);
3685 assert_eq!(x509_info.certificate_chain, vec![0]);
3686 let parsed_cert = &x509_info.parsed_certificates[0];
3687 assert!(!parsed_cert.subject_dn.is_empty());
3688 assert!(!parsed_cert.issuer_dn.is_empty());
3689 assert!(parsed_cert.subject_key_identifier.is_some());
3690 assert!(matches!(
3691 parsed_cert.public_key,
3692 X509PublicKeyInfo::Unsupported { .. }
3693 ));
3694 }
3695
3696 #[test]
3697 fn parse_key_info_orders_x509_certificate_chain_from_signing_cert() {
3698 let root = fixture_cert_base64("../../tests/fixtures/keys/cacert.pem");
3699 let intermediate = fixture_cert_base64("../../tests/fixtures/keys/ca2cert.pem");
3700 let leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
3701 let xml = format!(
3702 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3703 <X509Data>
3704 <X509Certificate>{root}</X509Certificate>
3705 <X509Certificate>{intermediate}</X509Certificate>
3706 <X509Certificate>{leaf}</X509Certificate>
3707 </X509Data>
3708 </KeyInfo>"#
3709 );
3710 let doc = Document::parse(&xml).unwrap();
3711
3712 let key_info = parse_key_info(doc.root_element()).unwrap();
3713 let x509_info = match &key_info.sources[0] {
3714 KeyInfoSource::X509Data(x509) => x509,
3715 other => panic!("expected X509Data source, got {other:?}"),
3716 };
3717
3718 assert_eq!(x509_info.certificate_chain, vec![2, 1, 0]);
3719 }
3720
3721 #[test]
3722 fn chain_builder_matches_x509_equivalent_distinguished_names() {
3723 let certificates = [
3727 fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem"),
3728 fixture_cert_base64("../../tests/fixtures/keys/ca2cert.pem"),
3729 fixture_cert_base64("../../tests/fixtures/keys/cacert.pem"),
3730 ]
3731 .map(|encoded| {
3732 base64::engine::general_purpose::STANDARD
3733 .decode(encoded)
3734 .unwrap()
3735 })
3736 .to_vec();
3737 let mut parsed_certificates = certificates
3738 .iter()
3739 .map(|certificate| parse_x509_certificate(certificate).unwrap())
3740 .collect::<Vec<_>>();
3741 parsed_certificates[0].issuer_dn = parsed_certificates[1].subject_dn.to_ascii_lowercase();
3742 parsed_certificates[1].issuer_dn = parsed_certificates[2].subject_dn.to_ascii_lowercase();
3743 let info = X509DataInfo {
3744 certificates,
3745 parsed_certificates,
3746 ..X509DataInfo::default()
3747 };
3748
3749 assert_eq!(
3750 select_x509_signing_certificate(&info, crate::provider::default_provider()).unwrap(),
3751 0
3752 );
3753 assert_eq!(
3754 build_x509_certificate_chain_from(&info, 0, crate::provider::default_provider(), None)
3755 .unwrap(),
3756 vec![0, 1, 2]
3757 );
3758 }
3759
3760 #[test]
3761 fn parse_key_info_uses_issuer_serial_to_select_x509_signing_certificate() {
3762 let root = fixture_cert_base64("../../tests/fixtures/keys/cacert.pem");
3763 let intermediate = fixture_cert_base64("../../tests/fixtures/keys/ca2cert.pem");
3764 let leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
3765 let xml = format!(
3766 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3767 <X509Data>
3768 <X509IssuerSerial>
3769 <X509IssuerName>Email=xmlsec@aleksey.com,CN=Aleksey Sanin,OU=Second level CA,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509IssuerName>
3770 <X509SerialNumber>680572598617295163017172295025714171905498632019</X509SerialNumber>
3771 </X509IssuerSerial>
3772 <X509Certificate>{root}</X509Certificate>
3773 <X509Certificate>{intermediate}</X509Certificate>
3774 <X509Certificate>{leaf}</X509Certificate>
3775 </X509Data>
3776 </KeyInfo>"#
3777 );
3778 let doc = Document::parse(&xml).unwrap();
3779
3780 let key_info = parse_key_info(doc.root_element()).unwrap();
3781 let x509_info = match &key_info.sources[0] {
3782 KeyInfoSource::X509Data(x509) => x509,
3783 other => panic!("expected X509Data source, got {other:?}"),
3784 };
3785
3786 assert_eq!(x509_info.certificate_chain, vec![2, 1, 0]);
3787 }
3788
3789 #[test]
3790 fn parse_key_info_allows_selectors_for_multiple_chain_members() {
3791 let root = fixture_cert_base64("../../tests/fixtures/keys/cacert.pem");
3794 let intermediate = fixture_cert_base64("../../tests/fixtures/keys/ca2cert.pem");
3795 let leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
3796 let xml = format!(
3797 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3798 <X509Data>
3799 <X509SubjectName>CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509SubjectName>
3800 <X509SKI>0X0XrEVCio75sBcl1TxymJ2IOiU=</X509SKI>
3801 <X509Certificate>{root}</X509Certificate>
3802 <X509Certificate>{intermediate}</X509Certificate>
3803 <X509Certificate>{leaf}</X509Certificate>
3804 </X509Data>
3805 </KeyInfo>"#
3806 );
3807 let doc = Document::parse(&xml).unwrap();
3808
3809 let key_info = parse_key_info(doc.root_element()).unwrap();
3810 let x509_info = match &key_info.sources[0] {
3811 KeyInfoSource::X509Data(x509) => x509,
3812 other => panic!("expected X509Data source, got {other:?}"),
3813 };
3814
3815 assert_eq!(x509_info.certificate_chain, vec![2, 1, 0]);
3816 }
3817
3818 #[test]
3819 fn parse_key_info_uses_decimal_issuer_serial_to_select_x509_signing_certificate() {
3820 let serial = "680572598617295163017172295025714171905498632019";
3821 let padded_serial = format!("{}{}", "0".repeat(64), serial);
3822 assert_eq!(
3823 x509_serial_decimal_to_hex(&padded_serial).as_deref(),
3824 Some("7735EE487F6862DAF1B3956D961CCB0FA6F34F53")
3825 );
3826 let root = fixture_cert_base64("../../tests/fixtures/keys/cacert.pem");
3827 let intermediate = fixture_cert_base64("../../tests/fixtures/keys/ca2cert.pem");
3828 let leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
3829 let other_leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-4096-cert.pem");
3830 let xml = format!(
3831 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3832 <X509Data>
3833 <X509IssuerSerial>
3834 <X509IssuerName>Email=xmlsec@aleksey.com,CN=Aleksey Sanin,OU=Second level CA,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509IssuerName>
3835 <X509SerialNumber>{padded_serial}</X509SerialNumber>
3836 </X509IssuerSerial>
3837 <X509Certificate>{root}</X509Certificate>
3838 <X509Certificate>{intermediate}</X509Certificate>
3839 <X509Certificate>{leaf}</X509Certificate>
3840 <X509Certificate>{other_leaf}</X509Certificate>
3841 </X509Data>
3842 </KeyInfo>"#
3843 );
3844 let doc = Document::parse(&xml).unwrap();
3845
3846 let key_info = parse_key_info(doc.root_element()).unwrap();
3847 let x509_info = match &key_info.sources[0] {
3848 KeyInfoSource::X509Data(x509) => x509,
3849 other => panic!("expected X509Data source, got {other:?}"),
3850 };
3851
3852 assert_eq!(x509_info.certificate_chain, vec![2, 1, 0]);
3853 }
3854
3855 #[test]
3856 fn parse_key_info_rejects_ambiguous_x509_signing_certificate_candidates() {
3857 let first_leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
3858 let second_leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-4096-cert.pem");
3859 let xml = format!(
3860 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3861 <X509Data>
3862 <X509Certificate>{first_leaf}</X509Certificate>
3863 <X509Certificate>{second_leaf}</X509Certificate>
3864 </X509Data>
3865 </KeyInfo>"#
3866 );
3867 let doc = Document::parse(&xml).unwrap();
3868
3869 let err = parse_key_info(doc.root_element()).unwrap_err();
3870 assert!(matches!(err, ParseError::InvalidStructure(_)));
3871 }
3872
3873 #[test]
3874 fn parse_key_info_rejects_unmatched_x509_lookup_identifier() {
3875 let cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
3876 let xml = format!(
3877 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3878 <X509Data>
3879 <X509SubjectName>CN=Not The Embedded Certificate</X509SubjectName>
3880 <X509Certificate>{cert}</X509Certificate>
3881 </X509Data>
3882 </KeyInfo>"#
3883 );
3884 let doc = Document::parse(&xml).unwrap();
3885
3886 let err = parse_key_info(doc.root_element()).unwrap_err();
3887 assert!(
3888 matches!(err, ParseError::InvalidStructure(message) if message.contains("lookup identifiers"))
3889 );
3890 }
3891
3892 #[test]
3893 fn parse_key_info_rejects_partially_matched_selector_category() {
3894 let cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
3897 let xml = format!(
3898 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3899 <X509Data>
3900 <X509SubjectName>CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509SubjectName>
3901 <X509SubjectName>CN=Not In The Embedded Chain</X509SubjectName>
3902 <X509Certificate>{cert}</X509Certificate>
3903 </X509Data>
3904 </KeyInfo>"#
3905 );
3906 let doc = Document::parse(&xml).unwrap();
3907
3908 let err = parse_key_info(doc.root_element()).unwrap_err();
3909 assert!(
3910 matches!(err, ParseError::InvalidStructure(message) if message.contains("lookup identifiers"))
3911 );
3912 }
3913
3914 #[test]
3915 fn parse_key_info_rejects_malformed_issuer_serial_even_with_matching_subject() {
3916 let cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
3919 let xml = format!(
3920 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3921 <X509Data>
3922 <X509SubjectName>CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509SubjectName>
3923 <X509IssuerSerial>
3924 <X509IssuerName>Email=xmlsec@aleksey.com,CN=Aleksey Sanin,OU=Second level CA,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509IssuerName>
3925 <X509SerialNumber>not-a-decimal-serial</X509SerialNumber>
3926 </X509IssuerSerial>
3927 <X509Certificate>{cert}</X509Certificate>
3928 </X509Data>
3929 </KeyInfo>"#
3930 );
3931 let doc = Document::parse(&xml).unwrap();
3932
3933 let err = parse_key_info(doc.root_element()).unwrap_err();
3934 assert!(
3935 matches!(err, ParseError::InvalidStructure(message) if message.contains("invalid X509SerialNumber"))
3936 );
3937 }
3938
3939 #[test]
3940 fn parse_key_info_rejects_unmatched_ski_even_with_matching_subject() {
3941 let cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
3942 let xml = format!(
3943 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3944 <X509Data>
3945 <X509SubjectName>CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509SubjectName>
3946 <X509SKI>AQIDBA==</X509SKI>
3947 <X509Certificate>{cert}</X509Certificate>
3948 </X509Data>
3949 </KeyInfo>"#
3950 );
3951 let doc = Document::parse(&xml).unwrap();
3952
3953 let err = parse_key_info(doc.root_element()).unwrap_err();
3954 assert!(
3955 matches!(err, ParseError::InvalidStructure(message) if message.contains("lookup identifiers"))
3956 );
3957 }
3958
3959 #[test]
3960 fn parse_key_info_rejects_lookup_hints_for_different_certificates() {
3961 let first_cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
3962 let second_cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-4096-cert.pem");
3963 let xml = format!(
3964 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3965 <X509Data>
3966 <X509SubjectName>CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509SubjectName>
3967 <X509SKI>60zMLKCfzQ3qnXAzABzRNpdgQ8Q=</X509SKI>
3968 <X509Certificate>{first_cert}</X509Certificate>
3969 <X509Certificate>{second_cert}</X509Certificate>
3970 </X509Data>
3971 </KeyInfo>"#
3972 );
3973 let doc = Document::parse(&xml).unwrap();
3974
3975 let err = parse_key_info(doc.root_element()).unwrap_err();
3976 assert!(
3977 matches!(err, ParseError::InvalidStructure(message) if message.contains("lookup identifiers match multiple certificates"))
3978 );
3979 }
3980
3981 #[test]
3982 fn configured_certificate_matching_requires_every_x509_selector_category() {
3983 let certificate = base64::engine::general_purpose::STANDARD
3986 .decode(fixture_rsa_cert_base64())
3987 .unwrap();
3988 let parsed = parse_x509_certificate(&certificate).unwrap();
3989 let digest = compute_digest_with_provider(
3990 crate::provider::default_provider(),
3991 DigestAlgorithm::Sha256,
3992 &certificate,
3993 )
3994 .unwrap();
3995 let matching = X509DataInfo {
3996 subject_names: vec![parsed.subject_dn.clone()],
3997 issuer_serials: vec![(
3998 parsed.issuer_dn.clone(),
3999 "680572598617295163017172295025714171905498632019".into(),
4000 )],
4001 skis: vec![parsed.subject_key_identifier.clone().unwrap()],
4002 digests: vec![(DigestAlgorithm::Sha256.uri().into(), digest)],
4003 ..X509DataInfo::default()
4004 };
4005
4006 assert!(
4007 x509_certificate_matches_selectors(
4008 &matching,
4009 &certificate,
4010 crate::provider::default_provider()
4011 )
4012 .unwrap()
4013 );
4014 for mismatching in [
4015 X509DataInfo {
4016 subject_names: vec!["CN=other".into()],
4017 ..matching.clone()
4018 },
4019 X509DataInfo {
4020 issuer_serials: vec![(parsed.issuer_dn.clone(), "1".into())],
4021 ..matching.clone()
4022 },
4023 X509DataInfo {
4024 skis: vec![vec![0]],
4025 ..matching.clone()
4026 },
4027 X509DataInfo {
4028 digests: vec![(DigestAlgorithm::Sha256.uri().into(), vec![0; 32])],
4029 ..matching.clone()
4030 },
4031 ] {
4032 assert!(
4033 !x509_certificate_matches_selectors(
4034 &mismatching,
4035 &certificate,
4036 crate::provider::default_provider()
4037 )
4038 .unwrap()
4039 );
4040 }
4041 }
4042
4043 #[test]
4044 fn build_x509_certificate_chain_rejects_chain_exceeding_max_depth() {
4045 let parsed_certificates: Vec<ParsedX509Certificate> = (0..=MAX_X509_CHAIN_DEPTH)
4046 .map(|idx| ParsedX509Certificate {
4047 subject_dn: format!("CN=cert-{idx}"),
4048 issuer_dn: if idx == MAX_X509_CHAIN_DEPTH {
4049 format!("CN=cert-{idx}")
4050 } else {
4051 format!("CN=cert-{}", idx + 1)
4052 },
4053 serial_number: vec![u8::try_from(idx).unwrap()],
4054 serial_number_hex: format!("{idx:02X}"),
4055 subject_key_identifier: None,
4056 public_key: X509PublicKeyInfo::Unsupported {
4057 algorithm_oid: "1.2.3.4".into(),
4058 },
4059 })
4060 .collect();
4061 let certificates = vec![Vec::new(); parsed_certificates.len()];
4062 let info = X509DataInfo {
4063 certificates,
4064 parsed_certificates,
4065 ..X509DataInfo::default()
4066 };
4067
4068 let err = build_x509_certificate_chain(&info, crate::provider::default_provider(), None)
4069 .unwrap_err();
4070 assert!(
4071 matches!(err, ParseError::InvalidStructure(message) if message.contains("maximum depth"))
4072 );
4073 }
4074
4075 #[test]
4076 fn x509_serial_hex_strips_der_sign_extension_zeroes() {
4077 assert_eq!(format_x509_serial_value_hex(&[0x00, 0xFF]), "FF");
4078 assert_eq!(format_x509_serial_value_hex(&[0x00, 0x7F]), "7F");
4079 assert_eq!(format_x509_serial_value_hex(&[0x00, 0x00]), "00");
4080 }
4081
4082 #[test]
4083 fn x509_serial_decimal_parser_enforces_rfc5280_positive_range() {
4084 let max_serial = "1461501637330902918203684832716283019655932542975";
4087 assert_eq!(
4088 x509_serial_decimal_to_hex(max_serial),
4089 Some("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".into())
4090 );
4091 assert_eq!(
4092 x509_serial_decimal_to_hex("730750818665451459101842416358141509827966271488"),
4093 Some("8000000000000000000000000000000000000000".into())
4094 );
4095 assert_eq!(
4096 x509_serial_decimal_to_hex("0000000000000000000000000000000000000000000000001"),
4097 Some("01".into())
4098 );
4099 assert_eq!(
4100 x509_serial_decimal_to_hex("00000000000000000000000000000000000000000000000001"),
4101 Some("01".into())
4102 );
4103 assert_eq!(x509_serial_decimal_to_hex("+1"), Some("01".into()));
4104
4105 for invalid in [
4106 "",
4107 "0",
4108 "000",
4109 "+0",
4110 "++1",
4111 "-1",
4112 "1a",
4113 "1461501637330902918203684832716283019655932542976",
4114 ] {
4115 assert_eq!(
4116 x509_serial_decimal_to_hex(invalid),
4117 None,
4118 "invalid serial {invalid:?} must be rejected"
4119 );
4120 }
4121 }
4122
4123 #[test]
4124 fn parse_x509_serial_normalizes_boundary_whitespace_and_rejects_overflow() {
4125 let max_serial = "1461501637330902918203684832716283019655932542975";
4128 let valid = format!(
4129 "<KeyInfo xmlns=\"{XMLDSIG_NS}\"><X509Data><X509IssuerSerial><X509IssuerName>CN=issuer</X509IssuerName><X509SerialNumber>\n {max_serial}\t</X509SerialNumber></X509IssuerSerial></X509Data></KeyInfo>"
4130 );
4131 let doc = Document::parse(&valid).unwrap();
4132 let parsed = parse_key_info(doc.root_element()).unwrap();
4133 let KeyInfoSource::X509Data(x509) = &parsed.sources[0] else {
4134 panic!("expected X509Data source");
4135 };
4136 assert_eq!(x509.issuer_serials[0].1, max_serial);
4137
4138 let explicit_positive = valid.replace(max_serial, "+42");
4139 let doc = Document::parse(&explicit_positive).unwrap();
4140 let parsed = parse_key_info(doc.root_element()).unwrap();
4141 let KeyInfoSource::X509Data(x509) = &parsed.sources[0] else {
4142 panic!("expected X509Data source");
4143 };
4144 assert_eq!(x509.issuer_serials[0].1, "42");
4145
4146 let overflow = valid.replace(
4147 max_serial,
4148 "1461501637330902918203684832716283019655932542976",
4149 );
4150 let doc = Document::parse(&overflow).unwrap();
4151 assert!(matches!(
4152 parse_key_info(doc.root_element()),
4153 Err(ParseError::InvalidStructure(message))
4154 if message.contains("invalid X509SerialNumber")
4155 ));
4156 }
4157
4158 #[test]
4159 fn issuer_selector_matches_a_sign_padded_twenty_octet_serial() {
4160 let serial_hex = "8000000000000000000000000000000000000000";
4163 let info = X509DataInfo {
4164 issuer_serials: vec![(
4165 "CN=issuer".into(),
4166 "730750818665451459101842416358141509827966271488".into(),
4167 )],
4168 parsed_certificates: vec![ParsedX509Certificate {
4169 subject_dn: "CN=leaf".into(),
4170 issuer_dn: "CN=issuer".into(),
4171 serial_number: [vec![0, 0x80], vec![0; 19]].concat(),
4172 serial_number_hex: serial_hex.into(),
4173 subject_key_identifier: None,
4174 public_key: X509PublicKeyInfo::Unsupported {
4175 algorithm_oid: "1.2.3.4".into(),
4176 },
4177 }],
4178 ..X509DataInfo::default()
4179 };
4180
4181 assert!(
4182 x509_selector_categories_match_chain(&info, crate::provider::default_provider())
4183 .unwrap()
4184 );
4185 }
4186
4187 #[test]
4188 fn distinguished_name_matching_preserves_rdn_order() {
4189 assert!(distinguished_names_equal(
4192 "CN=leaf, O=example",
4193 "CN=leaf,O=example"
4194 ));
4195 assert!(!distinguished_names_equal(
4196 "CN=leaf,O=example",
4197 "O=example,CN=leaf"
4198 ));
4199 }
4200
4201 #[test]
4202 fn distinguished_name_matching_applies_x520_string_preparation() {
4203 assert!(distinguished_names_equal(
4206 "CN= TEST key ,O=Example",
4207 "CN=test key,O=example"
4208 ));
4209 assert!(distinguished_names_equal(
4210 "CN=Straße,O=Example",
4211 "CN=STRASSE,O=EXAMPLE"
4212 ));
4213 assert!(distinguished_names_equal(
4214 "CN=test+OU=security,O=example",
4215 "OU=SECURITY+CN=TEST,O=EXAMPLE"
4216 ));
4217 assert!(!distinguished_names_equal(
4218 "1.2.3.4=#040141,O=example",
4219 "1.2.3.4=#040142,O=example"
4220 ));
4221 }
4222
4223 #[test]
4224 fn distinguished_name_matching_applies_ia5_matching_rules() {
4225 assert!(distinguished_names_equal(
4228 "EMAIL=ops@EXAMPLE.COM,DC=EXAMPLE,DC=COM",
4229 "EMAIL=ops@example.com,DC=example,DC=com"
4230 ));
4231 assert!(!distinguished_names_equal(
4232 "EMAIL=OPS@example.com,DC=example,DC=com",
4233 "EMAIL=ops@example.com,DC=example,DC=com"
4234 ));
4235 }
4236
4237 #[test]
4238 fn distinguished_name_matching_handles_rfc4514_escaped_values() {
4239 let value = " leading,plus+equals=slash\\trailing ";
4242 let mut params = rcgen::CertificateParams::new(Vec::new()).unwrap();
4243 params
4244 .distinguished_name
4245 .push(rcgen::DnType::CommonName, value);
4246 let key = rcgen::KeyPair::generate().unwrap();
4247 let certificate = params.self_signed(&key).unwrap();
4248 let parsed = parse_x509_certificate(certificate.der()).unwrap();
4249
4250 assert_eq!(
4251 parsed.subject_dn,
4252 r"CN=\ leading\,plus\+equals=slash\\trailing\ "
4253 );
4254 assert!(distinguished_names_equal(
4255 r"CN=\ leading\,plus\+equals=slash\\trailing\ ",
4256 &parsed.subject_dn
4257 ));
4258 assert!(distinguished_names_equal(
4259 "\n CN=\\ leading\\,plus\\+equals=slash\\\\trailing\\ \n",
4260 &parsed.subject_dn
4261 ));
4262 }
4263
4264 #[test]
4265 fn distinguished_name_trailing_escape_covers_all_xml_whitespace() {
4266 for whitespace in [' ', '\t', '\r', '\n'] {
4269 assert!(trailing_whitespace_is_escaped(&format!(
4270 "CN=value\\{whitespace}"
4271 )));
4272 assert!(!trailing_whitespace_is_escaped(&format!(
4273 "CN=value{whitespace}"
4274 )));
4275 }
4276 }
4277
4278 #[test]
4279 fn parse_key_info_accepts_large_textual_x509_entries_within_entry_budget() {
4280 let issuer_name = "C".repeat(MAX_X509_ISSUER_NAME_TEXT_LEN);
4281 let serial_number = "0".repeat(MAX_X509_SERIAL_NUMBER_VALUE_DIGITS - 1) + "1";
4282 let issuer_serials = (0..52)
4283 .map(|_| {
4284 format!(
4285 "<X509IssuerSerial><X509IssuerName>{issuer_name}</X509IssuerName><X509SerialNumber>{serial_number}</X509SerialNumber></X509IssuerSerial>"
4286 )
4287 })
4288 .collect::<Vec<_>>()
4289 .join("");
4290 let xml = format!(
4291 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><X509Data>{issuer_serials}</X509Data></KeyInfo>"
4292 );
4293 let doc = Document::parse(&xml).unwrap();
4294
4295 let key_info = parse_key_info(doc.root_element()).unwrap();
4296 let parsed = match &key_info.sources[0] {
4297 KeyInfoSource::X509Data(x509) => x509,
4298 _ => panic!("expected X509Data source"),
4299 };
4300 assert_eq!(parsed.issuer_serials.len(), 52);
4301 }
4302
4303 #[test]
4304 fn parse_key_info_bounds_raw_x509_serial_text() {
4305 let serial = "0".repeat(MAX_X509_SERIAL_NUMBER_RAW_TEXT_LEN + 1);
4308 let xml = format!(
4309 "<KeyInfo xmlns=\"{XMLDSIG_NS}\"><X509Data><X509IssuerSerial><X509IssuerName>CN=issuer</X509IssuerName><X509SerialNumber>{serial}</X509SerialNumber></X509IssuerSerial></X509Data></KeyInfo>"
4310 );
4311 let doc = Document::parse(&xml).unwrap();
4312
4313 let error = parse_key_info(doc.root_element()).unwrap_err();
4314
4315 assert!(matches!(
4316 error,
4317 ParseError::InvalidStructure(reason)
4318 if reason == "X509SerialNumber exceeds maximum allowed text length"
4319 ));
4320 }
4321
4322 #[test]
4323 fn parse_key_info_accepts_x509data_with_only_foreign_namespace_children() {
4324 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
4325 xmlns:foo="urn:example:foo">
4326 <X509Data>
4327 <foo:Bar/>
4328 </X509Data>
4329 </KeyInfo>"#;
4330 let doc = Document::parse(xml).unwrap();
4331
4332 let key_info = parse_key_info(doc.root_element()).unwrap();
4333 assert_eq!(
4334 key_info.sources,
4335 vec![KeyInfoSource::X509Data(X509DataInfo::default())]
4336 );
4337 }
4338
4339 #[test]
4340 fn parse_key_info_der_encoded_key_value_rejects_invalid_base64() {
4341 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
4342 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
4343 <dsig11:DEREncodedKeyValue>%%%invalid%%%</dsig11:DEREncodedKeyValue>
4344 </KeyInfo>"#;
4345 let doc = Document::parse(xml).unwrap();
4346
4347 let err = parse_key_info(doc.root_element()).unwrap_err();
4348 assert!(matches!(err, ParseError::Base64(_)));
4349 }
4350
4351 #[test]
4352 fn parse_key_info_der_encoded_key_value_accepts_xml_whitespace() {
4353 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
4354 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
4355 <dsig11:DEREncodedKeyValue>
4356 AQID
4357 BA==
4358 </dsig11:DEREncodedKeyValue>
4359 </KeyInfo>"#;
4360 let doc = Document::parse(xml).unwrap();
4361
4362 let key_info = parse_key_info(doc.root_element()).unwrap();
4363 assert_eq!(
4364 key_info.sources,
4365 vec![KeyInfoSource::DerEncodedKeyValue(vec![1, 2, 3, 4])]
4366 );
4367 }
4368
4369 #[test]
4370 fn parse_key_info_dispatches_dsig11_ec_keyvalue() {
4371 let public_key = "BJ/yaXNlq4FRObyJCBhb5jAz8GVzinK3bBGLjSDfjbJwNfydtgjnlS4EsDmxSRhWyJWq6GIqy5wvnaiARK04uB4=";
4372 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
4373 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
4374 <KeyValue>
4375 <dsig11:ECKeyValue>
4376 <dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/>
4377 <dsig11:PublicKey>BJ/yaXNlq4FRObyJCBhb5jAz8GVzinK3bBGLjSDfjbJwNfydtgjnlS4EsDmxSRhWyJWq6GIqy5wvnaiARK04uB4=</dsig11:PublicKey>
4378 </dsig11:ECKeyValue>
4379 </KeyValue>
4380 </KeyInfo>"#;
4381 let doc = Document::parse(xml).unwrap();
4382 let expected_public_key = base64::engine::general_purpose::STANDARD
4383 .decode(public_key)
4384 .expect("fixture EC point must be valid base64");
4385
4386 let key_info = parse_key_info(doc.root_element()).unwrap();
4387 assert_eq!(
4388 key_info.sources,
4389 vec![KeyInfoSource::KeyValue(KeyValueInfo::Ec {
4390 curve_oid: "1.2.840.10045.3.1.7".into(),
4391 public_key: expected_public_key,
4392 })]
4393 );
4394 }
4395
4396 #[test]
4397 fn parse_ec_key_value_accepts_bare_curve_oid() {
4398 use base64::Engine;
4399
4400 let encoded_public_key = "BO/yd/OZzDfjX4qivDY/vsUIuh6KWAxoxW5P4ukvwd+T6pVljWsX2UBJNNy5MdhTwB8e2YwB8kUbJwdsAS/XGi/fz8unFrs+lVlAgIs6s/xBYFbfUoRiAacD2SpVDe6XBA==";
4401 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
4402 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
4403 <KeyValue>
4404 <dsig11:ECKeyValue>
4405 <dsig11:NamedCurve URI="1.3.132.0.34"/>
4406 <dsig11:PublicKey>BO/yd/OZzDfjX4qivDY/vsUIuh6KWAxoxW5P4ukvwd+T6pVljWsX2UBJNNy5MdhTwB8e2YwB8kUbJwdsAS/XGi/fz8unFrs+lVlAgIs6s/xBYFbfUoRiAacD2SpVDe6XBA==</dsig11:PublicKey>
4407 </dsig11:ECKeyValue>
4408 </KeyValue>
4409 </KeyInfo>"#;
4410 let doc = Document::parse(xml).unwrap();
4411 let expected_public_key = base64::engine::general_purpose::STANDARD
4412 .decode(encoded_public_key)
4413 .unwrap();
4414
4415 let sources = parse_key_info(doc.root_element()).unwrap().sources;
4416
4417 assert!(matches!(
4418 &sources[0],
4419 KeyInfoSource::KeyValue(KeyValueInfo::Ec { curve_oid, public_key })
4420 if curve_oid == EC_P384_OID && public_key == &expected_public_key
4421 ));
4422 }
4423
4424 #[test]
4425 fn parse_ec_key_value_marks_ec_parameters_as_unsupported() {
4426 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
4427 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
4428 <KeyValue>
4429 <dsig11:ECKeyValue>
4430 <dsig11:ECParameters/>
4431 <dsig11:PublicKey>BA==</dsig11:PublicKey>
4432 </dsig11:ECKeyValue>
4433 </KeyValue>
4434 </KeyInfo>"#;
4435 let doc = Document::parse(xml).unwrap();
4436
4437 let key_info = parse_key_info(doc.root_element()).unwrap();
4438 assert_eq!(
4439 key_info.sources,
4440 vec![KeyInfoSource::KeyValue(KeyValueInfo::Unsupported {
4441 namespace: Some(XMLDSIG11_NS.to_string()),
4442 local_name: "ECKeyValue".into(),
4443 })]
4444 );
4445 }
4446
4447 #[test]
4448 fn parse_ec_key_value_marks_unsupported_curve_as_unsupported() {
4449 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
4450 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
4451 <KeyValue>
4452 <dsig11:ECKeyValue>
4453 <dsig11:NamedCurve URI="urn:oid:1.3.132.0.36"/>
4454 <dsig11:PublicKey>BA==</dsig11:PublicKey>
4455 </dsig11:ECKeyValue>
4456 </KeyValue>
4457 </KeyInfo>"#;
4458 let doc = Document::parse(xml).unwrap();
4459
4460 let key_info = parse_key_info(doc.root_element()).unwrap();
4461 assert_eq!(
4462 key_info.sources,
4463 vec![KeyInfoSource::KeyValue(KeyValueInfo::Unsupported {
4464 namespace: Some(XMLDSIG11_NS.to_string()),
4465 local_name: "ECKeyValue".into(),
4466 })]
4467 );
4468 }
4469
4470 #[test]
4471 fn parse_ec_key_value_marks_missing_named_curve_uri_invalid() {
4472 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
4473 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
4474 <KeyValue>
4475 <dsig11:ECKeyValue>
4476 <dsig11:NamedCurve/>
4477 <dsig11:PublicKey>BA==</dsig11:PublicKey>
4478 </dsig11:ECKeyValue>
4479 </KeyValue>
4480 </KeyInfo>"#;
4481 let doc = Document::parse(xml).unwrap();
4482
4483 let key_info = parse_key_info(doc.root_element()).unwrap();
4484 assert_eq!(
4485 key_info.sources,
4486 vec![KeyInfoSource::KeyValue(KeyValueInfo::InvalidEcKeyValue)]
4487 );
4488 }
4489
4490 #[test]
4491 fn parse_ec_key_value_marks_reordered_children_invalid() {
4492 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
4493 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
4494 <KeyValue>
4495 <dsig11:ECKeyValue>
4496 <dsig11:PublicKey>BJ/yaXNlq4FRObyJCBhb5jAz8GVzinK3bBGLjSDfjbJwNfydtgjnlS4EsDmxSRhWyJWq6GIqy5wvnaiARK04uB4=</dsig11:PublicKey>
4497 <dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/>
4498 </dsig11:ECKeyValue>
4499 </KeyValue>
4500 </KeyInfo>"#;
4501 let doc = Document::parse(xml).unwrap();
4502
4503 let key_info = parse_key_info(doc.root_element()).unwrap();
4504 assert_eq!(
4505 key_info.sources,
4506 vec![KeyInfoSource::KeyValue(KeyValueInfo::InvalidEcKeyValue)]
4507 );
4508 }
4509
4510 #[test]
4511 fn parse_ec_key_value_marks_non_uncompressed_point_invalid() {
4512 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
4513 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
4514 <KeyValue>
4515 <dsig11:ECKeyValue>
4516 <dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/>
4517 <dsig11:PublicKey>Ap/yaXNlq4FRObyJCBhb5jAz8GVzinK3bBGLjSDfjbJwNfydtgjnlS4EsDmxSRhWyJWq6GIqy5wvnaiARK04uB4=</dsig11:PublicKey>
4518 </dsig11:ECKeyValue>
4519 </KeyValue>
4520 </KeyInfo>"#;
4521 let doc = Document::parse(xml).unwrap();
4522
4523 let key_info = parse_key_info(doc.root_element()).unwrap();
4524 assert_eq!(
4525 key_info.sources,
4526 vec![KeyInfoSource::KeyValue(KeyValueInfo::InvalidEcKeyValue)]
4527 );
4528 }
4529
4530 #[test]
4531 fn parse_key_info_marks_ds_namespace_ec_keyvalue_as_unsupported() {
4532 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4533 <KeyValue>
4534 <ECKeyValue/>
4535 </KeyValue>
4536 </KeyInfo>"#;
4537 let doc = Document::parse(xml).unwrap();
4538
4539 let key_info = parse_key_info(doc.root_element()).unwrap();
4540 assert_eq!(
4541 key_info.sources,
4542 vec![KeyInfoSource::KeyValue(KeyValueInfo::Unsupported {
4543 namespace: Some(XMLDSIG_NS.to_string()),
4544 local_name: "ECKeyValue".into(),
4545 })]
4546 );
4547 }
4548
4549 #[test]
4550 fn parse_key_info_keeps_unsupported_keyvalue_child_as_marker() {
4551 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4552 <KeyValue>
4553 <FutureKeyValue/>
4554 </KeyValue>
4555 </KeyInfo>"#;
4556 let doc = Document::parse(xml).unwrap();
4557
4558 let key_info = parse_key_info(doc.root_element()).unwrap();
4559 assert_eq!(
4560 key_info.sources,
4561 vec![KeyInfoSource::KeyValue(KeyValueInfo::Unsupported {
4562 namespace: Some(XMLDSIG_NS.to_string()),
4563 local_name: "FutureKeyValue".into(),
4564 })]
4565 );
4566 }
4567
4568 #[test]
4569 fn parse_key_info_accepts_supported_x509_retrieval_xpath() {
4570 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4572 <RetrievalMethod Type="http://www.w3.org/2000/09/xmldsig#X509Data" URI="#keys">
4573 <Transforms><Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">
4574 <XPath xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">ancestor-or-self::dsig:X509Data</XPath>
4575 </Transform></Transforms>
4576 </RetrievalMethod>
4577 </KeyInfo>"##;
4578 let doc = Document::parse(xml).unwrap();
4579
4580 let key_info = parse_key_info(doc.root_element()).unwrap();
4581 assert!(matches!(
4582 key_info.sources.as_slice(),
4583 [KeyInfoSource::RetrievalMethod {
4584 uri,
4585 resource_type: Some(resource_type),
4586 transforms: RetrievalMethodTransforms::X509DataNodeSetFilter { .. },
4587 }] if uri == "#keys"
4588 && resource_type == "http://www.w3.org/2000/09/xmldsig#X509Data"
4589 ));
4590 }
4591
4592 #[test]
4593 fn retrieval_xpath_namespace_binding_policy_precedes_materialization() {
4594 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4597 <RetrievalMethod Type="http://www.w3.org/2000/09/xmldsig#X509Data" URI="#keys">
4598 <Transforms><Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">
4599 <XPath xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">ancestor-or-self::dsig:X509Data</XPath>
4600 </Transform></Transforms>
4601 </RetrievalMethod>
4602 </KeyInfo>"##;
4603 let document = Document::parse(xml).expect("fixed KeyInfo fixture must parse");
4604 let resources = crate::policy::ResourcePolicy {
4605 max_xpath_namespace_bindings: 0,
4606 ..crate::policy::ResourcePolicy::default()
4607 };
4608
4609 let error = parse_key_info_with_policy_budgets(
4610 document.root_element(),
4611 crate::provider::default_provider(),
4612 &XmlBaseResolutionBudget::default(),
4613 &resources,
4614 None,
4615 )
4616 .expect_err("zero namespace bindings must reject RetrievalMethod XPath");
4617
4618 assert!(matches!(
4619 error,
4620 ParseError::Transform(TransformError::Policy(
4621 crate::policy::PolicyViolation::ResourceLimit {
4622 resource: crate::policy::resource_name::XPATH_NAMESPACE_BINDINGS,
4623 maximum: 0,
4624 actual: 1,
4625 }
4626 ))
4627 ));
4628 }
4629
4630 #[test]
4631 fn retrieval_xpath_namespace_byte_policy_precedes_materialization() {
4632 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4635 <RetrievalMethod Type="http://www.w3.org/2000/09/xmldsig#X509Data" URI="#keys">
4636 <Transforms><Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">
4637 <XPath xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">ancestor-or-self::dsig:X509Data</XPath>
4638 </Transform></Transforms>
4639 </RetrievalMethod>
4640 </KeyInfo>"##;
4641 let document = Document::parse(xml).expect("fixed KeyInfo fixture must parse");
4642 let resources = crate::policy::ResourcePolicy {
4643 max_xpath_namespace_bytes: 0,
4644 ..crate::policy::ResourcePolicy::default()
4645 };
4646
4647 let error = parse_key_info_with_policy_budgets(
4648 document.root_element(),
4649 crate::provider::default_provider(),
4650 &XmlBaseResolutionBudget::default(),
4651 &resources,
4652 None,
4653 )
4654 .expect_err("zero namespace bytes must reject RetrievalMethod XPath");
4655
4656 assert!(matches!(
4657 error,
4658 ParseError::Transform(TransformError::Policy(
4659 crate::policy::PolicyViolation::ResourceLimit {
4660 resource: crate::policy::resource_name::XPATH_NAMESPACE_BYTES,
4661 maximum: 0,
4662 actual,
4663 }
4664 )) if actual == "dsig".len() + XMLDSIG_NS.len()
4665 ));
4666 }
4667
4668 #[test]
4669 fn parse_key_info_rejects_oversized_retrieval_method_type() {
4670 let oversized_type = "x".repeat(MAX_KEY_NAME_TEXT_LEN + 1);
4673 let xml = format!(
4674 r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"><RetrievalMethod URI="#key" Type="{oversized_type}"/></KeyInfo>"##
4675 );
4676 let document = Document::parse(&xml).unwrap();
4677
4678 assert!(matches!(
4679 parse_key_info(document.root_element()),
4680 Err(ParseError::InvalidStructure(reason))
4681 if reason == "RetrievalMethod Type exceeds maximum length"
4682 ));
4683 }
4684
4685 #[test]
4686 fn parse_key_info_bounds_retrieval_method_xml_base_chain() {
4687 let mut xml =
4690 format!(r#"<KeyInfo xmlns="{XMLDSIG_NS}"><RetrievalMethod URI="key.der"/></KeyInfo>"#);
4691 for _ in 0..65 {
4692 xml = format!(r#"<n xml:base="segment/">{xml}</n>"#);
4693 }
4694 let document = Document::parse(&xml).unwrap();
4695 let key_info = document
4696 .descendants()
4697 .find(|node| node.has_tag_name((XMLDSIG_NS, "KeyInfo")))
4698 .unwrap();
4699
4700 assert!(matches!(
4701 parse_key_info(key_info),
4702 Err(ParseError::InvalidStructure(reason))
4703 if reason.contains("XML Base resolution")
4704 ));
4705 }
4706
4707 #[test]
4708 fn parse_key_info_normalizes_external_retrieval_without_xml_base() {
4709 let xml = format!(
4712 r#"<KeyInfo xmlns="{XMLDSIG_NS}"><RetrievalMethod URI="https://example.test/a/../key.der"/></KeyInfo>"#
4713 );
4714 let document = Document::parse(&xml).unwrap();
4715 let key_info = parse_key_info(document.root_element()).unwrap();
4716
4717 assert!(matches!(
4718 key_info.sources.as_slice(),
4719 [KeyInfoSource::RetrievalMethod { uri, .. }]
4720 if uri == "https://example.test/key.der"
4721 ));
4722 }
4723
4724 #[test]
4725 fn parse_key_info_absolute_retrieval_bypasses_xml_base_chain() {
4726 let mut xml = format!(
4729 r#"<KeyInfo xmlns="{XMLDSIG_NS}"><RetrievalMethod URI="https://example.test/a/../key.der"/></KeyInfo>"#
4730 );
4731 for _ in 0..65 {
4732 xml = format!(r#"<n xml:base="segment/">{xml}</n>"#);
4733 }
4734 let document = Document::parse(&xml).unwrap();
4735 let key_info_node = document
4736 .descendants()
4737 .find(|node| node.has_tag_name((XMLDSIG_NS, "KeyInfo")))
4738 .unwrap();
4739 let key_info = parse_key_info(key_info_node)
4740 .expect("absolute RetrievalMethod must not consume ancestor-base budget");
4741
4742 assert!(matches!(
4743 key_info.sources.as_slice(),
4744 [KeyInfoSource::RetrievalMethod { uri, .. }]
4745 if uri == "https://example.test/key.der"
4746 ));
4747 }
4748
4749 #[test]
4750 fn parse_key_info_accepts_namespace_equivalent_retrieval_xpath_prefix() {
4751 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4752 <RetrievalMethod Type="http://www.w3.org/2000/09/xmldsig#X509Data" URI="#keys">
4753 <Transforms><Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">
4754 <XPath xmlns:ds="http://www.w3.org/2000/09/xmldsig#">ancestor-or-self::ds:X509Data</XPath>
4755 </Transform></Transforms>
4756 </RetrievalMethod>
4757 </KeyInfo>"##;
4758 let doc = Document::parse(xml).unwrap();
4759
4760 assert!(matches!(
4761 parse_key_info(doc.root_element())
4762 .unwrap()
4763 .sources
4764 .as_slice(),
4765 [KeyInfoSource::RetrievalMethod {
4766 transforms: RetrievalMethodTransforms::X509DataNodeSetFilter { .. },
4767 ..
4768 }]
4769 ));
4770 }
4771
4772 #[test]
4773 fn parse_key_info_reads_complete_retrieval_xpath_text() {
4774 let valid = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4777 <RetrievalMethod Type="http://www.w3.org/2000/09/xmldsig#X509Data" URI="#keys">
4778 <Transforms><Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">
4779 <XPath xmlns:ds="http://www.w3.org/2000/09/xmldsig#">ancestor-or-self::ds:X509<!-- split -->Data</XPath>
4780 </Transform></Transforms>
4781 </RetrievalMethod>
4782 </KeyInfo>"##;
4783 let document = Document::parse(valid).unwrap();
4784 assert!(parse_key_info(document.root_element()).is_ok());
4785
4786 let unsupported =
4787 valid.replace("X509<!-- split -->Data", "X509Data<!-- split -->[false()]");
4788 let document = Document::parse(&unsupported).unwrap();
4789 assert!(matches!(
4790 parse_key_info(document.root_element()),
4791 Err(ParseError::InvalidStructure(reason))
4792 if reason == "unsupported RetrievalMethod XPath selection"
4793 ));
4794 }
4795
4796 #[test]
4797 fn parse_dsa_key_value_accepts_schema_optional_parameters_and_rejects_half_pair() {
4798 let key_info = |parameters: &str| {
4799 format!(
4800 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"><KeyValue><DSAKeyValue>
4801 {parameters}
4802 </DSAKeyValue></KeyValue></KeyInfo>"#
4803 )
4804 };
4805 for parameters in [
4806 "<Y>AQ==</Y>",
4807 "<G>AQ==</G><Y>AQ==</Y>",
4808 "<P>AQ==</P><Q>AQ==</Q><Y>AQ==</Y>",
4809 "<P>AQ==</P><Q>AQ==</Q><G>AQ==</G><Y>AQ==</Y><J>AQ==</J>",
4810 "<Y>AQ==</Y><Seed>AQ==</Seed><PgenCounter>AQ==</PgenCounter>",
4811 "<Y>AQ==</Y><J>AQ==</J><Seed>AQ==</Seed><PgenCounter>AQ==</PgenCounter>",
4812 ] {
4813 let xml = key_info(parameters);
4814 let doc = Document::parse(&xml).unwrap();
4815 assert!(matches!(
4816 parse_key_info(doc.root_element())
4817 .unwrap()
4818 .sources
4819 .as_slice(),
4820 [KeyInfoSource::KeyValue(KeyValueInfo::Dsa { .. })]
4821 ));
4822 }
4823
4824 for invalid_parameters in [
4825 "<P>AQ==</P><Y>AQ==</Y>",
4826 "<Q>AQ==</Q><Y>AQ==</Y>",
4827 "<Y>AQ==</Y><Seed>AQ==</Seed>",
4828 "<Y>AQ==</Y><PgenCounter>AQ==</PgenCounter>",
4829 ] {
4830 let xml = key_info(invalid_parameters);
4831 let doc = Document::parse(&xml).unwrap();
4832 assert!(matches!(
4833 parse_key_info(doc.root_element()),
4834 Err(ParseError::InvalidStructure(_))
4835 ));
4836 }
4837 }
4838
4839 #[test]
4840 fn parse_dsa_crypto_binary_ignores_comment_nodes() {
4841 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4843 <KeyValue><DSAKeyValue><Y>AQ<!-- split -->ID</Y></DSAKeyValue></KeyValue>
4844 </KeyInfo>"#;
4845 let doc = Document::parse(xml).unwrap();
4846
4847 assert!(matches!(
4848 parse_key_info(doc.root_element())
4849 .unwrap()
4850 .sources
4851 .as_slice(),
4852 [KeyInfoSource::KeyValue(KeyValueInfo::Dsa { y, .. })] if y == &[1, 2, 3]
4853 ));
4854 }
4855
4856 #[test]
4857 fn parse_rsa_crypto_binary_ignores_comment_nodes() {
4858 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4860 <KeyValue><RSAKeyValue>
4861 <Modulus>AQ<!-- split -->ID</Modulus><Exponent>Aw==</Exponent>
4862 </RSAKeyValue></KeyValue>
4863 </KeyInfo>"#;
4864 let doc = Document::parse(xml).unwrap();
4865
4866 assert!(matches!(
4867 parse_key_info(doc.root_element())
4868 .unwrap()
4869 .sources
4870 .as_slice(),
4871 [KeyInfoSource::KeyValue(KeyValueInfo::Rsa { modulus, exponent })]
4872 if modulus == &[1, 2, 3] && exponent == &[3]
4873 ));
4874 }
4875
4876 #[test]
4877 fn parse_key_info_preserves_advisory_unsupported_retrieval_transform() {
4878 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4881 <RetrievalMethod URI="#keys" Type="urn:vendor:key"><Transforms>
4882 <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#base64"/>
4883 </Transforms></RetrievalMethod>
4884 <KeyName>fallback</KeyName>
4885 </KeyInfo>"##;
4886 let doc = Document::parse(xml).unwrap();
4887
4888 let key_info = parse_key_info(doc.root_element())
4889 .expect("unsupported advisory retrieval must not reject all KeyInfo sources");
4890 assert!(matches!(
4891 key_info.sources.as_slice(),
4892 [
4893 KeyInfoSource::RetrievalMethod { resource_type: Some(resource_type), .. },
4894 KeyInfoSource::KeyName(name),
4895 ] if resource_type == "urn:vendor:key" && name == "fallback"
4896 ));
4897 }
4898
4899 #[test]
4900 fn parse_key_info_preserves_empty_same_document_reference_uri() {
4901 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
4904 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
4905 <dsig11:KeyInfoReference URI=""/>
4906 </KeyInfo>"#;
4907 let document = Document::parse(xml).unwrap();
4908
4909 assert!(matches!(
4910 parse_key_info(document.root_element())
4911 .unwrap()
4912 .sources
4913 .as_slice(),
4914 [KeyInfoSource::KeyInfoReference { uri }] if uri.is_empty()
4915 ));
4916 }
4917
4918 #[test]
4919 fn parse_key_info_rejects_excessive_child_sources() {
4920 let children = (0..=64)
4922 .map(|index| format!(r#"<extension xmlns="urn:test" index="{index}"/>"#))
4923 .collect::<String>();
4924 let xml =
4925 format!(r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">{children}</KeyInfo>"#);
4926 let document = Document::parse(&xml).unwrap();
4927
4928 assert!(matches!(
4929 parse_key_info(document.root_element()),
4930 Err(ParseError::InvalidStructure(reason))
4931 if reason == "KeyInfo contains too many child elements"
4932 ));
4933 }
4934
4935 #[test]
4936 fn parse_key_info_rejects_keyname_with_child_elements() {
4937 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4938 <KeyName>ok<foo/></KeyName>
4939 </KeyInfo>"#;
4940 let doc = Document::parse(xml).unwrap();
4941
4942 let err = parse_key_info(doc.root_element()).unwrap_err();
4943 assert!(matches!(err, ParseError::InvalidStructure(_)));
4944 }
4945
4946 #[test]
4947 fn parse_key_info_preserves_keyname_text_without_trimming() {
4948 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4949 <KeyName> signing key </KeyName>
4950 </KeyInfo>"#;
4951 let doc = Document::parse(xml).unwrap();
4952
4953 let key_info = parse_key_info(doc.root_element()).unwrap();
4954 assert_eq!(
4955 key_info.sources,
4956 vec![KeyInfoSource::KeyName(" signing key ".into())]
4957 );
4958 }
4959
4960 #[test]
4961 fn parse_key_info_rejects_oversized_keyname_text() {
4962 let oversized = "A".repeat(4097);
4963 let xml = format!(
4964 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><KeyName>{oversized}</KeyName></KeyInfo>"
4965 );
4966 let doc = Document::parse(&xml).unwrap();
4967
4968 let err = parse_key_info(doc.root_element()).unwrap_err();
4969 assert!(matches!(err, ParseError::InvalidStructure(_)));
4970 }
4971
4972 #[test]
4973 fn parse_key_info_rejects_non_whitespace_mixed_content() {
4974 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">oops<KeyName>k</KeyName></KeyInfo>"#;
4975 let doc = Document::parse(xml).unwrap();
4976
4977 let err = parse_key_info(doc.root_element()).unwrap_err();
4978 assert!(matches!(err, ParseError::InvalidStructure(_)));
4979 }
4980
4981 #[test]
4982 fn parse_key_info_rejects_nbsp_as_non_xml_whitespace_mixed_content() {
4983 let xml = "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\u{00A0}<KeyName>k</KeyName></KeyInfo>";
4984 let doc = Document::parse(xml).unwrap();
4985
4986 let err = parse_key_info(doc.root_element()).unwrap_err();
4987 assert!(matches!(err, ParseError::InvalidStructure(_)));
4988 }
4989
4990 #[test]
4991 fn parse_key_info_der_encoded_key_value_rejects_oversized_payload() {
4992 let oversized =
4993 base64::engine::general_purpose::STANDARD
4994 .encode(vec![0u8; MAX_DER_ENCODED_KEY_VALUE_LEN + 1]);
4995 let xml = format!(
4996 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\" xmlns:dsig11=\"http://www.w3.org/2009/xmldsig11#\"><dsig11:DEREncodedKeyValue>{oversized}</dsig11:DEREncodedKeyValue></KeyInfo>"
4997 );
4998 let doc = Document::parse(&xml).unwrap();
4999
5000 let err = parse_key_info(doc.root_element()).unwrap_err();
5001 assert!(matches!(err, ParseError::InvalidStructure(_)));
5002 }
5003
5004 #[test]
5005 fn parse_key_info_der_encoded_key_value_rejects_empty_payload() {
5006 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
5007 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
5008 <dsig11:DEREncodedKeyValue>
5009
5010 </dsig11:DEREncodedKeyValue>
5011 </KeyInfo>"#;
5012 let doc = Document::parse(xml).unwrap();
5013
5014 let err = parse_key_info(doc.root_element()).unwrap_err();
5015 assert!(matches!(err, ParseError::InvalidStructure(_)));
5016 }
5017
5018 #[test]
5019 fn parse_key_info_der_encoded_key_value_non_xml_ascii_whitespace_is_not_parseable_xml() {
5020 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>";
5021 assert!(Document::parse(xml).is_err());
5022 }
5023
5024 #[test]
5027 fn parse_hmac_output_length_reads_all_text_nodes() {
5028 let xml = r#"<SignatureMethod xmlns="http://www.w3.org/2000/09/xmldsig#">
5030 <HMACOutputLength>8<!-- split -->0</HMACOutputLength>
5031 </SignatureMethod>"#;
5032 let document = Document::parse(xml).unwrap();
5033
5034 assert_eq!(
5035 parse_hmac_output_length(document.root_element(), SignatureAlgorithm::HmacSha1)
5036 .unwrap(),
5037 Some(80)
5038 );
5039 }
5040
5041 #[test]
5042 fn hmac_sha256_accepts_byte_aligned_output_length() {
5043 let document = Document::parse(
5044 r#"<ds:SignatureMethod xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Algorithm="http://www.w3.org/2001/04/xmldsig-more#hmac-sha256"><ds:HMACOutputLength>128</ds:HMACOutputLength></ds:SignatureMethod>"#,
5045 )
5046 .unwrap();
5047 let algorithm =
5048 SignatureAlgorithm::from_uri("http://www.w3.org/2001/04/xmldsig-more#hmac-sha256")
5049 .expect("HMAC-SHA256 must be recognized");
5050
5051 assert_eq!(
5052 parse_hmac_output_length(document.root_element(), algorithm).unwrap(),
5053 Some(128)
5054 );
5055 }
5056
5057 #[test]
5058 fn parse_hmac_output_length_rejects_hidden_suffix_text() {
5059 let xml = r#"<SignatureMethod xmlns="http://www.w3.org/2000/09/xmldsig#">
5061 <HMACOutputLength>80<!-- split -->0</HMACOutputLength>
5062 </SignatureMethod>"#;
5063 let document = Document::parse(xml).unwrap();
5064
5065 assert!(matches!(
5066 parse_hmac_output_length(document.root_element(), SignatureAlgorithm::HmacSha1),
5067 Err(ParseError::InvalidStructure(reason))
5068 if reason == "HMACOutputLength must be a positive byte-aligned value no greater than 160"
5069 ));
5070 }
5071
5072 #[test]
5073 fn hmac_output_length_rejects_non_xml_whitespace() {
5074 for separator in ['\u{a0}', '\u{2003}', '\u{85}'] {
5077 let text = format!("{separator}128{separator}");
5078 assert!(ByteAlignedHmacOutputLength::parse(&text, 256).is_err());
5079 }
5080 assert_eq!(
5081 ByteAlignedHmacOutputLength::parse(" \t\r\n+00128 \t", 256)
5082 .unwrap()
5083 .bits(),
5084 128
5085 );
5086 }
5087
5088 #[test]
5089 fn xmldsig_1_1_rejects_non_octet_hmac_output_length() {
5090 let xml = r#"<SignatureMethod xmlns="http://www.w3.org/2000/09/xmldsig#">
5093 <HMACOutputLength>129</HMACOutputLength>
5094 </SignatureMethod>"#;
5095 let document = Document::parse(xml).unwrap();
5096
5097 assert!(matches!(
5098 parse_hmac_output_length(document.root_element(), SignatureAlgorithm::HmacSha256),
5099 Err(ParseError::InvalidStructure(reason))
5100 if reason == "HMACOutputLength must be a positive byte-aligned value no greater than 256"
5101 ));
5102 }
5103
5104 #[test]
5105 fn parse_signed_info_rsa_sha256_with_reference() {
5106 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5107 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5108 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5109 <Reference URI="">
5110 <Transforms>
5111 <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
5112 <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5113 </Transforms>
5114 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5115 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
5116 </Reference>
5117 </SignedInfo>"#;
5118 let doc = Document::parse(xml).unwrap();
5119 let si = parse_signed_info(doc.root_element()).unwrap();
5120
5121 assert_eq!(si.signature_method, SignatureAlgorithm::RsaSha256);
5122 assert_eq!(si.references.len(), 1);
5123
5124 let r = &si.references[0];
5125 assert_eq!(r.uri.as_deref(), Some(""));
5126 assert_eq!(r.digest_method, DigestAlgorithm::Sha256);
5127 assert_eq!(r.digest_value, vec![0u8; 32]);
5128 assert_eq!(r.transforms.len(), 2);
5129 }
5130
5131 #[test]
5132 fn parse_signed_info_multiple_references() {
5133 let xml = r##"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5134 <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
5135 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"/>
5136 <Reference URI="#a">
5137 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5138 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
5139 </Reference>
5140 <Reference URI="#b">
5141 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
5142 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
5143 </Reference>
5144 </SignedInfo>"##;
5145 let doc = Document::parse(xml).unwrap();
5146 let si = parse_signed_info(doc.root_element()).unwrap();
5147
5148 assert_eq!(si.signature_method, SignatureAlgorithm::EcdsaSha256);
5149 assert_eq!(si.references.len(), 2);
5150 assert_eq!(si.references[0].uri.as_deref(), Some("#a"));
5151 assert_eq!(si.references[0].digest_method, DigestAlgorithm::Sha256);
5152 assert_eq!(si.references[1].uri.as_deref(), Some("#b"));
5153 assert_eq!(si.references[1].digest_method, DigestAlgorithm::Sha1);
5154 }
5155
5156 #[test]
5157 fn parse_signed_info_rejects_too_many_references() {
5158 let references = (0..=MAX_REFERENCES_PER_SIGNATURE)
5161 .map(|index| {
5162 format!(
5163 r##"<Reference URI="#item-{index}">
5164 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5165 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
5166 </Reference>"##
5167 )
5168 })
5169 .collect::<String>();
5170 let xml = format!(
5171 r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5172 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5173 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5174 {references}
5175 </SignedInfo>"#
5176 );
5177 let document = Document::parse(&xml).expect("fixed oversized fixture must parse");
5178
5179 let error = parse_signed_info(document.root_element())
5180 .expect_err("the parser must reject the 65th Reference");
5181
5182 assert!(matches!(
5183 error,
5184 ParseError::Policy(crate::policy::PolicyViolation::ResourceLimit {
5185 resource: crate::policy::resource_name::SIGNATURE_REFERENCES,
5186 maximum: MAX_REFERENCES_PER_SIGNATURE,
5187 actual: 65,
5188 })
5189 ));
5190 }
5191
5192 #[test]
5193 fn parse_signed_info_bounds_xpath_expressions_across_references() {
5194 let filters = r#"<XPath xmlns="http://www.w3.org/2002/06/xmldsig-filter2" Filter="intersect">true()</XPath>"#
5197 .repeat(64);
5198 let filter_transform = format!(
5199 r#"<Transform Algorithm="http://www.w3.org/2002/06/xmldsig-filter2">{filters}</Transform>"#
5200 );
5201 let reference = |index, transforms: &str| {
5202 format!(
5203 r##"<Reference URI="#item-{index}">
5204 <Transforms>{transforms}</Transforms>
5205 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5206 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
5207 </Reference>"##
5208 )
5209 };
5210 let signed_info = |references: &str| {
5211 format!(
5212 r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5213 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5214 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5215 {references}
5216 </SignedInfo>"#
5217 )
5218 };
5219
5220 let max_reference = reference(0, &filter_transform.repeat(64));
5221 let boundary_xml = signed_info(&max_reference);
5222 let boundary_document =
5223 Document::parse(&boundary_xml).expect("fixed boundary fixture must parse");
5224 parse_signed_info(boundary_document.root_element())
5225 .expect("one maximum-shaped Reference must remain accepted");
5226
5227 let extra_transform = r#"<Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116"><XPath>true()</XPath></Transform>"#;
5228 let xml = signed_info(&format!("{max_reference}{}", reference(1, extra_transform)));
5229 let document = Document::parse(&xml).expect("fixed aggregate fixture must parse");
5230
5231 let error = parse_signed_info(document.root_element())
5232 .expect_err("signature-wide XPath expression count must be bounded");
5233
5234 assert!(matches!(
5235 error,
5236 ParseError::Transform(TransformError::Policy(
5237 crate::policy::PolicyViolation::ResourceLimit {
5238 resource: "XPath expressions",
5239 ..
5240 }
5241 ))
5242 ));
5243 }
5244
5245 #[test]
5246 fn parse_reference_without_transforms() {
5247 let xml = r##"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5249 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5250 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5251 <Reference URI="#obj">
5252 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5253 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
5254 </Reference>
5255 </SignedInfo>"##;
5256 let doc = Document::parse(xml).unwrap();
5257 let si = parse_signed_info(doc.root_element()).unwrap();
5258
5259 assert!(si.references[0].transforms.is_empty());
5260 }
5261
5262 #[test]
5263 fn parse_reference_with_all_attributes() {
5264 let xml = r##"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5265 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5266 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5267 <Reference URI="#data" Id="ref1" Type="http://www.w3.org/2000/09/xmldsig#Object">
5268 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5269 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
5270 </Reference>
5271 </SignedInfo>"##;
5272 let doc = Document::parse(xml).unwrap();
5273 let si = parse_signed_info(doc.root_element()).unwrap();
5274 let r = &si.references[0];
5275
5276 assert_eq!(r.uri.as_deref(), Some("#data"));
5277 assert_eq!(r.id.as_deref(), Some("ref1"));
5278 assert_eq!(
5279 r.ref_type.as_deref(),
5280 Some("http://www.w3.org/2000/09/xmldsig#Object")
5281 );
5282 }
5283
5284 #[test]
5285 fn parse_reference_absent_uri() {
5286 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5288 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5289 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5290 <Reference>
5291 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5292 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
5293 </Reference>
5294 </SignedInfo>"#;
5295 let doc = Document::parse(xml).unwrap();
5296 let si = parse_signed_info(doc.root_element()).unwrap();
5297 assert!(si.references[0].uri.is_none());
5298 }
5299
5300 #[test]
5301 fn parse_signed_info_preserves_inclusive_prefixes() {
5302 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
5303 xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#">
5304 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
5305 <ec:InclusiveNamespaces PrefixList="ds saml #default"/>
5306 </CanonicalizationMethod>
5307 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5308 <Reference URI="">
5309 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5310 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
5311 </Reference>
5312 </SignedInfo>"#;
5313 let doc = Document::parse(xml).unwrap();
5314
5315 let si = parse_signed_info(doc.root_element()).unwrap();
5316 assert!(si.c14n_method.inclusive_prefixes().contains("ds"));
5317 assert!(si.c14n_method.inclusive_prefixes().contains("saml"));
5318 assert!(si.c14n_method.inclusive_prefixes().contains(""));
5319 }
5320
5321 #[test]
5324 fn missing_canonicalization_method() {
5325 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5326 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5327 <Reference URI="">
5328 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5329 <DigestValue>dGVzdA==</DigestValue>
5330 </Reference>
5331 </SignedInfo>"#;
5332 let doc = Document::parse(xml).unwrap();
5333 let result = parse_signed_info(doc.root_element());
5334 assert!(result.is_err());
5335 assert!(matches!(
5337 result.unwrap_err(),
5338 ParseError::InvalidStructure(_)
5339 ));
5340 }
5341
5342 #[test]
5343 fn missing_signature_method() {
5344 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5345 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5346 <Reference URI="">
5347 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5348 <DigestValue>dGVzdA==</DigestValue>
5349 </Reference>
5350 </SignedInfo>"#;
5351 let doc = Document::parse(xml).unwrap();
5352 let result = parse_signed_info(doc.root_element());
5353 assert!(result.is_err());
5354 assert!(matches!(
5356 result.unwrap_err(),
5357 ParseError::InvalidStructure(_)
5358 ));
5359 }
5360
5361 #[test]
5362 fn no_references() {
5363 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5364 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5365 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5366 </SignedInfo>"#;
5367 let doc = Document::parse(xml).unwrap();
5368 let result = parse_signed_info(doc.root_element());
5369 assert!(matches!(
5370 result.unwrap_err(),
5371 ParseError::MissingElement {
5372 element: "Reference"
5373 }
5374 ));
5375 }
5376
5377 #[test]
5378 fn unsupported_c14n_algorithm() {
5379 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5380 <CanonicalizationMethod Algorithm="http://example.com/bogus-c14n"/>
5381 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5382 <Reference URI="">
5383 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5384 <DigestValue>dGVzdA==</DigestValue>
5385 </Reference>
5386 </SignedInfo>"#;
5387 let doc = Document::parse(xml).unwrap();
5388 let result = parse_signed_info(doc.root_element());
5389 assert!(matches!(
5390 result.unwrap_err(),
5391 ParseError::UnsupportedAlgorithm { .. }
5392 ));
5393 }
5394
5395 #[test]
5396 fn unsupported_signature_algorithm() {
5397 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5398 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5399 <SignatureMethod Algorithm="http://example.com/bogus-sign"/>
5400 <Reference URI="">
5401 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5402 <DigestValue>dGVzdA==</DigestValue>
5403 </Reference>
5404 </SignedInfo>"#;
5405 let doc = Document::parse(xml).unwrap();
5406 let result = parse_signed_info(doc.root_element());
5407 assert!(matches!(
5408 result.unwrap_err(),
5409 ParseError::UnsupportedAlgorithm { .. }
5410 ));
5411 }
5412
5413 #[test]
5414 fn unsupported_digest_algorithm() {
5415 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5416 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5417 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5418 <Reference URI="">
5419 <DigestMethod Algorithm="http://example.com/bogus-digest"/>
5420 <DigestValue>dGVzdA==</DigestValue>
5421 </Reference>
5422 </SignedInfo>"#;
5423 let doc = Document::parse(xml).unwrap();
5424 let result = parse_signed_info(doc.root_element());
5425 assert!(matches!(
5426 result.unwrap_err(),
5427 ParseError::UnsupportedAlgorithm { .. }
5428 ));
5429 }
5430
5431 #[test]
5432 fn missing_digest_method() {
5433 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5434 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5435 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5436 <Reference URI="">
5437 <DigestValue>dGVzdA==</DigestValue>
5438 </Reference>
5439 </SignedInfo>"#;
5440 let doc = Document::parse(xml).unwrap();
5441 let result = parse_signed_info(doc.root_element());
5442 assert!(result.is_err());
5444 }
5445
5446 #[test]
5447 fn missing_digest_value() {
5448 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5449 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5450 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5451 <Reference URI="">
5452 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5453 </Reference>
5454 </SignedInfo>"#;
5455 let doc = Document::parse(xml).unwrap();
5456 let result = parse_signed_info(doc.root_element());
5457 assert!(matches!(
5458 result.unwrap_err(),
5459 ParseError::MissingElement {
5460 element: "DigestValue"
5461 }
5462 ));
5463 }
5464
5465 #[test]
5466 fn invalid_base64_digest_value() {
5467 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5468 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5469 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5470 <Reference URI="">
5471 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5472 <DigestValue>!!!not-base64!!!</DigestValue>
5473 </Reference>
5474 </SignedInfo>"#;
5475 let doc = Document::parse(xml).unwrap();
5476 let result = parse_signed_info(doc.root_element());
5477 assert!(matches!(result.unwrap_err(), ParseError::Base64(_)));
5478 }
5479
5480 #[test]
5481 fn digest_value_length_must_match_digest_method() {
5482 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5483 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5484 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5485 <Reference URI="">
5486 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5487 <DigestValue>dGVzdA==</DigestValue>
5488 </Reference>
5489 </SignedInfo>"#;
5490 let doc = Document::parse(xml).unwrap();
5491
5492 let result = parse_signed_info(doc.root_element());
5493 assert!(matches!(
5494 result.unwrap_err(),
5495 ParseError::DigestLengthMismatch {
5496 algorithm: "http://www.w3.org/2001/04/xmlenc#sha256",
5497 expected: 32,
5498 actual: 4,
5499 }
5500 ));
5501 }
5502
5503 #[test]
5504 fn inclusive_prefixes_on_inclusive_c14n_is_rejected() {
5505 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
5506 xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#">
5507 <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315">
5508 <ec:InclusiveNamespaces PrefixList="ds"/>
5509 </CanonicalizationMethod>
5510 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5511 <Reference URI="">
5512 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
5513 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
5514 </Reference>
5515 </SignedInfo>"#;
5516 let doc = Document::parse(xml).unwrap();
5517
5518 let result = parse_signed_info(doc.root_element());
5519 assert!(matches!(
5520 result.unwrap_err(),
5521 ParseError::UnsupportedAlgorithm { .. }
5522 ));
5523 }
5524
5525 #[test]
5526 fn extra_element_after_digest_value() {
5527 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5528 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5529 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5530 <Reference URI="">
5531 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5532 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
5533 <Unexpected/>
5534 </Reference>
5535 </SignedInfo>"#;
5536 let doc = Document::parse(xml).unwrap();
5537 let result = parse_signed_info(doc.root_element());
5538 assert!(matches!(
5539 result.unwrap_err(),
5540 ParseError::InvalidStructure(_)
5541 ));
5542 }
5543
5544 #[test]
5545 fn digest_value_with_element_child_is_rejected() {
5546 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5547 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5548 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5549 <Reference URI="">
5550 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
5551 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAA=<Junk/>AAAA</DigestValue>
5552 </Reference>
5553 </SignedInfo>"#;
5554 let doc = Document::parse(xml).unwrap();
5555
5556 let result = parse_signed_info(doc.root_element());
5557 assert!(matches!(
5558 result.unwrap_err(),
5559 ParseError::InvalidStructure(_)
5560 ));
5561 }
5562
5563 #[test]
5564 fn wrong_namespace_on_signed_info() {
5565 let xml = r#"<SignedInfo xmlns="http://example.com/fake">
5566 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5567 </SignedInfo>"#;
5568 let doc = Document::parse(xml).unwrap();
5569 let result = parse_signed_info(doc.root_element());
5570 assert!(matches!(
5571 result.unwrap_err(),
5572 ParseError::InvalidStructure(_)
5573 ));
5574 }
5575
5576 #[test]
5579 fn base64_with_whitespace() {
5580 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5581 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5582 <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
5583 <Reference URI="">
5584 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
5585 <DigestValue>
5586 AAAAAAAA
5587 AAAAAAAAAAAAAAAAAAA=
5588 </DigestValue>
5589 </Reference>
5590 </SignedInfo>"#;
5591 let doc = Document::parse(xml).unwrap();
5592 let si = parse_signed_info(doc.root_element()).unwrap();
5593 assert_eq!(si.references[0].digest_value, vec![0u8; 20]);
5594 }
5595
5596 #[test]
5597 fn digest_value_ignores_processing_instruction_data() {
5598 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5601 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5602 <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
5603 <Reference URI="">
5604 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
5605 <DigestValue>AAAAAAAA<?ignored not-base64?>AAAAAAAAAAAAAAAAAAA=</DigestValue>
5606 </Reference>
5607 </SignedInfo>"#;
5608 let document = Document::parse(xml).expect("fixture must parse");
5609 let signed_info =
5610 parse_signed_info(document.root_element()).expect("PI must not alter digest data");
5611
5612 assert_eq!(signed_info.references[0].digest_value, vec![0_u8; 20]);
5613 }
5614
5615 #[test]
5616 fn base64_decode_digest_accepts_xml_whitespace_chars() {
5617 let digest =
5618 base64_decode_digest("AAAA\tAAAA\rAAAA\nAAAA AAAAAAAAAAA=", DigestAlgorithm::Sha1)
5619 .expect("XML whitespace in DigestValue must be accepted");
5620 assert_eq!(digest, vec![0u8; 20]);
5621 }
5622
5623 #[test]
5624 fn base64_decode_digest_rejects_non_xml_ascii_whitespace() {
5625 let err = base64_decode_digest(
5626 "AAAA\u{000C}AAAAAAAAAAAAAAAAAAAAAAA=",
5627 DigestAlgorithm::Sha1,
5628 )
5629 .expect_err("form-feed/vertical-tab in DigestValue must be rejected");
5630 assert!(matches!(err, ParseError::Base64(_)));
5631 }
5632
5633 #[test]
5634 fn base64_decode_digest_rejects_oversized_base64_before_decode() {
5635 let err = base64_decode_digest("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", DigestAlgorithm::Sha1)
5636 .expect_err("oversized DigestValue base64 must fail before decode");
5637 match err {
5638 ParseError::Base64(message) => {
5639 assert!(
5640 message.contains("DigestValue exceeds maximum allowed base64 length"),
5641 "unexpected message: {message}"
5642 );
5643 }
5644 other => panic!("expected ParseError::Base64, got {other:?}"),
5645 }
5646 }
5647
5648 #[test]
5651 fn saml_response_signed_info() {
5652 let xml = r##"<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
5653 <ds:SignedInfo>
5654 <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5655 <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5656 <ds:Reference URI="#_resp1">
5657 <ds:Transforms>
5658 <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
5659 <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5660 </ds:Transforms>
5661 <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5662 <ds:DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</ds:DigestValue>
5663 </ds:Reference>
5664 </ds:SignedInfo>
5665 <ds:SignatureValue>ZmFrZQ==</ds:SignatureValue>
5666 </ds:Signature>"##;
5667 let doc = Document::parse(xml).unwrap();
5668
5669 let sig_node = doc.root_element();
5671 let signed_info_node = sig_node
5672 .children()
5673 .find(|n| n.is_element() && n.tag_name().name() == "SignedInfo")
5674 .unwrap();
5675
5676 let si = parse_signed_info(signed_info_node).unwrap();
5677 assert_eq!(si.signature_method, SignatureAlgorithm::RsaSha256);
5678 assert_eq!(si.references.len(), 1);
5679 assert_eq!(si.references[0].uri.as_deref(), Some("#_resp1"));
5680 assert_eq!(si.references[0].transforms.len(), 2);
5681 assert_eq!(si.references[0].digest_value, vec![0u8; 32]);
5682 }
5683}