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