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 pub fn new(resources: &'a crate::policy::ResourcePolicy) -> Result<Self, ParseError> {
1371 resources.validate()?;
1372 Ok(Self {
1373 resources,
1374 xml_base: XmlBaseResolutionBudget::with_limits(
1375 resources.effective_xml_base_components(),
1376 resources.effective_xml_base_resolution_bytes(),
1377 ),
1378 usage: KeyInfoParseUsage::default(),
1379 })
1380 }
1381
1382 pub fn parse(&mut self, node: Node) -> Result<KeyInfo, ParseError> {
1384 self.parse_with_provider(node, crate::provider::default_provider())
1385 }
1386
1387 pub fn parse_with_provider(
1389 &mut self,
1390 node: Node,
1391 provider: &dyn crate::provider::CryptoProvider,
1392 ) -> Result<KeyInfo, ParseError> {
1393 parse_key_info_in_session(
1394 node,
1395 provider,
1396 &self.xml_base,
1397 self.resources,
1398 None,
1399 None,
1400 &mut self.usage,
1401 )
1402 }
1403
1404 #[cfg(feature = "xmlenc")]
1407 pub(crate) fn parse_agreement_role(
1408 &mut self,
1409 node: Node,
1410 provider: &dyn crate::provider::CryptoProvider,
1411 ) -> Result<KeyInfo, ParseError> {
1412 let namespace = "http://www.w3.org/2001/04/xmlenc#";
1413 if !node.has_tag_name((namespace, "OriginatorKeyInfo"))
1414 && !node.has_tag_name((namespace, "RecipientKeyInfo"))
1415 {
1416 return Err(ParseError::InvalidStructure(
1417 "invalid agreement key role".into(),
1418 ));
1419 }
1420 ensure_no_non_whitespace_text(node, "agreement KeyInfo")?;
1421 parse_key_info_contents_in_session(
1422 node,
1423 provider,
1424 &self.xml_base,
1425 self.resources,
1426 None,
1427 None,
1428 &mut self.usage,
1429 )
1430 }
1431}
1432
1433pub(crate) fn parse_key_info_with_provider(
1434 key_info_node: Node,
1435 provider: &dyn crate::provider::CryptoProvider,
1436) -> Result<KeyInfo, ParseError> {
1437 let xml_base_budget = XmlBaseResolutionBudget::default();
1438 parse_key_info_with_policy_budgets(
1439 key_info_node,
1440 provider,
1441 &xml_base_budget,
1442 &crate::policy::ResourcePolicy::default(),
1443 None,
1444 )
1445}
1446
1447pub(crate) fn parse_key_info_with_policy_budgets(
1448 key_info_node: Node,
1449 provider: &dyn crate::provider::CryptoProvider,
1450 xml_base_budget: &XmlBaseResolutionBudget,
1451 resources: &crate::policy::ResourcePolicy,
1452 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
1453) -> Result<KeyInfo, ParseError> {
1454 parse_key_info_with_policy_budgets_and_document_base(
1455 key_info_node,
1456 provider,
1457 xml_base_budget,
1458 resources,
1459 None,
1460 allowed,
1461 )
1462}
1463
1464pub(crate) fn parse_key_info_with_policy_budgets_and_document_base(
1465 key_info_node: Node,
1466 provider: &dyn crate::provider::CryptoProvider,
1467 xml_base_budget: &XmlBaseResolutionBudget,
1468 resources: &crate::policy::ResourcePolicy,
1469 document_base: Option<&str>,
1470 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
1471) -> Result<KeyInfo, ParseError> {
1472 parse_key_info_in_session(
1473 key_info_node,
1474 provider,
1475 xml_base_budget,
1476 resources,
1477 document_base,
1478 allowed,
1479 &mut KeyInfoParseUsage::default(),
1480 )
1481}
1482
1483fn parse_key_info_in_session(
1484 key_info_node: Node,
1485 provider: &dyn crate::provider::CryptoProvider,
1486 xml_base_budget: &XmlBaseResolutionBudget,
1487 resources: &crate::policy::ResourcePolicy,
1488 document_base: Option<&str>,
1489 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
1490 usage: &mut KeyInfoParseUsage,
1491) -> Result<KeyInfo, ParseError> {
1492 validate_key_info_container(key_info_node)?;
1493
1494 parse_key_info_contents_in_session(
1495 key_info_node,
1496 provider,
1497 xml_base_budget,
1498 resources,
1499 document_base,
1500 allowed,
1501 usage,
1502 )
1503}
1504
1505fn parse_key_info_contents_in_session(
1506 key_info_node: Node,
1507 provider: &dyn crate::provider::CryptoProvider,
1508 xml_base_budget: &XmlBaseResolutionBudget,
1509 resources: &crate::policy::ResourcePolicy,
1510 document_base: Option<&str>,
1511 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
1512 usage: &mut KeyInfoParseUsage,
1513) -> Result<KeyInfo, ParseError> {
1514 let mut sources = Vec::new();
1515 for (index, child) in element_children(key_info_node).enumerate() {
1519 if index >= MAX_KEY_INFO_CHILD_COUNT {
1520 return Err(ParseError::InvalidStructure(
1521 "KeyInfo contains too many child elements".into(),
1522 ));
1523 }
1524 match (child.tag_name().namespace(), child.tag_name().name()) {
1525 (Some(XMLDSIG_NS), "KeyName") => {
1526 ensure_no_element_children(child, "KeyName")?;
1527 let key_name =
1528 collect_text_content_bounded(child, MAX_KEY_NAME_TEXT_LEN, "KeyName")?;
1529 sources.push(KeyInfoSource::KeyName(key_name));
1530 }
1531 (Some(XMLDSIG_NS), "KeyValue") => {
1532 charge_embedded_key_candidate(&mut usage.embedded_candidates, resources)?;
1533 let key_value = parse_key_value_dispatch(child)?;
1534 sources.push(KeyInfoSource::KeyValue(key_value));
1535 }
1536 (Some(XMLDSIG_NS), "X509Data") => {
1537 let x509 = parse_x509_data_dispatch_with_budget_and_provider(
1538 child,
1539 &mut usage.x509_binary_bytes,
1540 &mut usage.embedded_candidates,
1541 provider,
1542 resources,
1543 allowed,
1544 )?;
1545 sources.push(KeyInfoSource::X509Data(x509));
1546 }
1547 (Some(XMLDSIG_NS), "RetrievalMethod") => {
1548 ensure_no_non_whitespace_text(child, "RetrievalMethod")?;
1549 let lexical_uri = child.attribute("URI").ok_or_else(|| {
1550 ParseError::InvalidStructure("RetrievalMethod requires URI".into())
1551 })?;
1552 if lexical_uri.len() > MAX_KEY_NAME_TEXT_LEN {
1553 return Err(ParseError::InvalidStructure(
1554 "RetrievalMethod URI exceeds maximum length".into(),
1555 ));
1556 }
1557 let uri = if lexical_uri.is_empty() || lexical_uri.starts_with('#') {
1558 lexical_uri.to_owned()
1559 } else {
1560 resolve_uri_from_node_with_document_base_with_budget(
1563 child,
1564 lexical_uri,
1565 document_base,
1566 xml_base_budget,
1567 )
1568 .map_err(|error| ParseError::InvalidStructure(error.to_string()))?
1569 };
1570 let resource_type = child.attribute("Type");
1571 if resource_type.is_some_and(|value| value.len() > MAX_KEY_NAME_TEXT_LEN) {
1572 return Err(ParseError::InvalidStructure(
1573 "RetrievalMethod Type exceeds maximum length".into(),
1574 ));
1575 }
1576 let resource_type = resource_type.map(str::to_owned);
1577 let transforms = if resource_type.as_deref()
1578 == Some("http://www.w3.org/2000/09/xmldsig#X509Data")
1579 {
1580 parse_retrieval_method_transforms(child, resources)?
1581 } else if element_children(child).next().is_some() {
1582 RetrievalMethodTransforms::Unsupported
1583 } else {
1584 RetrievalMethodTransforms::None
1585 };
1586 sources.push(KeyInfoSource::RetrievalMethod {
1587 uri,
1588 resource_type,
1589 transforms,
1590 });
1591 }
1592 (Some(XMLDSIG11_NS), "DEREncodedKeyValue") => {
1593 charge_embedded_key_candidate(&mut usage.embedded_candidates, resources)?;
1594 ensure_no_element_children(child, "DEREncodedKeyValue")?;
1595 let der = decode_der_encoded_key_value_base64(child)?;
1596 sources.push(KeyInfoSource::DerEncodedKeyValue(der));
1597 }
1598 (Some(XMLDSIG11_NS), "KeyInfoReference") => {
1599 ensure_no_element_children(child, "KeyInfoReference")?;
1600 ensure_no_non_whitespace_text(child, "KeyInfoReference")?;
1601 let lexical_uri = child.attribute("URI").ok_or_else(|| {
1602 ParseError::InvalidStructure("KeyInfoReference requires URI".into())
1603 })?;
1604 if lexical_uri.len() > MAX_KEY_NAME_TEXT_LEN {
1605 return Err(ParseError::InvalidStructure(
1606 "KeyInfoReference URI exceeds maximum length".into(),
1607 ));
1608 }
1609 let uri = if lexical_uri.is_empty() || lexical_uri.starts_with('#') {
1610 lexical_uri.to_owned()
1611 } else {
1612 resolve_uri_from_node_with_document_base_with_budget(
1613 child,
1614 lexical_uri,
1615 document_base,
1616 xml_base_budget,
1617 )
1618 .map_err(|error| ParseError::InvalidStructure(error.to_string()))?
1619 };
1620 sources.push(KeyInfoSource::KeyInfoReference { uri });
1621 }
1622 _ => {}
1623 }
1624 }
1625
1626 Ok(KeyInfo { sources })
1627}
1628
1629fn charge_embedded_key_candidate(
1630 embedded_key_candidates: &mut usize,
1631 resources: &crate::policy::ResourcePolicy,
1632) -> Result<(), ParseError> {
1633 *embedded_key_candidates = embedded_key_candidates.saturating_add(1);
1634 resources.validate_key_candidates(*embedded_key_candidates)?;
1635 Ok(())
1636}
1637
1638fn parse_retrieval_method_transforms(
1639 node: Node<'_, '_>,
1640 resources: &crate::policy::ResourcePolicy,
1641) -> Result<RetrievalMethodTransforms, ParseError> {
1642 let mut children = element_children(node);
1643 let Some(transforms) = children.next() else {
1644 return Ok(RetrievalMethodTransforms::None);
1645 };
1646 if children.next().is_some()
1647 || transforms.tag_name().namespace() != Some(XMLDSIG_NS)
1648 || transforms.tag_name().name() != "Transforms"
1649 {
1650 return Err(ParseError::InvalidStructure(
1651 "RetrievalMethod accepts only one optional ds:Transforms child".into(),
1652 ));
1653 }
1654 ensure_no_non_whitespace_text(transforms, "Transforms")?;
1655 let mut transform_children = element_children(transforms);
1656 let transform = transform_children.next().ok_or_else(|| {
1657 ParseError::InvalidStructure("RetrievalMethod Transforms must not be empty".into())
1658 })?;
1659 if transform_children.next().is_some()
1660 || transform.tag_name().namespace() != Some(XMLDSIG_NS)
1661 || transform.tag_name().name() != "Transform"
1662 || transform.attribute("Algorithm") != Some(transforms::XPATH_TRANSFORM_URI)
1663 {
1664 return Err(ParseError::InvalidStructure(
1665 "unsupported RetrievalMethod transform chain".into(),
1666 ));
1667 }
1668 ensure_no_non_whitespace_text(transform, "Transform")?;
1669 let mut parameters = element_children(transform);
1670 let xpath = parameters.next().ok_or_else(|| {
1671 ParseError::InvalidStructure("RetrievalMethod XPath parameter is missing".into())
1672 })?;
1673 if parameters.next().is_some()
1674 || xpath.tag_name().namespace() != Some(XMLDSIG_NS)
1675 || xpath.tag_name().name() != "XPath"
1676 {
1677 return Err(ParseError::InvalidStructure(
1678 "unsupported RetrievalMethod transform chain".into(),
1679 ));
1680 }
1681 ensure_no_element_children(xpath, "XPath")?;
1682 let expression =
1683 collect_text_content_bounded(xpath, MAX_RETRIEVAL_XPATH_TEXT_LEN, "RetrievalMethod XPath")?;
1684 let normalized_expression = expression.trim();
1685 let selects_x509_data = normalized_expression
1686 .strip_prefix("ancestor-or-self::")
1687 .and_then(|step| step.split_once(':'))
1688 .is_some_and(|(prefix, local)| {
1689 local == "X509Data" && xpath.lookup_namespace_uri(Some(prefix)) == Some(XMLDSIG_NS)
1690 });
1691 if !selects_x509_data {
1692 return Err(ParseError::InvalidStructure(
1693 "unsupported RetrievalMethod XPath selection".into(),
1694 ));
1695 }
1696 let namespaces = transforms::collect_xpath_namespaces_with_resources(xpath, resources)?;
1697 Ok(RetrievalMethodTransforms::X509DataNodeSetFilter {
1698 expression,
1699 namespaces,
1700 })
1701}
1702
1703fn element_children<'a>(node: Node<'a, 'a>) -> impl Iterator<Item = Node<'a, 'a>> {
1707 node.children().filter(|n| n.is_element())
1708}
1709
1710fn verify_ds_element(node: Node, expected_name: &'static str) -> Result<(), ParseError> {
1712 verify_namespaced_element(node, expected_name, XMLDSIG_NS, "ds")
1713}
1714
1715fn verify_dsig11_element(node: Node, expected_name: &'static str) -> Result<(), ParseError> {
1717 verify_namespaced_element(node, expected_name, XMLDSIG11_NS, "dsig11")
1718}
1719
1720fn verify_namespaced_element(
1721 node: Node,
1722 expected_name: &'static str,
1723 expected_namespace: &'static str,
1724 diagnostic_prefix: &'static str,
1725) -> Result<(), ParseError> {
1726 if !node.is_element() {
1727 return Err(ParseError::InvalidStructure(format!(
1728 "expected element <{expected_name}>, got non-element node"
1729 )));
1730 }
1731 let tag = node.tag_name();
1732 if tag.name() != expected_name || tag.namespace() != Some(expected_namespace) {
1733 return Err(ParseError::InvalidStructure(format!(
1734 "expected <{diagnostic_prefix}:{expected_name}>, got <{}{}>",
1735 tag.namespace()
1736 .map(|ns| format!("{{{ns}}}"))
1737 .unwrap_or_default(),
1738 tag.name()
1739 )));
1740 }
1741 Ok(())
1742}
1743
1744fn required_algorithm_attr<'a>(
1746 node: Node<'a, 'a>,
1747 element_name: &'static str,
1748) -> Result<&'a str, ParseError> {
1749 node.attribute("Algorithm").ok_or_else(|| {
1750 ParseError::InvalidStructure(format!("missing Algorithm attribute on <{element_name}>"))
1751 })
1752}
1753
1754fn parse_inclusive_prefixes(node: Node) -> Result<Option<String>, ParseError> {
1760 const EXCLUSIVE_C14N_NS_URI: &str = "http://www.w3.org/2001/10/xml-exc-c14n#";
1761
1762 for child in node.children() {
1763 if child.is_element() {
1764 let tag = child.tag_name();
1765 if tag.name() == "InclusiveNamespaces" && tag.namespace() == Some(EXCLUSIVE_C14N_NS_URI)
1766 {
1767 return child
1768 .attribute("PrefixList")
1769 .map(str::to_string)
1770 .ok_or_else(|| {
1771 ParseError::InvalidStructure(
1772 "missing PrefixList attribute on <InclusiveNamespaces>".into(),
1773 )
1774 })
1775 .map(Some);
1776 }
1777 }
1778 }
1779
1780 Ok(None)
1781}
1782
1783pub(crate) fn parse_key_value_dispatch(node: Node) -> Result<KeyValueInfo, ParseError> {
1784 verify_ds_element(node, "KeyValue")?;
1785 ensure_no_non_whitespace_text(node, "KeyValue")?;
1786
1787 let mut children = element_children(node);
1788 let Some(first_child) = children.next() else {
1789 return Err(ParseError::InvalidStructure(
1790 "KeyValue must contain exactly one key-value child".into(),
1791 ));
1792 };
1793 if children.next().is_some() {
1794 return Err(ParseError::InvalidStructure(
1795 "KeyValue must contain exactly one key-value child".into(),
1796 ));
1797 }
1798
1799 match (
1800 first_child.tag_name().namespace(),
1801 first_child.tag_name().name(),
1802 ) {
1803 (Some(XMLDSIG_NS), "RSAKeyValue") => parse_rsa_key_value(first_child),
1804 (Some(XMLDSIG_NS), "DSAKeyValue") => parse_dsa_key_value(first_child),
1805 (Some(XMLDSIG11_NS), "ECKeyValue") => parse_ec_key_value(first_child),
1806 (Some("http://www.w3.org/2001/04/xmlenc#"), "DHKeyValue") => {
1807 parse_dh_key_value(first_child)
1808 }
1809 (namespace, child_name) => Ok(KeyValueInfo::Unsupported {
1810 namespace: namespace.map(str::to_string),
1811 local_name: child_name.to_string(),
1812 }),
1813 }
1814}
1815
1816pub(crate) fn validate_key_info_container(node: Node) -> Result<(), ParseError> {
1817 verify_ds_element(node, "KeyInfo")?;
1818 ensure_no_non_whitespace_text(node, "KeyInfo")
1819}
1820
1821fn parse_dsa_key_value(node: Node<'_, '_>) -> Result<KeyValueInfo, ParseError> {
1822 verify_ds_element(node, "DSAKeyValue")?;
1823 ensure_no_non_whitespace_text(node, "DSAKeyValue")?;
1824 let children = element_children(node).collect::<Vec<_>>();
1825 let mut index = 0;
1826 let p = take_dsa_crypto_binary(&children, &mut index, "P")?;
1827 let q = take_dsa_crypto_binary(&children, &mut index, "Q")?;
1828 if p.is_some() != q.is_some() {
1829 return Err(ParseError::InvalidStructure(
1830 "DSAKeyValue P and Q must be present together".into(),
1831 ));
1832 }
1833 let g = take_dsa_crypto_binary(&children, &mut index, "G")?;
1834 let y = take_dsa_crypto_binary(&children, &mut index, "Y")?
1835 .ok_or_else(|| ParseError::InvalidStructure("DSAKeyValue requires Y".into()))?;
1836 let _j = take_dsa_crypto_binary(&children, &mut index, "J")?;
1837 let seed = take_dsa_crypto_binary(&children, &mut index, "Seed")?;
1838 let counter = take_dsa_crypto_binary(&children, &mut index, "PgenCounter")?;
1839 if seed.is_some() != counter.is_some() {
1840 return Err(ParseError::InvalidStructure(
1841 "DSAKeyValue Seed and PgenCounter must be present together".into(),
1842 ));
1843 }
1844 if index != children.len() {
1845 return Err(ParseError::InvalidStructure(
1846 "DSAKeyValue children do not match the XMLDSig schema order".into(),
1847 ));
1848 }
1849 Ok(KeyValueInfo::Dsa { p, q, g, y })
1850}
1851
1852fn parse_dh_key_value(node: Node<'_, '_>) -> Result<KeyValueInfo, ParseError> {
1853 const NS: &str = "http://www.w3.org/2001/04/xmlenc#";
1854 ensure_no_non_whitespace_text(node, "DHKeyValue")?;
1855 let mut children = element_children(node).peekable();
1856 let mut take = |name: &'static str| -> Result<Option<Vec<u8>>, ParseError> {
1857 if !children
1858 .peek()
1859 .is_some_and(|child| child.has_tag_name((NS, name)))
1860 {
1861 return Ok(None);
1862 }
1863 let child = children.next().expect("peeked child");
1864 ensure_no_element_children(child, name)?;
1865 decode_crypto_binary(
1866 child,
1867 name,
1868 crate::hard_limits::DH_MODULUS_BIT_CEILING.div_ceil(8),
1869 )
1870 .map(Some)
1871 };
1872 let p = take("P")?;
1876 let q = take("Q")?;
1877 let generator = take("Generator")?;
1878 if p.is_some() != q.is_some() || p.is_some() != generator.is_some() {
1879 return Err(ParseError::InvalidStructure(
1880 "DHKeyValue requires P, Q and Generator together".into(),
1881 ));
1882 }
1883 let public = take("Public")?
1884 .ok_or_else(|| ParseError::InvalidStructure("DHKeyValue requires Public".into()))?;
1885 let seed = take("seed")?;
1886 let pgen_counter = take("pgenCounter")?;
1887 if seed.is_some() != pgen_counter.is_some() || children.next().is_some() {
1888 return Err(ParseError::InvalidStructure(
1889 "DHKeyValue children do not match the XMLEnc schema order".into(),
1890 ));
1891 }
1892 Ok(KeyValueInfo::Dh {
1893 p,
1894 q,
1895 generator,
1896 public,
1897 seed,
1898 pgen_counter,
1899 })
1900}
1901
1902fn take_dsa_crypto_binary(
1903 children: &[Node<'_, '_>],
1904 index: &mut usize,
1905 name: &'static str,
1906) -> Result<Option<Vec<u8>>, ParseError> {
1907 let Some(&child) = children.get(*index) else {
1908 return Ok(None);
1909 };
1910 if !is_ds_element(child, name) {
1911 return Ok(None);
1912 }
1913 *index += 1;
1914 ensure_no_element_children(child, name)?;
1915 decode_crypto_binary(child, name, MAX_RSA_MODULUS_LEN).map(Some)
1916}
1917
1918fn is_ds_element(node: Node<'_, '_>, name: &str) -> bool {
1919 node.tag_name().namespace() == Some(XMLDSIG_NS) && node.tag_name().name() == name
1920}
1921
1922fn parse_ec_key_value(node: Node<'_, '_>) -> Result<KeyValueInfo, ParseError> {
1923 verify_dsig11_element(node, "ECKeyValue")?;
1924 ensure_no_non_whitespace_text(node, "ECKeyValue")?;
1925
1926 let mut children = element_children(node);
1927 let Some(named_curve_node) = children.next() else {
1928 return Ok(KeyValueInfo::InvalidEcKeyValue);
1929 };
1930 if named_curve_node.tag_name().namespace() == Some(XMLDSIG11_NS)
1931 && named_curve_node.tag_name().name() == "ECParameters"
1932 {
1933 return Ok(KeyValueInfo::Unsupported {
1934 namespace: Some(XMLDSIG11_NS.to_string()),
1935 local_name: "ECKeyValue".into(),
1936 });
1937 }
1938 if named_curve_node.tag_name().namespace() != Some(XMLDSIG11_NS)
1939 || named_curve_node.tag_name().name() != "NamedCurve"
1940 {
1941 return Ok(KeyValueInfo::InvalidEcKeyValue);
1942 }
1943 ensure_no_element_children(named_curve_node, "NamedCurve")?;
1944 ensure_no_non_whitespace_text(named_curve_node, "NamedCurve")?;
1945 let Some((curve_oid, expected_public_key_len)) =
1946 (match parse_ec_named_curve_oid(named_curve_node) {
1947 Ok(curve) => curve,
1948 Err(_) => return Ok(KeyValueInfo::InvalidEcKeyValue),
1949 })
1950 else {
1951 return Ok(KeyValueInfo::Unsupported {
1952 namespace: Some(XMLDSIG11_NS.to_string()),
1953 local_name: "ECKeyValue".into(),
1954 });
1955 };
1956
1957 let Some(public_key_node) = children.next() else {
1958 return Ok(KeyValueInfo::InvalidEcKeyValue);
1959 };
1960 if public_key_node.tag_name().namespace() != Some(XMLDSIG11_NS)
1961 || public_key_node.tag_name().name() != "PublicKey"
1962 {
1963 return Ok(KeyValueInfo::InvalidEcKeyValue);
1964 }
1965 ensure_no_element_children(public_key_node, "PublicKey")?;
1966 if children.next().is_some() {
1967 return Ok(KeyValueInfo::InvalidEcKeyValue);
1968 }
1969
1970 let public_key = match decode_crypto_binary(public_key_node, "PublicKey", MAX_EC_PUBLIC_KEY_LEN)
1971 {
1972 Ok(public_key) => public_key,
1973 Err(_) => return Ok(KeyValueInfo::InvalidEcKeyValue),
1974 };
1975 if validate_ec_public_key_point(&public_key, expected_public_key_len).is_err() {
1976 return Ok(KeyValueInfo::InvalidEcKeyValue);
1977 }
1978
1979 Ok(KeyValueInfo::Ec {
1980 curve_oid,
1981 public_key,
1982 })
1983}
1984
1985fn parse_ec_named_curve_oid(node: Node<'_, '_>) -> Result<Option<(String, usize)>, ParseError> {
1986 let uri = node.attribute("URI").ok_or_else(|| {
1987 ParseError::InvalidStructure("ECKeyValue NamedCurve must include URI attribute".into())
1988 })?;
1989 let curve_oid = uri.strip_prefix("urn:oid:").unwrap_or(uri);
1990 if curve_oid.is_empty() {
1991 return Err(ParseError::InvalidStructure(
1992 "ECKeyValue NamedCurve URI must not be empty".into(),
1993 ));
1994 }
1995 let Some(public_key_len) = ec_public_key_len(curve_oid) else {
1996 return Ok(None);
1997 };
1998 Ok(Some((curve_oid.to_string(), public_key_len)))
1999}
2000
2001fn ec_public_key_len(curve_oid: &str) -> Option<usize> {
2002 match curve_oid {
2003 EC_P256_OID => Some(65),
2004 EC_P384_OID => Some(97),
2005 EC_P521_OID => Some(133),
2006 _ => None,
2007 }
2008}
2009
2010fn validate_ec_public_key_point(public_key: &[u8], expected_len: usize) -> Result<(), ParseError> {
2011 if public_key.len() != expected_len {
2012 return Err(ParseError::InvalidStructure(
2013 "ECKeyValue PublicKey length does not match NamedCurve".into(),
2014 ));
2015 }
2016 if public_key.first().copied() != Some(0x04) {
2017 return Err(ParseError::InvalidStructure(
2018 "ECKeyValue PublicKey must be an uncompressed SEC1 point".into(),
2019 ));
2020 }
2021 Ok(())
2022}
2023
2024fn parse_rsa_key_value(node: Node<'_, '_>) -> Result<KeyValueInfo, ParseError> {
2025 verify_ds_element(node, "RSAKeyValue")?;
2026 ensure_no_non_whitespace_text(node, "RSAKeyValue")?;
2027
2028 let mut children = element_children(node);
2029 let modulus_node = children.next().ok_or_else(|| {
2030 ParseError::InvalidStructure("RSAKeyValue requires Modulus and Exponent".into())
2031 })?;
2032 verify_ds_element(modulus_node, "Modulus")?;
2033 ensure_no_element_children(modulus_node, "Modulus")?;
2034
2035 let exponent_node = children.next().ok_or_else(|| {
2036 ParseError::InvalidStructure("RSAKeyValue requires Modulus and Exponent".into())
2037 })?;
2038 verify_ds_element(exponent_node, "Exponent")?;
2039 ensure_no_element_children(exponent_node, "Exponent")?;
2040 if children.next().is_some() {
2041 return Err(ParseError::InvalidStructure(
2042 "RSAKeyValue must contain exactly Modulus followed by Exponent".into(),
2043 ));
2044 }
2045
2046 Ok(KeyValueInfo::Rsa {
2047 modulus: decode_crypto_binary(modulus_node, "Modulus", MAX_RSA_MODULUS_LEN)?,
2048 exponent: decode_crypto_binary(exponent_node, "Exponent", MAX_RSA_EXPONENT_LEN)?,
2049 })
2050}
2051
2052fn decode_crypto_binary(
2053 node: Node<'_, '_>,
2054 element_name: &'static str,
2055 max_decoded_len: usize,
2056) -> Result<Vec<u8>, ParseError> {
2057 let max_base64_len = max_decoded_len.div_ceil(3) * 4;
2058 let payload = super::whitespace::XmlBase64Payload::bounded(node, usize::MAX, max_base64_len)
2059 .map_err(|reason| xml_base64_payload_error(element_name, reason))?;
2060 if payload.normalized_len > max_base64_len {
2061 return Err(ParseError::InvalidStructure(format!(
2062 "{element_name} exceeds maximum allowed base64 length"
2063 )));
2064 }
2065 if payload.decoded_len == 0 {
2066 return Err(ParseError::InvalidStructure(format!(
2067 "{element_name} must not be empty"
2068 )));
2069 }
2070 if payload.decoded_len > max_decoded_len {
2071 return Err(ParseError::InvalidStructure(format!(
2072 "{element_name} exceeds maximum allowed binary length"
2073 )));
2074 }
2075 payload
2076 .decode()
2077 .map_err(|reason| ParseError::Base64(format!("{element_name}: {reason}")))
2078}
2079
2080pub(crate) fn parse_x509_data_dispatch_with_budget_and_provider(
2081 node: Node,
2082 total_binary_len: &mut usize,
2083 embedded_key_candidates: &mut usize,
2084 provider: &dyn crate::provider::CryptoProvider,
2085 resources: &crate::policy::ResourcePolicy,
2086 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
2087) -> Result<X509DataInfo, ParseError> {
2088 verify_ds_element(node, "X509Data")?;
2089 ensure_no_non_whitespace_text(node, "X509Data")?;
2090
2091 let mut info = X509DataInfo::default();
2092 for child in element_children(node) {
2093 match (child.tag_name().namespace(), child.tag_name().name()) {
2094 (Some(XMLDSIG_NS), "X509Certificate") => {
2095 charge_embedded_key_candidate(embedded_key_candidates, resources)?;
2096 ensure_no_element_children(child, "X509Certificate")?;
2097 ensure_x509_data_entry_budget(&info)?;
2098 let cert =
2099 decode_x509_base64(child, "X509Certificate", total_binary_len, resources)?;
2100 let parsed_cert = parse_x509_certificate(cert.as_slice())?;
2101 info.parsed_certificates.push(parsed_cert);
2102 info.certificates.push(cert);
2103 }
2104 (Some(XMLDSIG_NS), "X509SubjectName") => {
2105 ensure_no_element_children(child, "X509SubjectName")?;
2106 ensure_x509_data_entry_budget(&info)?;
2107 let subject_name = collect_text_content_bounded(
2108 child,
2109 MAX_X509_SUBJECT_NAME_TEXT_LEN,
2110 "X509SubjectName",
2111 )?;
2112 info.subject_names.push(subject_name);
2113 }
2114 (Some(XMLDSIG_NS), "X509IssuerSerial") => {
2115 ensure_x509_data_entry_budget(&info)?;
2116 let issuer_serial = parse_x509_issuer_serial(child)?;
2117 info.issuer_serials.push(issuer_serial);
2118 }
2119 (Some(XMLDSIG_NS), "X509SKI") => {
2120 ensure_no_element_children(child, "X509SKI")?;
2121 ensure_x509_data_entry_budget(&info)?;
2122 let ski = decode_x509_base64(child, "X509SKI", total_binary_len, resources)?;
2123 info.skis.push(ski);
2124 }
2125 (Some(XMLDSIG_NS), "X509CRL") => {
2126 ensure_no_element_children(child, "X509CRL")?;
2127 ensure_x509_data_entry_budget(&info)?;
2128 let crl = decode_x509_base64(child, "X509CRL", total_binary_len, resources)?;
2129 info.crls.push(crl);
2130 }
2131 (Some(XMLDSIG11_NS), "X509Digest") => {
2132 ensure_no_element_children(child, "X509Digest")?;
2133 ensure_x509_data_entry_budget(&info)?;
2134 let algorithm = required_algorithm_attr(child, "X509Digest")?;
2135 let digest = decode_x509_base64(child, "X509Digest", total_binary_len, resources)?;
2136 info.digests.push((algorithm.to_string(), digest));
2137 }
2138 (Some(XMLDSIG_NS), child_name) | (Some(XMLDSIG11_NS), child_name) => {
2139 return Err(ParseError::InvalidStructure(format!(
2140 "X509Data contains unsupported XMLDSig child element <{child_name}>"
2141 )));
2142 }
2143 _ => {}
2144 }
2145 }
2146
2147 info.certificate_chain = build_x509_certificate_chain(&info, provider, allowed)?;
2148 Ok(info)
2149}
2150
2151pub(crate) fn validate_x509_digest_policy(
2154 node: Node,
2155 check: &impl Fn(DigestAlgorithm) -> Result<(), crate::policy::PolicyViolation>,
2156) -> Result<(), ParseError> {
2157 fn check_data(
2158 data: Node,
2159 check: &impl Fn(DigestAlgorithm) -> Result<(), crate::policy::PolicyViolation>,
2160 ) -> Result<(), ParseError> {
2161 for child in element_children(data) {
2162 if child.has_tag_name((XMLDSIG11_NS, "X509Digest")) {
2163 let uri = required_algorithm_attr(child, "X509Digest")?;
2164 if let Some(algorithm) = DigestAlgorithm::from_uri(uri) {
2168 check(algorithm)?;
2169 }
2170 }
2171 }
2172 Ok(())
2173 }
2174 if node.has_tag_name((XMLDSIG_NS, "X509Data")) {
2175 return check_data(node, check);
2176 }
2177 for child in element_children(node) {
2178 if child.has_tag_name((XMLDSIG_NS, "X509Data")) {
2179 check_data(child, check)?;
2180 }
2181 }
2182 Ok(())
2183}
2184
2185fn build_x509_certificate_chain(
2186 info: &X509DataInfo,
2187 provider: &dyn crate::provider::CryptoProvider,
2188 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
2189) -> Result<Vec<usize>, ParseError> {
2190 if info.parsed_certificates.is_empty() {
2191 return Ok(Vec::new());
2192 }
2193
2194 let signing_idx = select_x509_signing_certificate(info, provider)?;
2195 build_x509_certificate_chain_from(info, signing_idx, provider, allowed)
2196 .map_err(ParseError::from)
2197}
2198
2199#[derive(Debug, Clone, PartialEq, Eq)]
2200pub(crate) enum X509ChainBuildError {
2201 Policy(crate::policy::PolicyViolation),
2202 InconsistentMetadata,
2203 DepthExceeded,
2204 Cycle,
2205 IssuerSignatureMismatch,
2206 AmbiguousIssuer,
2207 UnsupportedSignatureAlgorithm { oid: String },
2208 Provider(crate::provider::ProviderError),
2209}
2210
2211impl From<X509ChainBuildError> for ParseError {
2212 fn from(error: X509ChainBuildError) -> Self {
2213 let reason = match error {
2214 X509ChainBuildError::InconsistentMetadata => {
2215 "X509Data certificate metadata is inconsistent"
2216 }
2217 X509ChainBuildError::DepthExceeded => {
2218 "X509Data certificate chain exceeds maximum depth"
2219 }
2220 X509ChainBuildError::Cycle => "X509Data certificate chain contains a cycle",
2221 X509ChainBuildError::IssuerSignatureMismatch => {
2222 "X509Data issuer candidates do not verify the certificate signature"
2223 }
2224 X509ChainBuildError::AmbiguousIssuer => {
2225 "X509Data certificate chain contains ambiguous issuer certificates"
2226 }
2227 X509ChainBuildError::UnsupportedSignatureAlgorithm { oid } => {
2228 return Self::InvalidStructure(format!(
2229 "X509Data certificate chain uses unsupported signature algorithm {oid}"
2230 ));
2231 }
2232 X509ChainBuildError::Provider(error) => return Self::Provider(error),
2233 X509ChainBuildError::Policy(error) => return Self::Policy(error),
2234 };
2235 Self::InvalidStructure(reason.into())
2236 }
2237}
2238
2239pub(crate) fn build_x509_certificate_chain_from(
2241 info: &X509DataInfo,
2242 signing_idx: usize,
2243 provider: &dyn crate::provider::CryptoProvider,
2244 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
2245) -> Result<Vec<usize>, X509ChainBuildError> {
2246 if signing_idx >= info.parsed_certificates.len()
2247 || info.parsed_certificates.len() != info.certificates.len()
2248 {
2249 return Err(X509ChainBuildError::InconsistentMetadata);
2250 }
2251 let mut chain = vec![signing_idx];
2252
2253 loop {
2254 let current_idx = *chain
2255 .last()
2256 .expect("chain starts with signing certificate index");
2257 let current = &info.parsed_certificates[current_idx];
2258 if distinguished_names_equal(¤t.subject_dn, ¤t.issuer_dn) {
2259 break;
2260 }
2261
2262 let candidates = info
2263 .parsed_certificates
2264 .iter()
2265 .enumerate()
2266 .filter(|(idx, cert)| {
2267 *idx != current_idx
2268 && distinguished_names_equal(&cert.subject_dn, ¤t.issuer_dn)
2269 })
2270 .map(|(idx, _)| idx)
2271 .collect::<Vec<_>>();
2272
2273 let issuer_idx = match candidates.as_slice() {
2274 [] => break,
2275 [issuer_idx] => *issuer_idx,
2276 _ => {
2277 let mut verified = Vec::new();
2278 let mut unsupported_oid = None;
2279 for issuer_idx in candidates {
2280 match certificate_signature_matches_with_provider(
2281 &info.certificates[current_idx],
2282 &info.certificates[issuer_idx],
2283 provider,
2284 allowed,
2285 ) {
2286 Ok(true) => verified.push(issuer_idx),
2287 Ok(false) => {}
2288 Err(super::X509ChainError::Provider(
2289 crate::provider::ProviderError::Unsupported {
2290 operation: crate::provider::ProviderOperation::VerifyCertificate,
2291 algorithm: Some(oid),
2292 },
2293 )) => {
2294 unsupported_oid.get_or_insert(oid);
2295 }
2296 Err(super::X509ChainError::Provider(error)) => {
2297 return Err(X509ChainBuildError::Provider(error));
2298 }
2299 Err(super::X509ChainError::UnsupportedSignatureAlgorithm { oid }) => {
2300 unsupported_oid.get_or_insert(oid);
2301 }
2302 Err(super::X509ChainError::Policy(error)) => {
2303 return Err(X509ChainBuildError::Policy(error));
2304 }
2305 Err(_) => return Err(X509ChainBuildError::IssuerSignatureMismatch),
2306 }
2307 }
2308 match verified.as_slice() {
2309 [issuer_idx] => *issuer_idx,
2310 [] => {
2311 if let Some(oid) = unsupported_oid {
2312 return Err(X509ChainBuildError::UnsupportedSignatureAlgorithm { oid });
2313 }
2314 return Err(X509ChainBuildError::IssuerSignatureMismatch);
2315 }
2316 _ => return Err(X509ChainBuildError::AmbiguousIssuer),
2317 }
2318 }
2319 };
2320 if chain.contains(&issuer_idx) {
2321 return Err(X509ChainBuildError::Cycle);
2322 }
2323 if chain.len() == crate::hard_limits::X509_CHAIN_DEPTH_CEILING {
2324 return Err(X509ChainBuildError::DepthExceeded);
2325 }
2326 chain.push(issuer_idx);
2327 }
2328
2329 Ok(chain)
2330}
2331
2332pub(crate) fn build_x509_certificate_paths_to_trusted_prefix(
2336 info: &X509DataInfo,
2337 signing_idx: usize,
2338 trusted_prefix_len: usize,
2339 max_depth: usize,
2340 max_candidate_paths: usize,
2341 provider: &dyn crate::provider::CryptoProvider,
2342 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
2343) -> Result<Vec<Vec<usize>>, X509ChainBuildError> {
2344 if trusted_prefix_len > info.certificates.len() {
2345 return Err(X509ChainBuildError::InconsistentMetadata);
2346 }
2347 build_x509_certificate_paths(
2348 info,
2349 signing_idx,
2350 |index| index < trusted_prefix_len,
2351 false,
2352 X509PathBounds {
2353 max_depth,
2354 max_candidate_paths,
2355 },
2356 provider,
2357 allowed,
2358 )
2359}
2360
2361pub(crate) fn build_x509_certificate_paths_to_selector_targets(
2365 info: &X509DataInfo,
2366 signing_idx: usize,
2367 targets: &[usize],
2368 max_depth: usize,
2369 max_candidate_paths: usize,
2370 provider: &dyn crate::provider::CryptoProvider,
2371 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
2372) -> Result<Vec<Vec<usize>>, X509ChainBuildError> {
2373 if targets
2374 .iter()
2375 .any(|index| *index >= info.certificates.len())
2376 {
2377 return Err(X509ChainBuildError::InconsistentMetadata);
2378 }
2379 build_x509_certificate_paths(
2380 info,
2381 signing_idx,
2382 |index| targets.contains(&index),
2383 true,
2384 X509PathBounds {
2385 max_depth,
2386 max_candidate_paths,
2387 },
2388 provider,
2389 allowed,
2390 )
2391}
2392
2393struct X509PathBounds {
2394 max_depth: usize,
2395 max_candidate_paths: usize,
2396}
2397
2398fn build_x509_certificate_paths(
2399 info: &X509DataInfo,
2400 signing_idx: usize,
2401 is_terminal: impl Fn(usize) -> bool,
2402 continue_after_terminal: bool,
2403 bounds: X509PathBounds,
2404 provider: &dyn crate::provider::CryptoProvider,
2405 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
2406) -> Result<Vec<Vec<usize>>, X509ChainBuildError> {
2407 let X509PathBounds {
2408 max_depth,
2409 max_candidate_paths,
2410 } = bounds;
2411 if signing_idx >= info.parsed_certificates.len()
2412 || info.parsed_certificates.len() != info.certificates.len()
2413 {
2414 return Err(X509ChainBuildError::InconsistentMetadata);
2415 }
2416 if max_candidate_paths == 0 {
2417 return Err(X509ChainBuildError::AmbiguousIssuer);
2418 }
2419
2420 let mut pending = vec![vec![signing_idx]];
2421 let mut completed = Vec::new();
2422 let mut generated_paths = 1usize;
2423 let mut depth_exceeded = false;
2424 let mut unsupported_oid = None;
2425 let mut policy_rejection = None;
2426 let mut issuer_cache = vec![None; info.parsed_certificates.len()];
2427 while let Some(path) = pending.pop() {
2428 let current_idx = *path
2429 .last()
2430 .expect("candidate path starts with signing certificate index");
2431 if is_terminal(current_idx) {
2432 completed.push(path.clone());
2433 if !continue_after_terminal {
2434 continue;
2435 }
2436 }
2437 if path.len() == max_depth {
2438 depth_exceeded = true;
2439 continue;
2440 }
2441
2442 let current = &info.parsed_certificates[current_idx];
2443 if issuer_cache[current_idx].is_none() {
2444 let mut verified = Vec::new();
2445 for (issuer_idx, issuer) in info.parsed_certificates.iter().enumerate() {
2446 if !distinguished_names_equal(&issuer.subject_dn, ¤t.issuer_dn) {
2447 continue;
2448 }
2449 match certificate_signature_matches_with_provider(
2450 &info.certificates[current_idx],
2451 &info.certificates[issuer_idx],
2452 provider,
2453 allowed,
2454 ) {
2455 Ok(true) => verified.push(issuer_idx),
2456 Ok(false) => {}
2457 Err(super::X509ChainError::Provider(
2458 crate::provider::ProviderError::Unsupported {
2459 operation: crate::provider::ProviderOperation::VerifyCertificate,
2460 algorithm: Some(oid),
2461 },
2462 )) => {
2463 unsupported_oid.get_or_insert(oid);
2466 continue;
2467 }
2468 Err(super::X509ChainError::Provider(error)) => {
2469 return Err(X509ChainBuildError::Provider(error));
2470 }
2471 Err(super::X509ChainError::UnsupportedSignatureAlgorithm { oid }) => {
2472 unsupported_oid.get_or_insert(oid);
2475 break;
2476 }
2477 Err(super::X509ChainError::Policy(error)) => {
2478 policy_rejection.get_or_insert(error);
2484 break;
2485 }
2486 Err(_) => return Err(X509ChainBuildError::IssuerSignatureMismatch),
2487 }
2488 }
2489 issuer_cache[current_idx] = Some(verified);
2490 }
2491 let issuers = issuer_cache[current_idx]
2492 .as_ref()
2493 .expect("issuer cache entry was initialized");
2494 let issuers = issuers
2495 .iter()
2496 .copied()
2497 .filter(|issuer_idx| !path.contains(issuer_idx))
2498 .collect::<Vec<_>>();
2499 if generated_paths.saturating_add(issuers.len()) > max_candidate_paths {
2500 return Err(X509ChainBuildError::AmbiguousIssuer);
2501 }
2502 generated_paths += issuers.len();
2503 for issuer_idx in issuers {
2504 let mut candidate = path.clone();
2505 candidate.push(issuer_idx);
2506 pending.push(candidate);
2507 }
2508 }
2509
2510 if completed.is_empty() {
2511 if let Some(error) = policy_rejection {
2512 return Err(X509ChainBuildError::Policy(error));
2513 }
2514 if let Some(oid) = unsupported_oid {
2515 return Err(X509ChainBuildError::UnsupportedSignatureAlgorithm { oid });
2516 }
2517 if depth_exceeded {
2518 return Err(X509ChainBuildError::DepthExceeded);
2519 }
2520 }
2521 Ok(completed)
2522}
2523
2524fn select_x509_signing_certificate(
2525 info: &X509DataInfo,
2526 provider: &dyn crate::provider::CryptoProvider,
2527) -> Result<usize, ParseError> {
2528 let has_lookup_identifiers = x509_data_has_lookup_identifiers(info);
2529 let mut candidates = Vec::new();
2530 if has_lookup_identifiers {
2531 for (idx, (parsed, der)) in info
2532 .parsed_certificates
2533 .iter()
2534 .zip(&info.certificates)
2535 .enumerate()
2536 {
2537 if x509_certificate_matches_any_selector(info, parsed, der, provider)? {
2538 candidates.push(idx);
2539 }
2540 }
2541 if !x509_selector_categories_match_chain(info, provider)? {
2542 return Err(ParseError::InvalidStructure(
2543 "X509Data lookup identifiers do not match the embedded certificate chain".into(),
2544 ));
2545 }
2546 }
2547
2548 match candidates.as_slice() {
2549 [idx] => return Ok(*idx),
2550 [] if has_lookup_identifiers => {
2551 return Err(ParseError::InvalidStructure(
2552 "X509Data lookup identifiers do not match any embedded certificate".into(),
2553 ));
2554 }
2555 [] => {}
2556 _ => {}
2557 }
2558
2559 let leaf_candidates = info
2560 .parsed_certificates
2561 .iter()
2562 .enumerate()
2563 .filter(|(_, cert)| {
2564 !distinguished_names_equal(&cert.subject_dn, &cert.issuer_dn)
2565 && !info
2566 .parsed_certificates
2567 .iter()
2568 .any(|other| distinguished_names_equal(&other.issuer_dn, &cert.subject_dn))
2569 })
2570 .map(|(idx, _)| idx)
2571 .collect::<Vec<_>>();
2572
2573 let selected_leaves = leaf_candidates
2574 .iter()
2575 .filter(|idx| !has_lookup_identifiers || candidates.contains(idx))
2576 .copied()
2577 .collect::<Vec<_>>();
2578
2579 match selected_leaves.as_slice() {
2580 [idx] => Ok(*idx),
2581 [] if !has_lookup_identifiers => Ok(0),
2582 [] => Err(ParseError::InvalidStructure(
2583 "X509Data lookup identifiers match multiple certificates without a unique signing certificate"
2584 .into(),
2585 )),
2586 _ => Err(ParseError::InvalidStructure(
2587 if has_lookup_identifiers {
2588 "X509Data lookup identifiers match multiple certificates"
2589 } else {
2590 "X509Data contains multiple possible signing certificates"
2591 }
2592 .into(),
2593 )),
2594 }
2595}
2596
2597pub(crate) fn x509_data_has_lookup_identifiers(info: &X509DataInfo) -> bool {
2598 !info.subject_names.is_empty()
2599 || !info.issuer_serials.is_empty()
2600 || !info.skis.is_empty()
2601 || !info.digests.is_empty()
2602}
2603
2604pub(crate) fn x509_certificate_matches_any_selector(
2605 info: &X509DataInfo,
2606 certificate: &ParsedX509Certificate,
2607 certificate_der: &[u8],
2608 provider: &dyn crate::provider::CryptoProvider,
2609) -> Result<bool, ParseError> {
2610 let subject_match = info
2611 .subject_names
2612 .iter()
2613 .any(|subject| distinguished_names_equal(subject, &certificate.subject_dn));
2614 let mut issuer_serial_match = false;
2615 for (issuer, serial) in &info.issuer_serials {
2616 let serial_hex = x509_serial_decimal_to_hex(serial).ok_or_else(|| {
2617 ParseError::InvalidStructure(
2618 "X509Data lookup identifiers contain an invalid serial number".into(),
2619 )
2620 })?;
2621 issuer_serial_match |= distinguished_names_equal(issuer, &certificate.issuer_dn)
2622 && serial_hex == certificate.serial_number_hex;
2623 }
2624 let ski_match = certificate
2625 .subject_key_identifier
2626 .as_ref()
2627 .is_some_and(|certificate_ski| info.skis.iter().any(|ski| ski == certificate_ski));
2628 let mut digest_match = false;
2629 for (algorithm_uri, expected) in &info.digests {
2630 let algorithm = DigestAlgorithm::from_uri(algorithm_uri).ok_or_else(|| {
2631 ParseError::UnsupportedAlgorithm {
2632 uri: algorithm_uri.clone(),
2633 }
2634 })?;
2635 digest_match |= constant_time_eq(
2636 &compute_digest_with_provider(provider, algorithm, certificate_der)?,
2637 expected,
2638 );
2639 }
2640 Ok(subject_match || issuer_serial_match || ski_match || digest_match)
2641}
2642
2643pub fn x509_certificate_matches_selectors(
2649 info: &X509DataInfo,
2650 certificate_der: &[u8],
2651 provider: &dyn crate::provider::CryptoProvider,
2652) -> Result<bool, ParseError> {
2653 let mut candidate = info.clone();
2654 candidate.certificates = vec![certificate_der.to_vec()];
2655 candidate.parsed_certificates = vec![parse_x509_certificate(certificate_der)?];
2656 candidate.certificate_chain = vec![0];
2657 x509_selector_categories_match_chain(&candidate, provider)
2658}
2659
2660pub(crate) fn x509_selector_categories_match_chain(
2661 info: &X509DataInfo,
2662 provider: &dyn crate::provider::CryptoProvider,
2663) -> Result<bool, ParseError> {
2664 let subject_match = info.subject_names.iter().all(|subject| {
2665 info.parsed_certificates
2666 .iter()
2667 .any(|certificate| distinguished_names_equal(subject, &certificate.subject_dn))
2668 });
2669
2670 let mut issuer_serial_match = true;
2671 for (issuer, serial) in &info.issuer_serials {
2672 let serial_hex = x509_serial_decimal_to_hex(serial).ok_or_else(|| {
2673 ParseError::InvalidStructure(
2674 "X509Data lookup identifiers contain an invalid serial number".into(),
2675 )
2676 })?;
2677 issuer_serial_match &= info.parsed_certificates.iter().any(|certificate| {
2678 distinguished_names_equal(issuer, &certificate.issuer_dn)
2679 && serial_hex == certificate.serial_number_hex
2680 });
2681 }
2682
2683 let ski_match = info.skis.iter().all(|ski| {
2684 info.parsed_certificates.iter().any(|certificate| {
2685 certificate
2686 .subject_key_identifier
2687 .as_ref()
2688 .is_some_and(|certificate_ski| ski == certificate_ski)
2689 })
2690 });
2691
2692 let mut digest_match = true;
2693 for (algorithm_uri, expected) in &info.digests {
2694 let algorithm = DigestAlgorithm::from_uri(algorithm_uri).ok_or_else(|| {
2695 ParseError::UnsupportedAlgorithm {
2696 uri: algorithm_uri.clone(),
2697 }
2698 })?;
2699 let mut category_match = false;
2700 for certificate in &info.certificates {
2701 category_match |= constant_time_eq(
2702 &compute_digest_with_provider(provider, algorithm, certificate)?,
2703 expected,
2704 );
2705 }
2706 digest_match &= category_match;
2707 }
2708
2709 Ok(subject_match && issuer_serial_match && ski_match && digest_match)
2710}
2711
2712fn x509_attribute_values_equal(
2713 left: &x509_cert::attr::AttributeTypeAndValue,
2714 right: &x509_cert::attr::AttributeTypeAndValue,
2715) -> bool {
2716 if left.oid != right.oid {
2717 return false;
2718 }
2719 const EMAIL_ADDRESS: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.9.1");
2720 const DOMAIN_COMPONENT: ObjectIdentifier =
2721 ObjectIdentifier::new_unwrap("0.9.2342.19200300.100.1.25");
2722 if left.oid == EMAIL_ADDRESS {
2723 let (Ok(left), Ok(right)) = (
2724 Ia5StringRef::try_from(&left.value),
2725 Ia5StringRef::try_from(&right.value),
2726 ) else {
2727 return false;
2728 };
2729 let (Some((left_local, left_domain)), Some((right_local, right_domain))) = (
2730 left.as_str().rsplit_once('@'),
2731 right.as_str().rsplit_once('@'),
2732 ) else {
2733 return false;
2734 };
2735 return left_local == right_local && left_domain.eq_ignore_ascii_case(right_domain);
2736 }
2737 if left.oid == DOMAIN_COMPONENT {
2738 let (Ok(left), Ok(right)) = (
2739 Ia5StringRef::try_from(&left.value),
2740 Ia5StringRef::try_from(&right.value),
2741 ) else {
2742 return false;
2743 };
2744 return left.as_str().eq_ignore_ascii_case(right.as_str());
2745 }
2746 match (
2747 DirectoryString::try_from(&left.value),
2748 DirectoryString::try_from(&right.value),
2749 ) {
2750 (Ok(left), Ok(right)) => {
2751 let Ok(left) =
2754 x520_stringprep::x520_stringprep_to_case_ignore_string(left.value().as_ref())
2755 else {
2756 return false;
2757 };
2758 let Ok(right) =
2759 x520_stringprep::x520_stringprep_to_case_ignore_string(right.value().as_ref())
2760 else {
2761 return false;
2762 };
2763 left.trim_matches(' ') == right.trim_matches(' ')
2764 }
2765 _ => left.value == right.value,
2766 }
2767}
2768
2769fn x509_rdns_equal(
2770 left: &x509_cert::name::RelativeDistinguishedName,
2771 right: &x509_cert::name::RelativeDistinguishedName,
2772) -> bool {
2773 if left.len() != right.len() {
2774 return false;
2775 }
2776 let right = right.iter().collect::<Vec<_>>();
2778 let mut matched = vec![false; right.len()];
2779 left.iter().all(|left_attribute| {
2780 right
2781 .iter()
2782 .enumerate()
2783 .find(|(index, right_attribute)| {
2784 !matched[*index] && x509_attribute_values_equal(left_attribute, right_attribute)
2785 })
2786 .is_some_and(|(index, _)| {
2787 matched[index] = true;
2788 true
2789 })
2790 })
2791}
2792
2793fn trailing_whitespace_is_escaped(value: &str) -> bool {
2794 let Some((&last, prefix)) = value.as_bytes().split_last() else {
2795 return false;
2796 };
2797 if !matches!(last, b' ' | b'\t' | b'\r' | b'\n') {
2798 return false;
2799 }
2800 prefix
2801 .iter()
2802 .rev()
2803 .take_while(|byte| **byte == b'\\')
2804 .count()
2805 % 2
2806 == 1
2807}
2808
2809fn parse_distinguished_name(value: &str) -> Option<Name> {
2810 let mut normalized = String::with_capacity(value.len());
2811 let mut chars = value
2812 .trim_start_matches([' ', '\t', '\r', '\n'])
2813 .chars()
2814 .peekable();
2815 let mut escaped = false;
2816
2817 while let Some(ch) = chars.next() {
2818 if escaped {
2819 normalized.push(ch);
2820 escaped = false;
2821 continue;
2822 }
2823 if ch == '\\' {
2824 normalized.push(ch);
2825 escaped = true;
2826 continue;
2827 }
2828 if matches!(ch, ',' | '+') {
2829 while normalized
2830 .chars()
2831 .next_back()
2832 .is_some_and(|last| matches!(last, ' ' | '\t' | '\r' | '\n'))
2833 && !trailing_whitespace_is_escaped(&normalized)
2834 {
2835 normalized.pop();
2836 }
2837 normalized.push(ch);
2838 while chars
2839 .next_if(|next| matches!(next, ' ' | '\t' | '\r' | '\n'))
2840 .is_some()
2841 {}
2842 continue;
2843 }
2844 normalized.push(ch);
2845 }
2846
2847 while normalized
2848 .chars()
2849 .next_back()
2850 .is_some_and(|last| matches!(last, ' ' | '\t' | '\r' | '\n'))
2851 && !trailing_whitespace_is_escaped(&normalized)
2852 {
2853 normalized.pop();
2854 }
2855 normalized.parse().ok()
2856}
2857
2858pub(crate) fn distinguished_names_equal(left: &str, right: &str) -> bool {
2859 parse_distinguished_name(left)
2860 .zip(parse_distinguished_name(right))
2861 .is_some_and(|(left, right)| {
2862 left.len() == right.len()
2863 && left
2864 .iter_rdn()
2865 .zip(right.iter_rdn())
2866 .all(|(left, right)| x509_rdns_equal(left, right))
2867 })
2868}
2869
2870pub(crate) fn distinguished_name_within_subtree(name: &str, subtree: &str) -> bool {
2871 parse_distinguished_name(name)
2872 .zip(parse_distinguished_name(subtree))
2873 .is_some_and(|(name, subtree)| {
2874 subtree.len() <= name.len()
2875 && name
2876 .iter_rdn()
2877 .zip(subtree.iter_rdn())
2878 .all(|(name, subtree)| x509_rdns_equal(name, subtree))
2879 })
2880}
2881
2882fn ensure_x509_data_entry_budget(info: &X509DataInfo) -> Result<(), ParseError> {
2883 let total_entries = info.certificates.len()
2884 + info.subject_names.len()
2885 + info.issuer_serials.len()
2886 + info.skis.len()
2887 + info.crls.len()
2888 + info.digests.len();
2889 if total_entries >= MAX_X509_DATA_ENTRY_COUNT {
2890 return Err(ParseError::InvalidStructure(
2891 "X509Data contains too many entries".into(),
2892 ));
2893 }
2894 Ok(())
2895}
2896
2897fn add_x509_data_usage(total_binary_len: &mut usize, delta: usize) -> Result<(), ParseError> {
2898 *total_binary_len = total_binary_len.checked_add(delta).ok_or_else(|| {
2899 ParseError::InvalidStructure("X509Data exceeds maximum allowed total binary length".into())
2900 })?;
2901 if *total_binary_len > MAX_X509_DATA_TOTAL_BINARY_LEN {
2902 return Err(ParseError::InvalidStructure(
2903 "X509Data exceeds maximum allowed total binary length".into(),
2904 ));
2905 }
2906 Ok(())
2907}
2908
2909fn decode_x509_base64(
2910 node: Node<'_, '_>,
2911 element_name: &'static str,
2912 total_binary_len: &mut usize,
2913 resources: &crate::policy::ResourcePolicy,
2914) -> Result<Vec<u8>, ParseError> {
2915 let payload = super::whitespace::XmlBase64Payload::bounded(
2916 node,
2917 MAX_X509_BASE64_TEXT_LEN,
2918 MAX_X509_BASE64_NORMALIZED_LEN,
2919 )
2920 .map_err(|reason| xml_base64_payload_error(element_name, reason))?;
2921 if payload.text_len > MAX_X509_BASE64_TEXT_LEN {
2922 return Err(ParseError::InvalidStructure(format!(
2923 "{element_name} exceeds maximum allowed text length"
2924 )));
2925 }
2926 if payload.normalized_len > MAX_X509_BASE64_NORMALIZED_LEN {
2927 return Err(ParseError::InvalidStructure(format!(
2928 "{element_name} exceeds maximum allowed base64 length"
2929 )));
2930 }
2931 if payload.decoded_len == 0 {
2932 return Err(ParseError::InvalidStructure(format!(
2933 "{element_name} must not be empty"
2934 )));
2935 }
2936 if payload.decoded_len > MAX_X509_DECODED_BINARY_LEN {
2937 return Err(ParseError::InvalidStructure(format!(
2938 "{element_name} exceeds maximum allowed binary length"
2939 )));
2940 }
2941 if payload.decoded_len > resources.max_external_resource_bytes {
2942 return Err(crate::policy::PolicyViolation::ResourceLimitExceeded {
2943 resource: crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES,
2944 maximum: resources.max_external_resource_bytes,
2945 }
2946 .into());
2947 }
2948 if *total_binary_len > resources.max_external_resource_total_bytes
2949 || payload.decoded_len > resources.max_external_resource_total_bytes - *total_binary_len
2950 {
2951 return Err(crate::policy::PolicyViolation::ResourceLimitExceeded {
2952 resource: crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
2953 maximum: resources.max_external_resource_total_bytes,
2954 }
2955 .into());
2956 }
2957 add_x509_data_usage(total_binary_len, payload.decoded_len)?;
2960 payload
2961 .decode()
2962 .map_err(|reason| ParseError::Base64(format!("{element_name}: {reason}")))
2963}
2964
2965fn xml_base64_payload_error(element: &str, reason: &'static str) -> ParseError {
2966 match reason {
2967 "maximum allowed text length" | "maximum allowed base64 length" => {
2968 ParseError::InvalidStructure(format!("{element} exceeds {reason}"))
2969 }
2970 _ => ParseError::Base64(format!("{element}: {reason}")),
2971 }
2972}
2973
2974pub(crate) fn parse_x509_certificate(cert_der: &[u8]) -> Result<ParsedX509Certificate, ParseError> {
2975 let (rest, cert) =
2976 x509_parser::certificate::X509Certificate::from_der(cert_der).map_err(|err| {
2977 ParseError::InvalidStructure(format!("X509Certificate is not valid DER X.509: {err}"))
2978 })?;
2979 if !rest.is_empty() {
2980 return Err(ParseError::InvalidStructure(
2981 "X509Certificate contains trailing bytes after DER certificate".into(),
2982 ));
2983 }
2984
2985 let subject_dn = x509_name_to_rfc4514(cert.subject())?;
2990 let issuer_dn = x509_name_to_rfc4514(cert.issuer())?;
2991 let serial_number = cert.tbs_certificate.raw_serial().to_vec();
2992 let serial_number_hex = format_x509_serial_value_hex(&serial_number);
2993
2994 let subject_key_identifier = cert.extensions().iter().find_map(|ext| {
2995 if let ParsedExtension::SubjectKeyIdentifier(ski) = ext.parsed_extension() {
2996 Some(ski.0.to_vec())
2997 } else {
2998 None
2999 }
3000 });
3001
3002 let spki = cert.public_key();
3003 let public_key = match spki.parsed().map_err(|err| {
3004 ParseError::InvalidStructure(format!("X509Certificate public key parse error: {err}"))
3005 })? {
3006 PublicKey::RSA(rsa) => {
3007 let modulus = trim_leading_zeroes(rsa.modulus);
3008 let exponent = trim_leading_zeroes(rsa.exponent);
3009 if modulus.is_empty() || exponent.is_empty() {
3010 return Err(ParseError::InvalidStructure(
3011 "X509Certificate RSA key contains empty modulus or exponent".into(),
3012 ));
3013 }
3014 X509PublicKeyInfo::Rsa { modulus, exponent }
3015 }
3016 PublicKey::EC(ec_point) => {
3017 let Some(params) = spki.algorithm.parameters.as_ref() else {
3018 return Err(ParseError::InvalidStructure(
3019 "X509Certificate EC key is missing curve parameters".into(),
3020 ));
3021 };
3022
3023 match params.as_oid() {
3024 Ok(oid) => X509PublicKeyInfo::Ec {
3025 curve_oid: oid.to_id_string(),
3026 public_key: ec_point.data().to_vec(),
3027 },
3028 Err(_) => X509PublicKeyInfo::Unsupported {
3029 algorithm_oid: spki.algorithm.algorithm.to_id_string(),
3030 },
3031 }
3032 }
3033 _ => X509PublicKeyInfo::Unsupported {
3034 algorithm_oid: spki.algorithm.algorithm.to_id_string(),
3035 },
3036 };
3037
3038 Ok(ParsedX509Certificate {
3039 subject_dn,
3040 issuer_dn,
3041 serial_number,
3042 serial_number_hex,
3043 subject_key_identifier,
3044 public_key,
3045 })
3046}
3047
3048pub(crate) fn x509_name_to_rfc4514(name: &X509Name<'_>) -> Result<String, ParseError> {
3049 let name = Name::from_der(name.as_raw()).map_err(|error| {
3050 ParseError::InvalidStructure(format!(
3051 "X509Certificate distinguished name is invalid DER: {error}"
3052 ))
3053 })?;
3054 Ok(name.to_string())
3055}
3056
3057fn format_x509_serial_hex(serial: &[u8]) -> String {
3058 serial
3059 .iter()
3060 .map(|byte| format!("{byte:02X}"))
3061 .collect::<String>()
3062}
3063
3064fn format_x509_serial_value_hex(serial: &[u8]) -> String {
3065 let first_non_zero = serial
3066 .iter()
3067 .position(|byte| *byte != 0)
3068 .unwrap_or(serial.len());
3069 let canonical = if first_non_zero == serial.len() {
3070 &[0]
3071 } else {
3072 &serial[first_non_zero..]
3073 };
3074 format_x509_serial_hex(canonical)
3075}
3076
3077fn x509_serial_decimal_to_hex(serial: &str) -> Option<String> {
3078 let serial = serial.trim();
3079 let serial = serial.strip_prefix('+').unwrap_or(serial);
3080 let serial = serial.trim_start_matches('0');
3081 let serial = if serial.is_empty() { "0" } else { serial };
3082 if serial.len() > MAX_X509_SERIAL_NUMBER_VALUE_DIGITS
3083 || !serial.bytes().all(|byte| byte.is_ascii_digit())
3084 {
3085 return None;
3086 }
3087
3088 let mut bytes = [0_u8; MAX_X509_SERIAL_NUMBER_BYTES];
3089 for digit in serial.bytes().map(|byte| byte - b'0') {
3090 let mut carry = u16::from(digit);
3091 for byte in bytes.iter_mut().rev() {
3092 let value = u16::from(*byte) * 10 + carry;
3093 *byte = value as u8;
3094 carry = value >> 8;
3095 }
3096 if carry != 0 {
3097 return None;
3098 }
3099 }
3100
3101 if bytes.iter().all(|byte| *byte == 0) {
3102 return None;
3103 }
3104
3105 Some(format_x509_serial_value_hex(&bytes))
3106}
3107
3108fn trim_leading_zeroes(bytes: &[u8]) -> Vec<u8> {
3109 let first_non_zero = bytes
3110 .iter()
3111 .position(|byte| *byte != 0)
3112 .unwrap_or(bytes.len());
3113 bytes[first_non_zero..].to_vec()
3114}
3115
3116fn parse_x509_issuer_serial(node: Node<'_, '_>) -> Result<(String, String), ParseError> {
3117 verify_ds_element(node, "X509IssuerSerial")?;
3118 ensure_no_non_whitespace_text(node, "X509IssuerSerial")?;
3119
3120 let children = element_children(node).collect::<Vec<_>>();
3121 if children.len() != 2 {
3122 return Err(ParseError::InvalidStructure(
3123 "X509IssuerSerial must contain exactly X509IssuerName then X509SerialNumber".into(),
3124 ));
3125 }
3126 if !matches!(
3127 (
3128 children[0].tag_name().namespace(),
3129 children[0].tag_name().name()
3130 ),
3131 (Some(XMLDSIG_NS), "X509IssuerName")
3132 ) {
3133 return Err(ParseError::InvalidStructure(
3134 "X509IssuerSerial must contain X509IssuerName as the first child element".into(),
3135 ));
3136 }
3137 if !matches!(
3138 (
3139 children[1].tag_name().namespace(),
3140 children[1].tag_name().name()
3141 ),
3142 (Some(XMLDSIG_NS), "X509SerialNumber")
3143 ) {
3144 return Err(ParseError::InvalidStructure(
3145 "X509IssuerSerial must contain X509SerialNumber as the second child element".into(),
3146 ));
3147 }
3148
3149 let issuer_node = children[0];
3150 ensure_no_element_children(issuer_node, "X509IssuerName")?;
3151 let issuer_name =
3152 collect_text_content_bounded(issuer_node, MAX_X509_ISSUER_NAME_TEXT_LEN, "X509IssuerName")?;
3153
3154 let serial_node = children[1];
3155 ensure_no_element_children(serial_node, "X509SerialNumber")?;
3156 let serial_number = collect_x509_serial_number(serial_node)?;
3157 if issuer_name.trim().is_empty() {
3158 return Err(ParseError::InvalidStructure(
3159 "X509IssuerSerial requires non-empty X509IssuerName and X509SerialNumber".into(),
3160 ));
3161 }
3162
3163 Ok((issuer_name, serial_number))
3164}
3165
3166fn base64_decode_digest(b64: &str, digest_method: DigestAlgorithm) -> Result<Vec<u8>, ParseError> {
3170 use base64::Engine;
3171 use base64::engine::general_purpose::STANDARD;
3172
3173 let expected = digest_method.output_len();
3174 let max_base64_len = expected.div_ceil(3) * 4;
3175 let mut cleaned = String::with_capacity(b64.len().min(max_base64_len));
3176 normalize_xml_base64_text(b64, &mut cleaned).map_err(|err| {
3177 ParseError::Base64(format!(
3178 "invalid XML whitespace U+{:04X} in DigestValue",
3179 err.invalid_byte
3180 ))
3181 })?;
3182 if cleaned.len() > max_base64_len {
3183 return Err(ParseError::Base64(
3184 "DigestValue exceeds maximum allowed base64 length".into(),
3185 ));
3186 }
3187 let digest = STANDARD
3188 .decode(&cleaned)
3189 .map_err(|e| ParseError::Base64(e.to_string()))?;
3190 let actual = digest.len();
3191 if actual != expected {
3192 return Err(ParseError::DigestLengthMismatch {
3193 algorithm: digest_method.uri(),
3194 expected,
3195 actual,
3196 });
3197 }
3198 Ok(digest)
3199}
3200
3201fn decode_digest_value_children(
3202 digest_value_node: Node<'_, '_>,
3203 digest_method: DigestAlgorithm,
3204) -> Result<Vec<u8>, ParseError> {
3205 let max_base64_len = digest_method.output_len().div_ceil(3) * 4;
3206 let mut cleaned = String::with_capacity(max_base64_len);
3207
3208 for child in digest_value_node.children() {
3209 if child.is_element() {
3210 return Err(ParseError::InvalidStructure(
3211 "DigestValue must not contain element children".into(),
3212 ));
3213 }
3214 if let Some(text) = child.text() {
3215 normalize_xml_base64_text(text, &mut cleaned).map_err(|err| {
3216 ParseError::Base64(format!(
3217 "invalid XML whitespace U+{:04X} in DigestValue",
3218 err.invalid_byte
3219 ))
3220 })?;
3221 if cleaned.len() > max_base64_len {
3222 return Err(ParseError::Base64(
3223 "DigestValue exceeds maximum allowed base64 length".into(),
3224 ));
3225 }
3226 }
3227 }
3228
3229 base64_decode_digest(&cleaned, digest_method)
3230}
3231
3232fn decode_der_encoded_key_value_base64(node: Node<'_, '_>) -> Result<Vec<u8>, ParseError> {
3233 use base64::Engine;
3234 use base64::engine::general_purpose::STANDARD;
3235
3236 let mut cleaned = String::new();
3237 let mut raw_text_len = 0usize;
3238 for text in node
3239 .children()
3240 .filter(|child| child.is_text())
3241 .filter_map(|child| child.text())
3242 {
3243 if raw_text_len.saturating_add(text.len()) > MAX_DER_ENCODED_KEY_VALUE_TEXT_LEN {
3244 return Err(ParseError::InvalidStructure(
3245 "DEREncodedKeyValue exceeds maximum allowed text length".into(),
3246 ));
3247 }
3248 raw_text_len = raw_text_len.saturating_add(text.len());
3249 normalize_xml_base64_text(text, &mut cleaned).map_err(|err| {
3250 ParseError::Base64(format!(
3251 "invalid XML whitespace U+{:04X} in base64 text",
3252 err.invalid_byte
3253 ))
3254 })?;
3255 if cleaned.len() > MAX_DER_ENCODED_KEY_VALUE_BASE64_LEN {
3256 return Err(ParseError::InvalidStructure(
3257 "DEREncodedKeyValue exceeds maximum allowed length".into(),
3258 ));
3259 }
3260 }
3261
3262 let der = STANDARD
3263 .decode(&cleaned)
3264 .map_err(|e| ParseError::Base64(e.to_string()))?;
3265 if der.is_empty() {
3266 return Err(ParseError::InvalidStructure(
3267 "DEREncodedKeyValue must not be empty".into(),
3268 ));
3269 }
3270 if der.len() > MAX_DER_ENCODED_KEY_VALUE_LEN {
3271 return Err(ParseError::InvalidStructure(
3272 "DEREncodedKeyValue exceeds maximum allowed length".into(),
3273 ));
3274 }
3275 Ok(der)
3276}
3277
3278fn collect_text_content_bounded(
3279 node: Node<'_, '_>,
3280 max_len: usize,
3281 element_name: &'static str,
3282) -> Result<String, ParseError> {
3283 let mut text = String::new();
3284 for chunk in node
3285 .children()
3286 .filter_map(|child| child.is_text().then(|| child.text()).flatten())
3287 {
3288 if text.len().saturating_add(chunk.len()) > max_len {
3289 return Err(ParseError::InvalidStructure(format!(
3290 "{element_name} exceeds maximum allowed text length"
3291 )));
3292 }
3293 text.push_str(chunk);
3294 }
3295 Ok(text)
3296}
3297
3298fn collect_x509_serial_number(node: Node<'_, '_>) -> Result<String, ParseError> {
3299 let mut serial = String::with_capacity(MAX_X509_SERIAL_NUMBER_VALUE_DIGITS);
3300 let mut raw_text_len = 0usize;
3301 let mut trailing_whitespace = false;
3302 let mut explicit_positive = false;
3303 let mut saw_digit = false;
3304
3305 for chunk in node
3306 .children()
3307 .filter_map(|child| child.is_text().then(|| child.text()).flatten())
3308 {
3309 raw_text_len = raw_text_len.saturating_add(chunk.len());
3310 if raw_text_len > MAX_X509_SERIAL_NUMBER_RAW_TEXT_LEN {
3311 return Err(ParseError::InvalidStructure(
3312 "X509SerialNumber exceeds maximum allowed text length".into(),
3313 ));
3314 }
3315 for byte in chunk.bytes() {
3316 if matches!(byte, b' ' | b'\t' | b'\r' | b'\n') {
3317 trailing_whitespace |= explicit_positive || saw_digit;
3318 continue;
3319 }
3320 if byte == b'+' && !saw_digit && !explicit_positive && !trailing_whitespace {
3321 explicit_positive = true;
3322 continue;
3323 }
3324 if trailing_whitespace || !byte.is_ascii_digit() {
3325 return Err(ParseError::InvalidStructure(
3326 "invalid X509SerialNumber decimal value".into(),
3327 ));
3328 }
3329 saw_digit = true;
3330 if byte == b'0' && serial.is_empty() {
3331 continue;
3332 }
3333 if serial.len() == MAX_X509_SERIAL_NUMBER_VALUE_DIGITS {
3334 return Err(ParseError::InvalidStructure(
3335 "X509SerialNumber exceeds maximum allowed decimal value".into(),
3336 ));
3337 }
3338 serial.push(char::from(byte));
3339 }
3340 }
3341
3342 if !saw_digit {
3343 return Err(ParseError::InvalidStructure(
3344 "X509IssuerSerial requires non-empty X509IssuerName and X509SerialNumber".into(),
3345 ));
3346 }
3347 if serial.is_empty() {
3348 serial.push('0');
3349 }
3350 if x509_serial_decimal_to_hex(&serial).is_none() {
3351 return Err(ParseError::InvalidStructure(
3352 "invalid X509SerialNumber decimal value or RFC 5280 range".into(),
3353 ));
3354 }
3355
3356 Ok(serial)
3357}
3358
3359fn ensure_no_element_children(node: Node<'_, '_>, element_name: &str) -> Result<(), ParseError> {
3360 if node.children().any(|child| child.is_element()) {
3361 return Err(ParseError::InvalidStructure(format!(
3362 "{element_name} must not contain child elements"
3363 )));
3364 }
3365 Ok(())
3366}
3367
3368fn ensure_no_non_whitespace_text(node: Node<'_, '_>, element_name: &str) -> Result<(), ParseError> {
3369 for child in node.children().filter(|child| child.is_text()) {
3370 if let Some(text) = child.text()
3371 && !is_xml_whitespace_only(text)
3372 {
3373 return Err(ParseError::InvalidStructure(format!(
3374 "{element_name} must not contain non-whitespace mixed content"
3375 )));
3376 }
3377 }
3378 Ok(())
3379}
3380
3381#[cfg(test)]
3382#[expect(clippy::unwrap_used, reason = "tests use trusted XML fixtures")]
3383mod tests {
3384 use super::*;
3385 use crate::xmldsig::TransformError;
3386 use base64::Engine;
3387
3388 fn fixture_rsa_cert_base64() -> String {
3389 fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem")
3390 }
3391
3392 fn fixture_cert_base64(path: &str) -> String {
3393 match path {
3394 "../../tests/fixtures/keys/rsa/rsa-2048-cert.pem" => {
3395 include_str!("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem")
3396 }
3397 "../../tests/fixtures/keys/rsa/rsa-4096-cert.pem" => {
3398 include_str!("../../tests/fixtures/keys/rsa/rsa-4096-cert.pem")
3399 }
3400 "../../tests/fixtures/keys/ca2cert.pem" => {
3401 include_str!("../../tests/fixtures/keys/ca2cert.pem")
3402 }
3403 "../../tests/fixtures/keys/cacert.pem" => {
3404 include_str!("../../tests/fixtures/keys/cacert.pem")
3405 }
3406 _ => unreachable!("unknown certificate fixture"),
3407 }
3408 .lines()
3409 .skip_while(|line| *line != "-----BEGIN CERTIFICATE-----")
3410 .skip(1)
3411 .take_while(|line| *line != "-----END CERTIFICATE-----")
3412 .collect::<String>()
3413 }
3414
3415 #[test]
3418 fn signature_algorithm_from_uri_rsa_sha256() {
3419 assert_eq!(
3420 SignatureAlgorithm::from_uri("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"),
3421 Some(SignatureAlgorithm::RsaSha256)
3422 );
3423 }
3424
3425 #[test]
3426 fn signature_algorithm_from_uri_rsa_sha1() {
3427 assert_eq!(
3428 SignatureAlgorithm::from_uri("http://www.w3.org/2000/09/xmldsig#rsa-sha1"),
3429 Some(SignatureAlgorithm::RsaSha1)
3430 );
3431 }
3432
3433 #[test]
3434 fn signature_algorithm_from_uri_ecdsa_sha256() {
3435 assert_eq!(
3436 SignatureAlgorithm::from_uri("http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"),
3437 Some(SignatureAlgorithm::EcdsaSha256)
3438 );
3439 }
3440
3441 #[test]
3442 fn signature_algorithm_from_uri_unknown() {
3443 assert_eq!(
3444 SignatureAlgorithm::from_uri("http://example.com/unknown"),
3445 None
3446 );
3447 }
3448
3449 #[test]
3450 fn signature_algorithm_uri_round_trip() {
3451 for algo in SignatureAlgorithm::ALL {
3452 assert_eq!(
3453 SignatureAlgorithm::from_uri(algo.uri()),
3454 Some(algo),
3455 "round-trip failed for {algo:?}"
3456 );
3457 }
3458 }
3459
3460 #[test]
3461 fn legacy_algorithms_are_verify_only() {
3462 assert!(!SignatureAlgorithm::DsaSha1.signing_allowed());
3463 assert!(!SignatureAlgorithm::HmacSha1.signing_allowed());
3464 assert!(!SignatureAlgorithm::RsaSha1.signing_allowed());
3465 assert!(SignatureAlgorithm::RsaSha256.signing_allowed());
3466 assert!(SignatureAlgorithm::EcdsaSha256.signing_allowed());
3467 }
3468
3469 #[test]
3472 fn find_signature_in_saml() {
3473 let xml = r#"<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
3474 <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
3475 <ds:SignedInfo/>
3476 </ds:Signature>
3477 </samlp:Response>"#;
3478 let doc = Document::parse(xml).unwrap();
3479 let sig = find_signature_node(&doc);
3480 assert!(sig.is_some());
3481 assert_eq!(sig.unwrap().tag_name().name(), "Signature");
3482 }
3483
3484 #[test]
3485 fn find_signature_missing() {
3486 let xml = "<root><child/></root>";
3487 let doc = Document::parse(xml).unwrap();
3488 assert!(find_signature_node(&doc).is_none());
3489 }
3490
3491 #[test]
3492 fn find_signature_ignores_wrong_namespace() {
3493 let xml = r#"<root><Signature xmlns="http://example.com/fake"/></root>"#;
3494 let doc = Document::parse(xml).unwrap();
3495 assert!(find_signature_node(&doc).is_none());
3496 }
3497
3498 #[test]
3501 fn key_info_session_applies_x509_byte_limits_before_decode() {
3502 let document = Document::parse("<KeyInfo xmlns='http://www.w3.org/2000/09/xmldsig#'><X509Data><X509SKI>AQID</X509SKI></X509Data></KeyInfo>").unwrap();
3504 for individual in [true, false] {
3505 let resources = crate::policy::ResourcePolicy {
3506 max_external_resource_bytes: if individual { 2 } else { 3 },
3507 max_external_resource_total_bytes: if individual { 100 } else { 5 },
3508 ..Default::default()
3509 };
3510 let mut session = KeyInfoParsingSession::new(&resources).unwrap();
3511 if !individual {
3512 session.parse(document.root_element()).unwrap();
3513 }
3514 assert!(matches!(
3515 session.parse(document.root_element()),
3516 Err(ParseError::Policy(_))
3517 ));
3518 }
3519 }
3520
3521 #[test]
3522 fn all_x509_binary_fields_preflight_active_byte_policy() {
3523 for element in ["X509Certificate", "X509SKI", "X509CRL", "X509Digest"] {
3526 let namespace = if element == "X509Digest" {
3527 XMLDSIG11_NS
3528 } else {
3529 XMLDSIG_NS
3530 };
3531 let xml = format!(
3532 "<KeyInfo xmlns='{XMLDSIG_NS}'><X509Data><v:{element} xmlns:v='{namespace}' Algorithm='test'>AQID</v:{element}></X509Data></KeyInfo>"
3533 );
3534 let document = Document::parse(&xml).unwrap();
3535 let resources = crate::policy::ResourcePolicy {
3536 max_external_resource_bytes: 2,
3537 ..Default::default()
3538 };
3539 let mut session = KeyInfoParsingSession::new(&resources).unwrap();
3540 assert!(
3541 matches!(
3542 session.parse(document.root_element()),
3543 Err(ParseError::Policy(_))
3544 ),
3545 "{element}"
3546 );
3547 }
3548 }
3549
3550 #[test]
3551 fn failed_x509_der_parse_keeps_session_byte_charge() {
3552 let invalid = Document::parse("<KeyInfo xmlns='http://www.w3.org/2000/09/xmldsig#'><X509Data><X509Certificate>AQID</X509Certificate></X509Data></KeyInfo>").unwrap();
3555 let valid = Document::parse("<KeyInfo xmlns='http://www.w3.org/2000/09/xmldsig#'><X509Data><X509SKI>AQID</X509SKI></X509Data></KeyInfo>").unwrap();
3556 let resources = crate::policy::ResourcePolicy {
3557 max_external_resource_bytes: 3,
3558 max_external_resource_total_bytes: 5,
3559 ..Default::default()
3560 };
3561 let mut session = KeyInfoParsingSession::new(&resources).unwrap();
3562 assert!(matches!(
3563 session.parse(invalid.root_element()),
3564 Err(ParseError::InvalidStructure(_))
3565 ));
3566 assert!(matches!(
3567 session.parse(valid.root_element()),
3568 Err(ParseError::Policy(_))
3569 ));
3570 assert!(
3571 KeyInfoParsingSession::new(&resources)
3572 .unwrap()
3573 .parse(valid.root_element())
3574 .is_ok()
3575 );
3576 }
3577
3578 #[test]
3579 fn key_info_session_retains_failed_work_and_applies_active_policy() {
3580 let resources = crate::policy::ResourcePolicy {
3583 max_key_candidates: 1,
3584 ..Default::default()
3585 };
3586 let xml = "<KeyInfo xmlns='http://www.w3.org/2000/09/xmldsig#'><KeyValue/></KeyInfo>";
3587 let document = Document::parse(xml).unwrap();
3588 let mut session = KeyInfoParsingSession::new(&resources).unwrap();
3589 assert!(matches!(
3590 session.parse(document.root_element()),
3591 Err(ParseError::InvalidStructure(_))
3592 ));
3593 assert!(matches!(
3594 session.parse(document.root_element()),
3595 Err(ParseError::Policy(_))
3596 ));
3597 let zero = crate::policy::ResourcePolicy {
3598 max_key_candidates: 0,
3599 ..resources
3600 };
3601 assert!(matches!(
3602 KeyInfoParsingSession::new(&zero)
3603 .unwrap()
3604 .parse(document.root_element()),
3605 Err(ParseError::Policy(_))
3606 ));
3607 }
3608
3609 #[test]
3610 fn key_info_candidate_budget_precedes_key_value_parsing() {
3611 let document = Document::parse(
3614 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3615 <KeyValue><RSAKeyValue><Modulus>AQAB</Modulus></RSAKeyValue></KeyValue>
3616 </KeyInfo>"#,
3617 )
3618 .expect("fixed KeyInfo fixture must parse as XML");
3619 let resources = crate::policy::ResourcePolicy {
3620 max_key_candidates: 0,
3621 ..crate::policy::ResourcePolicy::default()
3622 };
3623
3624 let error = parse_key_info_with_policy_budgets(
3625 document.root_element(),
3626 crate::provider::default_provider(),
3627 &XmlBaseResolutionBudget::default(),
3628 &resources,
3629 None,
3630 )
3631 .expect_err("candidate policy must reject KeyValue before RSA parsing");
3632
3633 assert!(matches!(
3634 error,
3635 ParseError::Policy(crate::policy::PolicyViolation::ResourceLimit {
3636 resource: crate::policy::resource_name::KEY_CANDIDATES,
3637 maximum: 0,
3638 actual: 1,
3639 })
3640 ));
3641 }
3642
3643 #[test]
3644 fn key_info_candidate_budget_precedes_der_key_decoding() {
3645 let document = Document::parse(
3648 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
3649 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
3650 <dsig11:DEREncodedKeyValue>%%%invalid%%%</dsig11:DEREncodedKeyValue>
3651 </KeyInfo>"#,
3652 )
3653 .expect("fixed KeyInfo fixture must parse as XML");
3654 let resources = crate::policy::ResourcePolicy {
3655 max_key_candidates: 0,
3656 ..crate::policy::ResourcePolicy::default()
3657 };
3658
3659 let error = parse_key_info_with_policy_budgets(
3660 document.root_element(),
3661 crate::provider::default_provider(),
3662 &XmlBaseResolutionBudget::default(),
3663 &resources,
3664 None,
3665 )
3666 .expect_err("candidate policy must reject DEREncodedKeyValue before decoding");
3667
3668 assert!(matches!(
3669 error,
3670 ParseError::Policy(crate::policy::PolicyViolation::ResourceLimit {
3671 resource: crate::policy::resource_name::KEY_CANDIDATES,
3672 maximum: 0,
3673 actual: 1,
3674 })
3675 ));
3676 }
3677
3678 #[test]
3679 fn key_info_embedded_candidate_count_matches_materialized_key_kinds() {
3680 let key_info = KeyInfo {
3683 sources: vec![
3684 KeyInfoSource::KeyName("configured-key".into()),
3685 KeyInfoSource::KeyValue(KeyValueInfo::Unsupported {
3686 namespace: Some(XMLDSIG_NS.into()),
3687 local_name: "FutureKeyValue".into(),
3688 }),
3689 KeyInfoSource::DerEncodedKeyValue(vec![1]),
3690 KeyInfoSource::X509Data(X509DataInfo {
3691 certificates: vec![vec![2], vec![3]],
3692 ..X509DataInfo::default()
3693 }),
3694 KeyInfoSource::RetrievalMethod {
3695 uri: "urn:certificate".into(),
3696 resource_type: None,
3697 transforms: RetrievalMethodTransforms::None,
3698 },
3699 ],
3700 };
3701
3702 assert_eq!(key_info.embedded_candidate_count(), 4);
3703 }
3704
3705 #[test]
3706 fn parse_key_info_dispatches_supported_children() {
3707 let cert_base64 = fixture_rsa_cert_base64();
3708 let expected_cert = base64::engine::general_purpose::STANDARD
3709 .decode(&cert_base64)
3710 .expect("fixture PEM must contain valid base64");
3711 let cert_digest = base64::engine::general_purpose::STANDARD
3712 .encode(compute_digest(DigestAlgorithm::Sha256, &expected_cert));
3713 let xml = format!(
3714 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
3715 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
3716 <KeyName>idp-signing-key</KeyName>
3717 <KeyValue>
3718 <RSAKeyValue>
3719 <Modulus>AQAB</Modulus>
3720 <Exponent>AQAB</Exponent>
3721 </RSAKeyValue>
3722 </KeyValue>
3723 <X509Data>
3724 <X509Certificate>{cert_base64}</X509Certificate>
3725 <X509SubjectName>CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509SubjectName>
3726 <X509IssuerSerial>
3727 <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>
3728 <X509SerialNumber>680572598617295163017172295025714171905498632019</X509SerialNumber>
3729 </X509IssuerSerial>
3730 <X509SKI>bcOXN/nsVl8GatRbcKrPbzIbw0Y=</X509SKI>
3731 <X509CRL>BAUGBw==</X509CRL>
3732 <dsig11:X509Digest Algorithm="http://www.w3.org/2001/04/xmlenc#sha256">{cert_digest}</dsig11:X509Digest>
3733 </X509Data>
3734 <dsig11:DEREncodedKeyValue>AQIDBA==</dsig11:DEREncodedKeyValue>
3735 </KeyInfo>"#
3736 );
3737 let doc = Document::parse(&xml).unwrap();
3738
3739 let key_info = parse_key_info(doc.root_element()).unwrap();
3740 assert_eq!(key_info.sources.len(), 4);
3741
3742 assert_eq!(
3743 key_info.sources[0],
3744 KeyInfoSource::KeyName("idp-signing-key".to_string())
3745 );
3746 assert_eq!(
3747 key_info.sources[1],
3748 KeyInfoSource::KeyValue(KeyValueInfo::Rsa {
3749 modulus: vec![1, 0, 1],
3750 exponent: vec![1, 0, 1],
3751 })
3752 );
3753 let x509_info = match &key_info.sources[2] {
3754 KeyInfoSource::X509Data(x509) => x509,
3755 other => panic!("expected X509Data source, got {other:?}"),
3756 };
3757 assert_eq!(x509_info.certificates, vec![expected_cert]);
3758 assert_eq!(
3759 x509_info.subject_names,
3760 vec![
3761 "CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US"
3762 .to_string()
3763 ]
3764 );
3765 assert_eq!(
3766 x509_info.issuer_serials,
3767 vec![(
3768 "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(),
3769 "680572598617295163017172295025714171905498632019".to_string()
3770 )]
3771 );
3772 assert_eq!(
3773 x509_info.skis,
3774 vec![vec![
3775 109, 195, 151, 55, 249, 236, 86, 95, 6, 106, 212, 91, 112, 170, 207, 111, 50, 27,
3776 195, 70
3777 ]]
3778 );
3779 assert_eq!(x509_info.crls, vec![vec![4, 5, 6, 7]]);
3780 assert_eq!(
3781 x509_info.digests,
3782 vec![(
3783 "http://www.w3.org/2001/04/xmlenc#sha256".to_string(),
3784 compute_digest(DigestAlgorithm::Sha256, &x509_info.certificates[0])
3785 )]
3786 );
3787 assert_eq!(x509_info.parsed_certificates.len(), 1);
3788 assert_eq!(x509_info.certificate_chain, vec![0]);
3789 let parsed_cert = &x509_info.parsed_certificates[0];
3790 assert!(!parsed_cert.subject_dn.is_empty());
3791 assert!(!parsed_cert.issuer_dn.is_empty());
3792 assert_eq!(
3793 parsed_cert.serial_number_hex,
3794 "7735EE487F6862DAF1B3956D961CCB0FA6F34F53"
3795 );
3796 assert!(parsed_cert.subject_key_identifier.is_some());
3797 assert!(matches!(
3798 parsed_cert.public_key,
3799 X509PublicKeyInfo::Rsa { .. }
3800 ));
3801
3802 assert_eq!(
3803 key_info.sources[3],
3804 KeyInfoSource::DerEncodedKeyValue(vec![1, 2, 3, 4])
3805 );
3806 }
3807
3808 #[test]
3809 fn parse_rsa_key_value_preserves_wrapped_crypto_binary() {
3810 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3812 <KeyValue><RSAKeyValue>
3813 <Modulus> AQID
3814BA== </Modulus>
3815 <Exponent> AQAB </Exponent>
3816 </RSAKeyValue></KeyValue>
3817 </KeyInfo>"##;
3818 let doc = Document::parse(xml).unwrap();
3819
3820 assert_eq!(
3821 parse_key_info(doc.root_element()).unwrap().sources,
3822 vec![KeyInfoSource::KeyValue(KeyValueInfo::Rsa {
3823 modulus: vec![1, 2, 3, 4],
3824 exponent: vec![1, 0, 1],
3825 })]
3826 );
3827 }
3828
3829 #[test]
3830 fn parse_rsa_key_value_rejects_reordered_parameters() {
3831 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3833 <KeyValue><RSAKeyValue>
3834 <Exponent>AQAB</Exponent><Modulus>AQID</Modulus>
3835 </RSAKeyValue></KeyValue>
3836 </KeyInfo>"##;
3837 let doc = Document::parse(xml).unwrap();
3838
3839 assert!(matches!(
3840 parse_key_info(doc.root_element()),
3841 Err(ParseError::InvalidStructure(_))
3842 ));
3843 }
3844
3845 #[test]
3846 fn parse_rsa_key_value_rejects_missing_exponent() {
3847 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3849 <KeyValue><RSAKeyValue><Modulus>AQID</Modulus></RSAKeyValue></KeyValue>
3850 </KeyInfo>"##;
3851 let doc = Document::parse(xml).unwrap();
3852
3853 assert!(matches!(
3854 parse_key_info(doc.root_element()),
3855 Err(ParseError::InvalidStructure(_))
3856 ));
3857 }
3858
3859 #[test]
3860 fn parse_rsa_key_value_rejects_duplicate_exponent() {
3861 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3863 <KeyValue><RSAKeyValue>
3864 <Modulus>AQID</Modulus><Exponent>AQAB</Exponent><Exponent>AQAB</Exponent>
3865 </RSAKeyValue></KeyValue>
3866 </KeyInfo>"##;
3867 let doc = Document::parse(xml).unwrap();
3868
3869 assert!(matches!(
3870 parse_key_info(doc.root_element()),
3871 Err(ParseError::InvalidStructure(_))
3872 ));
3873 }
3874
3875 #[test]
3876 fn parse_rsa_key_value_rejects_wrong_parameter_namespace() {
3877 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#" xmlns:bad="urn:bad">
3879 <KeyValue><RSAKeyValue>
3880 <bad:Modulus>AQID</bad:Modulus><Exponent>AQAB</Exponent>
3881 </RSAKeyValue></KeyValue>
3882 </KeyInfo>"#;
3883 let doc = Document::parse(xml).unwrap();
3884
3885 assert!(matches!(
3886 parse_key_info(doc.root_element()),
3887 Err(ParseError::InvalidStructure(_))
3888 ));
3889 }
3890
3891 #[test]
3892 fn parse_rsa_key_value_rejects_nested_crypto_binary() {
3893 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3895 <KeyValue><RSAKeyValue>
3896 <Modulus><chunk>AQID</chunk></Modulus><Exponent>AQAB</Exponent>
3897 </RSAKeyValue></KeyValue>
3898 </KeyInfo>"#;
3899 let doc = Document::parse(xml).unwrap();
3900
3901 assert!(matches!(
3902 parse_key_info(doc.root_element()),
3903 Err(ParseError::InvalidStructure(_))
3904 ));
3905 }
3906
3907 #[test]
3908 fn parse_rsa_key_value_rejects_malformed_base64() {
3909 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3911 <KeyValue><RSAKeyValue>
3912 <Modulus>%%%%</Modulus><Exponent>AQAB</Exponent>
3913 </RSAKeyValue></KeyValue>
3914 </KeyInfo>"#;
3915 let doc = Document::parse(xml).unwrap();
3916
3917 assert!(matches!(
3918 parse_key_info(doc.root_element()),
3919 Err(ParseError::Base64(_))
3920 ));
3921 }
3922
3923 #[test]
3924 fn parse_rsa_key_value_rejects_oversized_exponent_before_decode() {
3925 let exponent = "A".repeat(MAX_RSA_EXPONENT_LEN.div_ceil(3) * 4 + 1);
3927 let xml = format!(
3928 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3929 <KeyValue><RSAKeyValue>
3930 <Modulus>AQID</Modulus><Exponent>{exponent}</Exponent>
3931 </RSAKeyValue></KeyValue>
3932 </KeyInfo>"#
3933 );
3934 let doc = Document::parse(&xml).unwrap();
3935
3936 assert!(matches!(
3937 parse_key_info(doc.root_element()),
3938 Err(ParseError::InvalidStructure(_))
3939 ));
3940 }
3941
3942 #[test]
3943 fn parse_key_info_ignores_unknown_children() {
3944 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3945 <Foo>bar</Foo>
3946 <KeyName>ok</KeyName>
3947 </KeyInfo>"#;
3948 let doc = Document::parse(xml).unwrap();
3949
3950 let key_info = parse_key_info(doc.root_element()).unwrap();
3951 assert_eq!(key_info.sources, vec![KeyInfoSource::KeyName("ok".into())]);
3952 }
3953
3954 #[test]
3955 fn parse_key_info_keyvalue_requires_single_child() {
3956 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3957 <KeyValue/>
3958 </KeyInfo>"#;
3959 let doc = Document::parse(xml).unwrap();
3960
3961 let err = parse_key_info(doc.root_element()).unwrap_err();
3962 assert!(matches!(err, ParseError::InvalidStructure(_)));
3963 }
3964
3965 #[test]
3966 fn parse_key_info_accepts_empty_x509data() {
3967 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3968 <X509Data/>
3969 </KeyInfo>"#;
3970 let doc = Document::parse(xml).unwrap();
3971
3972 let key_info = parse_key_info(doc.root_element()).unwrap();
3973 assert_eq!(
3974 key_info.sources,
3975 vec![KeyInfoSource::X509Data(X509DataInfo::default())]
3976 );
3977 }
3978
3979 #[test]
3980 fn parse_key_info_rejects_unknown_xmlsig_child_in_x509data() {
3981 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
3982 <X509Data>
3983 <Foo/>
3984 </X509Data>
3985 </KeyInfo>"#;
3986 let doc = Document::parse(xml).unwrap();
3987
3988 let err = parse_key_info(doc.root_element()).unwrap_err();
3989 assert!(matches!(err, ParseError::InvalidStructure(_)));
3990 }
3991
3992 #[test]
3993 fn parse_key_info_rejects_unknown_xmlsig11_child_in_x509data() {
3994 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
3995 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
3996 <X509Data>
3997 <dsig11:Foo/>
3998 </X509Data>
3999 </KeyInfo>"#;
4000 let doc = Document::parse(xml).unwrap();
4001
4002 let err = parse_key_info(doc.root_element()).unwrap_err();
4003 assert!(matches!(err, ParseError::InvalidStructure(_)));
4004 }
4005
4006 #[test]
4007 fn parse_key_info_rejects_x509_issuer_serial_without_required_children() {
4008 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4009 <X509Data>
4010 <X509IssuerSerial>
4011 <X509IssuerName>CN=CA</X509IssuerName>
4012 </X509IssuerSerial>
4013 </X509Data>
4014 </KeyInfo>"#;
4015 let doc = Document::parse(xml).unwrap();
4016
4017 let err = parse_key_info(doc.root_element()).unwrap_err();
4018 assert!(matches!(err, ParseError::InvalidStructure(_)));
4019 }
4020
4021 #[test]
4022 fn parse_key_info_rejects_x509_issuer_serial_with_duplicate_issuer_name() {
4023 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4024 <X509Data>
4025 <X509IssuerSerial>
4026 <X509IssuerName>CN=CA-1</X509IssuerName>
4027 <X509IssuerName>CN=CA-2</X509IssuerName>
4028 <X509SerialNumber>42</X509SerialNumber>
4029 </X509IssuerSerial>
4030 </X509Data>
4031 </KeyInfo>"#;
4032 let doc = Document::parse(xml).unwrap();
4033
4034 let err = parse_key_info(doc.root_element()).unwrap_err();
4035 assert!(matches!(err, ParseError::InvalidStructure(_)));
4036 }
4037
4038 #[test]
4039 fn parse_key_info_rejects_x509_issuer_serial_with_duplicate_serial_number() {
4040 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4041 <X509Data>
4042 <X509IssuerSerial>
4043 <X509IssuerName>CN=CA</X509IssuerName>
4044 <X509SerialNumber>1</X509SerialNumber>
4045 <X509SerialNumber>2</X509SerialNumber>
4046 </X509IssuerSerial>
4047 </X509Data>
4048 </KeyInfo>"#;
4049 let doc = Document::parse(xml).unwrap();
4050
4051 let err = parse_key_info(doc.root_element()).unwrap_err();
4052 assert!(matches!(err, ParseError::InvalidStructure(_)));
4053 }
4054
4055 #[test]
4056 fn parse_key_info_rejects_x509_issuer_serial_with_whitespace_only_values() {
4057 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4058 <X509Data>
4059 <X509IssuerSerial>
4060 <X509IssuerName> </X509IssuerName>
4061 <X509SerialNumber>
4062
4063 </X509SerialNumber>
4064 </X509IssuerSerial>
4065 </X509Data>
4066 </KeyInfo>"#;
4067 let doc = Document::parse(xml).unwrap();
4068
4069 let err = parse_key_info(doc.root_element()).unwrap_err();
4070 assert!(matches!(err, ParseError::InvalidStructure(_)));
4071 }
4072
4073 #[test]
4074 fn parse_key_info_rejects_x509_issuer_serial_with_wrong_child_order() {
4075 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4076 <X509Data>
4077 <X509IssuerSerial>
4078 <X509SerialNumber>42</X509SerialNumber>
4079 <X509IssuerName>CN=CA</X509IssuerName>
4080 </X509IssuerSerial>
4081 </X509Data>
4082 </KeyInfo>"#;
4083 let doc = Document::parse(xml).unwrap();
4084
4085 let err = parse_key_info(doc.root_element()).unwrap_err();
4086 assert!(matches!(err, ParseError::InvalidStructure(_)));
4087 }
4088
4089 #[test]
4090 fn parse_key_info_rejects_x509_issuer_serial_with_extra_child_element() {
4091 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
4092 xmlns:foo="urn:example:foo">
4093 <X509Data>
4094 <X509IssuerSerial>
4095 <X509IssuerName>CN=CA</X509IssuerName>
4096 <X509SerialNumber>42</X509SerialNumber>
4097 <foo:Extra/>
4098 </X509IssuerSerial>
4099 </X509Data>
4100 </KeyInfo>"#;
4101 let doc = Document::parse(xml).unwrap();
4102
4103 let err = parse_key_info(doc.root_element()).unwrap_err();
4104 assert!(matches!(err, ParseError::InvalidStructure(_)));
4105 }
4106
4107 #[test]
4108 fn parse_key_info_rejects_x509_digest_without_algorithm() {
4109 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
4110 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
4111 <X509Data>
4112 <dsig11:X509Digest>AQID</dsig11:X509Digest>
4113 </X509Data>
4114 </KeyInfo>"#;
4115 let doc = Document::parse(xml).unwrap();
4116
4117 let err = parse_key_info(doc.root_element()).unwrap_err();
4118 assert!(matches!(err, ParseError::InvalidStructure(_)));
4119 }
4120
4121 #[test]
4122 fn parse_key_info_rejects_invalid_x509_certificate_base64() {
4123 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4124 <X509Data>
4125 <X509Certificate>%%%invalid%%%</X509Certificate>
4126 </X509Data>
4127 </KeyInfo>"#;
4128 let doc = Document::parse(xml).unwrap();
4129
4130 let err = parse_key_info(doc.root_element()).unwrap_err();
4131 assert!(matches!(err, ParseError::Base64(_)));
4132 }
4133
4134 #[test]
4135 fn parse_key_info_rejects_x509_data_exceeding_entry_budget() {
4136 let subjects = (0..(MAX_X509_DATA_ENTRY_COUNT + 1))
4137 .map(|idx| format!("<X509SubjectName>CN={idx}</X509SubjectName>"))
4138 .collect::<Vec<_>>()
4139 .join("");
4140 let xml = format!(
4141 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><X509Data>{subjects}</X509Data></KeyInfo>"
4142 );
4143 let doc = Document::parse(&xml).unwrap();
4144
4145 let err = parse_key_info(doc.root_element()).unwrap_err();
4146 assert!(matches!(err, ParseError::InvalidStructure(_)));
4147 }
4148
4149 #[test]
4150 fn parse_key_info_rejects_x509_data_exceeding_total_binary_budget() {
4151 let payload = base64::engine::general_purpose::STANDARD.encode(vec![0u8; 190_000]);
4152 let entries = (0..6)
4153 .map(|_| format!("<X509SKI>{payload}</X509SKI>"))
4154 .collect::<Vec<_>>()
4155 .join("");
4156 let xml = format!(
4157 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><X509Data>{entries}</X509Data></KeyInfo>"
4158 );
4159 let doc = Document::parse(&xml).unwrap();
4160
4161 let err = parse_key_info(doc.root_element()).unwrap_err();
4162 assert!(matches!(err, ParseError::InvalidStructure(_)));
4163 }
4164
4165 #[test]
4166 fn parse_key_info_rejects_x509_certificate_with_invalid_der() {
4167 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4168 <X509Data>
4169 <X509Certificate>AQID</X509Certificate>
4170 </X509Data>
4171 </KeyInfo>"#;
4172 let doc = Document::parse(xml).unwrap();
4173
4174 let err = parse_key_info(doc.root_element()).unwrap_err();
4175 assert!(matches!(err, ParseError::InvalidStructure(_)));
4176 }
4177
4178 #[test]
4179 fn parse_key_info_rejects_x509_certificate_with_trailing_der_bytes() {
4180 let mut cert = base64::engine::general_purpose::STANDARD
4181 .decode(fixture_rsa_cert_base64())
4182 .unwrap();
4183 cert.extend_from_slice(&[0x00, 0x01]);
4184 let cert_base64 = base64::engine::general_purpose::STANDARD.encode(cert);
4185 let xml = format!(
4186 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4187 <X509Data>
4188 <X509Certificate>{cert_base64}</X509Certificate>
4189 </X509Data>
4190 </KeyInfo>"#
4191 );
4192 let doc = Document::parse(&xml).unwrap();
4193
4194 let err = parse_key_info(doc.root_element()).unwrap_err();
4195 assert!(matches!(err, ParseError::InvalidStructure(_)));
4196 }
4197
4198 #[test]
4199 fn parse_key_info_marks_unsupported_spki_algorithm_as_unsupported() {
4200 let xml = include_str!(
4201 "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt.xml"
4202 );
4203 let doc = Document::parse(xml).unwrap();
4204 let key_info_node = doc
4205 .descendants()
4206 .find(|node| {
4207 node.is_element()
4208 && node.tag_name().namespace() == Some(XMLDSIG_NS)
4209 && node.tag_name().name() == "KeyInfo"
4210 })
4211 .expect("fixture must contain ds:KeyInfo");
4212
4213 let key_info = parse_key_info(key_info_node).expect("KeyInfo parse should succeed");
4214 let x509_info = match &key_info.sources[0] {
4215 KeyInfoSource::X509Data(x509) => x509,
4216 other => panic!("expected X509Data source, got {other:?}"),
4217 };
4218 assert_eq!(x509_info.certificates.len(), 1);
4219 assert_eq!(x509_info.parsed_certificates.len(), 1);
4220 assert_eq!(x509_info.certificate_chain, vec![0]);
4221 let parsed_cert = &x509_info.parsed_certificates[0];
4222 assert!(!parsed_cert.subject_dn.is_empty());
4223 assert!(!parsed_cert.issuer_dn.is_empty());
4224 assert!(parsed_cert.subject_key_identifier.is_some());
4225 assert!(matches!(
4226 parsed_cert.public_key,
4227 X509PublicKeyInfo::Unsupported { .. }
4228 ));
4229 }
4230
4231 #[test]
4232 fn parse_key_info_orders_x509_certificate_chain_from_signing_cert() {
4233 let root = fixture_cert_base64("../../tests/fixtures/keys/cacert.pem");
4234 let intermediate = fixture_cert_base64("../../tests/fixtures/keys/ca2cert.pem");
4235 let leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
4236 let xml = format!(
4237 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4238 <X509Data>
4239 <X509Certificate>{root}</X509Certificate>
4240 <X509Certificate>{intermediate}</X509Certificate>
4241 <X509Certificate>{leaf}</X509Certificate>
4242 </X509Data>
4243 </KeyInfo>"#
4244 );
4245 let doc = Document::parse(&xml).unwrap();
4246
4247 let key_info = parse_key_info(doc.root_element()).unwrap();
4248 let x509_info = match &key_info.sources[0] {
4249 KeyInfoSource::X509Data(x509) => x509,
4250 other => panic!("expected X509Data source, got {other:?}"),
4251 };
4252
4253 assert_eq!(x509_info.certificate_chain, vec![2, 1, 0]);
4254 }
4255
4256 #[test]
4257 fn chain_builder_matches_x509_equivalent_distinguished_names() {
4258 let certificates = [
4262 fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem"),
4263 fixture_cert_base64("../../tests/fixtures/keys/ca2cert.pem"),
4264 fixture_cert_base64("../../tests/fixtures/keys/cacert.pem"),
4265 ]
4266 .map(|encoded| {
4267 base64::engine::general_purpose::STANDARD
4268 .decode(encoded)
4269 .unwrap()
4270 })
4271 .to_vec();
4272 let mut parsed_certificates = certificates
4273 .iter()
4274 .map(|certificate| parse_x509_certificate(certificate).unwrap())
4275 .collect::<Vec<_>>();
4276 parsed_certificates[0].issuer_dn = parsed_certificates[1].subject_dn.to_ascii_lowercase();
4277 parsed_certificates[1].issuer_dn = parsed_certificates[2].subject_dn.to_ascii_lowercase();
4278 let info = X509DataInfo {
4279 certificates,
4280 parsed_certificates,
4281 ..X509DataInfo::default()
4282 };
4283
4284 assert_eq!(
4285 select_x509_signing_certificate(&info, crate::provider::default_provider()).unwrap(),
4286 0
4287 );
4288 assert_eq!(
4289 build_x509_certificate_chain_from(&info, 0, crate::provider::default_provider(), None)
4290 .unwrap(),
4291 vec![0, 1, 2]
4292 );
4293 }
4294
4295 #[test]
4296 fn parse_key_info_uses_issuer_serial_to_select_x509_signing_certificate() {
4297 let root = fixture_cert_base64("../../tests/fixtures/keys/cacert.pem");
4298 let intermediate = fixture_cert_base64("../../tests/fixtures/keys/ca2cert.pem");
4299 let leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
4300 let xml = format!(
4301 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4302 <X509Data>
4303 <X509IssuerSerial>
4304 <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>
4305 <X509SerialNumber>680572598617295163017172295025714171905498632019</X509SerialNumber>
4306 </X509IssuerSerial>
4307 <X509Certificate>{root}</X509Certificate>
4308 <X509Certificate>{intermediate}</X509Certificate>
4309 <X509Certificate>{leaf}</X509Certificate>
4310 </X509Data>
4311 </KeyInfo>"#
4312 );
4313 let doc = Document::parse(&xml).unwrap();
4314
4315 let key_info = parse_key_info(doc.root_element()).unwrap();
4316 let x509_info = match &key_info.sources[0] {
4317 KeyInfoSource::X509Data(x509) => x509,
4318 other => panic!("expected X509Data source, got {other:?}"),
4319 };
4320
4321 assert_eq!(x509_info.certificate_chain, vec![2, 1, 0]);
4322 }
4323
4324 #[test]
4325 fn parse_key_info_allows_selectors_for_multiple_chain_members() {
4326 let root = fixture_cert_base64("../../tests/fixtures/keys/cacert.pem");
4329 let intermediate = fixture_cert_base64("../../tests/fixtures/keys/ca2cert.pem");
4330 let leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
4331 let xml = format!(
4332 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4333 <X509Data>
4334 <X509SubjectName>CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509SubjectName>
4335 <X509SKI>0X0XrEVCio75sBcl1TxymJ2IOiU=</X509SKI>
4336 <X509Certificate>{root}</X509Certificate>
4337 <X509Certificate>{intermediate}</X509Certificate>
4338 <X509Certificate>{leaf}</X509Certificate>
4339 </X509Data>
4340 </KeyInfo>"#
4341 );
4342 let doc = Document::parse(&xml).unwrap();
4343
4344 let key_info = parse_key_info(doc.root_element()).unwrap();
4345 let x509_info = match &key_info.sources[0] {
4346 KeyInfoSource::X509Data(x509) => x509,
4347 other => panic!("expected X509Data source, got {other:?}"),
4348 };
4349
4350 assert_eq!(x509_info.certificate_chain, vec![2, 1, 0]);
4351 }
4352
4353 #[test]
4354 fn parse_key_info_uses_decimal_issuer_serial_to_select_x509_signing_certificate() {
4355 let serial = "680572598617295163017172295025714171905498632019";
4356 let padded_serial = format!("{}{}", "0".repeat(64), serial);
4357 assert_eq!(
4358 x509_serial_decimal_to_hex(&padded_serial).as_deref(),
4359 Some("7735EE487F6862DAF1B3956D961CCB0FA6F34F53")
4360 );
4361 let root = fixture_cert_base64("../../tests/fixtures/keys/cacert.pem");
4362 let intermediate = fixture_cert_base64("../../tests/fixtures/keys/ca2cert.pem");
4363 let leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
4364 let other_leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-4096-cert.pem");
4365 let xml = format!(
4366 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4367 <X509Data>
4368 <X509IssuerSerial>
4369 <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>
4370 <X509SerialNumber>{padded_serial}</X509SerialNumber>
4371 </X509IssuerSerial>
4372 <X509Certificate>{root}</X509Certificate>
4373 <X509Certificate>{intermediate}</X509Certificate>
4374 <X509Certificate>{leaf}</X509Certificate>
4375 <X509Certificate>{other_leaf}</X509Certificate>
4376 </X509Data>
4377 </KeyInfo>"#
4378 );
4379 let doc = Document::parse(&xml).unwrap();
4380
4381 let key_info = parse_key_info(doc.root_element()).unwrap();
4382 let x509_info = match &key_info.sources[0] {
4383 KeyInfoSource::X509Data(x509) => x509,
4384 other => panic!("expected X509Data source, got {other:?}"),
4385 };
4386
4387 assert_eq!(x509_info.certificate_chain, vec![2, 1, 0]);
4388 }
4389
4390 #[test]
4391 fn parse_key_info_rejects_ambiguous_x509_signing_certificate_candidates() {
4392 let first_leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
4393 let second_leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-4096-cert.pem");
4394 let xml = format!(
4395 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4396 <X509Data>
4397 <X509Certificate>{first_leaf}</X509Certificate>
4398 <X509Certificate>{second_leaf}</X509Certificate>
4399 </X509Data>
4400 </KeyInfo>"#
4401 );
4402 let doc = Document::parse(&xml).unwrap();
4403
4404 let err = parse_key_info(doc.root_element()).unwrap_err();
4405 assert!(matches!(err, ParseError::InvalidStructure(_)));
4406 }
4407
4408 #[test]
4409 fn parse_key_info_rejects_unmatched_x509_lookup_identifier() {
4410 let cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
4411 let xml = format!(
4412 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4413 <X509Data>
4414 <X509SubjectName>CN=Not The Embedded Certificate</X509SubjectName>
4415 <X509Certificate>{cert}</X509Certificate>
4416 </X509Data>
4417 </KeyInfo>"#
4418 );
4419 let doc = Document::parse(&xml).unwrap();
4420
4421 let err = parse_key_info(doc.root_element()).unwrap_err();
4422 assert!(
4423 matches!(err, ParseError::InvalidStructure(message) if message.contains("lookup identifiers"))
4424 );
4425 }
4426
4427 #[test]
4428 fn parse_key_info_rejects_partially_matched_selector_category() {
4429 let cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
4432 let xml = format!(
4433 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4434 <X509Data>
4435 <X509SubjectName>CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509SubjectName>
4436 <X509SubjectName>CN=Not In The Embedded Chain</X509SubjectName>
4437 <X509Certificate>{cert}</X509Certificate>
4438 </X509Data>
4439 </KeyInfo>"#
4440 );
4441 let doc = Document::parse(&xml).unwrap();
4442
4443 let err = parse_key_info(doc.root_element()).unwrap_err();
4444 assert!(
4445 matches!(err, ParseError::InvalidStructure(message) if message.contains("lookup identifiers"))
4446 );
4447 }
4448
4449 #[test]
4450 fn parse_key_info_rejects_malformed_issuer_serial_even_with_matching_subject() {
4451 let cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
4454 let xml = format!(
4455 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4456 <X509Data>
4457 <X509SubjectName>CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509SubjectName>
4458 <X509IssuerSerial>
4459 <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>
4460 <X509SerialNumber>not-a-decimal-serial</X509SerialNumber>
4461 </X509IssuerSerial>
4462 <X509Certificate>{cert}</X509Certificate>
4463 </X509Data>
4464 </KeyInfo>"#
4465 );
4466 let doc = Document::parse(&xml).unwrap();
4467
4468 let err = parse_key_info(doc.root_element()).unwrap_err();
4469 assert!(
4470 matches!(err, ParseError::InvalidStructure(message) if message.contains("invalid X509SerialNumber"))
4471 );
4472 }
4473
4474 #[test]
4475 fn parse_key_info_rejects_unmatched_ski_even_with_matching_subject() {
4476 let cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
4477 let xml = format!(
4478 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4479 <X509Data>
4480 <X509SubjectName>CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509SubjectName>
4481 <X509SKI>AQIDBA==</X509SKI>
4482 <X509Certificate>{cert}</X509Certificate>
4483 </X509Data>
4484 </KeyInfo>"#
4485 );
4486 let doc = Document::parse(&xml).unwrap();
4487
4488 let err = parse_key_info(doc.root_element()).unwrap_err();
4489 assert!(
4490 matches!(err, ParseError::InvalidStructure(message) if message.contains("lookup identifiers"))
4491 );
4492 }
4493
4494 #[test]
4495 fn parse_key_info_rejects_lookup_hints_for_different_certificates() {
4496 let first_cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
4497 let second_cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-4096-cert.pem");
4498 let xml = format!(
4499 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4500 <X509Data>
4501 <X509SubjectName>CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509SubjectName>
4502 <X509SKI>60zMLKCfzQ3qnXAzABzRNpdgQ8Q=</X509SKI>
4503 <X509Certificate>{first_cert}</X509Certificate>
4504 <X509Certificate>{second_cert}</X509Certificate>
4505 </X509Data>
4506 </KeyInfo>"#
4507 );
4508 let doc = Document::parse(&xml).unwrap();
4509
4510 let err = parse_key_info(doc.root_element()).unwrap_err();
4511 assert!(
4512 matches!(err, ParseError::InvalidStructure(message) if message.contains("lookup identifiers match multiple certificates"))
4513 );
4514 }
4515
4516 #[test]
4517 fn configured_certificate_matching_requires_every_x509_selector_category() {
4518 let certificate = base64::engine::general_purpose::STANDARD
4521 .decode(fixture_rsa_cert_base64())
4522 .unwrap();
4523 let parsed = parse_x509_certificate(&certificate).unwrap();
4524 let digest = compute_digest_with_provider(
4525 crate::provider::default_provider(),
4526 DigestAlgorithm::Sha256,
4527 &certificate,
4528 )
4529 .unwrap();
4530 let matching = X509DataInfo {
4531 subject_names: vec![parsed.subject_dn.clone()],
4532 issuer_serials: vec![(
4533 parsed.issuer_dn.clone(),
4534 "680572598617295163017172295025714171905498632019".into(),
4535 )],
4536 skis: vec![parsed.subject_key_identifier.clone().unwrap()],
4537 digests: vec![(DigestAlgorithm::Sha256.uri().into(), digest)],
4538 ..X509DataInfo::default()
4539 };
4540
4541 assert!(
4542 x509_certificate_matches_selectors(
4543 &matching,
4544 &certificate,
4545 crate::provider::default_provider()
4546 )
4547 .unwrap()
4548 );
4549 for mismatching in [
4550 X509DataInfo {
4551 subject_names: vec!["CN=other".into()],
4552 ..matching.clone()
4553 },
4554 X509DataInfo {
4555 issuer_serials: vec![(parsed.issuer_dn.clone(), "1".into())],
4556 ..matching.clone()
4557 },
4558 X509DataInfo {
4559 skis: vec![vec![0]],
4560 ..matching.clone()
4561 },
4562 X509DataInfo {
4563 digests: vec![(DigestAlgorithm::Sha256.uri().into(), vec![0; 32])],
4564 ..matching.clone()
4565 },
4566 ] {
4567 assert!(
4568 !x509_certificate_matches_selectors(
4569 &mismatching,
4570 &certificate,
4571 crate::provider::default_provider()
4572 )
4573 .unwrap()
4574 );
4575 }
4576 }
4577
4578 #[test]
4579 fn build_x509_certificate_chain_rejects_chain_exceeding_max_depth() {
4580 let parsed_certificates: Vec<ParsedX509Certificate> = (0..=MAX_X509_CHAIN_DEPTH)
4581 .map(|idx| ParsedX509Certificate {
4582 subject_dn: format!("CN=cert-{idx}"),
4583 issuer_dn: if idx == MAX_X509_CHAIN_DEPTH {
4584 format!("CN=cert-{idx}")
4585 } else {
4586 format!("CN=cert-{}", idx + 1)
4587 },
4588 serial_number: vec![u8::try_from(idx).unwrap()],
4589 serial_number_hex: format!("{idx:02X}"),
4590 subject_key_identifier: None,
4591 public_key: X509PublicKeyInfo::Unsupported {
4592 algorithm_oid: "1.2.3.4".into(),
4593 },
4594 })
4595 .collect();
4596 let certificates = vec![Vec::new(); parsed_certificates.len()];
4597 let info = X509DataInfo {
4598 certificates,
4599 parsed_certificates,
4600 ..X509DataInfo::default()
4601 };
4602
4603 let err = build_x509_certificate_chain(&info, crate::provider::default_provider(), None)
4604 .unwrap_err();
4605 assert!(
4606 matches!(err, ParseError::InvalidStructure(message) if message.contains("maximum depth"))
4607 );
4608 }
4609
4610 #[test]
4611 fn x509_serial_hex_strips_der_sign_extension_zeroes() {
4612 assert_eq!(format_x509_serial_value_hex(&[0x00, 0xFF]), "FF");
4613 assert_eq!(format_x509_serial_value_hex(&[0x00, 0x7F]), "7F");
4614 assert_eq!(format_x509_serial_value_hex(&[0x00, 0x00]), "00");
4615 }
4616
4617 #[test]
4618 fn x509_serial_decimal_parser_enforces_rfc5280_positive_range() {
4619 let max_serial = "1461501637330902918203684832716283019655932542975";
4622 assert_eq!(
4623 x509_serial_decimal_to_hex(max_serial),
4624 Some("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".into())
4625 );
4626 assert_eq!(
4627 x509_serial_decimal_to_hex("730750818665451459101842416358141509827966271488"),
4628 Some("8000000000000000000000000000000000000000".into())
4629 );
4630 assert_eq!(
4631 x509_serial_decimal_to_hex("0000000000000000000000000000000000000000000000001"),
4632 Some("01".into())
4633 );
4634 assert_eq!(
4635 x509_serial_decimal_to_hex("00000000000000000000000000000000000000000000000001"),
4636 Some("01".into())
4637 );
4638 assert_eq!(x509_serial_decimal_to_hex("+1"), Some("01".into()));
4639
4640 for invalid in [
4641 "",
4642 "0",
4643 "000",
4644 "+0",
4645 "++1",
4646 "-1",
4647 "1a",
4648 "1461501637330902918203684832716283019655932542976",
4649 ] {
4650 assert_eq!(
4651 x509_serial_decimal_to_hex(invalid),
4652 None,
4653 "invalid serial {invalid:?} must be rejected"
4654 );
4655 }
4656 }
4657
4658 #[test]
4659 fn parse_x509_serial_normalizes_boundary_whitespace_and_rejects_overflow() {
4660 let max_serial = "1461501637330902918203684832716283019655932542975";
4663 let valid = format!(
4664 "<KeyInfo xmlns=\"{XMLDSIG_NS}\"><X509Data><X509IssuerSerial><X509IssuerName>CN=issuer</X509IssuerName><X509SerialNumber>\n {max_serial}\t</X509SerialNumber></X509IssuerSerial></X509Data></KeyInfo>"
4665 );
4666 let doc = Document::parse(&valid).unwrap();
4667 let parsed = parse_key_info(doc.root_element()).unwrap();
4668 let KeyInfoSource::X509Data(x509) = &parsed.sources[0] else {
4669 panic!("expected X509Data source");
4670 };
4671 assert_eq!(x509.issuer_serials[0].1, max_serial);
4672
4673 let explicit_positive = valid.replace(max_serial, "+42");
4674 let doc = Document::parse(&explicit_positive).unwrap();
4675 let parsed = parse_key_info(doc.root_element()).unwrap();
4676 let KeyInfoSource::X509Data(x509) = &parsed.sources[0] else {
4677 panic!("expected X509Data source");
4678 };
4679 assert_eq!(x509.issuer_serials[0].1, "42");
4680
4681 let overflow = valid.replace(
4682 max_serial,
4683 "1461501637330902918203684832716283019655932542976",
4684 );
4685 let doc = Document::parse(&overflow).unwrap();
4686 assert!(matches!(
4687 parse_key_info(doc.root_element()),
4688 Err(ParseError::InvalidStructure(message))
4689 if message.contains("invalid X509SerialNumber")
4690 ));
4691 }
4692
4693 #[test]
4694 fn issuer_selector_matches_a_sign_padded_twenty_octet_serial() {
4695 let serial_hex = "8000000000000000000000000000000000000000";
4698 let info = X509DataInfo {
4699 issuer_serials: vec![(
4700 "CN=issuer".into(),
4701 "730750818665451459101842416358141509827966271488".into(),
4702 )],
4703 parsed_certificates: vec![ParsedX509Certificate {
4704 subject_dn: "CN=leaf".into(),
4705 issuer_dn: "CN=issuer".into(),
4706 serial_number: [vec![0, 0x80], vec![0; 19]].concat(),
4707 serial_number_hex: serial_hex.into(),
4708 subject_key_identifier: None,
4709 public_key: X509PublicKeyInfo::Unsupported {
4710 algorithm_oid: "1.2.3.4".into(),
4711 },
4712 }],
4713 ..X509DataInfo::default()
4714 };
4715
4716 assert!(
4717 x509_selector_categories_match_chain(&info, crate::provider::default_provider())
4718 .unwrap()
4719 );
4720 }
4721
4722 #[test]
4723 fn distinguished_name_matching_preserves_rdn_order() {
4724 assert!(distinguished_names_equal(
4727 "CN=leaf, O=example",
4728 "CN=leaf,O=example"
4729 ));
4730 assert!(!distinguished_names_equal(
4731 "CN=leaf,O=example",
4732 "O=example,CN=leaf"
4733 ));
4734 }
4735
4736 #[test]
4737 fn distinguished_name_matching_applies_x520_string_preparation() {
4738 assert!(distinguished_names_equal(
4741 "CN= TEST key ,O=Example",
4742 "CN=test key,O=example"
4743 ));
4744 assert!(distinguished_names_equal(
4745 "CN=Straße,O=Example",
4746 "CN=STRASSE,O=EXAMPLE"
4747 ));
4748 assert!(distinguished_names_equal(
4749 "CN=test+OU=security,O=example",
4750 "OU=SECURITY+CN=TEST,O=EXAMPLE"
4751 ));
4752 assert!(!distinguished_names_equal(
4753 "1.2.3.4=#040141,O=example",
4754 "1.2.3.4=#040142,O=example"
4755 ));
4756 }
4757
4758 #[test]
4759 fn distinguished_name_matching_applies_ia5_matching_rules() {
4760 assert!(distinguished_names_equal(
4763 "EMAIL=ops@EXAMPLE.COM,DC=EXAMPLE,DC=COM",
4764 "EMAIL=ops@example.com,DC=example,DC=com"
4765 ));
4766 assert!(!distinguished_names_equal(
4767 "EMAIL=OPS@example.com,DC=example,DC=com",
4768 "EMAIL=ops@example.com,DC=example,DC=com"
4769 ));
4770 }
4771
4772 #[test]
4773 fn distinguished_name_matching_handles_rfc4514_escaped_values() {
4774 let value = " leading,plus+equals=slash\\trailing ";
4777 let mut params = rcgen::CertificateParams::new(Vec::new()).unwrap();
4778 params
4779 .distinguished_name
4780 .push(rcgen::DnType::CommonName, value);
4781 let key = rcgen::KeyPair::generate().unwrap();
4782 let certificate = params.self_signed(&key).unwrap();
4783 let parsed = parse_x509_certificate(certificate.der()).unwrap();
4784
4785 assert_eq!(
4786 parsed.subject_dn,
4787 r"CN=\ leading\,plus\+equals=slash\\trailing\ "
4788 );
4789 assert!(distinguished_names_equal(
4790 r"CN=\ leading\,plus\+equals=slash\\trailing\ ",
4791 &parsed.subject_dn
4792 ));
4793 assert!(distinguished_names_equal(
4794 "\n CN=\\ leading\\,plus\\+equals=slash\\\\trailing\\ \n",
4795 &parsed.subject_dn
4796 ));
4797 }
4798
4799 #[test]
4800 fn distinguished_name_trailing_escape_covers_all_xml_whitespace() {
4801 for whitespace in [' ', '\t', '\r', '\n'] {
4804 assert!(trailing_whitespace_is_escaped(&format!(
4805 "CN=value\\{whitespace}"
4806 )));
4807 assert!(!trailing_whitespace_is_escaped(&format!(
4808 "CN=value{whitespace}"
4809 )));
4810 }
4811 }
4812
4813 #[test]
4814 fn parse_key_info_accepts_large_textual_x509_entries_within_entry_budget() {
4815 let issuer_name = "C".repeat(MAX_X509_ISSUER_NAME_TEXT_LEN);
4816 let serial_number = "0".repeat(MAX_X509_SERIAL_NUMBER_VALUE_DIGITS - 1) + "1";
4817 let issuer_serials = (0..52)
4818 .map(|_| {
4819 format!(
4820 "<X509IssuerSerial><X509IssuerName>{issuer_name}</X509IssuerName><X509SerialNumber>{serial_number}</X509SerialNumber></X509IssuerSerial>"
4821 )
4822 })
4823 .collect::<Vec<_>>()
4824 .join("");
4825 let xml = format!(
4826 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><X509Data>{issuer_serials}</X509Data></KeyInfo>"
4827 );
4828 let doc = Document::parse(&xml).unwrap();
4829
4830 let key_info = parse_key_info(doc.root_element()).unwrap();
4831 let parsed = match &key_info.sources[0] {
4832 KeyInfoSource::X509Data(x509) => x509,
4833 _ => panic!("expected X509Data source"),
4834 };
4835 assert_eq!(parsed.issuer_serials.len(), 52);
4836 }
4837
4838 #[test]
4839 fn parse_key_info_bounds_raw_x509_serial_text() {
4840 let serial = "0".repeat(MAX_X509_SERIAL_NUMBER_RAW_TEXT_LEN + 1);
4843 let xml = format!(
4844 "<KeyInfo xmlns=\"{XMLDSIG_NS}\"><X509Data><X509IssuerSerial><X509IssuerName>CN=issuer</X509IssuerName><X509SerialNumber>{serial}</X509SerialNumber></X509IssuerSerial></X509Data></KeyInfo>"
4845 );
4846 let doc = Document::parse(&xml).unwrap();
4847
4848 let error = parse_key_info(doc.root_element()).unwrap_err();
4849
4850 assert!(matches!(
4851 error,
4852 ParseError::InvalidStructure(reason)
4853 if reason == "X509SerialNumber exceeds maximum allowed text length"
4854 ));
4855 }
4856
4857 #[test]
4858 fn parse_key_info_accepts_x509data_with_only_foreign_namespace_children() {
4859 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
4860 xmlns:foo="urn:example:foo">
4861 <X509Data>
4862 <foo:Bar/>
4863 </X509Data>
4864 </KeyInfo>"#;
4865 let doc = Document::parse(xml).unwrap();
4866
4867 let key_info = parse_key_info(doc.root_element()).unwrap();
4868 assert_eq!(
4869 key_info.sources,
4870 vec![KeyInfoSource::X509Data(X509DataInfo::default())]
4871 );
4872 }
4873
4874 #[test]
4875 fn parse_key_info_der_encoded_key_value_rejects_invalid_base64() {
4876 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
4877 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
4878 <dsig11:DEREncodedKeyValue>%%%invalid%%%</dsig11:DEREncodedKeyValue>
4879 </KeyInfo>"#;
4880 let doc = Document::parse(xml).unwrap();
4881
4882 let err = parse_key_info(doc.root_element()).unwrap_err();
4883 assert!(matches!(err, ParseError::Base64(_)));
4884 }
4885
4886 #[test]
4887 fn parse_key_info_der_encoded_key_value_accepts_xml_whitespace() {
4888 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
4889 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
4890 <dsig11:DEREncodedKeyValue>
4891 AQID
4892 BA==
4893 </dsig11:DEREncodedKeyValue>
4894 </KeyInfo>"#;
4895 let doc = Document::parse(xml).unwrap();
4896
4897 let key_info = parse_key_info(doc.root_element()).unwrap();
4898 assert_eq!(
4899 key_info.sources,
4900 vec![KeyInfoSource::DerEncodedKeyValue(vec![1, 2, 3, 4])]
4901 );
4902 }
4903
4904 #[test]
4905 fn parse_key_info_dispatches_dsig11_ec_keyvalue() {
4906 let public_key = "BJ/yaXNlq4FRObyJCBhb5jAz8GVzinK3bBGLjSDfjbJwNfydtgjnlS4EsDmxSRhWyJWq6GIqy5wvnaiARK04uB4=";
4907 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
4908 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
4909 <KeyValue>
4910 <dsig11:ECKeyValue>
4911 <dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/>
4912 <dsig11:PublicKey>BJ/yaXNlq4FRObyJCBhb5jAz8GVzinK3bBGLjSDfjbJwNfydtgjnlS4EsDmxSRhWyJWq6GIqy5wvnaiARK04uB4=</dsig11:PublicKey>
4913 </dsig11:ECKeyValue>
4914 </KeyValue>
4915 </KeyInfo>"#;
4916 let doc = Document::parse(xml).unwrap();
4917 let expected_public_key = base64::engine::general_purpose::STANDARD
4918 .decode(public_key)
4919 .expect("fixture EC point must be valid base64");
4920
4921 let key_info = parse_key_info(doc.root_element()).unwrap();
4922 assert_eq!(
4923 key_info.sources,
4924 vec![KeyInfoSource::KeyValue(KeyValueInfo::Ec {
4925 curve_oid: "1.2.840.10045.3.1.7".into(),
4926 public_key: expected_public_key,
4927 })]
4928 );
4929 }
4930
4931 #[test]
4932 fn parse_ec_key_value_accepts_bare_curve_oid() {
4933 use base64::Engine;
4934
4935 let encoded_public_key = "BO/yd/OZzDfjX4qivDY/vsUIuh6KWAxoxW5P4ukvwd+T6pVljWsX2UBJNNy5MdhTwB8e2YwB8kUbJwdsAS/XGi/fz8unFrs+lVlAgIs6s/xBYFbfUoRiAacD2SpVDe6XBA==";
4936 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
4937 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
4938 <KeyValue>
4939 <dsig11:ECKeyValue>
4940 <dsig11:NamedCurve URI="1.3.132.0.34"/>
4941 <dsig11:PublicKey>BO/yd/OZzDfjX4qivDY/vsUIuh6KWAxoxW5P4ukvwd+T6pVljWsX2UBJNNy5MdhTwB8e2YwB8kUbJwdsAS/XGi/fz8unFrs+lVlAgIs6s/xBYFbfUoRiAacD2SpVDe6XBA==</dsig11:PublicKey>
4942 </dsig11:ECKeyValue>
4943 </KeyValue>
4944 </KeyInfo>"#;
4945 let doc = Document::parse(xml).unwrap();
4946 let expected_public_key = base64::engine::general_purpose::STANDARD
4947 .decode(encoded_public_key)
4948 .unwrap();
4949
4950 let sources = parse_key_info(doc.root_element()).unwrap().sources;
4951
4952 assert!(matches!(
4953 &sources[0],
4954 KeyInfoSource::KeyValue(KeyValueInfo::Ec { curve_oid, public_key })
4955 if curve_oid == EC_P384_OID && public_key == &expected_public_key
4956 ));
4957 }
4958
4959 #[test]
4960 fn parse_ec_key_value_marks_ec_parameters_as_unsupported() {
4961 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
4962 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
4963 <KeyValue>
4964 <dsig11:ECKeyValue>
4965 <dsig11:ECParameters/>
4966 <dsig11:PublicKey>BA==</dsig11:PublicKey>
4967 </dsig11:ECKeyValue>
4968 </KeyValue>
4969 </KeyInfo>"#;
4970 let doc = Document::parse(xml).unwrap();
4971
4972 let key_info = parse_key_info(doc.root_element()).unwrap();
4973 assert_eq!(
4974 key_info.sources,
4975 vec![KeyInfoSource::KeyValue(KeyValueInfo::Unsupported {
4976 namespace: Some(XMLDSIG11_NS.to_string()),
4977 local_name: "ECKeyValue".into(),
4978 })]
4979 );
4980 }
4981
4982 #[test]
4983 fn parse_ec_key_value_marks_unsupported_curve_as_unsupported() {
4984 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
4985 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
4986 <KeyValue>
4987 <dsig11:ECKeyValue>
4988 <dsig11:NamedCurve URI="urn:oid:1.3.132.0.36"/>
4989 <dsig11:PublicKey>BA==</dsig11:PublicKey>
4990 </dsig11:ECKeyValue>
4991 </KeyValue>
4992 </KeyInfo>"#;
4993 let doc = Document::parse(xml).unwrap();
4994
4995 let key_info = parse_key_info(doc.root_element()).unwrap();
4996 assert_eq!(
4997 key_info.sources,
4998 vec![KeyInfoSource::KeyValue(KeyValueInfo::Unsupported {
4999 namespace: Some(XMLDSIG11_NS.to_string()),
5000 local_name: "ECKeyValue".into(),
5001 })]
5002 );
5003 }
5004
5005 #[test]
5006 fn parse_ec_key_value_marks_missing_named_curve_uri_invalid() {
5007 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
5008 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
5009 <KeyValue>
5010 <dsig11:ECKeyValue>
5011 <dsig11:NamedCurve/>
5012 <dsig11:PublicKey>BA==</dsig11:PublicKey>
5013 </dsig11:ECKeyValue>
5014 </KeyValue>
5015 </KeyInfo>"#;
5016 let doc = Document::parse(xml).unwrap();
5017
5018 let key_info = parse_key_info(doc.root_element()).unwrap();
5019 assert_eq!(
5020 key_info.sources,
5021 vec![KeyInfoSource::KeyValue(KeyValueInfo::InvalidEcKeyValue)]
5022 );
5023 }
5024
5025 #[test]
5026 fn parse_ec_key_value_marks_reordered_children_invalid() {
5027 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
5028 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
5029 <KeyValue>
5030 <dsig11:ECKeyValue>
5031 <dsig11:PublicKey>BJ/yaXNlq4FRObyJCBhb5jAz8GVzinK3bBGLjSDfjbJwNfydtgjnlS4EsDmxSRhWyJWq6GIqy5wvnaiARK04uB4=</dsig11:PublicKey>
5032 <dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/>
5033 </dsig11:ECKeyValue>
5034 </KeyValue>
5035 </KeyInfo>"#;
5036 let doc = Document::parse(xml).unwrap();
5037
5038 let key_info = parse_key_info(doc.root_element()).unwrap();
5039 assert_eq!(
5040 key_info.sources,
5041 vec![KeyInfoSource::KeyValue(KeyValueInfo::InvalidEcKeyValue)]
5042 );
5043 }
5044
5045 #[test]
5046 fn parse_ec_key_value_marks_non_uncompressed_point_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 URI="urn:oid:1.2.840.10045.3.1.7"/>
5052 <dsig11:PublicKey>Ap/yaXNlq4FRObyJCBhb5jAz8GVzinK3bBGLjSDfjbJwNfydtgjnlS4EsDmxSRhWyJWq6GIqy5wvnaiARK04uB4=</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_key_info_marks_ds_namespace_ec_keyvalue_as_unsupported() {
5067 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5068 <KeyValue>
5069 <ECKeyValue/>
5070 </KeyValue>
5071 </KeyInfo>"#;
5072 let doc = Document::parse(xml).unwrap();
5073
5074 let key_info = parse_key_info(doc.root_element()).unwrap();
5075 assert_eq!(
5076 key_info.sources,
5077 vec![KeyInfoSource::KeyValue(KeyValueInfo::Unsupported {
5078 namespace: Some(XMLDSIG_NS.to_string()),
5079 local_name: "ECKeyValue".into(),
5080 })]
5081 );
5082 }
5083
5084 #[test]
5085 fn parse_key_info_keeps_unsupported_keyvalue_child_as_marker() {
5086 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5087 <KeyValue>
5088 <FutureKeyValue/>
5089 </KeyValue>
5090 </KeyInfo>"#;
5091 let doc = Document::parse(xml).unwrap();
5092
5093 let key_info = parse_key_info(doc.root_element()).unwrap();
5094 assert_eq!(
5095 key_info.sources,
5096 vec![KeyInfoSource::KeyValue(KeyValueInfo::Unsupported {
5097 namespace: Some(XMLDSIG_NS.to_string()),
5098 local_name: "FutureKeyValue".into(),
5099 })]
5100 );
5101 }
5102
5103 #[test]
5104 fn parse_key_info_accepts_supported_x509_retrieval_xpath() {
5105 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5107 <RetrievalMethod Type="http://www.w3.org/2000/09/xmldsig#X509Data" URI="#keys">
5108 <Transforms><Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">
5109 <XPath xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">ancestor-or-self::dsig:X509Data</XPath>
5110 </Transform></Transforms>
5111 </RetrievalMethod>
5112 </KeyInfo>"##;
5113 let doc = Document::parse(xml).unwrap();
5114
5115 let key_info = parse_key_info(doc.root_element()).unwrap();
5116 assert!(matches!(
5117 key_info.sources.as_slice(),
5118 [KeyInfoSource::RetrievalMethod {
5119 uri,
5120 resource_type: Some(resource_type),
5121 transforms: RetrievalMethodTransforms::X509DataNodeSetFilter { .. },
5122 }] if uri == "#keys"
5123 && resource_type == "http://www.w3.org/2000/09/xmldsig#X509Data"
5124 ));
5125 }
5126
5127 #[test]
5128 fn retrieval_xpath_namespace_binding_policy_precedes_materialization() {
5129 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5132 <RetrievalMethod Type="http://www.w3.org/2000/09/xmldsig#X509Data" URI="#keys">
5133 <Transforms><Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">
5134 <XPath xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">ancestor-or-self::dsig:X509Data</XPath>
5135 </Transform></Transforms>
5136 </RetrievalMethod>
5137 </KeyInfo>"##;
5138 let document = Document::parse(xml).expect("fixed KeyInfo fixture must parse");
5139 let resources = crate::policy::ResourcePolicy {
5140 max_xpath_namespace_bindings: 0,
5141 ..crate::policy::ResourcePolicy::default()
5142 };
5143
5144 let error = parse_key_info_with_policy_budgets(
5145 document.root_element(),
5146 crate::provider::default_provider(),
5147 &XmlBaseResolutionBudget::default(),
5148 &resources,
5149 None,
5150 )
5151 .expect_err("zero namespace bindings must reject RetrievalMethod XPath");
5152
5153 assert!(matches!(
5154 error,
5155 ParseError::Transform(TransformError::Policy(
5156 crate::policy::PolicyViolation::ResourceLimit {
5157 resource: crate::policy::resource_name::XPATH_NAMESPACE_BINDINGS,
5158 maximum: 0,
5159 actual: 1,
5160 }
5161 ))
5162 ));
5163 }
5164
5165 #[test]
5166 fn retrieval_xpath_namespace_byte_policy_precedes_materialization() {
5167 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5170 <RetrievalMethod Type="http://www.w3.org/2000/09/xmldsig#X509Data" URI="#keys">
5171 <Transforms><Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">
5172 <XPath xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">ancestor-or-self::dsig:X509Data</XPath>
5173 </Transform></Transforms>
5174 </RetrievalMethod>
5175 </KeyInfo>"##;
5176 let document = Document::parse(xml).expect("fixed KeyInfo fixture must parse");
5177 let resources = crate::policy::ResourcePolicy {
5178 max_xpath_namespace_bytes: 0,
5179 ..crate::policy::ResourcePolicy::default()
5180 };
5181
5182 let error = parse_key_info_with_policy_budgets(
5183 document.root_element(),
5184 crate::provider::default_provider(),
5185 &XmlBaseResolutionBudget::default(),
5186 &resources,
5187 None,
5188 )
5189 .expect_err("zero namespace bytes must reject RetrievalMethod XPath");
5190
5191 assert!(matches!(
5192 error,
5193 ParseError::Transform(TransformError::Policy(
5194 crate::policy::PolicyViolation::ResourceLimit {
5195 resource: crate::policy::resource_name::XPATH_NAMESPACE_BYTES,
5196 maximum: 0,
5197 actual,
5198 }
5199 )) if actual == "dsig".len() + XMLDSIG_NS.len()
5200 ));
5201 }
5202
5203 #[test]
5204 fn parse_key_info_rejects_oversized_retrieval_method_type() {
5205 let oversized_type = "x".repeat(MAX_KEY_NAME_TEXT_LEN + 1);
5208 let xml = format!(
5209 r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"><RetrievalMethod URI="#key" Type="{oversized_type}"/></KeyInfo>"##
5210 );
5211 let document = Document::parse(&xml).unwrap();
5212
5213 assert!(matches!(
5214 parse_key_info(document.root_element()),
5215 Err(ParseError::InvalidStructure(reason))
5216 if reason == "RetrievalMethod Type exceeds maximum length"
5217 ));
5218 }
5219
5220 #[test]
5221 fn parse_key_info_bounds_retrieval_method_xml_base_chain() {
5222 let mut xml =
5225 format!(r#"<KeyInfo xmlns="{XMLDSIG_NS}"><RetrievalMethod URI="key.der"/></KeyInfo>"#);
5226 for _ in 0..65 {
5227 xml = format!(r#"<n xml:base="segment/">{xml}</n>"#);
5228 }
5229 let document = Document::parse(&xml).unwrap();
5230 let key_info = document
5231 .descendants()
5232 .find(|node| node.has_tag_name((XMLDSIG_NS, "KeyInfo")))
5233 .unwrap();
5234
5235 assert!(matches!(
5236 parse_key_info(key_info),
5237 Err(ParseError::InvalidStructure(reason))
5238 if reason.contains("XML Base resolution")
5239 ));
5240 }
5241
5242 #[test]
5243 fn parse_key_info_normalizes_external_retrieval_without_xml_base() {
5244 let xml = format!(
5247 r#"<KeyInfo xmlns="{XMLDSIG_NS}"><RetrievalMethod URI="https://example.test/a/../key.der"/></KeyInfo>"#
5248 );
5249 let document = Document::parse(&xml).unwrap();
5250 let key_info = parse_key_info(document.root_element()).unwrap();
5251
5252 assert!(matches!(
5253 key_info.sources.as_slice(),
5254 [KeyInfoSource::RetrievalMethod { uri, .. }]
5255 if uri == "https://example.test/key.der"
5256 ));
5257 }
5258
5259 #[test]
5260 fn parse_key_info_absolute_retrieval_bypasses_xml_base_chain() {
5261 let mut xml = format!(
5264 r#"<KeyInfo xmlns="{XMLDSIG_NS}"><RetrievalMethod URI="https://example.test/a/../key.der"/></KeyInfo>"#
5265 );
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_node = document
5271 .descendants()
5272 .find(|node| node.has_tag_name((XMLDSIG_NS, "KeyInfo")))
5273 .unwrap();
5274 let key_info = parse_key_info(key_info_node)
5275 .expect("absolute RetrievalMethod must not consume ancestor-base budget");
5276
5277 assert!(matches!(
5278 key_info.sources.as_slice(),
5279 [KeyInfoSource::RetrievalMethod { uri, .. }]
5280 if uri == "https://example.test/key.der"
5281 ));
5282 }
5283
5284 #[test]
5285 fn parse_key_info_accepts_namespace_equivalent_retrieval_xpath_prefix() {
5286 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5287 <RetrievalMethod Type="http://www.w3.org/2000/09/xmldsig#X509Data" URI="#keys">
5288 <Transforms><Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">
5289 <XPath xmlns:ds="http://www.w3.org/2000/09/xmldsig#">ancestor-or-self::ds:X509Data</XPath>
5290 </Transform></Transforms>
5291 </RetrievalMethod>
5292 </KeyInfo>"##;
5293 let doc = Document::parse(xml).unwrap();
5294
5295 assert!(matches!(
5296 parse_key_info(doc.root_element())
5297 .unwrap()
5298 .sources
5299 .as_slice(),
5300 [KeyInfoSource::RetrievalMethod {
5301 transforms: RetrievalMethodTransforms::X509DataNodeSetFilter { .. },
5302 ..
5303 }]
5304 ));
5305 }
5306
5307 #[test]
5308 fn parse_key_info_reads_complete_retrieval_xpath_text() {
5309 let valid = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5312 <RetrievalMethod Type="http://www.w3.org/2000/09/xmldsig#X509Data" URI="#keys">
5313 <Transforms><Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">
5314 <XPath xmlns:ds="http://www.w3.org/2000/09/xmldsig#">ancestor-or-self::ds:X509<!-- split -->Data</XPath>
5315 </Transform></Transforms>
5316 </RetrievalMethod>
5317 </KeyInfo>"##;
5318 let document = Document::parse(valid).unwrap();
5319 assert!(parse_key_info(document.root_element()).is_ok());
5320
5321 let unsupported =
5322 valid.replace("X509<!-- split -->Data", "X509Data<!-- split -->[false()]");
5323 let document = Document::parse(&unsupported).unwrap();
5324 assert!(matches!(
5325 parse_key_info(document.root_element()),
5326 Err(ParseError::InvalidStructure(reason))
5327 if reason == "unsupported RetrievalMethod XPath selection"
5328 ));
5329 }
5330
5331 #[test]
5332 fn parse_dsa_key_value_accepts_schema_optional_parameters_and_rejects_half_pair() {
5333 let key_info = |parameters: &str| {
5334 format!(
5335 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"><KeyValue><DSAKeyValue>
5336 {parameters}
5337 </DSAKeyValue></KeyValue></KeyInfo>"#
5338 )
5339 };
5340 for parameters in [
5341 "<Y>AQ==</Y>",
5342 "<G>AQ==</G><Y>AQ==</Y>",
5343 "<P>AQ==</P><Q>AQ==</Q><Y>AQ==</Y>",
5344 "<P>AQ==</P><Q>AQ==</Q><G>AQ==</G><Y>AQ==</Y><J>AQ==</J>",
5345 "<Y>AQ==</Y><Seed>AQ==</Seed><PgenCounter>AQ==</PgenCounter>",
5346 "<Y>AQ==</Y><J>AQ==</J><Seed>AQ==</Seed><PgenCounter>AQ==</PgenCounter>",
5347 ] {
5348 let xml = key_info(parameters);
5349 let doc = Document::parse(&xml).unwrap();
5350 assert!(matches!(
5351 parse_key_info(doc.root_element())
5352 .unwrap()
5353 .sources
5354 .as_slice(),
5355 [KeyInfoSource::KeyValue(KeyValueInfo::Dsa { .. })]
5356 ));
5357 }
5358
5359 for invalid_parameters in [
5360 "<P>AQ==</P><Y>AQ==</Y>",
5361 "<Q>AQ==</Q><Y>AQ==</Y>",
5362 "<Y>AQ==</Y><Seed>AQ==</Seed>",
5363 "<Y>AQ==</Y><PgenCounter>AQ==</PgenCounter>",
5364 ] {
5365 let xml = key_info(invalid_parameters);
5366 let doc = Document::parse(&xml).unwrap();
5367 assert!(matches!(
5368 parse_key_info(doc.root_element()),
5369 Err(ParseError::InvalidStructure(_))
5370 ));
5371 }
5372 }
5373
5374 #[test]
5375 fn parse_dsa_crypto_binary_ignores_comment_nodes() {
5376 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5378 <KeyValue><DSAKeyValue><Y>AQ<!-- split -->ID</Y></DSAKeyValue></KeyValue>
5379 </KeyInfo>"#;
5380 let doc = Document::parse(xml).unwrap();
5381
5382 assert!(matches!(
5383 parse_key_info(doc.root_element())
5384 .unwrap()
5385 .sources
5386 .as_slice(),
5387 [KeyInfoSource::KeyValue(KeyValueInfo::Dsa { y, .. })] if y == &[1, 2, 3]
5388 ));
5389 }
5390
5391 #[test]
5392 fn parse_rsa_crypto_binary_ignores_comment_nodes() {
5393 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5395 <KeyValue><RSAKeyValue>
5396 <Modulus>AQ<!-- split -->ID</Modulus><Exponent>Aw==</Exponent>
5397 </RSAKeyValue></KeyValue>
5398 </KeyInfo>"#;
5399 let doc = Document::parse(xml).unwrap();
5400
5401 assert!(matches!(
5402 parse_key_info(doc.root_element())
5403 .unwrap()
5404 .sources
5405 .as_slice(),
5406 [KeyInfoSource::KeyValue(KeyValueInfo::Rsa { modulus, exponent })]
5407 if modulus == &[1, 2, 3] && exponent == &[3]
5408 ));
5409 }
5410
5411 #[test]
5412 fn parse_key_info_preserves_advisory_unsupported_retrieval_transform() {
5413 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5416 <RetrievalMethod URI="#keys" Type="urn:vendor:key"><Transforms>
5417 <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#base64"/>
5418 </Transforms></RetrievalMethod>
5419 <KeyName>fallback</KeyName>
5420 </KeyInfo>"##;
5421 let doc = Document::parse(xml).unwrap();
5422
5423 let key_info = parse_key_info(doc.root_element())
5424 .expect("unsupported advisory retrieval must not reject all KeyInfo sources");
5425 assert!(matches!(
5426 key_info.sources.as_slice(),
5427 [
5428 KeyInfoSource::RetrievalMethod { resource_type: Some(resource_type), .. },
5429 KeyInfoSource::KeyName(name),
5430 ] if resource_type == "urn:vendor:key" && name == "fallback"
5431 ));
5432 }
5433
5434 #[test]
5435 fn parse_key_info_preserves_empty_same_document_reference_uri() {
5436 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
5439 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
5440 <dsig11:KeyInfoReference URI=""/>
5441 </KeyInfo>"#;
5442 let document = Document::parse(xml).unwrap();
5443
5444 assert!(matches!(
5445 parse_key_info(document.root_element())
5446 .unwrap()
5447 .sources
5448 .as_slice(),
5449 [KeyInfoSource::KeyInfoReference { uri }] if uri.is_empty()
5450 ));
5451 }
5452
5453 #[test]
5454 fn parse_key_info_rejects_excessive_child_sources() {
5455 let children = (0..=64)
5457 .map(|index| format!(r#"<extension xmlns="urn:test" index="{index}"/>"#))
5458 .collect::<String>();
5459 let xml =
5460 format!(r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">{children}</KeyInfo>"#);
5461 let document = Document::parse(&xml).unwrap();
5462
5463 assert!(matches!(
5464 parse_key_info(document.root_element()),
5465 Err(ParseError::InvalidStructure(reason))
5466 if reason == "KeyInfo contains too many child elements"
5467 ));
5468 }
5469
5470 #[test]
5471 fn parse_key_info_rejects_keyname_with_child_elements() {
5472 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5473 <KeyName>ok<foo/></KeyName>
5474 </KeyInfo>"#;
5475 let doc = Document::parse(xml).unwrap();
5476
5477 let err = parse_key_info(doc.root_element()).unwrap_err();
5478 assert!(matches!(err, ParseError::InvalidStructure(_)));
5479 }
5480
5481 #[test]
5482 fn parse_key_info_preserves_keyname_text_without_trimming() {
5483 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5484 <KeyName> signing key </KeyName>
5485 </KeyInfo>"#;
5486 let doc = Document::parse(xml).unwrap();
5487
5488 let key_info = parse_key_info(doc.root_element()).unwrap();
5489 assert_eq!(
5490 key_info.sources,
5491 vec![KeyInfoSource::KeyName(" signing key ".into())]
5492 );
5493 }
5494
5495 #[test]
5496 fn parse_key_info_rejects_oversized_keyname_text() {
5497 let oversized = "A".repeat(4097);
5498 let xml = format!(
5499 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><KeyName>{oversized}</KeyName></KeyInfo>"
5500 );
5501 let doc = Document::parse(&xml).unwrap();
5502
5503 let err = parse_key_info(doc.root_element()).unwrap_err();
5504 assert!(matches!(err, ParseError::InvalidStructure(_)));
5505 }
5506
5507 #[test]
5508 fn parse_key_info_rejects_non_whitespace_mixed_content() {
5509 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">oops<KeyName>k</KeyName></KeyInfo>"#;
5510 let doc = Document::parse(xml).unwrap();
5511
5512 let err = parse_key_info(doc.root_element()).unwrap_err();
5513 assert!(matches!(err, ParseError::InvalidStructure(_)));
5514 }
5515
5516 #[test]
5517 fn parse_key_info_rejects_nbsp_as_non_xml_whitespace_mixed_content() {
5518 let xml = "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\u{00A0}<KeyName>k</KeyName></KeyInfo>";
5519 let doc = Document::parse(xml).unwrap();
5520
5521 let err = parse_key_info(doc.root_element()).unwrap_err();
5522 assert!(matches!(err, ParseError::InvalidStructure(_)));
5523 }
5524
5525 #[test]
5526 fn parse_key_info_der_encoded_key_value_rejects_oversized_payload() {
5527 let oversized =
5528 base64::engine::general_purpose::STANDARD
5529 .encode(vec![0u8; MAX_DER_ENCODED_KEY_VALUE_LEN + 1]);
5530 let xml = format!(
5531 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\" xmlns:dsig11=\"http://www.w3.org/2009/xmldsig11#\"><dsig11:DEREncodedKeyValue>{oversized}</dsig11:DEREncodedKeyValue></KeyInfo>"
5532 );
5533 let doc = Document::parse(&xml).unwrap();
5534
5535 let err = parse_key_info(doc.root_element()).unwrap_err();
5536 assert!(matches!(err, ParseError::InvalidStructure(_)));
5537 }
5538
5539 #[test]
5540 fn parse_key_info_der_encoded_key_value_rejects_empty_payload() {
5541 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
5542 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
5543 <dsig11:DEREncodedKeyValue>
5544
5545 </dsig11:DEREncodedKeyValue>
5546 </KeyInfo>"#;
5547 let doc = Document::parse(xml).unwrap();
5548
5549 let err = parse_key_info(doc.root_element()).unwrap_err();
5550 assert!(matches!(err, ParseError::InvalidStructure(_)));
5551 }
5552
5553 #[test]
5554 fn parse_key_info_der_encoded_key_value_non_xml_ascii_whitespace_is_not_parseable_xml() {
5555 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>";
5556 assert!(Document::parse(xml).is_err());
5557 }
5558
5559 #[test]
5562 fn parse_hmac_output_length_reads_all_text_nodes() {
5563 let xml = r#"<SignatureMethod xmlns="http://www.w3.org/2000/09/xmldsig#">
5565 <HMACOutputLength>8<!-- split -->0</HMACOutputLength>
5566 </SignatureMethod>"#;
5567 let document = Document::parse(xml).unwrap();
5568
5569 assert_eq!(
5570 parse_hmac_output_length(document.root_element(), SignatureAlgorithm::HmacSha1)
5571 .unwrap(),
5572 Some(80)
5573 );
5574 }
5575
5576 #[test]
5577 fn hmac_sha256_accepts_byte_aligned_output_length() {
5578 let document = Document::parse(
5579 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>"#,
5580 )
5581 .unwrap();
5582 let algorithm =
5583 SignatureAlgorithm::from_uri("http://www.w3.org/2001/04/xmldsig-more#hmac-sha256")
5584 .expect("HMAC-SHA256 must be recognized");
5585
5586 assert_eq!(
5587 parse_hmac_output_length(document.root_element(), algorithm).unwrap(),
5588 Some(128)
5589 );
5590 }
5591
5592 #[test]
5593 fn parse_hmac_output_length_rejects_hidden_suffix_text() {
5594 let xml = r#"<SignatureMethod xmlns="http://www.w3.org/2000/09/xmldsig#">
5596 <HMACOutputLength>80<!-- split -->0</HMACOutputLength>
5597 </SignatureMethod>"#;
5598 let document = Document::parse(xml).unwrap();
5599
5600 assert!(matches!(
5601 parse_hmac_output_length(document.root_element(), SignatureAlgorithm::HmacSha1),
5602 Err(ParseError::InvalidStructure(reason))
5603 if reason == "HMACOutputLength must be a positive byte-aligned value no greater than 160"
5604 ));
5605 }
5606
5607 #[test]
5608 fn hmac_output_length_rejects_non_xml_whitespace() {
5609 for separator in ['\u{a0}', '\u{2003}', '\u{85}'] {
5612 let text = format!("{separator}128{separator}");
5613 assert!(ByteAlignedHmacOutputLength::parse(&text, 256).is_err());
5614 }
5615 assert_eq!(
5616 ByteAlignedHmacOutputLength::parse(" \t\r\n+00128 \t", 256)
5617 .unwrap()
5618 .bits(),
5619 128
5620 );
5621 }
5622
5623 #[test]
5624 fn xmldsig_1_1_rejects_non_octet_hmac_output_length() {
5625 let xml = r#"<SignatureMethod xmlns="http://www.w3.org/2000/09/xmldsig#">
5628 <HMACOutputLength>129</HMACOutputLength>
5629 </SignatureMethod>"#;
5630 let document = Document::parse(xml).unwrap();
5631
5632 assert!(matches!(
5633 parse_hmac_output_length(document.root_element(), SignatureAlgorithm::HmacSha256),
5634 Err(ParseError::InvalidStructure(reason))
5635 if reason == "HMACOutputLength must be a positive byte-aligned value no greater than 256"
5636 ));
5637 }
5638
5639 #[test]
5640 fn parse_signed_info_rsa_sha256_with_reference() {
5641 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5642 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5643 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5644 <Reference URI="">
5645 <Transforms>
5646 <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
5647 <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5648 </Transforms>
5649 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5650 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
5651 </Reference>
5652 </SignedInfo>"#;
5653 let doc = Document::parse(xml).unwrap();
5654 let si = parse_signed_info(doc.root_element()).unwrap();
5655
5656 assert_eq!(si.signature_method, SignatureAlgorithm::RsaSha256);
5657 assert_eq!(si.references.len(), 1);
5658
5659 let r = &si.references[0];
5660 assert_eq!(r.uri.as_deref(), Some(""));
5661 assert_eq!(r.digest_method, DigestAlgorithm::Sha256);
5662 assert_eq!(r.digest_value, vec![0u8; 32]);
5663 assert_eq!(r.transforms.len(), 2);
5664 }
5665
5666 #[test]
5667 fn parse_signed_info_multiple_references() {
5668 let xml = r##"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5669 <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
5670 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"/>
5671 <Reference URI="#a">
5672 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5673 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
5674 </Reference>
5675 <Reference URI="#b">
5676 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
5677 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
5678 </Reference>
5679 </SignedInfo>"##;
5680 let doc = Document::parse(xml).unwrap();
5681 let si = parse_signed_info(doc.root_element()).unwrap();
5682
5683 assert_eq!(si.signature_method, SignatureAlgorithm::EcdsaSha256);
5684 assert_eq!(si.references.len(), 2);
5685 assert_eq!(si.references[0].uri.as_deref(), Some("#a"));
5686 assert_eq!(si.references[0].digest_method, DigestAlgorithm::Sha256);
5687 assert_eq!(si.references[1].uri.as_deref(), Some("#b"));
5688 assert_eq!(si.references[1].digest_method, DigestAlgorithm::Sha1);
5689 }
5690
5691 #[test]
5692 fn parse_signed_info_rejects_too_many_references() {
5693 let references = (0..=MAX_REFERENCES_PER_SIGNATURE)
5696 .map(|index| {
5697 format!(
5698 r##"<Reference URI="#item-{index}">
5699 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5700 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
5701 </Reference>"##
5702 )
5703 })
5704 .collect::<String>();
5705 let xml = format!(
5706 r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5707 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5708 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5709 {references}
5710 </SignedInfo>"#
5711 );
5712 let document = Document::parse(&xml).expect("fixed oversized fixture must parse");
5713
5714 let error = parse_signed_info(document.root_element())
5715 .expect_err("the parser must reject the 65th Reference");
5716
5717 assert!(matches!(
5718 error,
5719 ParseError::Policy(crate::policy::PolicyViolation::ResourceLimit {
5720 resource: crate::policy::resource_name::SIGNATURE_REFERENCES,
5721 maximum: MAX_REFERENCES_PER_SIGNATURE,
5722 actual: 65,
5723 })
5724 ));
5725 }
5726
5727 #[test]
5728 fn parse_signed_info_bounds_xpath_expressions_across_references() {
5729 let filters = r#"<XPath xmlns="http://www.w3.org/2002/06/xmldsig-filter2" Filter="intersect">true()</XPath>"#
5732 .repeat(64);
5733 let filter_transform = format!(
5734 r#"<Transform Algorithm="http://www.w3.org/2002/06/xmldsig-filter2">{filters}</Transform>"#
5735 );
5736 let reference = |index, transforms: &str| {
5737 format!(
5738 r##"<Reference URI="#item-{index}">
5739 <Transforms>{transforms}</Transforms>
5740 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5741 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
5742 </Reference>"##
5743 )
5744 };
5745 let signed_info = |references: &str| {
5746 format!(
5747 r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5748 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5749 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5750 {references}
5751 </SignedInfo>"#
5752 )
5753 };
5754
5755 let max_reference = reference(0, &filter_transform.repeat(64));
5756 let boundary_xml = signed_info(&max_reference);
5757 let boundary_document =
5758 Document::parse(&boundary_xml).expect("fixed boundary fixture must parse");
5759 parse_signed_info(boundary_document.root_element())
5760 .expect("one maximum-shaped Reference must remain accepted");
5761
5762 let extra_transform = r#"<Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116"><XPath>true()</XPath></Transform>"#;
5763 let xml = signed_info(&format!("{max_reference}{}", reference(1, extra_transform)));
5764 let document = Document::parse(&xml).expect("fixed aggregate fixture must parse");
5765
5766 let error = parse_signed_info(document.root_element())
5767 .expect_err("signature-wide XPath expression count must be bounded");
5768
5769 assert!(matches!(
5770 error,
5771 ParseError::Transform(TransformError::Policy(
5772 crate::policy::PolicyViolation::ResourceLimit {
5773 resource: "XPath expressions",
5774 ..
5775 }
5776 ))
5777 ));
5778 }
5779
5780 #[test]
5781 fn parse_reference_without_transforms() {
5782 let xml = r##"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5784 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5785 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5786 <Reference URI="#obj">
5787 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5788 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
5789 </Reference>
5790 </SignedInfo>"##;
5791 let doc = Document::parse(xml).unwrap();
5792 let si = parse_signed_info(doc.root_element()).unwrap();
5793
5794 assert!(si.references[0].transforms.is_empty());
5795 }
5796
5797 #[test]
5798 fn parse_reference_with_all_attributes() {
5799 let xml = r##"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5800 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5801 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5802 <Reference URI="#data" Id="ref1" Type="http://www.w3.org/2000/09/xmldsig#Object">
5803 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5804 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
5805 </Reference>
5806 </SignedInfo>"##;
5807 let doc = Document::parse(xml).unwrap();
5808 let si = parse_signed_info(doc.root_element()).unwrap();
5809 let r = &si.references[0];
5810
5811 assert_eq!(r.uri.as_deref(), Some("#data"));
5812 assert_eq!(r.id.as_deref(), Some("ref1"));
5813 assert_eq!(
5814 r.ref_type.as_deref(),
5815 Some("http://www.w3.org/2000/09/xmldsig#Object")
5816 );
5817 }
5818
5819 #[test]
5820 fn parse_reference_absent_uri() {
5821 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5823 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5824 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5825 <Reference>
5826 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5827 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
5828 </Reference>
5829 </SignedInfo>"#;
5830 let doc = Document::parse(xml).unwrap();
5831 let si = parse_signed_info(doc.root_element()).unwrap();
5832 assert!(si.references[0].uri.is_none());
5833 }
5834
5835 #[test]
5836 fn parse_signed_info_preserves_inclusive_prefixes() {
5837 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
5838 xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#">
5839 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
5840 <ec:InclusiveNamespaces PrefixList="ds saml #default"/>
5841 </CanonicalizationMethod>
5842 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5843 <Reference URI="">
5844 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5845 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
5846 </Reference>
5847 </SignedInfo>"#;
5848 let doc = Document::parse(xml).unwrap();
5849
5850 let si = parse_signed_info(doc.root_element()).unwrap();
5851 assert!(si.c14n_method.inclusive_prefixes().contains("ds"));
5852 assert!(si.c14n_method.inclusive_prefixes().contains("saml"));
5853 assert!(si.c14n_method.inclusive_prefixes().contains(""));
5854 }
5855
5856 #[test]
5859 fn missing_canonicalization_method() {
5860 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5861 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5862 <Reference URI="">
5863 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5864 <DigestValue>dGVzdA==</DigestValue>
5865 </Reference>
5866 </SignedInfo>"#;
5867 let doc = Document::parse(xml).unwrap();
5868 let result = parse_signed_info(doc.root_element());
5869 assert!(result.is_err());
5870 assert!(matches!(
5872 result.unwrap_err(),
5873 ParseError::InvalidStructure(_)
5874 ));
5875 }
5876
5877 #[test]
5878 fn missing_signature_method() {
5879 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5880 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5881 <Reference URI="">
5882 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5883 <DigestValue>dGVzdA==</DigestValue>
5884 </Reference>
5885 </SignedInfo>"#;
5886 let doc = Document::parse(xml).unwrap();
5887 let result = parse_signed_info(doc.root_element());
5888 assert!(result.is_err());
5889 assert!(matches!(
5891 result.unwrap_err(),
5892 ParseError::InvalidStructure(_)
5893 ));
5894 }
5895
5896 #[test]
5897 fn no_references() {
5898 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5899 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5900 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5901 </SignedInfo>"#;
5902 let doc = Document::parse(xml).unwrap();
5903 let result = parse_signed_info(doc.root_element());
5904 assert!(matches!(
5905 result.unwrap_err(),
5906 ParseError::MissingElement {
5907 element: "Reference"
5908 }
5909 ));
5910 }
5911
5912 #[test]
5913 fn unsupported_c14n_algorithm() {
5914 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5915 <CanonicalizationMethod Algorithm="http://example.com/bogus-c14n"/>
5916 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5917 <Reference URI="">
5918 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5919 <DigestValue>dGVzdA==</DigestValue>
5920 </Reference>
5921 </SignedInfo>"#;
5922 let doc = Document::parse(xml).unwrap();
5923 let result = parse_signed_info(doc.root_element());
5924 assert!(matches!(
5925 result.unwrap_err(),
5926 ParseError::UnsupportedAlgorithm { .. }
5927 ));
5928 }
5929
5930 #[test]
5931 fn unsupported_signature_algorithm() {
5932 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5933 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5934 <SignatureMethod Algorithm="http://example.com/bogus-sign"/>
5935 <Reference URI="">
5936 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5937 <DigestValue>dGVzdA==</DigestValue>
5938 </Reference>
5939 </SignedInfo>"#;
5940 let doc = Document::parse(xml).unwrap();
5941 let result = parse_signed_info(doc.root_element());
5942 assert!(matches!(
5943 result.unwrap_err(),
5944 ParseError::UnsupportedAlgorithm { .. }
5945 ));
5946 }
5947
5948 #[test]
5949 fn unsupported_digest_algorithm() {
5950 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5951 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5952 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5953 <Reference URI="">
5954 <DigestMethod Algorithm="http://example.com/bogus-digest"/>
5955 <DigestValue>dGVzdA==</DigestValue>
5956 </Reference>
5957 </SignedInfo>"#;
5958 let doc = Document::parse(xml).unwrap();
5959 let result = parse_signed_info(doc.root_element());
5960 assert!(matches!(
5961 result.unwrap_err(),
5962 ParseError::UnsupportedAlgorithm { .. }
5963 ));
5964 }
5965
5966 #[test]
5967 fn missing_digest_method() {
5968 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5969 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5970 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5971 <Reference URI="">
5972 <DigestValue>dGVzdA==</DigestValue>
5973 </Reference>
5974 </SignedInfo>"#;
5975 let doc = Document::parse(xml).unwrap();
5976 let result = parse_signed_info(doc.root_element());
5977 assert!(result.is_err());
5979 }
5980
5981 #[test]
5982 fn missing_digest_value() {
5983 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5984 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5985 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5986 <Reference URI="">
5987 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
5988 </Reference>
5989 </SignedInfo>"#;
5990 let doc = Document::parse(xml).unwrap();
5991 let result = parse_signed_info(doc.root_element());
5992 assert!(matches!(
5993 result.unwrap_err(),
5994 ParseError::MissingElement {
5995 element: "DigestValue"
5996 }
5997 ));
5998 }
5999
6000 #[test]
6001 fn invalid_base64_digest_value() {
6002 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
6003 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
6004 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
6005 <Reference URI="">
6006 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
6007 <DigestValue>!!!not-base64!!!</DigestValue>
6008 </Reference>
6009 </SignedInfo>"#;
6010 let doc = Document::parse(xml).unwrap();
6011 let result = parse_signed_info(doc.root_element());
6012 assert!(matches!(result.unwrap_err(), ParseError::Base64(_)));
6013 }
6014
6015 #[test]
6016 fn digest_value_length_must_match_digest_method() {
6017 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
6018 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
6019 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
6020 <Reference URI="">
6021 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
6022 <DigestValue>dGVzdA==</DigestValue>
6023 </Reference>
6024 </SignedInfo>"#;
6025 let doc = Document::parse(xml).unwrap();
6026
6027 let result = parse_signed_info(doc.root_element());
6028 assert!(matches!(
6029 result.unwrap_err(),
6030 ParseError::DigestLengthMismatch {
6031 algorithm: "http://www.w3.org/2001/04/xmlenc#sha256",
6032 expected: 32,
6033 actual: 4,
6034 }
6035 ));
6036 }
6037
6038 #[test]
6039 fn inclusive_prefixes_on_inclusive_c14n_is_rejected() {
6040 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
6041 xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#">
6042 <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315">
6043 <ec:InclusiveNamespaces PrefixList="ds"/>
6044 </CanonicalizationMethod>
6045 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
6046 <Reference URI="">
6047 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
6048 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
6049 </Reference>
6050 </SignedInfo>"#;
6051 let doc = Document::parse(xml).unwrap();
6052
6053 let result = parse_signed_info(doc.root_element());
6054 assert!(matches!(
6055 result.unwrap_err(),
6056 ParseError::UnsupportedAlgorithm { .. }
6057 ));
6058 }
6059
6060 #[test]
6061 fn extra_element_after_digest_value() {
6062 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
6063 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
6064 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
6065 <Reference URI="">
6066 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
6067 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
6068 <Unexpected/>
6069 </Reference>
6070 </SignedInfo>"#;
6071 let doc = Document::parse(xml).unwrap();
6072 let result = parse_signed_info(doc.root_element());
6073 assert!(matches!(
6074 result.unwrap_err(),
6075 ParseError::InvalidStructure(_)
6076 ));
6077 }
6078
6079 #[test]
6080 fn digest_value_with_element_child_is_rejected() {
6081 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
6082 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
6083 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
6084 <Reference URI="">
6085 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
6086 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAA=<Junk/>AAAA</DigestValue>
6087 </Reference>
6088 </SignedInfo>"#;
6089 let doc = Document::parse(xml).unwrap();
6090
6091 let result = parse_signed_info(doc.root_element());
6092 assert!(matches!(
6093 result.unwrap_err(),
6094 ParseError::InvalidStructure(_)
6095 ));
6096 }
6097
6098 #[test]
6099 fn wrong_namespace_on_signed_info() {
6100 let xml = r#"<SignedInfo xmlns="http://example.com/fake">
6101 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
6102 </SignedInfo>"#;
6103 let doc = Document::parse(xml).unwrap();
6104 let result = parse_signed_info(doc.root_element());
6105 assert!(matches!(
6106 result.unwrap_err(),
6107 ParseError::InvalidStructure(_)
6108 ));
6109 }
6110
6111 #[test]
6114 fn base64_with_whitespace() {
6115 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
6116 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
6117 <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
6118 <Reference URI="">
6119 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
6120 <DigestValue>
6121 AAAAAAAA
6122 AAAAAAAAAAAAAAAAAAA=
6123 </DigestValue>
6124 </Reference>
6125 </SignedInfo>"#;
6126 let doc = Document::parse(xml).unwrap();
6127 let si = parse_signed_info(doc.root_element()).unwrap();
6128 assert_eq!(si.references[0].digest_value, vec![0u8; 20]);
6129 }
6130
6131 #[test]
6132 fn digest_value_ignores_processing_instruction_data() {
6133 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
6136 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
6137 <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
6138 <Reference URI="">
6139 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
6140 <DigestValue>AAAAAAAA<?ignored not-base64?>AAAAAAAAAAAAAAAAAAA=</DigestValue>
6141 </Reference>
6142 </SignedInfo>"#;
6143 let document = Document::parse(xml).expect("fixture must parse");
6144 let signed_info =
6145 parse_signed_info(document.root_element()).expect("PI must not alter digest data");
6146
6147 assert_eq!(signed_info.references[0].digest_value, vec![0_u8; 20]);
6148 }
6149
6150 #[test]
6151 fn base64_decode_digest_accepts_xml_whitespace_chars() {
6152 let digest =
6153 base64_decode_digest("AAAA\tAAAA\rAAAA\nAAAA AAAAAAAAAAA=", DigestAlgorithm::Sha1)
6154 .expect("XML whitespace in DigestValue must be accepted");
6155 assert_eq!(digest, vec![0u8; 20]);
6156 }
6157
6158 #[test]
6159 fn base64_decode_digest_rejects_non_xml_ascii_whitespace() {
6160 let err = base64_decode_digest(
6161 "AAAA\u{000C}AAAAAAAAAAAAAAAAAAAAAAA=",
6162 DigestAlgorithm::Sha1,
6163 )
6164 .expect_err("form-feed/vertical-tab in DigestValue must be rejected");
6165 assert!(matches!(err, ParseError::Base64(_)));
6166 }
6167
6168 #[test]
6169 fn base64_decode_digest_rejects_oversized_base64_before_decode() {
6170 let err = base64_decode_digest("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", DigestAlgorithm::Sha1)
6171 .expect_err("oversized DigestValue base64 must fail before decode");
6172 match err {
6173 ParseError::Base64(message) => {
6174 assert!(
6175 message.contains("DigestValue exceeds maximum allowed base64 length"),
6176 "unexpected message: {message}"
6177 );
6178 }
6179 other => panic!("expected ParseError::Base64, got {other:?}"),
6180 }
6181 }
6182
6183 #[test]
6186 fn saml_response_signed_info() {
6187 let xml = r##"<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
6188 <ds:SignedInfo>
6189 <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
6190 <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
6191 <ds:Reference URI="#_resp1">
6192 <ds:Transforms>
6193 <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
6194 <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
6195 </ds:Transforms>
6196 <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
6197 <ds:DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</ds:DigestValue>
6198 </ds:Reference>
6199 </ds:SignedInfo>
6200 <ds:SignatureValue>ZmFrZQ==</ds:SignatureValue>
6201 </ds:Signature>"##;
6202 let doc = Document::parse(xml).unwrap();
6203
6204 let sig_node = doc.root_element();
6206 let signed_info_node = sig_node
6207 .children()
6208 .find(|n| n.is_element() && n.tag_name().name() == "SignedInfo")
6209 .unwrap();
6210
6211 let si = parse_signed_info(signed_info_node).unwrap();
6212 assert_eq!(si.signature_method, SignatureAlgorithm::RsaSha256);
6213 assert_eq!(si.references.len(), 1);
6214 assert_eq!(si.references[0].uri.as_deref(), Some("#_resp1"));
6215 assert_eq!(si.references[0].transforms.len(), 2);
6216 assert_eq!(si.references[0].digest_value, vec![0u8; 32]);
6217 }
6218}