1use crate::xml::dom::{Document, Node};
20use der::{Decode, asn1::ObjectIdentifier};
21use std::collections::BTreeMap;
22
23#[cfg(test)]
24use super::digest::compute_digest;
25use super::digest::{DigestAlgorithm, compute_digest_with_provider, constant_time_eq};
26use super::pkix::X509DataSource;
27use super::transforms::{self, Transform};
28use super::whitespace::{is_xml_whitespace_only, normalize_xml_base64_text};
29use super::x509::certificate_signature_matches_with_provider;
30use crate::c14n::C14nAlgorithm;
31use crate::c14n::xml_base::{
32 XmlBaseResolutionBudget, resolve_uri_from_node_with_document_base_with_budget,
33};
34
35pub(crate) use crate::hard_limits::SIGNATURE_REFERENCE_CEILING as MAX_REFERENCES_PER_SIGNATURE;
36#[cfg(test)]
37use crate::hard_limits::X509_CHAIN_DEPTH_CEILING as MAX_X509_CHAIN_DEPTH;
38
39pub(crate) const XMLDSIG_NS: &str = "http://www.w3.org/2000/09/xmldsig#";
41pub(crate) const XMLDSIG11_NS: &str = "http://www.w3.org/2009/xmldsig11#";
43const MAX_DER_ENCODED_KEY_VALUE_LEN: usize = 8192;
44const MAX_DER_ENCODED_KEY_VALUE_TEXT_LEN: usize = 65_536;
45const MAX_DER_ENCODED_KEY_VALUE_BASE64_LEN: usize = MAX_DER_ENCODED_KEY_VALUE_LEN.div_ceil(3) * 4;
46pub(crate) const MAX_KEY_NAME_TEXT_LEN: usize = 4096;
47const MAX_KEY_INFO_CHILD_COUNT: usize = 64;
48const MAX_HMAC_OUTPUT_LENGTH_TEXT_LEN: usize = 32;
49pub(crate) const EXPERIMENTAL_SIGNATURE_NS: &str =
50 "http://www.aleksey.com/xmlsec/2025/12/xmldsig-more#";
51
52#[derive(Debug, Clone, Default, PartialEq, Eq)]
54pub struct SignatureContext(Vec<u8>);
55
56impl SignatureContext {
57 pub fn new(bytes: &[u8]) -> Result<Self, ParseError> {
60 if bytes.len() > 255 {
61 return Err(ParseError::InvalidStructure(
62 "signature context exceeds 255 octets".into(),
63 ));
64 }
65 Ok(Self(bytes.to_vec()))
66 }
67
68 pub fn as_bytes(&self) -> &[u8] {
70 &self.0
71 }
72}
73const MAX_RETRIEVAL_XPATH_TEXT_LEN: usize = 256;
74const MAX_RSA_MODULUS_LEN: usize = 1024;
75const MAX_RSA_EXPONENT_LEN: usize = 8;
76pub(crate) const EC_P256_OID: &str = "1.2.840.10045.3.1.7";
77pub(crate) const EC_P384_OID: &str = "1.3.132.0.34";
78pub(crate) const EC_P521_OID: &str = "1.3.132.0.35";
79const MAX_EC_PUBLIC_KEY_LEN: usize = 133;
80const MAX_X509_BASE64_TEXT_LEN: usize = 262_144;
81const MAX_X509_BASE64_NORMALIZED_LEN: usize = MAX_X509_BASE64_TEXT_LEN;
82pub(crate) const MAX_X509_DECODED_BINARY_LEN: usize =
83 MAX_X509_BASE64_NORMALIZED_LEN.div_ceil(4) * 3;
84const MAX_X509_SUBJECT_NAME_TEXT_LEN: usize = 16_384;
85const MAX_X509_ISSUER_NAME_TEXT_LEN: usize = 16_384;
86const MAX_X509_SERIAL_NUMBER_RAW_TEXT_LEN: usize = 16_384;
87const MAX_X509_SERIAL_NUMBER_VALUE_DIGITS: usize = 49;
91const MAX_X509_SERIAL_NUMBER_BYTES: usize = 20;
92const MAX_X509_DATA_ENTRY_COUNT: usize = 64;
93pub(crate) const MAX_X509_DATA_TOTAL_BINARY_LEN: usize = 1_048_576;
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
97pub struct RsaPssParameters {
98 pub digest: DigestAlgorithm,
100 pub mgf_digest: DigestAlgorithm,
102 pub salt_len: usize,
104}
105
106impl Default for RsaPssParameters {
107 fn default() -> Self {
108 Self::DEFAULT
109 }
110}
111
112impl RsaPssParameters {
113 pub const DEFAULT: Self = Self {
115 digest: DigestAlgorithm::Sha256,
116 mgf_digest: DigestAlgorithm::Sha256,
117 salt_len: 32,
118 };
119
120 pub(crate) fn fits_modulus_bits(self, bits: usize) -> bool {
121 if bits < 2 || self.salt_len > i32::MAX as usize {
124 return false;
125 }
126 let em_len = (bits - 1).div_ceil(8);
127 let hash_len = self.digest.output_len();
128 em_len >= hash_len + 2 && self.salt_len <= em_len - hash_len - 2
129 }
130}
131
132pub(crate) const RSA_PSS_NS: &str = "http://www.w3.org/2007/05/xmldsig-more#";
133
134#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
136#[non_exhaustive]
137pub enum SignatureAlgorithm {
138 #[cfg(feature = "legacy-algorithms")]
140 RsaMd5,
141 #[cfg(feature = "legacy-algorithms")]
143 RsaRipemd160,
144 #[cfg(feature = "legacy-algorithms")]
146 HmacMd5,
147 #[cfg(feature = "legacy-algorithms")]
149 HmacRipemd160,
150 #[cfg(feature = "legacy-algorithms")]
152 EcdsaRipemd160,
153 DsaSha1,
155 DsaSha256,
157 HmacSha1,
159 HmacSha224,
161 HmacSha256,
163 HmacSha384,
165 HmacSha512,
167 RsaSha1,
169 RsaSha224,
171 RsaSha256,
173 RsaSha384,
175 RsaSha512,
177 RsaPssSha1,
179 RsaPssSha224,
181 RsaPssSha256,
183 RsaPssSha384,
185 RsaPssSha512,
187 RsaPssSha3_224,
189 RsaPssSha3_256,
191 RsaPssSha3_384,
193 RsaPssSha3_512,
195 RsaPss(RsaPssParameters),
197 EcdsaSha1,
199 EcdsaSha224,
201 EcdsaSha256,
203 EcdsaSha384,
205 EcdsaSha512,
207 EcdsaSha3_224,
209 EcdsaSha3_256,
211 EcdsaSha3_384,
213 EcdsaSha3_512,
215 Ed25519,
217 Ed25519Ctx,
219 Ed25519Ph,
221 Ed448,
223 Ed448Ph,
225 PostQuantum(super::PqAlgorithm),
227}
228
229impl SignatureAlgorithm {
230 pub fn rsa_pss_parameters(self) -> Option<RsaPssParameters> {
232 let digest = match self {
233 Self::RsaPss(parameters) => return Some(parameters),
234 Self::RsaPssSha1 => DigestAlgorithm::Sha1,
235 Self::RsaPssSha224 => DigestAlgorithm::Sha224,
236 Self::RsaPssSha256 => DigestAlgorithm::Sha256,
237 Self::RsaPssSha384 => DigestAlgorithm::Sha384,
238 Self::RsaPssSha512 => DigestAlgorithm::Sha512,
239 Self::RsaPssSha3_224 => DigestAlgorithm::Sha3_224,
240 Self::RsaPssSha3_256 => DigestAlgorithm::Sha3_256,
241 Self::RsaPssSha3_384 => DigestAlgorithm::Sha3_384,
242 Self::RsaPssSha3_512 => DigestAlgorithm::Sha3_512,
243 _ => return None,
244 };
245 Some(RsaPssParameters {
246 digest,
247 mgf_digest: digest,
248 salt_len: digest.output_len(),
249 })
250 }
251
252 pub fn uses_legacy_pss_hash(self) -> bool {
254 self.rsa_pss_parameters().is_some_and(|parameters| {
255 [parameters.digest, parameters.mgf_digest]
256 .into_iter()
257 .any(|digest| match digest {
258 DigestAlgorithm::Sha1 => true,
259 #[cfg(feature = "legacy-algorithms")]
260 DigestAlgorithm::Md5 | DigestAlgorithm::Ripemd160 => true,
261 _ => false,
262 })
263 })
264 }
265 pub const ALL: [Self;
267 45 + if cfg!(feature = "legacy-algorithms") {
268 5
269 } else {
270 0
271 }] = [
272 #[cfg(feature = "legacy-algorithms")]
273 Self::RsaMd5,
274 #[cfg(feature = "legacy-algorithms")]
275 Self::RsaRipemd160,
276 #[cfg(feature = "legacy-algorithms")]
277 Self::HmacMd5,
278 #[cfg(feature = "legacy-algorithms")]
279 Self::HmacRipemd160,
280 #[cfg(feature = "legacy-algorithms")]
281 Self::EcdsaRipemd160,
282 Self::DsaSha1,
283 Self::DsaSha256,
284 Self::HmacSha1,
285 Self::HmacSha224,
286 Self::HmacSha256,
287 Self::HmacSha384,
288 Self::HmacSha512,
289 Self::RsaSha1,
290 Self::RsaSha224,
291 Self::RsaSha256,
292 Self::RsaSha384,
293 Self::RsaSha512,
294 Self::RsaPssSha1,
295 Self::RsaPssSha224,
296 Self::RsaPssSha256,
297 Self::RsaPssSha384,
298 Self::RsaPssSha512,
299 Self::RsaPssSha3_224,
300 Self::RsaPssSha3_256,
301 Self::RsaPssSha3_384,
302 Self::RsaPssSha3_512,
303 Self::RsaPss(RsaPssParameters::DEFAULT),
304 Self::EcdsaSha1,
305 Self::EcdsaSha224,
306 Self::EcdsaSha256,
307 Self::EcdsaSha384,
308 Self::EcdsaSha512,
309 Self::EcdsaSha3_224,
310 Self::EcdsaSha3_256,
311 Self::EcdsaSha3_384,
312 Self::EcdsaSha3_512,
313 Self::Ed25519,
314 Self::Ed25519Ctx,
315 Self::Ed25519Ph,
316 Self::Ed448,
317 Self::Ed448Ph,
318 Self::PostQuantum(super::PqAlgorithm::MlDsa44),
319 Self::PostQuantum(super::PqAlgorithm::MlDsa65),
320 Self::PostQuantum(super::PqAlgorithm::MlDsa87),
321 Self::PostQuantum(super::PqAlgorithm::SlhDsaSha2_128f),
322 Self::PostQuantum(super::PqAlgorithm::SlhDsaSha2_128s),
323 Self::PostQuantum(super::PqAlgorithm::SlhDsaSha2_192f),
324 Self::PostQuantum(super::PqAlgorithm::SlhDsaSha2_192s),
325 Self::PostQuantum(super::PqAlgorithm::SlhDsaSha2_256f),
326 Self::PostQuantum(super::PqAlgorithm::SlhDsaSha2_256s),
327 ];
328
329 pub const fn ecdsa_digest(self) -> Option<DigestAlgorithm> {
331 match self {
332 #[cfg(feature = "legacy-algorithms")]
333 Self::EcdsaRipemd160 => Some(DigestAlgorithm::Ripemd160),
334 Self::EcdsaSha1 => Some(DigestAlgorithm::Sha1),
335 Self::EcdsaSha224 => Some(DigestAlgorithm::Sha224),
336 Self::EcdsaSha256 => Some(DigestAlgorithm::Sha256),
337 Self::EcdsaSha384 => Some(DigestAlgorithm::Sha384),
338 Self::EcdsaSha512 => Some(DigestAlgorithm::Sha512),
339 Self::EcdsaSha3_224 => Some(DigestAlgorithm::Sha3_224),
340 Self::EcdsaSha3_256 => Some(DigestAlgorithm::Sha3_256),
341 Self::EcdsaSha3_384 => Some(DigestAlgorithm::Sha3_384),
342 Self::EcdsaSha3_512 => Some(DigestAlgorithm::Sha3_512),
343 _ => None,
344 }
345 }
346
347 pub(crate) const fn eddsa_signature_len(self) -> Option<usize> {
349 match self {
350 Self::Ed25519 | Self::Ed25519Ctx | Self::Ed25519Ph => Some(64),
351 Self::Ed448 | Self::Ed448Ph => Some(114),
352 _ => None,
353 }
354 }
355
356 pub(crate) const fn eddsa_key_algorithm(self) -> Option<Self> {
357 match self {
358 Self::Ed25519 | Self::Ed25519Ctx | Self::Ed25519Ph => Some(Self::Ed25519),
359 Self::Ed448 | Self::Ed448Ph => Some(Self::Ed448),
360 _ => None,
361 }
362 }
363
364 pub(crate) const fn context_element(self) -> Option<&'static str> {
365 match self {
366 Self::Ed25519Ctx | Self::Ed25519Ph | Self::Ed448 | Self::Ed448Ph => {
367 Some("EdDSAContextString")
368 }
369 Self::PostQuantum(algorithm) => Some(algorithm.context_element()),
370 _ => None,
371 }
372 }
373
374 pub(crate) const fn dsa_component_len(self) -> Option<usize> {
376 match self {
377 Self::DsaSha1 => Some(20),
378 Self::DsaSha256 => Some(32),
379 _ => None,
380 }
381 }
382
383 #[must_use]
385 pub const fn hmac_output_bits(self) -> Option<usize> {
386 match self {
387 #[cfg(feature = "legacy-algorithms")]
388 Self::HmacMd5 => Some(128),
389 #[cfg(feature = "legacy-algorithms")]
390 Self::HmacRipemd160 => Some(160),
391 Self::HmacSha1 => Some(160),
392 Self::HmacSha224 => Some(224),
393 Self::HmacSha256 => Some(256),
394 Self::HmacSha384 => Some(384),
395 Self::HmacSha512 => Some(512),
396 _ => None,
397 }
398 }
399
400 #[must_use]
402 pub fn from_uri(uri: &str) -> Option<Self> {
403 match uri {
404 "http://www.w3.org/2007/05/xmldsig-more#sha1-rsa-MGF1" => Some(Self::RsaPssSha1),
405 "http://www.w3.org/2007/05/xmldsig-more#sha224-rsa-MGF1" => Some(Self::RsaPssSha224),
406 "http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1" => Some(Self::RsaPssSha256),
407 "http://www.w3.org/2007/05/xmldsig-more#sha384-rsa-MGF1" => Some(Self::RsaPssSha384),
408 "http://www.w3.org/2007/05/xmldsig-more#sha512-rsa-MGF1" => Some(Self::RsaPssSha512),
409 "http://www.w3.org/2007/05/xmldsig-more#sha3-224-rsa-MGF1" => {
410 Some(Self::RsaPssSha3_224)
411 }
412 "http://www.w3.org/2007/05/xmldsig-more#sha3-256-rsa-MGF1" => {
413 Some(Self::RsaPssSha3_256)
414 }
415 "http://www.w3.org/2007/05/xmldsig-more#sha3-384-rsa-MGF1" => {
416 Some(Self::RsaPssSha3_384)
417 }
418 "http://www.w3.org/2007/05/xmldsig-more#sha3-512-rsa-MGF1" => {
419 Some(Self::RsaPssSha3_512)
420 }
421 "http://www.w3.org/2007/05/xmldsig-more#rsa-pss" => {
422 Some(Self::RsaPss(RsaPssParameters::DEFAULT))
423 }
424 #[cfg(feature = "legacy-algorithms")]
425 "http://www.w3.org/2001/04/xmldsig-more#rsa-md5" => Some(Self::RsaMd5),
426 #[cfg(feature = "legacy-algorithms")]
427 "http://www.w3.org/2001/04/xmldsig-more#rsa-ripemd160" => Some(Self::RsaRipemd160),
428 #[cfg(feature = "legacy-algorithms")]
429 "http://www.w3.org/2001/04/xmldsig-more#hmac-md5" => Some(Self::HmacMd5),
430 #[cfg(feature = "legacy-algorithms")]
431 "http://www.w3.org/2001/04/xmldsig-more#hmac-ripemd160" => Some(Self::HmacRipemd160),
432 #[cfg(feature = "legacy-algorithms")]
435 "http://www.w3.org/2007/05/xmldsig-more#ecdsa-ripemd160" => Some(Self::EcdsaRipemd160),
436 "http://www.w3.org/2000/09/xmldsig#dsa-sha1" => Some(Self::DsaSha1),
437 "http://www.w3.org/2009/xmldsig11#dsa-sha256" => Some(Self::DsaSha256),
438 "http://www.w3.org/2000/09/xmldsig#hmac-sha1" => Some(Self::HmacSha1),
439 "http://www.w3.org/2001/04/xmldsig-more#hmac-sha224" => Some(Self::HmacSha224),
440 "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256" => Some(Self::HmacSha256),
441 "http://www.w3.org/2001/04/xmldsig-more#hmac-sha384" => Some(Self::HmacSha384),
442 "http://www.w3.org/2001/04/xmldsig-more#hmac-sha512" => Some(Self::HmacSha512),
443 "http://www.w3.org/2000/09/xmldsig#rsa-sha1" => Some(Self::RsaSha1),
444 "http://www.w3.org/2001/04/xmldsig-more#rsa-sha224" => Some(Self::RsaSha224),
445 "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" => Some(Self::RsaSha256),
446 "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384" => Some(Self::RsaSha384),
447 "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512" => Some(Self::RsaSha512),
448 "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1" => Some(Self::EcdsaSha1),
449 "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha224" => Some(Self::EcdsaSha224),
450 "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256" => Some(Self::EcdsaSha256),
451 "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384" => Some(Self::EcdsaSha384),
452 "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512" => Some(Self::EcdsaSha512),
453 "http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-224" => Some(Self::EcdsaSha3_224),
456 "http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-256" => Some(Self::EcdsaSha3_256),
457 "http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-384" => Some(Self::EcdsaSha3_384),
458 "http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-512" => Some(Self::EcdsaSha3_512),
459 "http://www.w3.org/2021/04/xmldsig-more#eddsa-ed25519" => Some(Self::Ed25519),
462 "http://www.w3.org/2021/04/xmldsig-more#eddsa-ed25519ctx" => Some(Self::Ed25519Ctx),
463 "http://www.w3.org/2021/04/xmldsig-more#eddsa-ed25519ph" => Some(Self::Ed25519Ph),
464 "http://www.w3.org/2021/04/xmldsig-more#eddsa-ed448" => Some(Self::Ed448),
465 "http://www.w3.org/2021/04/xmldsig-more#eddsa-ed448ph" => Some(Self::Ed448Ph),
466 _ => super::PqAlgorithm::ALL
467 .into_iter()
468 .find(|algorithm| algorithm.uri() == uri)
469 .map(Self::PostQuantum),
470 }
471 }
472
473 #[must_use]
475 pub fn uri(self) -> &'static str {
476 match self {
477 Self::RsaPssSha1 => "http://www.w3.org/2007/05/xmldsig-more#sha1-rsa-MGF1",
478 Self::RsaPssSha224 => "http://www.w3.org/2007/05/xmldsig-more#sha224-rsa-MGF1",
479 Self::RsaPssSha256 => "http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1",
480 Self::RsaPssSha384 => "http://www.w3.org/2007/05/xmldsig-more#sha384-rsa-MGF1",
481 Self::RsaPssSha512 => "http://www.w3.org/2007/05/xmldsig-more#sha512-rsa-MGF1",
482 Self::RsaPssSha3_224 => "http://www.w3.org/2007/05/xmldsig-more#sha3-224-rsa-MGF1",
483 Self::RsaPssSha3_256 => "http://www.w3.org/2007/05/xmldsig-more#sha3-256-rsa-MGF1",
484 Self::RsaPssSha3_384 => "http://www.w3.org/2007/05/xmldsig-more#sha3-384-rsa-MGF1",
485 Self::RsaPssSha3_512 => "http://www.w3.org/2007/05/xmldsig-more#sha3-512-rsa-MGF1",
486 Self::RsaPss(_) => "http://www.w3.org/2007/05/xmldsig-more#rsa-pss",
487 #[cfg(feature = "legacy-algorithms")]
488 Self::RsaMd5 => "http://www.w3.org/2001/04/xmldsig-more#rsa-md5",
489 #[cfg(feature = "legacy-algorithms")]
490 Self::RsaRipemd160 => "http://www.w3.org/2001/04/xmldsig-more#rsa-ripemd160",
491 #[cfg(feature = "legacy-algorithms")]
492 Self::HmacMd5 => "http://www.w3.org/2001/04/xmldsig-more#hmac-md5",
493 #[cfg(feature = "legacy-algorithms")]
494 Self::HmacRipemd160 => "http://www.w3.org/2001/04/xmldsig-more#hmac-ripemd160",
495 #[cfg(feature = "legacy-algorithms")]
496 Self::EcdsaRipemd160 => "http://www.w3.org/2007/05/xmldsig-more#ecdsa-ripemd160",
497 Self::DsaSha1 => "http://www.w3.org/2000/09/xmldsig#dsa-sha1",
498 Self::DsaSha256 => "http://www.w3.org/2009/xmldsig11#dsa-sha256",
499 Self::HmacSha1 => "http://www.w3.org/2000/09/xmldsig#hmac-sha1",
500 Self::HmacSha224 => "http://www.w3.org/2001/04/xmldsig-more#hmac-sha224",
501 Self::HmacSha256 => "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256",
502 Self::HmacSha384 => "http://www.w3.org/2001/04/xmldsig-more#hmac-sha384",
503 Self::HmacSha512 => "http://www.w3.org/2001/04/xmldsig-more#hmac-sha512",
504 Self::RsaSha1 => "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
505 Self::RsaSha224 => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha224",
506 Self::RsaSha256 => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
507 Self::RsaSha384 => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384",
508 Self::RsaSha512 => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512",
509 Self::EcdsaSha1 => "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1",
510 Self::EcdsaSha224 => "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha224",
511 Self::EcdsaSha256 => "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256",
512 Self::EcdsaSha384 => "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384",
513 Self::EcdsaSha512 => "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512",
514 Self::EcdsaSha3_224 => "http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-224",
515 Self::EcdsaSha3_256 => "http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-256",
516 Self::EcdsaSha3_384 => "http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-384",
517 Self::EcdsaSha3_512 => "http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-512",
518 Self::Ed25519 => "http://www.w3.org/2021/04/xmldsig-more#eddsa-ed25519",
519 Self::Ed25519Ctx => "http://www.w3.org/2021/04/xmldsig-more#eddsa-ed25519ctx",
520 Self::Ed25519Ph => "http://www.w3.org/2021/04/xmldsig-more#eddsa-ed25519ph",
521 Self::Ed448 => "http://www.w3.org/2021/04/xmldsig-more#eddsa-ed448",
522 Self::Ed448Ph => "http://www.w3.org/2021/04/xmldsig-more#eddsa-ed448ph",
523 Self::PostQuantum(algorithm) => algorithm.uri(),
524 }
525 }
526
527 #[must_use]
529 pub fn signing_allowed(self) -> bool {
530 if self.requires_explicit_permission() || self.uses_legacy_pss_hash() {
531 return false;
532 }
533 !matches!(
534 self,
535 Self::RsaSha1 | Self::DsaSha1 | Self::HmacSha1 | Self::EcdsaSha1 | Self::PostQuantum(_)
536 )
537 }
538
539 pub const fn requires_explicit_permission(self) -> bool {
541 match self {
542 #[cfg(feature = "legacy-algorithms")]
543 Self::RsaMd5
544 | Self::RsaRipemd160
545 | Self::HmacMd5
546 | Self::HmacRipemd160
547 | Self::EcdsaRipemd160 => true,
548 _ => false,
549 }
550 }
551
552 pub const fn is_rsa(self) -> bool {
554 match self {
555 Self::RsaPssSha1 => true,
556 Self::RsaPssSha224 => true,
557 Self::RsaPssSha256 => true,
558 Self::RsaPssSha384 => true,
559 Self::RsaPssSha512 => true,
560 Self::RsaPssSha3_224 => true,
561 Self::RsaPssSha3_256 => true,
562 Self::RsaPssSha3_384 => true,
563 Self::RsaPssSha3_512 => true,
564 Self::RsaPss(_) => true,
565 #[cfg(feature = "legacy-algorithms")]
566 Self::RsaMd5 | Self::RsaRipemd160 => true,
567 Self::RsaSha1
568 | Self::RsaSha224
569 | Self::RsaSha256
570 | Self::RsaSha384
571 | Self::RsaSha512 => true,
572 _ => false,
573 }
574 }
575}
576
577#[derive(Debug)]
579#[non_exhaustive]
580pub struct SignedInfo {
581 pub c14n_method: C14nAlgorithm,
583 pub signature_method: SignatureAlgorithm,
585 pub hmac_output_length_bits: Option<usize>,
587 pub signature_context: SignatureContext,
589 pub references: Vec<Reference>,
591}
592
593#[derive(Debug)]
595pub struct Reference {
596 pub uri: Option<String>,
598 pub id: Option<String>,
600 pub ref_type: Option<String>,
602 pub transforms: Vec<Transform>,
604 pub digest_method: DigestAlgorithm,
606 pub digest_value: Vec<u8>,
608}
609
610#[derive(Debug, Default, Clone, PartialEq, Eq)]
612#[non_exhaustive]
613pub struct KeyInfo {
614 pub sources: Vec<KeyInfoSource>,
616}
617
618impl KeyInfo {
619 pub(crate) fn embedded_candidate_count(&self) -> usize {
625 self.sources.iter().fold(0usize, |count, source| {
626 count.saturating_add(match source {
627 KeyInfoSource::KeyValue(_) | KeyInfoSource::DerEncodedKeyValue(_) => 1,
628 KeyInfoSource::X509Data(info) => info.certificates.len(),
629 KeyInfoSource::KeyName(_)
630 | KeyInfoSource::RetrievalMethod { .. }
631 | KeyInfoSource::KeyInfoReference { .. } => 0,
632 })
633 })
634 }
635}
636
637#[derive(Debug, Clone, PartialEq, Eq)]
639#[non_exhaustive]
640pub enum KeyInfoSource {
641 KeyName(String),
643 KeyValue(KeyValueInfo),
645 X509Data(X509DataInfo),
647 DerEncodedKeyValue(Vec<u8>),
649 RetrievalMethod {
651 uri: String,
653 resource_type: Option<String>,
655 transforms: RetrievalMethodTransforms,
657 },
658 KeyInfoReference {
660 uri: String,
662 },
663}
664
665#[derive(Debug, Clone, PartialEq, Eq, Hash)]
667#[non_exhaustive]
668pub enum RetrievalMethodTransforms {
669 None,
671 X509DataNodeSetFilter {
673 expression: String,
675 namespaces: BTreeMap<String, String>,
677 },
678 Unsupported,
682}
683
684#[derive(Debug, Clone, PartialEq, Eq)]
686#[non_exhaustive]
687pub enum KeyValueInfo {
688 Dh {
690 p: Option<Vec<u8>>,
692 q: Option<Vec<u8>>,
694 generator: Option<Vec<u8>>,
696 public: Vec<u8>,
698 seed: Option<Vec<u8>>,
700 pgen_counter: Option<Vec<u8>>,
702 },
703 Dsa {
705 p: Option<Vec<u8>>,
707 q: Option<Vec<u8>>,
709 g: Option<Vec<u8>>,
711 y: Vec<u8>,
713 },
714 Rsa {
716 modulus: Vec<u8>,
718 exponent: Vec<u8>,
720 },
721 Ec {
723 curve_oid: String,
725 public_key: Vec<u8>,
727 },
728 InvalidEcKeyValue,
730 Unsupported {
732 namespace: Option<String>,
734 local_name: String,
736 },
737}
738
739#[derive(Debug, Default, Clone, PartialEq, Eq)]
741#[non_exhaustive]
742pub struct X509DataInfo {
743 pub certificates: Vec<Vec<u8>>,
747 pub subject_names: Vec<String>,
749 pub issuer_serials: Vec<(String, String)>,
751 pub skis: Vec<Vec<u8>>,
753 pub crls: Vec<Vec<u8>>,
755 pub digests: Vec<(String, Vec<u8>)>,
757 pub parsed_certificates: Vec<ParsedX509Certificate>,
761 pub certificate_chain: Vec<usize>,
763}
764
765#[derive(Debug, Clone, PartialEq, Eq)]
767#[non_exhaustive]
768pub struct ParsedX509Certificate {
769 pub subject_dn: String,
771 pub issuer_dn: String,
773 pub serial_number: Vec<u8>,
775 pub serial_number_hex: String,
777 pub subject_key_identifier: Option<Vec<u8>>,
779 pub public_key: X509PublicKeyInfo,
781}
782
783#[derive(Debug, Clone, PartialEq, Eq)]
785#[non_exhaustive]
786pub enum X509PublicKeyInfo {
787 Rsa {
789 modulus: Vec<u8>,
791 exponent: Vec<u8>,
793 },
794 Ec {
796 curve_oid: String,
798 public_key: Vec<u8>,
800 },
801 Unsupported {
803 algorithm_oid: String,
805 },
806}
807
808#[derive(Debug, thiserror::Error)]
810#[non_exhaustive]
811pub enum ParseError {
812 #[error("X.509 processing failed: {0}")]
814 Certificate(#[source] structured_pkix::Error),
815 #[error("XMLDSig policy violation: {0}")]
817 Policy(#[from] crate::policy::PolicyViolation),
818
819 #[error("cryptographic provider error: {0}")]
821 Provider(#[from] crate::provider::ProviderError),
822
823 #[error("missing required element: <{element}>")]
825 MissingElement {
826 element: &'static str,
828 },
829
830 #[error("invalid structure: {0}")]
832 InvalidStructure(String),
833
834 #[error("invalid structure: {0}")]
836 AccountedInvalidStructure(structured_asn1::memory::OwnedText),
837
838 #[error("unsupported algorithm: {uri}")]
840 UnsupportedAlgorithm {
841 uri: String,
843 },
844
845 #[error("unsupported algorithm: {uri}")]
847 AccountedUnsupportedAlgorithm {
848 uri: structured_asn1::memory::OwnedText,
850 },
851
852 #[error("base64 decode error: {0}")]
854 Base64(String),
855
856 #[error(
858 "digest length mismatch for {algorithm}: expected {expected} bytes, got {actual} bytes"
859 )]
860 DigestLengthMismatch {
861 algorithm: &'static str,
863 expected: usize,
865 actual: usize,
867 },
868
869 #[error("transform error: {0}")]
871 Transform(#[from] super::types::TransformError),
872}
873
874#[must_use]
876pub fn find_signature_node<'a>(doc: &'a Document<'a>) -> Option<Node<'a, 'a>> {
877 doc.descendants().find(|n| {
878 n.is_element()
879 && n.tag_name().name() == "Signature"
880 && n.tag_name().namespace() == Some(XMLDSIG_NS)
881 })
882}
883
884pub fn parse_signed_info(signed_info_node: Node) -> Result<SignedInfo, ParseError> {
889 parse_signed_info_with_xpath_budget(
890 signed_info_node,
891 &mut transforms::XPathSignatureParseBudget::default(),
892 &crate::policy::ResourcePolicy::default(),
893 )
894}
895
896pub(crate) fn parse_signed_info_with_xpath_budget(
897 signed_info_node: Node,
898 xpath_budget: &mut transforms::XPathSignatureParseBudget,
899 resources: &crate::policy::ResourcePolicy,
900) -> Result<SignedInfo, ParseError> {
901 verify_ds_element(signed_info_node, "SignedInfo")?;
902
903 let mut children = element_children(signed_info_node);
904
905 let c14n_node = children.next().ok_or(ParseError::MissingElement {
907 element: "CanonicalizationMethod",
908 })?;
909 verify_ds_element(c14n_node, "CanonicalizationMethod")?;
910 let c14n_uri = required_algorithm_attr(c14n_node, "CanonicalizationMethod")?;
911 let mut c14n_method =
912 C14nAlgorithm::from_uri(c14n_uri).ok_or_else(|| ParseError::UnsupportedAlgorithm {
913 uri: c14n_uri.to_string(),
914 })?;
915 if let Some(prefix_list) = parse_inclusive_prefixes(c14n_node)? {
916 if c14n_method.mode() == crate::c14n::C14nMode::Exclusive1_0 {
917 c14n_method = c14n_method.with_prefix_list(prefix_list);
918 } else {
919 return Err(ParseError::UnsupportedAlgorithm {
920 uri: c14n_uri.to_string(),
921 });
922 }
923 }
924
925 let sig_method_node = children.next().ok_or(ParseError::MissingElement {
927 element: "SignatureMethod",
928 })?;
929 let (signature_method, hmac_output_length_bits, signature_context) =
930 parse_signature_method(sig_method_node, resources)?;
931
932 let mut references = Vec::new();
934 for child in children {
935 verify_ds_element(child, "Reference")?;
936 if references.len() == crate::hard_limits::SIGNATURE_REFERENCE_CEILING {
937 return Err(crate::policy::PolicyViolation::ResourceLimit {
938 resource: crate::policy::resource_name::SIGNATURE_REFERENCES,
939 maximum: crate::hard_limits::SIGNATURE_REFERENCE_CEILING,
940 actual: references.len().saturating_add(1),
941 }
942 .into());
943 }
944 references.push(parse_reference_with_xpath_budget(child, xpath_budget)?);
945 }
946 if references.is_empty() {
947 return Err(ParseError::MissingElement {
948 element: "Reference",
949 });
950 }
951
952 Ok(SignedInfo {
953 c14n_method,
954 signature_method,
955 hmac_output_length_bits,
956 signature_context,
957 references,
958 })
959}
960
961#[derive(Clone, Copy)]
962struct ByteAlignedHmacOutputLength(usize);
963
964pub fn parse_signature_method(
968 node: Node<'_, '_>,
969 resources: &crate::policy::ResourcePolicy,
970) -> Result<(SignatureAlgorithm, Option<usize>, SignatureContext), ParseError> {
971 verify_ds_element(node, "SignatureMethod")?;
972 let uri = required_algorithm_attr(node, "SignatureMethod")?;
973 let mut algorithm =
974 SignatureAlgorithm::from_uri(uri).ok_or_else(|| ParseError::UnsupportedAlgorithm {
975 uri: uri.to_string(),
976 })?;
977 ensure_no_non_whitespace_text(node, "SignatureMethod")?;
978 if matches!(algorithm, SignatureAlgorithm::RsaPss(_)) {
979 algorithm = SignatureAlgorithm::RsaPss(parse_rsa_pss_parameters(node)?);
980 return Ok((algorithm, None, SignatureContext::default()));
981 }
982 let (hmac, context) = if algorithm.context_element().is_some() {
983 (None, parse_signature_context(node, algorithm, resources)?)
984 } else {
985 (
986 parse_hmac_output_length(node, algorithm)?,
987 SignatureContext::default(),
988 )
989 };
990 Ok((algorithm, hmac, context))
991}
992
993fn parse_rsa_pss_parameters(node: Node<'_, '_>) -> Result<RsaPssParameters, ParseError> {
994 let mut parameters = RsaPssParameters::DEFAULT;
998 let mut children = element_children(node);
999 let Some(params) = children.next() else {
1000 return Ok(parameters);
1001 };
1002 if !params.has_tag_name((RSA_PSS_NS, "RSAPSSParams")) || children.next().is_some() {
1003 return Err(ParseError::InvalidStructure(
1004 "invalid RSAPSSParams element".into(),
1005 ));
1006 }
1007 ensure_no_non_whitespace_text(params, "RSAPSSParams")?;
1008 let mut last = 0;
1009 let mut salt_seen = false;
1010 let mut mgf_seen = false;
1011 for child in element_children(params) {
1012 let rank = match (child.tag_name().namespace(), child.tag_name().name()) {
1013 (Some(XMLDSIG_NS), "DigestMethod") => 1,
1014 (Some(RSA_PSS_NS), "MaskGenerationFunction") => 2,
1015 (Some(RSA_PSS_NS), "SaltLength") => 3,
1016 (Some(RSA_PSS_NS), "TrailerField") => 4,
1017 _ => {
1018 return Err(ParseError::InvalidStructure(
1019 "unknown RSAPSSParams child".into(),
1020 ));
1021 }
1022 };
1023 if rank <= last {
1024 return Err(ParseError::InvalidStructure(
1025 "duplicate or unordered RSAPSSParams child".into(),
1026 ));
1027 }
1028 last = rank;
1029 match rank {
1030 1 => parameters.digest = parse_pss_digest(child)?,
1031 2 => {
1032 ensure_no_non_whitespace_text(child, "MaskGenerationFunction")?;
1033 if child
1034 .attribute("Algorithm")
1035 .is_some_and(|uri| uri != "http://www.w3.org/2007/05/xmldsig-more#MGF1")
1036 {
1037 return Err(ParseError::InvalidStructure(
1038 "unsupported PSS mask generation function".into(),
1039 ));
1040 }
1041 let mut digests = element_children(child);
1042 parameters.mgf_digest = match digests.next() {
1043 Some(digest) => parse_pss_digest(digest)?,
1044 None => DigestAlgorithm::Sha256,
1048 };
1049 if digests.next().is_some() {
1050 return Err(ParseError::InvalidStructure(
1051 "duplicate MGF1 DigestMethod".into(),
1052 ));
1053 }
1054 mgf_seen = true;
1055 }
1056 3 => {
1057 parameters.salt_len = parse_pss_integer(child)?;
1058 salt_seen = true;
1059 }
1060 4 => {
1061 if parse_pss_integer(child)? != 1 {
1062 return Err(ParseError::InvalidStructure(
1065 "PSS TrailerField must be 1".into(),
1066 ));
1067 }
1068 }
1069 _ => unreachable!(),
1070 }
1071 }
1072 if !mgf_seen {
1073 parameters.mgf_digest = parameters.digest;
1074 }
1075 if !salt_seen {
1076 parameters.salt_len = parameters.digest.output_len();
1077 }
1078 Ok(parameters)
1079}
1080
1081fn parse_pss_digest(node: Node<'_, '_>) -> Result<DigestAlgorithm, ParseError> {
1082 verify_ds_element(node, "DigestMethod")?;
1083 ensure_no_element_children(node, "DigestMethod")?;
1084 ensure_no_non_whitespace_text(node, "DigestMethod")?;
1085 let uri = required_algorithm_attr(node, "DigestMethod")?;
1086 DigestAlgorithm::from_uri(uri)
1087 .ok_or_else(|| ParseError::UnsupportedAlgorithm { uri: uri.into() })
1088}
1089
1090fn parse_pss_integer(node: Node<'_, '_>) -> Result<usize, ParseError> {
1091 ensure_no_element_children(node, "PSS integer")?;
1092 let invalid = || ParseError::InvalidStructure("PSS integer must be nonnegative xs:int".into());
1096 let mut value = 0usize;
1097 let mut started = false;
1098 let mut digit_seen = false;
1099 let mut ended = false;
1100 let mut negative = false;
1101 for character in node
1102 .children()
1103 .filter_map(|child| child.text().filter(|_| child.is_text()))
1104 .flat_map(str::chars)
1105 {
1106 if matches!(character, ' ' | '\t' | '\r' | '\n') {
1107 if started {
1108 ended = true;
1109 }
1110 continue;
1111 }
1112 if ended {
1113 return Err(invalid());
1114 }
1115 if !started && matches!(character, '+' | '-') {
1116 started = true;
1117 negative = character == '-';
1118 continue;
1119 }
1120 let Some(digit) = character
1121 .to_digit(10)
1122 .filter(|_| character.is_ascii_digit())
1123 else {
1124 return Err(invalid());
1125 };
1126 if value > (i32::MAX as usize - digit as usize) / 10 {
1127 return Err(invalid());
1128 }
1129 value = value * 10 + digit as usize;
1130 started = true;
1131 digit_seen = true;
1132 }
1133 if !digit_seen || (negative && value != 0) {
1134 return Err(invalid());
1135 }
1136 Ok(value)
1137}
1138
1139impl ByteAlignedHmacOutputLength {
1140 fn parse(text: &str, maximum_bits: usize) -> Result<Self, ParseError> {
1141 let bits = text
1145 .trim_matches([' ', '\t', '\r', '\n'])
1146 .parse::<usize>()
1147 .map_err(|_| ParseError::InvalidStructure("invalid HMACOutputLength".into()))?;
1148 if bits == 0 || bits > maximum_bits || !bits.is_multiple_of(8) {
1152 return Err(ParseError::InvalidStructure(format!(
1153 "HMACOutputLength must be a positive byte-aligned value no greater than {maximum_bits}"
1154 )));
1155 }
1156 Ok(Self(bits))
1157 }
1158
1159 const fn bits(self) -> usize {
1160 self.0
1161 }
1162}
1163
1164fn parse_hmac_output_length(
1165 node: Node<'_, '_>,
1166 algorithm: SignatureAlgorithm,
1167) -> Result<Option<usize>, ParseError> {
1168 ensure_no_non_whitespace_text(node, "SignatureMethod")?;
1169 let mut children = element_children(node);
1170 let Some(child) = children.next() else {
1171 return Ok(None);
1172 };
1173 let Some(maximum_bits) = algorithm.hmac_output_bits() else {
1174 return Err(ParseError::InvalidStructure(
1175 "SignatureMethod parameters do not match the selected algorithm".into(),
1176 ));
1177 };
1178 if child.tag_name().namespace() != Some(XMLDSIG_NS)
1179 || child.tag_name().name() != "HMACOutputLength"
1180 || children.next().is_some()
1181 {
1182 return Err(ParseError::InvalidStructure(
1183 "SignatureMethod parameters do not match the selected algorithm".into(),
1184 ));
1185 }
1186 ensure_no_element_children(child, "HMACOutputLength")?;
1187 let text =
1188 collect_text_content_bounded(child, MAX_HMAC_OUTPUT_LENGTH_TEXT_LEN, "HMACOutputLength")?;
1189 Ok(Some(
1190 ByteAlignedHmacOutputLength::parse(&text, maximum_bits)?.bits(),
1191 ))
1192}
1193
1194fn parse_signature_context(
1195 node: Node<'_, '_>,
1196 algorithm: SignatureAlgorithm,
1197 resources: &crate::policy::ResourcePolicy,
1198) -> Result<SignatureContext, ParseError> {
1199 let Some(element_name) = algorithm.context_element() else {
1200 return Ok(SignatureContext::default());
1201 };
1202 let mut children = element_children(node);
1203 let Some(child) = children.next() else {
1204 return Ok(SignatureContext::default());
1205 };
1206 if child.tag_name().namespace() != Some(EXPERIMENTAL_SIGNATURE_NS)
1209 || child.tag_name().name() != element_name
1210 || children.next().is_some()
1211 {
1212 return Err(ParseError::InvalidStructure(
1213 "SignatureMethod parameters do not match the selected algorithm".into(),
1214 ));
1215 }
1216 let payload =
1222 super::whitespace::XmlBase64Payload::bounded(child, resources.max_xml_document_bytes, 340)
1223 .map_err(|reason| {
1224 ParseError::InvalidStructure(format!("signature context: {reason}"))
1225 })?;
1226 if payload.decoded_len > 255 {
1227 return Err(ParseError::InvalidStructure(
1228 "signature context exceeds 255 octets".into(),
1229 ));
1230 }
1231 let bytes = payload
1232 .decode()
1233 .map_err(|reason| ParseError::InvalidStructure(format!("signature context: {reason}")))?;
1234 Ok(SignatureContext(bytes))
1235}
1236
1237pub fn parse_reference(reference_node: Node) -> Result<Reference, ParseError> {
1241 parse_reference_with_xpath_budget(
1242 reference_node,
1243 &mut transforms::XPathSignatureParseBudget::default(),
1244 )
1245}
1246
1247pub(crate) fn parse_reference_with_xpath_budget(
1248 reference_node: Node,
1249 xpath_budget: &mut transforms::XPathSignatureParseBudget,
1250) -> Result<Reference, ParseError> {
1251 verify_ds_element(reference_node, "Reference")?;
1252 ensure_no_non_whitespace_text(reference_node, "Reference")?;
1253 let uri = reference_node.attribute("URI").map(String::from);
1254 let id = reference_node.attribute("Id").map(String::from);
1255 let ref_type = reference_node.attribute("Type").map(String::from);
1256
1257 let mut transforms = Vec::new();
1259 let mut transform_error = None;
1260 let mut children = element_children(reference_node);
1261 let (transforms_node, digest_method_node) =
1262 reference_transforms_and_digest_method(&mut children)?;
1263
1264 if let Some(transforms_node) = transforms_node {
1265 match transforms::parse_transforms_with_budget(transforms_node, xpath_budget) {
1266 Ok(parsed) => transforms = parsed,
1267 Err(error) => transform_error = Some(error),
1268 }
1269 }
1270
1271 let digest_uri = required_algorithm_attr(digest_method_node, "DigestMethod")?;
1273 let digest_method =
1274 DigestAlgorithm::from_uri(digest_uri).ok_or_else(|| ParseError::UnsupportedAlgorithm {
1275 uri: digest_uri.to_string(),
1276 })?;
1277
1278 let digest_value_node = reference_digest_value_node(&mut children)?;
1280 let digest_value = decode_digest_value_children(digest_value_node, digest_method)?;
1281 finish_reference_children(&mut children)?;
1282
1283 if let Some(error) = transform_error {
1287 return Err(ParseError::Transform(error));
1288 }
1289
1290 Ok(Reference {
1291 uri,
1292 id,
1293 ref_type,
1294 transforms,
1295 digest_method,
1296 digest_value,
1297 })
1298}
1299
1300pub(crate) type ReferenceLayout<'a> = (Option<Node<'a, 'a>>, Node<'a, 'a>, Node<'a, 'a>);
1302
1303#[derive(Debug)]
1304pub(crate) enum ReferenceLayoutFault<'a, 'input> {
1305 Missing(&'static str),
1306 WrongElement(Node<'a, 'input>, &'static str),
1307 MixedContent,
1308 TrailingElement(Node<'a, 'input>),
1309}
1310
1311impl ReferenceLayoutFault<'_, '_> {
1312 fn diagnostic(self) -> ParseError {
1313 self.diagnostic_in(&crate::c14n::scratch::Native)
1314 }
1315
1316 fn diagnostic_in<'s, S: transforms::ParseStorage<'s>>(self, storage: &S) -> ParseError {
1319 match self {
1320 Self::Missing(element) => ParseError::MissingElement { element },
1321 Self::WrongElement(node, expected) => {
1322 element_mismatch(node, expected, XMLDSIG_NS, "ds", storage)
1323 .expect_err("layout fault records a failed element check")
1324 }
1325 Self::MixedContent => storage.structure(format_args!(
1326 "Reference must not contain non-whitespace mixed content"
1327 )),
1328 Self::TrailingElement(node) => storage.structure(format_args!(
1329 "unexpected element <{}> after <DigestValue> in <Reference>",
1330 node.tag_name().name()
1331 )),
1332 }
1333 }
1334}
1335
1336fn layout_element<'a, 'input>(
1337 node: Node<'a, 'input>,
1338 expected: &'static str,
1339) -> Result<(), ReferenceLayoutFault<'a, 'input>> {
1340 if node.has_tag_name((XMLDSIG_NS, expected)) {
1341 Ok(())
1342 } else {
1343 Err(ReferenceLayoutFault::WrongElement(node, expected))
1344 }
1345}
1346
1347pub(crate) fn inspect_reference_layout<'a>(
1350 node: Node<'a, 'a>,
1351) -> Result<ReferenceLayout<'a>, ReferenceLayoutFault<'a, 'a>> {
1352 match inspect_charged_reference_layout(node, |_| Ok::<(), core::convert::Infallible>(())) {
1353 Ok(layout) => layout,
1354 Err(never) => match never {},
1355 }
1356}
1357
1358fn inspect_charged_reference_layout<'a, E>(
1361 node: Node<'a, 'a>,
1362 mut charge: impl FnMut(usize) -> Result<(), E>,
1363) -> Result<Result<ReferenceLayout<'a>, ReferenceLayoutFault<'a, 'a>>, E> {
1364 if let Err(fault) = layout_element(node, "Reference") {
1365 return Ok(Err(fault));
1366 }
1367 for child in node.children() {
1368 if let Some(text) = child.text().filter(|_| child.is_text()) {
1369 charge(text.len())?;
1370 if !is_xml_whitespace_only(text) {
1371 return Ok(Err(ReferenceLayoutFault::MixedContent));
1372 }
1373 }
1374 }
1375 Ok(layout_reference_children(node))
1376}
1377
1378fn layout_reference_children<'a>(
1379 node: Node<'a, 'a>,
1380) -> Result<ReferenceLayout<'a>, ReferenceLayoutFault<'a, 'a>> {
1381 let mut children = element_children(node);
1382 let (transforms, method) = layout_transforms_and_digest_method(&mut children)?;
1383 let value = layout_digest_value(&mut children)?;
1384 layout_finish(&mut children)?;
1385 Ok((transforms, method, value))
1386}
1387
1388fn reference_digest_value_node<'a>(
1389 children: &mut impl Iterator<Item = Node<'a, 'a>>,
1390) -> Result<Node<'a, 'a>, ParseError> {
1391 layout_digest_value(children).map_err(ReferenceLayoutFault::diagnostic)
1392}
1393
1394fn layout_digest_value<'a, 'input>(
1395 children: &mut impl Iterator<Item = Node<'a, 'input>>,
1396) -> Result<Node<'a, 'input>, ReferenceLayoutFault<'a, 'input>> {
1397 let value = children
1398 .next()
1399 .ok_or(ReferenceLayoutFault::Missing("DigestValue"))?;
1400 layout_element(value, "DigestValue")?;
1401 Ok(value)
1402}
1403
1404fn finish_reference_children<'a>(
1405 children: &mut impl Iterator<Item = Node<'a, 'a>>,
1406) -> Result<(), ParseError> {
1407 layout_finish(children).map_err(ReferenceLayoutFault::diagnostic)
1408}
1409
1410fn layout_finish<'a, 'input>(
1411 children: &mut impl Iterator<Item = Node<'a, 'input>>,
1412) -> Result<(), ReferenceLayoutFault<'a, 'input>> {
1413 if let Some(unexpected) = children.next() {
1414 return Err(ReferenceLayoutFault::TrailingElement(unexpected));
1415 }
1416 Ok(())
1417}
1418
1419pub(crate) fn parse_replay_transforms<'a, S: transforms::ParseStorage<'a>>(
1423 node: Node<'a, 'a>,
1424 budget: &mut transforms::XPathSignatureParseBudget,
1425 scratch: &S,
1426) -> Result<S::Values<S::Instruction>, ParseError> {
1427 scratch
1428 .charge(node.children().len())
1429 .map_err(super::types::TransformError::from)?;
1430 let (transforms, method, _) =
1431 inspect_charged_reference_layout(node, |bytes| scratch.charge(bytes))
1432 .map_err(super::types::TransformError::from)?
1433 .map_err(|fault| fault.diagnostic_in(scratch))?;
1434 scratch
1435 .charge(method.attributes().len())
1436 .map_err(super::types::TransformError::from)?;
1437 required_algorithm_attr(method, "DigestMethod")?;
1438 transforms.map_or_else(
1439 || Ok(scratch.values()),
1440 |node| {
1441 transforms::parse_reference_transforms_in(node, XMLDSIG_NS, budget, scratch)
1442 .map_err(Into::into)
1443 },
1444 )
1445}
1446
1447pub(crate) fn reference_digest_method(
1448 reference_node: Node<'_, '_>,
1449) -> Result<DigestAlgorithm, ParseError> {
1450 verify_ds_element(reference_node, "Reference")?;
1451 let mut children = element_children(reference_node);
1452 let (_, digest_method_node) = reference_transforms_and_digest_method(&mut children)?;
1453 let uri = required_algorithm_attr(digest_method_node, "DigestMethod")?;
1454 DigestAlgorithm::from_uri(uri).ok_or_else(|| ParseError::UnsupportedAlgorithm {
1455 uri: uri.to_owned(),
1456 })
1457}
1458
1459fn reference_transforms_and_digest_method<'a, 'input>(
1460 children: &mut impl Iterator<Item = Node<'a, 'input>>,
1461) -> Result<(Option<Node<'a, 'input>>, Node<'a, 'input>), ParseError> {
1462 layout_transforms_and_digest_method(children).map_err(ReferenceLayoutFault::diagnostic)
1463}
1464
1465fn layout_transforms_and_digest_method<'a, 'input>(
1466 children: &mut impl Iterator<Item = Node<'a, 'input>>,
1467) -> Result<(Option<Node<'a, 'input>>, Node<'a, 'input>), ReferenceLayoutFault<'a, 'input>> {
1468 let first = children
1469 .next()
1470 .ok_or(ReferenceLayoutFault::Missing("DigestMethod"))?;
1471 let transforms_node = is_ds_element(first, "Transforms").then_some(first);
1472 let digest_method_node = if transforms_node.is_some() {
1473 children
1474 .next()
1475 .ok_or(ReferenceLayoutFault::Missing("DigestMethod"))?
1476 } else {
1477 first
1478 };
1479 layout_element(digest_method_node, "DigestMethod")?;
1480 Ok((transforms_node, digest_method_node))
1481}
1482
1483pub fn parse_key_info(key_info_node: Node) -> Result<KeyInfo, ParseError> {
1496 parse_key_info_with_provider(key_info_node, crate::provider::default_provider())
1497}
1498
1499pub struct KeyInfoParsingSession<'a> {
1505 resources: &'a crate::policy::ResourcePolicy,
1506 xml_base: XmlBaseResolutionBudget,
1507 usage: KeyInfoParseUsage,
1508}
1509
1510pub(crate) struct KeyInfoParseUsage {
1511 embedded_candidates: usize,
1512 x509_binary_bytes: usize,
1513 pub(crate) certificates: super::pkix::CertificateSession,
1514}
1515
1516impl KeyInfoParseUsage {
1517 pub(crate) fn new(resources: &crate::policy::ResourcePolicy) -> Self {
1518 Self {
1519 embedded_candidates: 0,
1520 x509_binary_bytes: 0,
1521 certificates: super::pkix::CertificateSession::new(resources),
1522 }
1523 }
1524}
1525
1526impl<'a> KeyInfoParsingSession<'a> {
1527 pub(crate) fn resources(&self) -> &crate::policy::ResourcePolicy {
1528 self.resources
1529 }
1530 pub(crate) fn with_certificate_account(
1531 resources: &'a crate::policy::ResourcePolicy,
1532 certificates: &super::pkix::CertificateSession,
1533 ) -> Result<Self, ParseError> {
1534 certificates.require_resources(resources)?;
1535 Ok(Self {
1536 resources,
1537 xml_base: XmlBaseResolutionBudget::with_limits(
1538 resources.effective_xml_base_components(),
1539 resources.effective_xml_base_resolution_bytes(),
1540 ),
1541 usage: KeyInfoParseUsage {
1542 embedded_candidates: 0,
1543 x509_binary_bytes: 0,
1544 certificates: certificates.share(),
1545 },
1546 })
1547 }
1548 pub fn with_certificate_public_key<R>(
1552 &mut self,
1553 der: &[u8],
1554 inspect: impl FnOnce(&[u8]) -> R,
1555 ) -> Result<R, ParseError> {
1556 self.admit_certificate(der)?;
1557 let certificate = self
1558 .usage
1559 .certificates
1560 .read(der)
1561 .map_err(|error| self.usage.certificates.parse_error(error))?;
1562 Ok(inspect(certificate.tbs().public_key().as_bytes()))
1563 }
1564
1565 pub fn certificate_matches_selectors(
1567 &mut self,
1568 info: &X509DataInfo,
1569 der: &[u8],
1570 provider: &dyn crate::provider::CryptoProvider,
1571 ) -> Result<bool, ParseError> {
1572 self.admit_certificate(der)?;
1573 x509_selector_categories_match_candidates(
1574 info,
1575 core::iter::once(der),
1576 provider,
1577 &self.usage.certificates,
1578 )
1579 }
1580
1581 fn admit_certificate(&mut self, der: &[u8]) -> Result<(), ParseError> {
1582 charge_embedded_key_candidate(&mut self.usage.embedded_candidates, self.resources)?;
1583 if der.len() > self.resources.max_external_resource_bytes {
1584 return Err(ParseError::Policy(
1585 crate::policy::PolicyViolation::ResourceLimitExceeded {
1586 resource: crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES,
1587 maximum: self.resources.max_external_resource_bytes,
1588 },
1589 ));
1590 }
1591 let total = self.usage.x509_binary_bytes.checked_add(der.len());
1592 if total.is_none_or(|bytes| bytes > self.resources.max_external_resource_total_bytes) {
1593 return Err(ParseError::Policy(
1594 crate::policy::PolicyViolation::ResourceLimitExceeded {
1595 resource: crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
1596 maximum: self.resources.max_external_resource_total_bytes,
1597 },
1598 ));
1599 }
1600 self.usage.x509_binary_bytes = total.expect("admitted checked sum");
1601 Ok(())
1602 }
1603
1604 #[cfg(feature = "xmlenc")]
1607 pub(crate) fn charge_encryption_source(&mut self) -> Result<(), ParseError> {
1608 charge_embedded_key_candidate(&mut self.usage.embedded_candidates, self.resources)
1609 }
1610
1611 #[cfg(feature = "xmlenc")]
1615 pub(crate) fn preflight_agreement_role_candidates(&self, node: Node) -> Result<(), ParseError> {
1616 let mut projected = self.usage.embedded_candidates;
1617 for child in element_children(node) {
1618 match (child.tag_name().namespace(), child.tag_name().name()) {
1619 (Some(XMLDSIG_NS), "KeyValue") | (Some(XMLDSIG11_NS), "DEREncodedKeyValue") => {
1620 charge_embedded_key_candidate(&mut projected, self.resources)?;
1621 }
1622 (Some(XMLDSIG_NS), "X509Data") => {
1623 for _ in element_children(child)
1624 .filter(|node| node.has_tag_name((XMLDSIG_NS, "X509Certificate")))
1625 {
1626 charge_embedded_key_candidate(&mut projected, self.resources)?;
1627 }
1628 }
1629 _ => {}
1630 }
1631 }
1632 Ok(())
1633 }
1634
1635 pub fn new(resources: &'a crate::policy::ResourcePolicy) -> Result<Self, ParseError> {
1637 resources.validate()?;
1638 Ok(Self {
1639 resources,
1640 xml_base: XmlBaseResolutionBudget::with_limits(
1641 resources.effective_xml_base_components(),
1642 resources.effective_xml_base_resolution_bytes(),
1643 ),
1644 usage: KeyInfoParseUsage::new(resources),
1645 })
1646 }
1647
1648 pub fn parse(&mut self, node: Node) -> Result<KeyInfo, ParseError> {
1650 self.parse_with_provider(node, crate::provider::default_provider())
1651 }
1652
1653 pub fn parse_with_provider(
1655 &mut self,
1656 node: Node,
1657 provider: &dyn crate::provider::CryptoProvider,
1658 ) -> Result<KeyInfo, ParseError> {
1659 self.parse_with_provider_and_document_base(node, provider, None)
1660 }
1661
1662 pub(crate) fn parse_with_provider_and_document_base(
1663 &mut self,
1664 node: Node,
1665 provider: &dyn crate::provider::CryptoProvider,
1666 document_base: Option<&str>,
1667 ) -> Result<KeyInfo, ParseError> {
1668 parse_key_info_in_session(
1669 node,
1670 provider,
1671 &self.xml_base,
1672 self.resources,
1673 document_base,
1674 None,
1675 &mut self.usage,
1676 )
1677 }
1678
1679 #[cfg(feature = "xmlenc")]
1682 pub(crate) fn parse_agreement_role(
1683 &mut self,
1684 node: Node,
1685 provider: &dyn crate::provider::CryptoProvider,
1686 ) -> Result<KeyInfo, ParseError> {
1687 let namespace = "http://www.w3.org/2001/04/xmlenc#";
1688 if !node.has_tag_name((namespace, "OriginatorKeyInfo"))
1689 && !node.has_tag_name((namespace, "RecipientKeyInfo"))
1690 {
1691 return Err(ParseError::InvalidStructure(
1692 "invalid agreement key role".into(),
1693 ));
1694 }
1695 ensure_no_non_whitespace_text(node, "agreement KeyInfo")?;
1696 parse_key_info_contents_in_session(
1697 node,
1698 provider,
1699 &self.xml_base,
1700 self.resources,
1701 None,
1702 None,
1703 &mut self.usage,
1704 )
1705 }
1706}
1707
1708pub(crate) fn parse_key_info_with_provider(
1709 key_info_node: Node,
1710 provider: &dyn crate::provider::CryptoProvider,
1711) -> Result<KeyInfo, ParseError> {
1712 let xml_base_budget = XmlBaseResolutionBudget::default();
1713 parse_key_info_with_policy_budgets(
1714 key_info_node,
1715 provider,
1716 &xml_base_budget,
1717 &crate::policy::ResourcePolicy::default(),
1718 None,
1719 )
1720}
1721
1722pub(crate) fn parse_key_info_with_policy_budgets(
1723 key_info_node: Node,
1724 provider: &dyn crate::provider::CryptoProvider,
1725 xml_base_budget: &XmlBaseResolutionBudget,
1726 resources: &crate::policy::ResourcePolicy,
1727 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
1728) -> Result<KeyInfo, ParseError> {
1729 parse_key_info_in_session(
1730 key_info_node,
1731 provider,
1732 xml_base_budget,
1733 resources,
1734 None,
1735 allowed,
1736 &mut KeyInfoParseUsage::new(resources),
1737 )
1738}
1739
1740pub(crate) fn parse_key_info_in_session(
1741 key_info_node: Node,
1742 provider: &dyn crate::provider::CryptoProvider,
1743 xml_base_budget: &XmlBaseResolutionBudget,
1744 resources: &crate::policy::ResourcePolicy,
1745 document_base: Option<&str>,
1746 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
1747 usage: &mut KeyInfoParseUsage,
1748) -> Result<KeyInfo, ParseError> {
1749 validate_key_info_container(key_info_node)?;
1750
1751 parse_key_info_contents_in_session(
1752 key_info_node,
1753 provider,
1754 xml_base_budget,
1755 resources,
1756 document_base,
1757 allowed,
1758 usage,
1759 )
1760}
1761
1762fn parse_key_info_contents_in_session(
1763 key_info_node: Node,
1764 provider: &dyn crate::provider::CryptoProvider,
1765 xml_base_budget: &XmlBaseResolutionBudget,
1766 resources: &crate::policy::ResourcePolicy,
1767 document_base: Option<&str>,
1768 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
1769 usage: &mut KeyInfoParseUsage,
1770) -> Result<KeyInfo, ParseError> {
1771 let mut sources = Vec::new();
1772 for (index, child) in element_children(key_info_node).enumerate() {
1776 if index >= MAX_KEY_INFO_CHILD_COUNT {
1777 return Err(ParseError::InvalidStructure(
1778 "KeyInfo contains too many child elements".into(),
1779 ));
1780 }
1781 match (child.tag_name().namespace(), child.tag_name().name()) {
1782 (Some(XMLDSIG_NS), "KeyName") => {
1783 ensure_no_element_children(child, "KeyName")?;
1784 let key_name =
1785 collect_text_content_bounded(child, MAX_KEY_NAME_TEXT_LEN, "KeyName")?;
1786 sources.push(KeyInfoSource::KeyName(key_name));
1787 }
1788 (Some(XMLDSIG_NS), "KeyValue") => {
1789 charge_embedded_key_candidate(&mut usage.embedded_candidates, resources)?;
1790 let key_value = parse_key_value_dispatch(child)?;
1791 sources.push(KeyInfoSource::KeyValue(key_value));
1792 }
1793 (Some(XMLDSIG_NS), "X509Data") => {
1794 let x509 = parse_x509_data_in_session(
1795 child,
1796 &mut usage.x509_binary_bytes,
1797 &mut usage.embedded_candidates,
1798 provider,
1799 resources,
1800 allowed,
1801 &usage.certificates,
1802 )?;
1803 sources.push(KeyInfoSource::X509Data(x509));
1804 }
1805 (Some(XMLDSIG_NS), "RetrievalMethod") => {
1806 ensure_no_non_whitespace_text(child, "RetrievalMethod")?;
1807 let lexical_uri = child.attribute("URI").ok_or_else(|| {
1808 ParseError::InvalidStructure("RetrievalMethod requires URI".into())
1809 })?;
1810 if lexical_uri.len() > MAX_KEY_NAME_TEXT_LEN {
1811 return Err(ParseError::InvalidStructure(
1812 "RetrievalMethod URI exceeds maximum length".into(),
1813 ));
1814 }
1815 let uri = if lexical_uri.is_empty() || lexical_uri.starts_with('#') {
1816 lexical_uri.to_owned()
1817 } else {
1818 resolve_uri_from_node_with_document_base_with_budget(
1821 child,
1822 lexical_uri,
1823 document_base,
1824 xml_base_budget,
1825 )
1826 .map_err(|error| ParseError::InvalidStructure(error.to_string()))?
1827 };
1828 let resource_type = child.attribute("Type");
1829 if resource_type.is_some_and(|value| value.len() > MAX_KEY_NAME_TEXT_LEN) {
1830 return Err(ParseError::InvalidStructure(
1831 "RetrievalMethod Type exceeds maximum length".into(),
1832 ));
1833 }
1834 let resource_type = resource_type.map(str::to_owned);
1835 let transforms = if resource_type.as_deref()
1836 == Some("http://www.w3.org/2000/09/xmldsig#X509Data")
1837 {
1838 parse_retrieval_method_transforms(child, resources)?
1839 } else if element_children(child).next().is_some() {
1840 RetrievalMethodTransforms::Unsupported
1841 } else {
1842 RetrievalMethodTransforms::None
1843 };
1844 sources.push(KeyInfoSource::RetrievalMethod {
1845 uri,
1846 resource_type,
1847 transforms,
1848 });
1849 }
1850 (Some(XMLDSIG11_NS), "DEREncodedKeyValue") => {
1851 charge_embedded_key_candidate(&mut usage.embedded_candidates, resources)?;
1852 ensure_no_element_children(child, "DEREncodedKeyValue")?;
1853 let der = decode_der_encoded_key_value_base64(child)?;
1854 sources.push(KeyInfoSource::DerEncodedKeyValue(der));
1855 }
1856 (Some(XMLDSIG11_NS), "KeyInfoReference") => {
1857 ensure_no_element_children(child, "KeyInfoReference")?;
1858 ensure_no_non_whitespace_text(child, "KeyInfoReference")?;
1859 let lexical_uri = child.attribute("URI").ok_or_else(|| {
1860 ParseError::InvalidStructure("KeyInfoReference requires URI".into())
1861 })?;
1862 if lexical_uri.len() > MAX_KEY_NAME_TEXT_LEN {
1863 return Err(ParseError::InvalidStructure(
1864 "KeyInfoReference URI exceeds maximum length".into(),
1865 ));
1866 }
1867 let uri = if lexical_uri.is_empty() || lexical_uri.starts_with('#') {
1868 lexical_uri.to_owned()
1869 } else {
1870 resolve_uri_from_node_with_document_base_with_budget(
1871 child,
1872 lexical_uri,
1873 document_base,
1874 xml_base_budget,
1875 )
1876 .map_err(|error| ParseError::InvalidStructure(error.to_string()))?
1877 };
1878 sources.push(KeyInfoSource::KeyInfoReference { uri });
1879 }
1880 _ => {}
1881 }
1882 }
1883
1884 Ok(KeyInfo { sources })
1885}
1886
1887fn charge_embedded_key_candidate(
1888 embedded_key_candidates: &mut usize,
1889 resources: &crate::policy::ResourcePolicy,
1890) -> Result<(), ParseError> {
1891 *embedded_key_candidates = embedded_key_candidates.saturating_add(1);
1892 resources.validate_key_candidates(*embedded_key_candidates)?;
1893 Ok(())
1894}
1895
1896fn parse_retrieval_method_transforms(
1897 node: Node<'_, '_>,
1898 resources: &crate::policy::ResourcePolicy,
1899) -> Result<RetrievalMethodTransforms, ParseError> {
1900 let mut children = element_children(node);
1901 let Some(transforms) = children.next() else {
1902 return Ok(RetrievalMethodTransforms::None);
1903 };
1904 if children.next().is_some()
1905 || transforms.tag_name().namespace() != Some(XMLDSIG_NS)
1906 || transforms.tag_name().name() != "Transforms"
1907 {
1908 return Err(ParseError::InvalidStructure(
1909 "RetrievalMethod accepts only one optional ds:Transforms child".into(),
1910 ));
1911 }
1912 ensure_no_non_whitespace_text(transforms, "Transforms")?;
1913 let mut transform_children = element_children(transforms);
1914 let transform = transform_children.next().ok_or_else(|| {
1915 ParseError::InvalidStructure("RetrievalMethod Transforms must not be empty".into())
1916 })?;
1917 if transform_children.next().is_some()
1918 || transform.tag_name().namespace() != Some(XMLDSIG_NS)
1919 || transform.tag_name().name() != "Transform"
1920 || transform.attribute("Algorithm") != Some(transforms::XPATH_TRANSFORM_URI)
1921 {
1922 return Err(ParseError::InvalidStructure(
1923 "unsupported RetrievalMethod transform chain".into(),
1924 ));
1925 }
1926 ensure_no_non_whitespace_text(transform, "Transform")?;
1927 let mut parameters = element_children(transform);
1928 let xpath = parameters.next().ok_or_else(|| {
1929 ParseError::InvalidStructure("RetrievalMethod XPath parameter is missing".into())
1930 })?;
1931 if parameters.next().is_some()
1932 || xpath.tag_name().namespace() != Some(XMLDSIG_NS)
1933 || xpath.tag_name().name() != "XPath"
1934 {
1935 return Err(ParseError::InvalidStructure(
1936 "unsupported RetrievalMethod transform chain".into(),
1937 ));
1938 }
1939 ensure_no_element_children(xpath, "XPath")?;
1940 let expression =
1941 collect_text_content_bounded(xpath, MAX_RETRIEVAL_XPATH_TEXT_LEN, "RetrievalMethod XPath")?;
1942 let normalized_expression = expression.trim();
1943 let selects_x509_data = normalized_expression
1944 .strip_prefix("ancestor-or-self::")
1945 .and_then(|step| step.split_once(':'))
1946 .is_some_and(|(prefix, local)| {
1947 local == "X509Data" && xpath.lookup_namespace_uri(Some(prefix)) == Some(XMLDSIG_NS)
1948 });
1949 if !selects_x509_data {
1950 return Err(ParseError::InvalidStructure(
1951 "unsupported RetrievalMethod XPath selection".into(),
1952 ));
1953 }
1954 let namespaces = transforms::collect_xpath_namespaces_with_resources(xpath, resources)?;
1955 Ok(RetrievalMethodTransforms::X509DataNodeSetFilter {
1956 expression,
1957 namespaces,
1958 })
1959}
1960
1961fn element_children<'a>(node: Node<'a, 'a>) -> impl Iterator<Item = Node<'a, 'a>> {
1965 node.children().filter(|n| n.is_element())
1966}
1967
1968fn verify_ds_element(node: Node, expected_name: &'static str) -> Result<(), ParseError> {
1970 verify_namespaced_element(node, expected_name, XMLDSIG_NS, "ds")
1971}
1972
1973fn verify_dsig11_element(node: Node, expected_name: &'static str) -> Result<(), ParseError> {
1975 verify_namespaced_element(node, expected_name, XMLDSIG11_NS, "dsig11")
1976}
1977
1978fn verify_namespaced_element(
1979 node: Node,
1980 expected_name: &'static str,
1981 expected_namespace: &'static str,
1982 diagnostic_prefix: &'static str,
1983) -> Result<(), ParseError> {
1984 element_mismatch(
1985 node,
1986 expected_name,
1987 expected_namespace,
1988 diagnostic_prefix,
1989 &crate::c14n::scratch::Native,
1990 )
1991}
1992
1993fn element_mismatch<'s, S: transforms::ParseStorage<'s>>(
1994 node: Node,
1995 expected_name: &'static str,
1996 expected_namespace: &'static str,
1997 diagnostic_prefix: &'static str,
1998 storage: &S,
1999) -> Result<(), ParseError> {
2000 if !node.is_element() {
2001 return Err(storage.structure(format_args!(
2002 "expected element <{expected_name}>, got non-element node"
2003 )));
2004 }
2005 let tag = node.tag_name();
2006 if tag.name() != expected_name || tag.namespace() != Some(expected_namespace) {
2007 let name = tag.name();
2008 return Err(match tag.namespace() {
2009 Some(namespace) => storage.structure(format_args!(
2010 "expected <{diagnostic_prefix}:{expected_name}>, got <{{{namespace}}}{name}>"
2011 )),
2012 None => storage.structure(format_args!(
2013 "expected <{diagnostic_prefix}:{expected_name}>, got <{name}>"
2014 )),
2015 });
2016 }
2017 Ok(())
2018}
2019
2020fn required_algorithm_attr<'a>(
2022 node: Node<'a, 'a>,
2023 element_name: &'static str,
2024) -> Result<&'a str, ParseError> {
2025 node.attribute("Algorithm").ok_or_else(|| {
2026 ParseError::InvalidStructure(format!("missing Algorithm attribute on <{element_name}>"))
2027 })
2028}
2029
2030fn parse_inclusive_prefixes<'a>(node: Node<'a, '_>) -> Result<Option<&'a str>, ParseError> {
2036 const EXCLUSIVE_C14N_NS_URI: &str = "http://www.w3.org/2001/10/xml-exc-c14n#";
2037
2038 for child in node.children() {
2039 if child.is_element() {
2040 let tag = child.tag_name();
2041 if tag.name() == "InclusiveNamespaces" && tag.namespace() == Some(EXCLUSIVE_C14N_NS_URI)
2042 {
2043 return child
2044 .attribute("PrefixList")
2045 .ok_or_else(|| {
2046 ParseError::InvalidStructure(
2047 "missing PrefixList attribute on <InclusiveNamespaces>".into(),
2048 )
2049 })
2050 .map(Some);
2051 }
2052 }
2053 }
2054
2055 Ok(None)
2056}
2057
2058pub(crate) fn parse_replay_canonicalization_method<'a, 's, S: transforms::ParseStorage<'s>>(
2061 node: Node<'a, '_>,
2062 scratch: &S,
2063) -> Result<(C14nAlgorithm, Option<&'a str>), ParseError> {
2064 element_mismatch(node, "CanonicalizationMethod", XMLDSIG_NS, "ds", scratch)?;
2065 ensure_charged_whitespace_text(node, "CanonicalizationMethod", scratch)?;
2066 scratch
2067 .charge(node.attributes().len())
2068 .map_err(super::types::TransformError::from)?;
2069 let uri = node.attribute("Algorithm").ok_or_else(|| {
2070 scratch.structure(format_args!(
2071 "missing Algorithm attribute on <CanonicalizationMethod>"
2072 ))
2073 })?;
2074 let algorithm =
2075 C14nAlgorithm::from_uri(uri).ok_or_else(|| scratch.unsupported_algorithm(uri))?;
2076 let mut prefixes = None;
2077 for child in element_children(node) {
2078 if prefixes.is_some()
2082 || algorithm.mode() != crate::c14n::C14nMode::Exclusive1_0
2083 || !child.has_tag_name((
2084 "http://www.w3.org/2001/10/xml-exc-c14n#",
2085 "InclusiveNamespaces",
2086 ))
2087 {
2088 return Err(scratch.structure(format_args!("invalid canonicalization parameter")));
2089 }
2090 ensure_charged_whitespace_text(child, "InclusiveNamespaces", scratch)?;
2091 if child.children().any(|child| child.is_element()) {
2092 return Err(scratch.structure(format_args!(
2093 "InclusiveNamespaces must not contain child elements"
2094 )));
2095 }
2096 scratch
2097 .charge(child.attributes().len())
2098 .map_err(super::types::TransformError::from)?;
2099 prefixes = Some(child.attribute("PrefixList").ok_or_else(|| {
2100 scratch.structure(format_args!(
2101 "missing PrefixList attribute on <InclusiveNamespaces>"
2102 ))
2103 })?);
2104 }
2105 Ok((algorithm, prefixes))
2106}
2107
2108pub(crate) fn parse_key_value_dispatch(node: Node) -> Result<KeyValueInfo, ParseError> {
2109 verify_ds_element(node, "KeyValue")?;
2110 ensure_no_non_whitespace_text(node, "KeyValue")?;
2111
2112 let mut children = element_children(node);
2113 let Some(first_child) = children.next() else {
2114 return Err(ParseError::InvalidStructure(
2115 "KeyValue must contain exactly one key-value child".into(),
2116 ));
2117 };
2118 if children.next().is_some() {
2119 return Err(ParseError::InvalidStructure(
2120 "KeyValue must contain exactly one key-value child".into(),
2121 ));
2122 }
2123
2124 match (
2125 first_child.tag_name().namespace(),
2126 first_child.tag_name().name(),
2127 ) {
2128 (Some(XMLDSIG_NS), "RSAKeyValue") => parse_rsa_key_value(first_child),
2129 (Some(XMLDSIG_NS), "DSAKeyValue") => parse_dsa_key_value(first_child),
2130 (Some(XMLDSIG11_NS), "ECKeyValue") => parse_ec_key_value(first_child),
2131 (Some("http://www.w3.org/2001/04/xmlenc#"), "DHKeyValue") => {
2132 parse_dh_key_value(first_child)
2133 }
2134 (namespace, child_name) => Ok(KeyValueInfo::Unsupported {
2135 namespace: namespace.map(str::to_string),
2136 local_name: child_name.to_string(),
2137 }),
2138 }
2139}
2140
2141pub(crate) fn validate_key_info_container(node: Node) -> Result<(), ParseError> {
2142 verify_ds_element(node, "KeyInfo")?;
2143 ensure_no_non_whitespace_text(node, "KeyInfo")
2144}
2145
2146fn parse_dsa_key_value(node: Node<'_, '_>) -> Result<KeyValueInfo, ParseError> {
2147 verify_ds_element(node, "DSAKeyValue")?;
2148 ensure_no_non_whitespace_text(node, "DSAKeyValue")?;
2149 let children = element_children(node).collect::<Vec<_>>();
2150 let mut index = 0;
2151 let p = take_dsa_crypto_binary(&children, &mut index, "P")?;
2152 let q = take_dsa_crypto_binary(&children, &mut index, "Q")?;
2153 if p.is_some() != q.is_some() {
2154 return Err(ParseError::InvalidStructure(
2155 "DSAKeyValue P and Q must be present together".into(),
2156 ));
2157 }
2158 let g = take_dsa_crypto_binary(&children, &mut index, "G")?;
2159 let y = take_dsa_crypto_binary(&children, &mut index, "Y")?
2160 .ok_or_else(|| ParseError::InvalidStructure("DSAKeyValue requires Y".into()))?;
2161 let _j = take_dsa_crypto_binary(&children, &mut index, "J")?;
2162 let seed = take_dsa_crypto_binary(&children, &mut index, "Seed")?;
2163 let counter = take_dsa_crypto_binary(&children, &mut index, "PgenCounter")?;
2164 if seed.is_some() != counter.is_some() {
2165 return Err(ParseError::InvalidStructure(
2166 "DSAKeyValue Seed and PgenCounter must be present together".into(),
2167 ));
2168 }
2169 if index != children.len() {
2170 return Err(ParseError::InvalidStructure(
2171 "DSAKeyValue children do not match the XMLDSig schema order".into(),
2172 ));
2173 }
2174 Ok(KeyValueInfo::Dsa { p, q, g, y })
2175}
2176
2177fn parse_dh_key_value(node: Node<'_, '_>) -> Result<KeyValueInfo, ParseError> {
2178 const NS: &str = "http://www.w3.org/2001/04/xmlenc#";
2179 ensure_no_non_whitespace_text(node, "DHKeyValue")?;
2180 let mut children = element_children(node).peekable();
2181 let mut take = |name: &'static str| -> Result<Option<Vec<u8>>, ParseError> {
2182 if !children
2183 .peek()
2184 .is_some_and(|child| child.has_tag_name((NS, name)))
2185 {
2186 return Ok(None);
2187 }
2188 let child = children.next().expect("peeked child");
2189 ensure_no_element_children(child, name)?;
2190 decode_crypto_binary(
2191 child,
2192 name,
2193 crate::hard_limits::DH_MODULUS_BIT_CEILING.div_ceil(8),
2194 )
2195 .map(Some)
2196 };
2197 let p = take("P")?;
2201 let q = take("Q")?;
2202 let generator = take("Generator")?;
2203 if p.is_some() != q.is_some() || p.is_some() != generator.is_some() {
2204 return Err(ParseError::InvalidStructure(
2205 "DHKeyValue requires P, Q and Generator together".into(),
2206 ));
2207 }
2208 let public = take("Public")?
2209 .ok_or_else(|| ParseError::InvalidStructure("DHKeyValue requires Public".into()))?;
2210 let seed = take("seed")?;
2211 let pgen_counter = take("pgenCounter")?;
2212 if seed.is_some() != pgen_counter.is_some() || children.next().is_some() {
2213 return Err(ParseError::InvalidStructure(
2214 "DHKeyValue children do not match the XMLEnc schema order".into(),
2215 ));
2216 }
2217 Ok(KeyValueInfo::Dh {
2218 p,
2219 q,
2220 generator,
2221 public,
2222 seed,
2223 pgen_counter,
2224 })
2225}
2226
2227fn take_dsa_crypto_binary(
2228 children: &[Node<'_, '_>],
2229 index: &mut usize,
2230 name: &'static str,
2231) -> Result<Option<Vec<u8>>, ParseError> {
2232 let Some(&child) = children.get(*index) else {
2233 return Ok(None);
2234 };
2235 if !is_ds_element(child, name) {
2236 return Ok(None);
2237 }
2238 *index += 1;
2239 ensure_no_element_children(child, name)?;
2240 decode_crypto_binary(child, name, MAX_RSA_MODULUS_LEN).map(Some)
2241}
2242
2243fn is_ds_element(node: Node<'_, '_>, name: &str) -> bool {
2244 node.tag_name().namespace() == Some(XMLDSIG_NS) && node.tag_name().name() == name
2245}
2246
2247fn parse_ec_key_value(node: Node<'_, '_>) -> Result<KeyValueInfo, ParseError> {
2248 verify_dsig11_element(node, "ECKeyValue")?;
2249 ensure_no_non_whitespace_text(node, "ECKeyValue")?;
2250
2251 let mut children = element_children(node);
2252 let Some(named_curve_node) = children.next() else {
2253 return Ok(KeyValueInfo::InvalidEcKeyValue);
2254 };
2255 if named_curve_node.tag_name().namespace() == Some(XMLDSIG11_NS)
2256 && named_curve_node.tag_name().name() == "ECParameters"
2257 {
2258 return Ok(KeyValueInfo::Unsupported {
2259 namespace: Some(XMLDSIG11_NS.to_string()),
2260 local_name: "ECKeyValue".into(),
2261 });
2262 }
2263 if named_curve_node.tag_name().namespace() != Some(XMLDSIG11_NS)
2264 || named_curve_node.tag_name().name() != "NamedCurve"
2265 {
2266 return Ok(KeyValueInfo::InvalidEcKeyValue);
2267 }
2268 ensure_no_element_children(named_curve_node, "NamedCurve")?;
2269 ensure_no_non_whitespace_text(named_curve_node, "NamedCurve")?;
2270 let Some((curve_oid, expected_public_key_len)) =
2271 (match parse_ec_named_curve_oid(named_curve_node) {
2272 Ok(curve) => curve,
2273 Err(_) => return Ok(KeyValueInfo::InvalidEcKeyValue),
2274 })
2275 else {
2276 return Ok(KeyValueInfo::Unsupported {
2277 namespace: Some(XMLDSIG11_NS.to_string()),
2278 local_name: "ECKeyValue".into(),
2279 });
2280 };
2281
2282 let Some(public_key_node) = children.next() else {
2283 return Ok(KeyValueInfo::InvalidEcKeyValue);
2284 };
2285 if public_key_node.tag_name().namespace() != Some(XMLDSIG11_NS)
2286 || public_key_node.tag_name().name() != "PublicKey"
2287 {
2288 return Ok(KeyValueInfo::InvalidEcKeyValue);
2289 }
2290 ensure_no_element_children(public_key_node, "PublicKey")?;
2291 if children.next().is_some() {
2292 return Ok(KeyValueInfo::InvalidEcKeyValue);
2293 }
2294
2295 let public_key = match decode_crypto_binary(public_key_node, "PublicKey", MAX_EC_PUBLIC_KEY_LEN)
2296 {
2297 Ok(public_key) => public_key,
2298 Err(_) => return Ok(KeyValueInfo::InvalidEcKeyValue),
2299 };
2300 if validate_ec_public_key_point(&public_key, expected_public_key_len).is_err() {
2301 return Ok(KeyValueInfo::InvalidEcKeyValue);
2302 }
2303
2304 Ok(KeyValueInfo::Ec {
2305 curve_oid,
2306 public_key,
2307 })
2308}
2309
2310fn parse_ec_named_curve_oid(node: Node<'_, '_>) -> Result<Option<(String, usize)>, ParseError> {
2311 let uri = node.attribute("URI").ok_or_else(|| {
2312 ParseError::InvalidStructure("ECKeyValue NamedCurve must include URI attribute".into())
2313 })?;
2314 let curve_oid = uri.strip_prefix("urn:oid:").unwrap_or(uri);
2315 if curve_oid.is_empty() {
2316 return Err(ParseError::InvalidStructure(
2317 "ECKeyValue NamedCurve URI must not be empty".into(),
2318 ));
2319 }
2320 let Some(public_key_len) = ec_public_key_len(curve_oid) else {
2321 return Ok(None);
2322 };
2323 Ok(Some((curve_oid.to_string(), public_key_len)))
2324}
2325
2326fn ec_public_key_len(curve_oid: &str) -> Option<usize> {
2327 match curve_oid {
2328 EC_P256_OID => Some(65),
2329 EC_P384_OID => Some(97),
2330 EC_P521_OID => Some(133),
2331 _ => None,
2332 }
2333}
2334
2335fn validate_ec_public_key_point(public_key: &[u8], expected_len: usize) -> Result<(), ParseError> {
2336 if public_key.len() != expected_len {
2337 return Err(ParseError::InvalidStructure(
2338 "ECKeyValue PublicKey length does not match NamedCurve".into(),
2339 ));
2340 }
2341 if public_key.first().copied() != Some(0x04) {
2342 return Err(ParseError::InvalidStructure(
2343 "ECKeyValue PublicKey must be an uncompressed SEC1 point".into(),
2344 ));
2345 }
2346 Ok(())
2347}
2348
2349fn parse_rsa_key_value(node: Node<'_, '_>) -> Result<KeyValueInfo, ParseError> {
2350 verify_ds_element(node, "RSAKeyValue")?;
2351 ensure_no_non_whitespace_text(node, "RSAKeyValue")?;
2352
2353 let mut children = element_children(node);
2354 let modulus_node = children.next().ok_or_else(|| {
2355 ParseError::InvalidStructure("RSAKeyValue requires Modulus and Exponent".into())
2356 })?;
2357 verify_ds_element(modulus_node, "Modulus")?;
2358 ensure_no_element_children(modulus_node, "Modulus")?;
2359
2360 let exponent_node = children.next().ok_or_else(|| {
2361 ParseError::InvalidStructure("RSAKeyValue requires Modulus and Exponent".into())
2362 })?;
2363 verify_ds_element(exponent_node, "Exponent")?;
2364 ensure_no_element_children(exponent_node, "Exponent")?;
2365 if children.next().is_some() {
2366 return Err(ParseError::InvalidStructure(
2367 "RSAKeyValue must contain exactly Modulus followed by Exponent".into(),
2368 ));
2369 }
2370
2371 Ok(KeyValueInfo::Rsa {
2372 modulus: decode_crypto_binary(modulus_node, "Modulus", MAX_RSA_MODULUS_LEN)?,
2373 exponent: decode_crypto_binary(exponent_node, "Exponent", MAX_RSA_EXPONENT_LEN)?,
2374 })
2375}
2376
2377fn decode_crypto_binary(
2378 node: Node<'_, '_>,
2379 element_name: &'static str,
2380 max_decoded_len: usize,
2381) -> Result<Vec<u8>, ParseError> {
2382 let max_base64_len = max_decoded_len.div_ceil(3) * 4;
2383 let payload = super::whitespace::XmlBase64Payload::bounded(node, usize::MAX, max_base64_len)
2384 .map_err(|reason| xml_base64_payload_error(element_name, reason))?;
2385 if payload.normalized_len > max_base64_len {
2386 return Err(ParseError::InvalidStructure(format!(
2387 "{element_name} exceeds maximum allowed base64 length"
2388 )));
2389 }
2390 if payload.decoded_len == 0 {
2391 return Err(ParseError::InvalidStructure(format!(
2392 "{element_name} must not be empty"
2393 )));
2394 }
2395 if payload.decoded_len > max_decoded_len {
2396 return Err(ParseError::InvalidStructure(format!(
2397 "{element_name} exceeds maximum allowed binary length"
2398 )));
2399 }
2400 payload
2401 .decode()
2402 .map_err(|reason| ParseError::Base64(format!("{element_name}: {reason}")))
2403}
2404
2405pub(crate) fn parse_x509_data_dispatch_with_budget_and_provider(
2406 node: Node,
2407 total_binary_len: &mut usize,
2408 embedded_key_candidates: &mut usize,
2409 provider: &dyn crate::provider::CryptoProvider,
2410 resources: &crate::policy::ResourcePolicy,
2411 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
2412 certificates: &super::pkix::CertificateSession,
2413) -> Result<X509DataInfo, ParseError> {
2414 parse_x509_data_in_session(
2415 node,
2416 total_binary_len,
2417 embedded_key_candidates,
2418 provider,
2419 resources,
2420 allowed,
2421 certificates,
2422 )
2423}
2424
2425fn parse_x509_data_in_session(
2426 node: Node,
2427 total_binary_len: &mut usize,
2428 embedded_key_candidates: &mut usize,
2429 provider: &dyn crate::provider::CryptoProvider,
2430 resources: &crate::policy::ResourcePolicy,
2431 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
2432 certificates: &super::pkix::CertificateSession,
2433) -> Result<X509DataInfo, ParseError> {
2434 verify_ds_element(node, "X509Data")?;
2435 ensure_no_non_whitespace_text(node, "X509Data")?;
2436
2437 let mut info = X509DataInfo::default();
2438 for child in element_children(node) {
2439 match (child.tag_name().namespace(), child.tag_name().name()) {
2440 (Some(XMLDSIG_NS), "X509Certificate") => {
2441 charge_embedded_key_candidate(embedded_key_candidates, resources)?;
2442 ensure_no_element_children(child, "X509Certificate")?;
2443 ensure_x509_data_entry_budget(&info)?;
2444 let cert =
2445 decode_x509_base64(child, "X509Certificate", total_binary_len, resources)?;
2446 let parsed_cert = parse_x509_certificate_in(cert.as_slice(), certificates)?;
2447 info.parsed_certificates.push(parsed_cert);
2448 info.certificates.push(cert);
2449 }
2450 (Some(XMLDSIG_NS), "X509SubjectName") => {
2451 ensure_no_element_children(child, "X509SubjectName")?;
2452 ensure_x509_data_entry_budget(&info)?;
2453 let subject_name = collect_text_content_bounded(
2454 child,
2455 MAX_X509_SUBJECT_NAME_TEXT_LEN,
2456 "X509SubjectName",
2457 )?;
2458 info.subject_names.push(subject_name);
2459 }
2460 (Some(XMLDSIG_NS), "X509IssuerSerial") => {
2461 ensure_x509_data_entry_budget(&info)?;
2462 let issuer_serial = parse_x509_issuer_serial(child)?;
2463 info.issuer_serials.push(issuer_serial);
2464 }
2465 (Some(XMLDSIG_NS), "X509SKI") => {
2466 ensure_no_element_children(child, "X509SKI")?;
2467 ensure_x509_data_entry_budget(&info)?;
2468 let ski = decode_x509_base64(child, "X509SKI", total_binary_len, resources)?;
2469 info.skis.push(ski);
2470 }
2471 (Some(XMLDSIG_NS), "X509CRL") => {
2472 ensure_no_element_children(child, "X509CRL")?;
2473 ensure_x509_data_entry_budget(&info)?;
2474 let crl = decode_x509_base64(child, "X509CRL", total_binary_len, resources)?;
2475 info.crls.push(crl);
2476 }
2477 (Some(XMLDSIG11_NS), "X509Digest") => {
2478 ensure_no_element_children(child, "X509Digest")?;
2479 ensure_x509_data_entry_budget(&info)?;
2480 let algorithm = required_algorithm_attr(child, "X509Digest")?;
2481 let digest = decode_x509_base64(child, "X509Digest", total_binary_len, resources)?;
2482 info.digests.push((algorithm.to_string(), digest));
2483 }
2484 (Some(XMLDSIG_NS), child_name) | (Some(XMLDSIG11_NS), child_name) => {
2485 return Err(ParseError::InvalidStructure(format!(
2486 "X509Data contains unsupported XMLDSig child element <{child_name}>"
2487 )));
2488 }
2489 _ => {}
2490 }
2491 }
2492
2493 info.certificate_chain = build_x509_certificate_chain(&info, provider, allowed, certificates)?;
2494 Ok(info)
2495}
2496
2497pub(crate) fn validate_x509_digest_policy(
2500 node: Node,
2501 check: &impl Fn(DigestAlgorithm) -> Result<(), crate::policy::PolicyViolation>,
2502) -> Result<(), ParseError> {
2503 fn check_data(
2504 data: Node,
2505 check: &impl Fn(DigestAlgorithm) -> Result<(), crate::policy::PolicyViolation>,
2506 ) -> Result<(), ParseError> {
2507 for child in element_children(data) {
2508 if child.has_tag_name((XMLDSIG11_NS, "X509Digest")) {
2509 let uri = required_algorithm_attr(child, "X509Digest")?;
2510 if let Some(algorithm) = DigestAlgorithm::from_uri(uri) {
2514 check(algorithm)?;
2515 }
2516 }
2517 }
2518 Ok(())
2519 }
2520 if node.has_tag_name((XMLDSIG_NS, "X509Data")) {
2521 return check_data(node, check);
2522 }
2523 for child in element_children(node) {
2524 if child.has_tag_name((XMLDSIG_NS, "X509Data")) {
2525 check_data(child, check)?;
2526 }
2527 }
2528 Ok(())
2529}
2530
2531fn build_x509_certificate_chain(
2532 info: &X509DataInfo,
2533 provider: &dyn crate::provider::CryptoProvider,
2534 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
2535 certificates: &super::pkix::CertificateSession,
2536) -> Result<Vec<usize>, ParseError> {
2537 if info.parsed_certificates.is_empty() {
2538 return Ok(Vec::new());
2539 }
2540
2541 let signing_idx = select_x509_signing_certificate(info, provider, certificates)?;
2542 build_x509_certificate_chain_from(info, signing_idx, provider, allowed, certificates)
2543 .map_err(ParseError::from)
2544}
2545
2546#[derive(Debug, Clone, PartialEq, Eq)]
2547pub(crate) enum X509ChainBuildError {
2548 Certificate(super::X509ChainError),
2549 Policy(crate::policy::PolicyViolation),
2550 InconsistentMetadata,
2551 DepthExceeded,
2552 Cycle,
2553 IssuerSignatureMismatch,
2554 AmbiguousIssuer,
2555 UnsupportedSignatureAlgorithm { oid: String },
2556 Provider(crate::provider::ProviderError),
2557}
2558
2559impl From<super::X509ChainError> for X509ChainBuildError {
2560 fn from(error: super::X509ChainError) -> Self {
2561 match error {
2562 super::X509ChainError::Policy(error) => Self::Policy(error),
2563 super::X509ChainError::Provider(error) => Self::Provider(error),
2564 error => Self::Certificate(error),
2565 }
2566 }
2567}
2568
2569impl From<X509ChainBuildError> for ParseError {
2570 fn from(error: X509ChainBuildError) -> Self {
2571 let reason = match error {
2572 X509ChainBuildError::Certificate(error) => {
2573 return match error {
2574 super::X509ChainError::Policy(error) => Self::Policy(error),
2575 super::X509ChainError::Processing(error) => Self::Certificate(error),
2576 error => Self::InvalidStructure(error.to_string()),
2577 };
2578 }
2579 X509ChainBuildError::InconsistentMetadata => {
2580 "X509Data certificate metadata is inconsistent"
2581 }
2582 X509ChainBuildError::DepthExceeded => {
2583 "X509Data certificate chain exceeds maximum depth"
2584 }
2585 X509ChainBuildError::Cycle => "X509Data certificate chain contains a cycle",
2586 X509ChainBuildError::IssuerSignatureMismatch => {
2587 "X509Data issuer candidates do not verify the certificate signature"
2588 }
2589 X509ChainBuildError::AmbiguousIssuer => {
2590 "X509Data certificate chain contains ambiguous issuer certificates"
2591 }
2592 X509ChainBuildError::UnsupportedSignatureAlgorithm { oid } => {
2593 return Self::InvalidStructure(format!(
2594 "X509Data certificate chain uses unsupported signature algorithm {oid}"
2595 ));
2596 }
2597 X509ChainBuildError::Provider(error) => return Self::Provider(error),
2598 X509ChainBuildError::Policy(error) => return Self::Policy(error),
2599 };
2600 Self::InvalidStructure(reason.into())
2601 }
2602}
2603
2604pub(crate) fn build_x509_certificate_chain_from(
2606 info: &X509DataInfo,
2607 signing_idx: usize,
2608 provider: &dyn crate::provider::CryptoProvider,
2609 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
2610 certificates: &super::pkix::CertificateSession,
2611) -> Result<Vec<usize>, X509ChainBuildError> {
2612 if signing_idx >= info.parsed_certificates.len()
2613 || info.parsed_certificates.len() != info.certificates.len()
2614 {
2615 return Err(X509ChainBuildError::InconsistentMetadata);
2616 }
2617 let mut chain = vec![signing_idx];
2618 let source = certificates
2619 .read_pool(&info.certificates)
2620 .map_err(X509ChainBuildError::from)?;
2621
2622 loop {
2623 let current_idx = *chain
2624 .last()
2625 .expect("chain starts with signing certificate index");
2626 let current = &source[current_idx];
2627 if certificates
2628 .source_names_equal(current.tbs().subject(), current.tbs().issuer())
2629 .map_err(X509ChainBuildError::from)?
2630 {
2631 break;
2632 }
2633
2634 let mut candidates = Vec::new();
2635 for (idx, cert) in source.iter().enumerate() {
2636 if idx != current_idx
2637 && certificates
2638 .source_names_equal(cert.tbs().subject(), current.tbs().issuer())
2639 .map_err(X509ChainBuildError::from)?
2640 {
2641 candidates.push(idx);
2642 }
2643 }
2644
2645 let issuer_idx = match candidates.as_slice() {
2646 [] => break,
2647 [issuer_idx] => *issuer_idx,
2648 _ => {
2649 let mut verified = Vec::new();
2650 let mut unsupported_oid = None;
2651 for issuer_idx in candidates {
2652 match certificate_signature_matches_with_provider(
2653 &info.certificates[current_idx],
2654 &info.certificates[issuer_idx],
2655 provider,
2656 allowed,
2657 certificates,
2658 ) {
2659 Ok(true) => verified.push(issuer_idx),
2660 Ok(false) => {}
2661 Err(super::X509ChainError::Provider(
2662 crate::provider::ProviderError::Unsupported {
2663 operation: crate::provider::ProviderOperation::VerifyCertificate,
2664 algorithm: Some(oid),
2665 },
2666 )) => {
2667 unsupported_oid.get_or_insert(oid);
2668 }
2669 Err(super::X509ChainError::Provider(error)) => {
2670 return Err(X509ChainBuildError::Provider(error));
2671 }
2672 Err(super::X509ChainError::UnsupportedSignatureAlgorithm { oid }) => {
2673 unsupported_oid.get_or_insert(oid);
2674 }
2675 Err(super::X509ChainError::Policy(error)) => {
2676 return Err(X509ChainBuildError::Policy(error));
2677 }
2678 Err(_) => return Err(X509ChainBuildError::IssuerSignatureMismatch),
2679 }
2680 }
2681 match verified.as_slice() {
2682 [issuer_idx] => *issuer_idx,
2683 [] => {
2684 if let Some(oid) = unsupported_oid {
2685 return Err(X509ChainBuildError::UnsupportedSignatureAlgorithm { oid });
2686 }
2687 return Err(X509ChainBuildError::IssuerSignatureMismatch);
2688 }
2689 _ => return Err(X509ChainBuildError::AmbiguousIssuer),
2690 }
2691 }
2692 };
2693 if chain.contains(&issuer_idx) {
2694 return Err(X509ChainBuildError::Cycle);
2695 }
2696 if chain.len() == crate::hard_limits::X509_CHAIN_DEPTH_CEILING {
2697 return Err(X509ChainBuildError::DepthExceeded);
2698 }
2699 chain.push(issuer_idx);
2700 }
2701
2702 Ok(chain)
2703}
2704
2705pub(crate) fn build_x509_certificate_paths_to_trusted_prefix(
2709 info: &impl X509DataSource,
2710 signing_idx: usize,
2711 trusted_prefix_len: usize,
2712 bounds: X509PathBounds,
2713 provider: &dyn crate::provider::CryptoProvider,
2714 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
2715 certificates: &super::pkix::CertificateSession,
2716) -> Result<X509CandidatePaths, X509ChainBuildError> {
2717 if trusted_prefix_len > info.certificate_count() {
2718 return Err(X509ChainBuildError::InconsistentMetadata);
2719 }
2720 build_x509_certificate_paths(
2721 info,
2722 signing_idx,
2723 |index| index < trusted_prefix_len,
2724 false,
2725 bounds,
2726 super::pkix::ProviderVerifier { provider, allowed },
2727 certificates,
2728 )
2729}
2730
2731pub(crate) fn build_x509_certificate_paths_to_selector_targets(
2735 info: &impl X509DataSource,
2736 signing_idx: usize,
2737 targets: &[usize],
2738 bounds: X509PathBounds,
2739 provider: &dyn crate::provider::CryptoProvider,
2740 allowed: Option<&crate::policy::CertificateSignatureAlgorithms>,
2741 certificates: &super::pkix::CertificateSession,
2742) -> Result<X509CandidatePaths, X509ChainBuildError> {
2743 if targets
2744 .iter()
2745 .any(|index| *index >= info.certificate_count())
2746 {
2747 return Err(X509ChainBuildError::InconsistentMetadata);
2748 }
2749 build_x509_certificate_paths(
2750 info,
2751 signing_idx,
2752 |index| targets.contains(&index),
2753 true,
2754 bounds,
2755 super::pkix::ProviderVerifier { provider, allowed },
2756 certificates,
2757 )
2758}
2759
2760pub(crate) struct X509PathBounds {
2761 pub(crate) max_depth: usize,
2762 pub(crate) max_candidate_paths: usize,
2763}
2764
2765type X509CandidatePaths =
2766 structured_asn1::memory::OwnedVec<structured_asn1::memory::OwnedVec<usize>>;
2767
2768fn build_x509_certificate_paths(
2769 info: &impl X509DataSource,
2770 signing_idx: usize,
2771 is_terminal: impl Fn(usize) -> bool,
2772 continue_after_terminal: bool,
2773 bounds: X509PathBounds,
2774 verifier: super::pkix::ProviderVerifier<'_>,
2775 certificates: &super::pkix::CertificateSession,
2776) -> Result<X509CandidatePaths, X509ChainBuildError> {
2777 let X509PathBounds {
2778 max_depth,
2779 max_candidate_paths,
2780 } = bounds;
2781 if signing_idx >= info.certificate_count() {
2782 return Err(X509ChainBuildError::InconsistentMetadata);
2783 }
2784 if max_candidate_paths == 0 {
2785 return Err(X509ChainBuildError::AmbiguousIssuer);
2786 }
2787 let source = certificates
2788 .read_sources(info.encoded_certificates())
2789 .map_err(X509ChainBuildError::from)?;
2790
2791 let memory = certificates.memory();
2792 let allocation_error = |reason| {
2793 X509ChainBuildError::from(certificates.error(structured_pkix::Error::resource(
2794 reason,
2795 "certificate.paths",
2796 0,
2797 )))
2798 };
2799 let mut initial = memory.vector();
2800 initial.try_push(signing_idx).map_err(allocation_error)?;
2801 let mut pending = memory.vector();
2802 pending.try_push(initial).map_err(allocation_error)?;
2803 let mut completed = memory.vector();
2804 let mut generated_paths = 1usize;
2805 let mut depth_exceeded = false;
2806 let mut unsupported_oid = None;
2807 let mut policy_rejection = None;
2808 let mut issuer_cache = memory.vector();
2809 issuer_cache
2810 .try_reserve_exact(info.certificate_count())
2811 .map_err(allocation_error)?;
2812 for _ in 0..info.certificate_count() {
2813 issuer_cache.try_push(None).map_err(allocation_error)?;
2814 }
2815 while let Some(path) = pending.pop() {
2816 memory
2817 .charge_work(info.certificate_count() as u64)
2818 .map_err(allocation_error)?;
2819 let current_idx = *path
2820 .last()
2821 .expect("candidate path starts with signing certificate index");
2822 if is_terminal(current_idx) {
2823 if !continue_after_terminal {
2824 completed.try_push(path).map_err(allocation_error)?;
2825 continue;
2826 }
2827 memory
2828 .charge_work(path.len() as u64)
2829 .map_err(allocation_error)?;
2830 completed
2831 .try_push(path.try_copy().map_err(allocation_error)?)
2832 .map_err(allocation_error)?;
2833 }
2834 if path.len() == max_depth {
2835 depth_exceeded = true;
2836 continue;
2837 }
2838
2839 let current = &source[current_idx];
2840 if issuer_cache[current_idx].is_none() {
2841 let mut verified = memory.vector();
2842 for (issuer_idx, issuer) in source.iter().enumerate() {
2843 if !certificates
2844 .source_names_equal(issuer.tbs().subject(), current.tbs().issuer())
2845 .map_err(X509ChainBuildError::from)?
2846 {
2847 continue;
2848 }
2849 match super::x509::shared_certificate_signature_outcome(
2850 current,
2851 issuer,
2852 verifier.provider,
2853 verifier.allowed,
2854 certificates,
2855 ) {
2856 Ok(
2857 structured_pkix::path::Verified::Yes
2858 | structured_pkix::path::Verified::Unsupported,
2859 ) => {
2860 verified.try_push(issuer_idx).map_err(allocation_error)?;
2863 }
2864 Ok(structured_pkix::path::Verified::No) => {}
2865 Ok(_) => return Err(X509ChainBuildError::IssuerSignatureMismatch),
2866 Err(super::X509ChainError::Provider(
2867 crate::provider::ProviderError::Unsupported {
2868 operation: crate::provider::ProviderOperation::VerifyCertificate,
2869 algorithm: Some(oid),
2870 },
2871 )) => {
2872 unsupported_oid.get_or_insert(oid);
2875 continue;
2876 }
2877 Err(super::X509ChainError::Provider(error)) => {
2878 return Err(X509ChainBuildError::Provider(error));
2879 }
2880 Err(super::X509ChainError::UnsupportedSignatureAlgorithm { oid }) => {
2881 unsupported_oid.get_or_insert(oid);
2884 break;
2885 }
2886 Err(super::X509ChainError::Policy(error)) => {
2887 if matches!(
2888 error,
2889 crate::policy::PolicyViolation::ResourceLimit { .. }
2890 | crate::policy::PolicyViolation::ResourceLimitExceeded { .. }
2891 ) {
2892 return Err(X509ChainBuildError::Policy(error));
2893 }
2894 policy_rejection.get_or_insert(error);
2900 break;
2901 }
2902 Err(_) => return Err(X509ChainBuildError::IssuerSignatureMismatch),
2903 }
2904 }
2905 issuer_cache[current_idx] = Some(verified);
2906 }
2907 let issuers = issuer_cache[current_idx]
2908 .as_ref()
2909 .expect("issuer cache entry was initialized");
2910 for &issuer_idx in issuers {
2911 memory
2912 .charge_work(path.len() as u64 + 1)
2913 .map_err(allocation_error)?;
2914 if path.contains(&issuer_idx) {
2915 continue;
2916 }
2917 if generated_paths == max_candidate_paths {
2918 return Err(X509ChainBuildError::AmbiguousIssuer);
2919 }
2920 generated_paths += 1;
2921 memory
2922 .charge_work(path.len() as u64 + 1)
2923 .map_err(allocation_error)?;
2924 let mut candidate = memory.vector();
2925 candidate
2926 .try_reserve_exact(path.len() + 1)
2927 .map_err(allocation_error)?;
2928 candidate
2929 .try_extend_from_slice(&path)
2930 .map_err(allocation_error)?;
2931 candidate.try_push(issuer_idx).map_err(allocation_error)?;
2932 pending.try_push(candidate).map_err(allocation_error)?;
2933 }
2934 }
2935
2936 if completed.is_empty() {
2937 if let Some(error) = policy_rejection {
2938 return Err(X509ChainBuildError::Policy(error));
2939 }
2940 if let Some(oid) = unsupported_oid {
2941 return Err(X509ChainBuildError::UnsupportedSignatureAlgorithm { oid });
2942 }
2943 if depth_exceeded {
2944 return Err(X509ChainBuildError::DepthExceeded);
2945 }
2946 }
2947 Ok(completed)
2948}
2949
2950fn select_x509_signing_certificate(
2951 info: &X509DataInfo,
2952 provider: &dyn crate::provider::CryptoProvider,
2953 certificates: &super::pkix::CertificateSession,
2954) -> Result<usize, ParseError> {
2955 let has_lookup_identifiers = x509_data_has_lookup_identifiers(info);
2956 let mut candidates = Vec::new();
2957 if has_lookup_identifiers {
2958 for (idx, der) in info.certificates.iter().enumerate() {
2959 if x509_certificate_matches_any_selector(info, der, provider, certificates)? {
2960 candidates.push(idx);
2961 }
2962 }
2963 if !x509_selector_categories_match_chain(info, provider, certificates)? {
2964 return Err(ParseError::InvalidStructure(
2965 "X509Data lookup identifiers do not match the embedded certificate chain".into(),
2966 ));
2967 }
2968 }
2969
2970 match candidates.as_slice() {
2971 [idx] => return Ok(*idx),
2972 [] if has_lookup_identifiers => {
2973 return Err(ParseError::InvalidStructure(
2974 "X509Data lookup identifiers do not match any embedded certificate".into(),
2975 ));
2976 }
2977 [] => {}
2978 _ => {}
2979 }
2980
2981 let source = certificates
2982 .read_pool(&info.certificates)
2983 .map_err(|error| certificates.parse_error(error))?;
2984 let mut leaf_candidates = Vec::new();
2985 for (idx, cert) in source.iter().enumerate() {
2986 if certificates
2987 .source_names_equal(cert.tbs().subject(), cert.tbs().issuer())
2988 .map_err(|error| certificates.parse_error(error))?
2989 {
2990 continue;
2991 }
2992 let mut issuer = false;
2993 for other in &source {
2994 if certificates
2995 .source_names_equal(other.tbs().issuer(), cert.tbs().subject())
2996 .map_err(|error| certificates.parse_error(error))?
2997 {
2998 issuer = true;
2999 break;
3000 }
3001 }
3002 if !issuer {
3003 leaf_candidates.push(idx);
3004 }
3005 }
3006
3007 let selected_leaves = leaf_candidates
3008 .iter()
3009 .filter(|idx| !has_lookup_identifiers || candidates.contains(idx))
3010 .copied()
3011 .collect::<Vec<_>>();
3012
3013 match selected_leaves.as_slice() {
3014 [idx] => Ok(*idx),
3015 [] if !has_lookup_identifiers => Ok(0),
3016 [] => Err(ParseError::InvalidStructure(
3017 "X509Data lookup identifiers match multiple certificates without a unique signing certificate"
3018 .into(),
3019 )),
3020 _ => Err(ParseError::InvalidStructure(
3021 if has_lookup_identifiers {
3022 "X509Data lookup identifiers match multiple certificates"
3023 } else {
3024 "X509Data contains multiple possible signing certificates"
3025 }
3026 .into(),
3027 )),
3028 }
3029}
3030
3031pub(crate) fn x509_data_has_lookup_identifiers(info: &X509DataInfo) -> bool {
3032 !info.subject_names.is_empty()
3033 || !info.issuer_serials.is_empty()
3034 || !info.skis.is_empty()
3035 || !info.digests.is_empty()
3036}
3037
3038pub(crate) fn x509_certificate_matches_any_selector(
3039 info: &X509DataInfo,
3040 certificate_der: &[u8],
3041 provider: &dyn crate::provider::CryptoProvider,
3042 certificates: &super::pkix::CertificateSession,
3043) -> Result<bool, ParseError> {
3044 let decoded = certificates
3045 .read(certificate_der)
3046 .map_err(|error| certificates.parse_error(error))?;
3047 let mut subject_match = false;
3048 for subject in &info.subject_names {
3049 let selector = certificates
3050 .selector(subject)
3051 .map_err(|error| certificates.parse_error(error))?;
3052 subject_match |= selector
3053 .matches_name(decoded.tbs().subject(), certificates.memory())
3054 .map_err(|error| certificates.parse_error(certificates.error(error)))?;
3055 }
3056 let mut issuer_serial_match = false;
3057 for (issuer, serial) in &info.issuer_serials {
3058 let serial = selector_serial(serial, certificates)?;
3059 let selector = certificates
3060 .selector(issuer)
3061 .map_err(|error| certificates.parse_error(error))?;
3062 issuer_serial_match |= selector
3063 .matches_name(decoded.tbs().issuer(), certificates.memory())
3064 .map_err(|error| certificates.parse_error(certificates.error(error)))?
3065 && source_serial_matches(&serial, &decoded, certificates)?;
3066 }
3067 let mut ski_match = false;
3068 for ski in &info.skis {
3069 ski_match |= source_ski_matches(ski, &decoded, certificates)?;
3070 }
3071 let mut digest_match = false;
3072 for (algorithm_uri, expected) in &info.digests {
3073 let algorithm = DigestAlgorithm::from_uri(algorithm_uri).ok_or_else(|| {
3074 ParseError::UnsupportedAlgorithm {
3075 uri: algorithm_uri.clone(),
3076 }
3077 })?;
3078 digest_match |=
3079 source_digest_matches(provider, algorithm, certificate_der, expected, certificates)?;
3080 }
3081 Ok(subject_match || issuer_serial_match || ski_match || digest_match)
3082}
3083
3084pub fn x509_certificate_matches_selectors(
3090 info: &X509DataInfo,
3091 certificate_der: &[u8],
3092 provider: &dyn crate::provider::CryptoProvider,
3093) -> Result<bool, ParseError> {
3094 let certificates =
3095 super::pkix::CertificateSession::new(&crate::policy::ResourcePolicy::default());
3096 x509_selector_categories_match_candidates(
3097 info,
3098 core::iter::once(certificate_der),
3099 provider,
3100 &certificates,
3101 )
3102}
3103
3104pub(crate) fn x509_selector_categories_match_chain(
3105 info: &X509DataInfo,
3106 provider: &dyn crate::provider::CryptoProvider,
3107 certificates: &super::pkix::CertificateSession,
3108) -> Result<bool, ParseError> {
3109 x509_selector_categories_match_candidates(
3110 info,
3111 info.certificates.iter().map(Vec::as_slice),
3112 provider,
3113 certificates,
3114 )
3115}
3116
3117pub(crate) fn x509_selector_categories_match_candidates<'a>(
3118 info: &X509DataInfo,
3119 candidates: impl Iterator<Item = &'a [u8]>,
3120 provider: &dyn crate::provider::CryptoProvider,
3121 certificates: &super::pkix::CertificateSession,
3122) -> Result<bool, ParseError> {
3123 let mut decoded = certificates.memory().vector();
3124 for der in candidates {
3125 let certificate = certificates
3126 .read(der)
3127 .map_err(|error| certificates.parse_error(error))?;
3128 decoded.try_push(certificate).map_err(|reason| {
3129 certificates.parse_error(certificates.error(structured_pkix::Error::resource(
3130 reason,
3131 "name.candidates",
3132 0,
3133 )))
3134 })?;
3135 }
3136 let mut subject_match = true;
3137 for subject in &info.subject_names {
3138 let selector = certificates
3139 .selector(subject)
3140 .map_err(|error| certificates.parse_error(error))?;
3141 let mut matched = false;
3142 for certificate in &decoded {
3143 if selector
3144 .matches_name(certificate.tbs().subject(), certificates.memory())
3145 .map_err(|error| certificates.parse_error(certificates.error(error)))?
3146 {
3147 matched = true;
3148 break;
3149 }
3150 }
3151 subject_match &= matched;
3152 }
3153
3154 let mut issuer_serial_match = true;
3155 for (issuer, serial) in &info.issuer_serials {
3156 let serial = selector_serial(serial, certificates)?;
3157 let selector = certificates
3158 .selector(issuer)
3159 .map_err(|error| certificates.parse_error(error))?;
3160 let mut matched = false;
3161 for certificate in &decoded {
3162 if source_serial_matches(&serial, certificate, certificates)?
3163 && selector
3164 .matches_name(certificate.tbs().issuer(), certificates.memory())
3165 .map_err(|error| certificates.parse_error(certificates.error(error)))?
3166 {
3167 matched = true;
3168 break;
3169 }
3170 }
3171 issuer_serial_match &= matched;
3172 }
3173
3174 let mut ski_match = true;
3175 for ski in &info.skis {
3176 let mut matched = false;
3177 for certificate in &decoded {
3178 if source_ski_matches(ski, certificate, certificates)? {
3179 matched = true;
3180 break;
3181 }
3182 }
3183 ski_match &= matched;
3184 }
3185
3186 let mut digest_match = true;
3187 for (algorithm_uri, expected) in &info.digests {
3188 let algorithm = DigestAlgorithm::from_uri(algorithm_uri).ok_or_else(|| {
3189 ParseError::UnsupportedAlgorithm {
3190 uri: algorithm_uri.clone(),
3191 }
3192 })?;
3193 let mut category_match = false;
3194 for certificate in &decoded {
3195 category_match |= source_digest_matches(
3196 provider,
3197 algorithm,
3198 certificate.as_bytes(),
3199 expected,
3200 certificates,
3201 )?;
3202 }
3203 digest_match &= category_match;
3204 }
3205
3206 Ok(subject_match && issuer_serial_match && ski_match && digest_match)
3207}
3208
3209#[cfg(test)]
3210fn distinguished_names_equal(left: &str, right: &str) -> bool {
3211 super::pkix::CertificateSession::new(&crate::policy::ResourcePolicy::default())
3212 .names_equal(left, right)
3213 .expect("supported test names")
3214}
3215
3216fn selector_serial(
3217 serial: &str,
3218 session: &super::pkix::CertificateSession,
3219) -> Result<[u8; MAX_X509_SERIAL_NUMBER_BYTES], ParseError> {
3220 let work = (serial.len() as u64)
3221 .checked_mul(3)
3222 .and_then(|work| {
3223 work.checked_add(
3224 (MAX_X509_SERIAL_NUMBER_BYTES * MAX_X509_SERIAL_NUMBER_VALUE_DIGITS) as u64,
3225 )
3226 })
3227 .ok_or_else(|| {
3228 session.parse_error(session.error(structured_pkix::Error::resource(
3229 structured_asn1::memory::MemoryError::WorkExceeded,
3230 "certificate.serial_selector",
3231 0,
3232 )))
3233 })?;
3234 session.memory().charge_work(work).map_err(|reason| {
3235 session.parse_error(session.error(structured_pkix::Error::resource(
3236 reason,
3237 "certificate.serial_selector",
3238 0,
3239 )))
3240 })?;
3241 x509_serial_decimal_bytes(serial).ok_or_else(|| {
3242 ParseError::InvalidStructure(
3243 "X509Data lookup identifiers contain an invalid serial number".into(),
3244 )
3245 })
3246}
3247
3248fn source_serial_matches(
3249 expected: &[u8; MAX_X509_SERIAL_NUMBER_BYTES],
3250 certificate: &structured_pkix::Certificate<'_>,
3251 session: &super::pkix::CertificateSession,
3252) -> Result<bool, ParseError> {
3253 let bytes = certificate.tbs().serial().as_bytes();
3254 if bytes.first().is_none_or(|first| first & 0x80 != 0) {
3255 return Ok(false);
3256 }
3257 let bytes = bytes.strip_prefix(&[0]).unwrap_or(bytes);
3258 if bytes.len() > expected.len() {
3259 return Ok(false);
3260 }
3261 session
3262 .memory()
3263 .charge_work(expected.len() as u64 + bytes.len() as u64)
3264 .map_err(|reason| {
3265 session.parse_error(session.error(structured_pkix::Error::resource(
3266 reason,
3267 "certificate.serial_selector",
3268 certificate.span().start,
3269 )))
3270 })?;
3271 let padding = expected.len() - bytes.len();
3272 Ok(expected[..padding].iter().all(|byte| *byte == 0) && expected[padding..] == *bytes)
3273}
3274
3275fn source_ski_matches(
3276 expected: &[u8],
3277 certificate: &structured_pkix::Certificate<'_>,
3278 session: &super::pkix::CertificateSession,
3279) -> Result<bool, ParseError> {
3280 let Some(ski) = certificate
3281 .tbs()
3282 .extensions()
3283 .subject_key_identifier(session.memory())
3284 .map_err(|error| session.parse_error(session.error(error)))?
3285 else {
3286 return Ok(false);
3287 };
3288 if ski.as_bytes().len() != expected.len() {
3289 return Ok(false);
3290 }
3291 session
3292 .memory()
3293 .charge_work(expected.len() as u64)
3294 .map_err(|reason| {
3295 session.parse_error(session.error(structured_pkix::Error::resource(
3296 reason,
3297 "certificate.ski_selector",
3298 certificate.span().start,
3299 )))
3300 })?;
3301 Ok(ski.as_bytes() == expected)
3302}
3303
3304fn source_digest_matches(
3305 provider: &dyn crate::provider::CryptoProvider,
3306 algorithm: DigestAlgorithm,
3307 source: &[u8],
3308 expected: &[u8],
3309 session: &super::pkix::CertificateSession,
3310) -> Result<bool, ParseError> {
3311 if !provider.supports(crate::provider::ProviderCapability::Digest(algorithm)) {
3312 return Err(ParseError::Provider(
3313 crate::provider::ProviderError::Unsupported {
3314 operation: crate::provider::ProviderOperation::Digest,
3315 algorithm: Some(algorithm.uri().into()),
3316 },
3317 ));
3318 }
3319 if expected.len() != algorithm.output_len() {
3320 return Ok(false);
3321 }
3322 let resource = |reason| {
3323 session.parse_error(session.error(structured_pkix::Error::resource(
3324 reason,
3325 "certificate.digest_selector",
3326 0,
3327 )))
3328 };
3329 session
3330 .memory()
3331 .charge_work(source.len() as u64 + expected.len() as u64)
3332 .map_err(resource)?;
3333 let _output = session
3334 .memory()
3335 .reserve(algorithm.output_len())
3336 .map_err(resource)?;
3337 let actual = compute_digest_with_provider(provider, algorithm, source)?;
3340 Ok(constant_time_eq(&actual, expected))
3341}
3342
3343fn ensure_x509_data_entry_budget(info: &X509DataInfo) -> Result<(), ParseError> {
3344 let total_entries = info.certificates.len()
3345 + info.subject_names.len()
3346 + info.issuer_serials.len()
3347 + info.skis.len()
3348 + info.crls.len()
3349 + info.digests.len();
3350 if total_entries >= MAX_X509_DATA_ENTRY_COUNT {
3351 return Err(ParseError::InvalidStructure(
3352 "X509Data contains too many entries".into(),
3353 ));
3354 }
3355 Ok(())
3356}
3357
3358fn add_x509_data_usage(total_binary_len: &mut usize, delta: usize) -> Result<(), ParseError> {
3359 *total_binary_len = total_binary_len.checked_add(delta).ok_or_else(|| {
3360 ParseError::InvalidStructure("X509Data exceeds maximum allowed total binary length".into())
3361 })?;
3362 if *total_binary_len > MAX_X509_DATA_TOTAL_BINARY_LEN {
3363 return Err(ParseError::InvalidStructure(
3364 "X509Data exceeds maximum allowed total binary length".into(),
3365 ));
3366 }
3367 Ok(())
3368}
3369
3370fn decode_x509_base64(
3371 node: Node<'_, '_>,
3372 element_name: &'static str,
3373 total_binary_len: &mut usize,
3374 resources: &crate::policy::ResourcePolicy,
3375) -> Result<Vec<u8>, ParseError> {
3376 let payload = super::whitespace::XmlBase64Payload::bounded(
3377 node,
3378 MAX_X509_BASE64_TEXT_LEN,
3379 MAX_X509_BASE64_NORMALIZED_LEN,
3380 )
3381 .map_err(|reason| xml_base64_payload_error(element_name, reason))?;
3382 if payload.text_len > MAX_X509_BASE64_TEXT_LEN {
3383 return Err(ParseError::InvalidStructure(format!(
3384 "{element_name} exceeds maximum allowed text length"
3385 )));
3386 }
3387 if payload.normalized_len > MAX_X509_BASE64_NORMALIZED_LEN {
3388 return Err(ParseError::InvalidStructure(format!(
3389 "{element_name} exceeds maximum allowed base64 length"
3390 )));
3391 }
3392 if payload.decoded_len == 0 {
3393 return Err(ParseError::InvalidStructure(format!(
3394 "{element_name} must not be empty"
3395 )));
3396 }
3397 if payload.decoded_len > MAX_X509_DECODED_BINARY_LEN {
3398 return Err(ParseError::InvalidStructure(format!(
3399 "{element_name} exceeds maximum allowed binary length"
3400 )));
3401 }
3402 if payload.decoded_len > resources.max_external_resource_bytes {
3403 return Err(crate::policy::PolicyViolation::ResourceLimitExceeded {
3404 resource: crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES,
3405 maximum: resources.max_external_resource_bytes,
3406 }
3407 .into());
3408 }
3409 if *total_binary_len > resources.max_external_resource_total_bytes
3410 || payload.decoded_len > resources.max_external_resource_total_bytes - *total_binary_len
3411 {
3412 return Err(crate::policy::PolicyViolation::ResourceLimitExceeded {
3413 resource: crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
3414 maximum: resources.max_external_resource_total_bytes,
3415 }
3416 .into());
3417 }
3418 add_x509_data_usage(total_binary_len, payload.decoded_len)?;
3421 payload
3422 .decode()
3423 .map_err(|reason| ParseError::Base64(format!("{element_name}: {reason}")))
3424}
3425
3426fn xml_base64_payload_error(element: &str, reason: &'static str) -> ParseError {
3427 match reason {
3428 "maximum allowed text length" | "maximum allowed base64 length" => {
3429 ParseError::InvalidStructure(format!("{element} exceeds {reason}"))
3430 }
3431 _ => ParseError::Base64(format!("{element}: {reason}")),
3432 }
3433}
3434
3435#[cfg(test)]
3436pub(crate) fn parse_x509_certificate(cert_der: &[u8]) -> Result<ParsedX509Certificate, ParseError> {
3437 let session = super::pkix::CertificateSession::new(&crate::policy::ResourcePolicy::default());
3438 parse_x509_certificate_in(cert_der, &session)
3439}
3440
3441pub(crate) fn parse_x509_certificate_in(
3442 cert_der: &[u8],
3443 session: &super::pkix::CertificateSession,
3444) -> Result<ParsedX509Certificate, ParseError> {
3445 let cert = session
3446 .read(cert_der)
3447 .map_err(|error| session.parse_error(error))?;
3448 certificate_metadata(&cert, session)
3449}
3450
3451pub(crate) fn certificate_metadata(
3452 cert: &structured_pkix::Certificate<'_>,
3453 session: &super::pkix::CertificateSession,
3454) -> Result<ParsedX509Certificate, ParseError> {
3455 let subject_dn = shared_name_text(cert.tbs().subject(), session)?;
3460 let issuer_dn = shared_name_text(cert.tbs().issuer(), session)?;
3461 let serial_number = cert.tbs().serial().as_bytes().to_vec();
3462 let serial_number_hex = format_x509_serial_value_hex(&serial_number);
3463
3464 let subject_key_identifier = cert
3465 .tbs()
3466 .extensions()
3467 .subject_key_identifier(session.memory())
3468 .map_err(|error| session.parse_error(session.error(error)))?
3469 .map(|ski| ski.as_bytes().to_vec());
3470
3471 let spki = cert.tbs().public_key();
3472 let public_key = if spki.algorithm().algorithm() == structured_pkix::oid::RSA_ENCRYPTION {
3476 let rsa = structured_pkix::key::rsa::read(spki, session.memory()).map_err(|error| {
3478 if error.is_resource() {
3479 session.parse_error(session.error(error))
3480 } else {
3481 ParseError::InvalidStructure(format!("X509Certificate RSA key: {error}"))
3482 }
3483 })?;
3484 X509PublicKeyInfo::Rsa {
3485 modulus: rsa.modulus().as_bytes().to_vec(),
3486 exponent: rsa.exponent().as_bytes().to_vec(),
3487 }
3488 } else if spki.algorithm().algorithm() == structured_pkix::oid::EC_PUBLIC_KEY {
3489 let key_octets = spki.key_octets().ok_or_else(|| {
3490 ParseError::InvalidStructure(
3491 "X509Certificate EC public key is not octet-aligned".into(),
3492 )
3493 })?;
3494 let Some(params) = spki.algorithm().parameters() else {
3495 return Err(ParseError::InvalidStructure(
3496 "X509Certificate EC key is missing curve parameters".into(),
3497 ));
3498 };
3499
3500 match <ObjectIdentifier>::from_der(params) {
3501 Ok(oid) => X509PublicKeyInfo::Ec {
3502 curve_oid: oid.to_string(),
3503 public_key: key_octets.to_vec(),
3504 },
3505 Err(_) => X509PublicKeyInfo::Unsupported {
3506 algorithm_oid: shared_oid_text(spki.algorithm().algorithm(), session)?,
3507 },
3508 }
3509 } else {
3510 X509PublicKeyInfo::Unsupported {
3511 algorithm_oid: shared_oid_text(spki.algorithm().algorithm(), session)?,
3512 }
3513 };
3514
3515 Ok(ParsedX509Certificate {
3516 subject_dn,
3517 issuer_dn,
3518 serial_number,
3519 serial_number_hex,
3520 subject_key_identifier,
3521 public_key,
3522 })
3523}
3524
3525fn shared_name_text(
3526 name: &structured_pkix::name::Name<'_>,
3527 session: &super::pkix::CertificateSession,
3528) -> Result<String, ParseError> {
3529 let text = name
3530 .rfc4514_readable(session.memory())
3531 .map_err(|error| session.parse_error(session.error(error)))?;
3532 text.as_str(session.memory())
3533 .map(str::to_owned)
3534 .map_err(|reason| {
3535 session.parse_error(session.error(structured_pkix::Error::resource(reason, "name", 0)))
3536 })
3537}
3538
3539fn shared_oid_text(
3540 oid: structured_asn1::Oid<'_>,
3541 session: &super::pkix::CertificateSession,
3542) -> Result<String, ParseError> {
3543 let text = oid.text(session.memory()).map_err(|error| {
3544 session.parse_error(session.error(structured_pkix::Error::resource(error, "algorithm", 0)))
3545 })?;
3546 text.as_str(session.memory())
3547 .map(str::to_owned)
3548 .map_err(|error| {
3549 session.parse_error(session.error(structured_pkix::Error::resource(
3550 error,
3551 "algorithm",
3552 0,
3553 )))
3554 })
3555}
3556
3557fn format_x509_serial_hex(serial: &[u8]) -> String {
3558 serial
3559 .iter()
3560 .map(|byte| format!("{byte:02X}"))
3561 .collect::<String>()
3562}
3563
3564fn format_x509_serial_value_hex(serial: &[u8]) -> String {
3565 let first_non_zero = serial
3566 .iter()
3567 .position(|byte| *byte != 0)
3568 .unwrap_or(serial.len());
3569 let canonical = if first_non_zero == serial.len() {
3570 &[0]
3571 } else {
3572 &serial[first_non_zero..]
3573 };
3574 format_x509_serial_hex(canonical)
3575}
3576
3577fn x509_serial_decimal_bytes(serial: &str) -> Option<[u8; MAX_X509_SERIAL_NUMBER_BYTES]> {
3578 let serial = serial.trim();
3579 let serial = serial.strip_prefix('+').unwrap_or(serial);
3580 let serial = serial.trim_start_matches('0');
3581 let serial = if serial.is_empty() { "0" } else { serial };
3582 if serial.len() > MAX_X509_SERIAL_NUMBER_VALUE_DIGITS
3583 || !serial.bytes().all(|byte| byte.is_ascii_digit())
3584 {
3585 return None;
3586 }
3587
3588 let mut bytes = [0_u8; MAX_X509_SERIAL_NUMBER_BYTES];
3589 for digit in serial.bytes().map(|byte| byte - b'0') {
3590 let mut carry = u16::from(digit);
3591 for byte in bytes.iter_mut().rev() {
3592 let value = u16::from(*byte) * 10 + carry;
3593 *byte = value as u8;
3594 carry = value >> 8;
3595 }
3596 if carry != 0 {
3597 return None;
3598 }
3599 }
3600
3601 if bytes.iter().all(|byte| *byte == 0) {
3602 return None;
3603 }
3604
3605 Some(bytes)
3606}
3607
3608#[cfg(test)]
3609fn x509_serial_decimal_to_hex(serial: &str) -> Option<String> {
3610 x509_serial_decimal_bytes(serial).map(|bytes| format_x509_serial_value_hex(&bytes))
3611}
3612
3613fn parse_x509_issuer_serial(node: Node<'_, '_>) -> Result<(String, String), ParseError> {
3614 verify_ds_element(node, "X509IssuerSerial")?;
3615 ensure_no_non_whitespace_text(node, "X509IssuerSerial")?;
3616
3617 let children = element_children(node).collect::<Vec<_>>();
3618 if children.len() != 2 {
3619 return Err(ParseError::InvalidStructure(
3620 "X509IssuerSerial must contain exactly X509IssuerName then X509SerialNumber".into(),
3621 ));
3622 }
3623 if !matches!(
3624 (
3625 children[0].tag_name().namespace(),
3626 children[0].tag_name().name()
3627 ),
3628 (Some(XMLDSIG_NS), "X509IssuerName")
3629 ) {
3630 return Err(ParseError::InvalidStructure(
3631 "X509IssuerSerial must contain X509IssuerName as the first child element".into(),
3632 ));
3633 }
3634 if !matches!(
3635 (
3636 children[1].tag_name().namespace(),
3637 children[1].tag_name().name()
3638 ),
3639 (Some(XMLDSIG_NS), "X509SerialNumber")
3640 ) {
3641 return Err(ParseError::InvalidStructure(
3642 "X509IssuerSerial must contain X509SerialNumber as the second child element".into(),
3643 ));
3644 }
3645
3646 let issuer_node = children[0];
3647 ensure_no_element_children(issuer_node, "X509IssuerName")?;
3648 let issuer_name =
3649 collect_text_content_bounded(issuer_node, MAX_X509_ISSUER_NAME_TEXT_LEN, "X509IssuerName")?;
3650
3651 let serial_node = children[1];
3652 ensure_no_element_children(serial_node, "X509SerialNumber")?;
3653 let serial_number = collect_x509_serial_number(serial_node)?;
3654 if issuer_name.trim().is_empty() {
3655 return Err(ParseError::InvalidStructure(
3656 "X509IssuerSerial requires non-empty X509IssuerName and X509SerialNumber".into(),
3657 ));
3658 }
3659
3660 Ok((issuer_name, serial_number))
3661}
3662
3663fn base64_decode_digest(b64: &str, digest_method: DigestAlgorithm) -> Result<Vec<u8>, ParseError> {
3667 use base64::Engine;
3668 use base64::engine::general_purpose::STANDARD;
3669
3670 let expected = digest_method.output_len();
3671 let max_base64_len = expected.div_ceil(3) * 4;
3672 let mut cleaned = String::with_capacity(b64.len().min(max_base64_len));
3673 normalize_xml_base64_text(b64, &mut cleaned).map_err(|err| {
3674 ParseError::Base64(format!(
3675 "invalid XML whitespace U+{:04X} in DigestValue",
3676 err.invalid_byte
3677 ))
3678 })?;
3679 if cleaned.len() > max_base64_len {
3680 return Err(ParseError::Base64(
3681 "DigestValue exceeds maximum allowed base64 length".into(),
3682 ));
3683 }
3684 let digest = STANDARD
3685 .decode(&cleaned)
3686 .map_err(|e| ParseError::Base64(e.to_string()))?;
3687 let actual = digest.len();
3688 if actual != expected {
3689 return Err(ParseError::DigestLengthMismatch {
3690 algorithm: digest_method.uri(),
3691 expected,
3692 actual,
3693 });
3694 }
3695 Ok(digest)
3696}
3697
3698fn decode_digest_value_children(
3699 digest_value_node: Node<'_, '_>,
3700 digest_method: DigestAlgorithm,
3701) -> Result<Vec<u8>, ParseError> {
3702 let max_base64_len = digest_method.output_len().div_ceil(3) * 4;
3703 let mut cleaned = String::with_capacity(max_base64_len);
3704
3705 for child in digest_value_node.children() {
3706 if child.is_element() {
3707 return Err(ParseError::InvalidStructure(
3708 "DigestValue must not contain element children".into(),
3709 ));
3710 }
3711 if let Some(text) = child.text() {
3712 normalize_xml_base64_text(text, &mut cleaned).map_err(|err| {
3713 ParseError::Base64(format!(
3714 "invalid XML whitespace U+{:04X} in DigestValue",
3715 err.invalid_byte
3716 ))
3717 })?;
3718 if cleaned.len() > max_base64_len {
3719 return Err(ParseError::Base64(
3720 "DigestValue exceeds maximum allowed base64 length".into(),
3721 ));
3722 }
3723 }
3724 }
3725
3726 base64_decode_digest(&cleaned, digest_method)
3727}
3728
3729fn decode_der_encoded_key_value_base64(node: Node<'_, '_>) -> Result<Vec<u8>, ParseError> {
3730 use base64::Engine;
3731 use base64::engine::general_purpose::STANDARD;
3732
3733 let mut cleaned = String::new();
3734 let mut raw_text_len = 0usize;
3735 for text in node
3736 .children()
3737 .filter(|child| child.is_text())
3738 .filter_map(|child| child.text())
3739 {
3740 if raw_text_len.saturating_add(text.len()) > MAX_DER_ENCODED_KEY_VALUE_TEXT_LEN {
3741 return Err(ParseError::InvalidStructure(
3742 "DEREncodedKeyValue exceeds maximum allowed text length".into(),
3743 ));
3744 }
3745 raw_text_len = raw_text_len.saturating_add(text.len());
3746 normalize_xml_base64_text(text, &mut cleaned).map_err(|err| {
3747 ParseError::Base64(format!(
3748 "invalid XML whitespace U+{:04X} in base64 text",
3749 err.invalid_byte
3750 ))
3751 })?;
3752 if cleaned.len() > MAX_DER_ENCODED_KEY_VALUE_BASE64_LEN {
3753 return Err(ParseError::InvalidStructure(
3754 "DEREncodedKeyValue exceeds maximum allowed length".into(),
3755 ));
3756 }
3757 }
3758
3759 let der = STANDARD
3760 .decode(&cleaned)
3761 .map_err(|e| ParseError::Base64(e.to_string()))?;
3762 if der.is_empty() {
3763 return Err(ParseError::InvalidStructure(
3764 "DEREncodedKeyValue must not be empty".into(),
3765 ));
3766 }
3767 if der.len() > MAX_DER_ENCODED_KEY_VALUE_LEN {
3768 return Err(ParseError::InvalidStructure(
3769 "DEREncodedKeyValue exceeds maximum allowed length".into(),
3770 ));
3771 }
3772 Ok(der)
3773}
3774
3775fn collect_text_content_bounded(
3776 node: Node<'_, '_>,
3777 max_len: usize,
3778 element_name: &'static str,
3779) -> Result<String, ParseError> {
3780 let mut text = String::new();
3781 for chunk in node
3782 .children()
3783 .filter_map(|child| child.is_text().then(|| child.text()).flatten())
3784 {
3785 if text.len().saturating_add(chunk.len()) > max_len {
3786 return Err(ParseError::InvalidStructure(format!(
3787 "{element_name} exceeds maximum allowed text length"
3788 )));
3789 }
3790 text.push_str(chunk);
3791 }
3792 Ok(text)
3793}
3794
3795fn collect_x509_serial_number(node: Node<'_, '_>) -> Result<String, ParseError> {
3796 let mut serial = String::with_capacity(MAX_X509_SERIAL_NUMBER_VALUE_DIGITS);
3797 let mut raw_text_len = 0usize;
3798 let mut trailing_whitespace = false;
3799 let mut explicit_positive = false;
3800 let mut saw_digit = false;
3801
3802 for chunk in node
3803 .children()
3804 .filter_map(|child| child.is_text().then(|| child.text()).flatten())
3805 {
3806 raw_text_len = raw_text_len.saturating_add(chunk.len());
3807 if raw_text_len > MAX_X509_SERIAL_NUMBER_RAW_TEXT_LEN {
3808 return Err(ParseError::InvalidStructure(
3809 "X509SerialNumber exceeds maximum allowed text length".into(),
3810 ));
3811 }
3812 for byte in chunk.bytes() {
3813 if matches!(byte, b' ' | b'\t' | b'\r' | b'\n') {
3814 trailing_whitespace |= explicit_positive || saw_digit;
3815 continue;
3816 }
3817 if byte == b'+' && !saw_digit && !explicit_positive && !trailing_whitespace {
3818 explicit_positive = true;
3819 continue;
3820 }
3821 if trailing_whitespace || !byte.is_ascii_digit() {
3822 return Err(ParseError::InvalidStructure(
3823 "invalid X509SerialNumber decimal value".into(),
3824 ));
3825 }
3826 saw_digit = true;
3827 if byte == b'0' && serial.is_empty() {
3828 continue;
3829 }
3830 if serial.len() == MAX_X509_SERIAL_NUMBER_VALUE_DIGITS {
3831 return Err(ParseError::InvalidStructure(
3832 "X509SerialNumber exceeds maximum allowed decimal value".into(),
3833 ));
3834 }
3835 serial.push(char::from(byte));
3836 }
3837 }
3838
3839 if !saw_digit {
3840 return Err(ParseError::InvalidStructure(
3841 "X509IssuerSerial requires non-empty X509IssuerName and X509SerialNumber".into(),
3842 ));
3843 }
3844 if serial.is_empty() {
3845 serial.push('0');
3846 }
3847 if x509_serial_decimal_bytes(&serial).is_none() {
3848 return Err(ParseError::InvalidStructure(
3849 "invalid X509SerialNumber decimal value or RFC 5280 range".into(),
3850 ));
3851 }
3852
3853 Ok(serial)
3854}
3855
3856fn ensure_no_element_children(node: Node<'_, '_>, element_name: &str) -> Result<(), ParseError> {
3857 if node.children().any(|child| child.is_element()) {
3858 return Err(ParseError::InvalidStructure(format!(
3859 "{element_name} must not contain child elements"
3860 )));
3861 }
3862 Ok(())
3863}
3864
3865fn ensure_no_non_whitespace_text(node: Node<'_, '_>, element_name: &str) -> Result<(), ParseError> {
3866 ensure_charged_whitespace_text(node, element_name, &crate::c14n::scratch::Native)
3867}
3868
3869fn ensure_charged_whitespace_text<'s, S: transforms::ParseStorage<'s>>(
3871 node: Node<'_, '_>,
3872 element_name: &str,
3873 storage: &S,
3874) -> Result<(), ParseError> {
3875 storage
3876 .charge(node.children().len())
3877 .map_err(super::types::TransformError::from)?;
3878 for child in node.children() {
3879 if let Some(text) = child.text().filter(|_| child.is_text()) {
3880 storage
3881 .charge(text.len())
3882 .map_err(super::types::TransformError::from)?;
3883 if !is_xml_whitespace_only(text) {
3884 return Err(storage.structure(format_args!(
3885 "{element_name} must not contain non-whitespace mixed content"
3886 )));
3887 }
3888 }
3889 }
3890 Ok(())
3891}
3892
3893#[cfg(test)]
3894#[expect(clippy::unwrap_used, reason = "tests use trusted XML fixtures")]
3895mod tests {
3896 #[test]
3897 fn replay_layout_admits_ignored_siblings_under_current_work() {
3898 use structured_asn1::memory::{Memory, MemoryAccount};
3901 let cost = |comments: usize| {
3902 let source = format!(
3903 "<Reference xmlns='{XMLDSIG_NS}'>{}<DigestMethod Algorithm='urn:historical'/><DigestValue/></Reference>",
3904 "<!--ignored-->".repeat(comments)
3905 );
3906 let document = Document::parse(&source).expect("fixed Reference layout");
3907 let current = Memory::new(std::sync::Arc::new(MemoryAccount::new(1 << 20, 1 << 20)));
3908 let transforms = parse_replay_transforms(
3909 document.root_element(),
3910 &mut transforms::XPathSignatureParseBudget::default(),
3911 &crate::c14n::scratch::Accounted(¤t),
3912 )
3913 .expect("valid empty transform chain");
3914 assert!(transforms.is_empty());
3915 drop(transforms);
3916 assert_eq!(current.used(), 0);
3917 current.work_used()
3918 };
3919 assert!(cost(1024) >= cost(1) + 1023);
3920 }
3921
3922 #[test]
3923 fn inspected_reference_layout_errors_do_not_own_diagnostics() {
3924 let doc = crate::xml::dom::Document::parse(
3927 "<Reference xmlns='http://www.w3.org/2000/09/xmldsig#'><wrong/></Reference>",
3928 )
3929 .unwrap();
3930 let fault = super::inspect_reference_layout(doc.root_element()).unwrap_err();
3931 fn owns_storage<T>(_: &T) -> bool {
3932 std::mem::needs_drop::<T>()
3933 }
3934 assert!(!owns_storage(&fault));
3935 }
3936
3937 #[test]
3938 fn certificate_processing_diagnostic_does_not_blame_der_for_selector() {
3939 let error = super::ParseError::Certificate(structured_pkix::Error::new(
3941 structured_pkix::ErrorCode::UnsupportedNameSyntax,
3942 "name.selector",
3943 0,
3944 ));
3945 assert!(error.to_string().starts_with("X.509 processing failed:"));
3946 }
3947 #[test]
3948 fn certificate_spki_inspection_borrows_source_and_retains_ingestion_budget() {
3949 let der = pem::parse(include_bytes!(
3952 "../../tests/fixtures/keys/rsa/rsa-2048-cert.pem"
3953 ))
3954 .expect("public certificate PEM")
3955 .into_contents();
3956 let resources = crate::policy::ResourcePolicy {
3957 max_key_candidates: 1,
3958 ..crate::policy::ResourcePolicy::default()
3959 };
3960 let mut session = super::KeyInfoParsingSession::new(&resources).expect("resource policy");
3961 session
3962 .with_certificate_public_key(&der, |spki| {
3963 assert!(!spki.is_empty());
3964 assert!(spki.as_ptr() >= der.as_ptr());
3965 assert!(spki.as_ptr() as usize + spki.len() <= der.as_ptr() as usize + der.len());
3966 })
3967 .expect("borrowed SPKI");
3968 assert!(matches!(
3969 session.with_certificate_public_key(&der, |_| panic!("unadmitted inspection")),
3970 Err(super::ParseError::Policy(
3971 crate::policy::PolicyViolation::ResourceLimit {
3972 resource: crate::policy::resource_name::KEY_CANDIDATES,
3973 maximum: 1,
3974 actual: 2
3975 }
3976 ))
3977 ));
3978 }
3979
3980 use super::*;
3981 use crate::xmldsig::TransformError;
3982 use base64::Engine;
3983
3984 fn fixture_rsa_cert_base64() -> String {
3985 fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem")
3986 }
3987
3988 fn fixture_cert_base64(path: &str) -> String {
3989 match path {
3990 "../../tests/fixtures/keys/rsa/rsa-2048-cert.pem" => {
3991 include_str!("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem")
3992 }
3993 "../../tests/fixtures/keys/rsa/rsa-4096-cert.pem" => {
3994 include_str!("../../tests/fixtures/keys/rsa/rsa-4096-cert.pem")
3995 }
3996 "../../tests/fixtures/keys/ca2cert.pem" => {
3997 include_str!("../../tests/fixtures/keys/ca2cert.pem")
3998 }
3999 "../../tests/fixtures/keys/cacert.pem" => {
4000 include_str!("../../tests/fixtures/keys/cacert.pem")
4001 }
4002 _ => unreachable!("unknown certificate fixture"),
4003 }
4004 .lines()
4005 .skip_while(|line| *line != "-----BEGIN CERTIFICATE-----")
4006 .skip(1)
4007 .take_while(|line| *line != "-----END CERTIFICATE-----")
4008 .collect::<String>()
4009 }
4010
4011 #[test]
4014 fn signature_algorithm_from_uri_rsa_sha256() {
4015 assert_eq!(
4016 SignatureAlgorithm::from_uri("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"),
4017 Some(SignatureAlgorithm::RsaSha256)
4018 );
4019 }
4020
4021 #[test]
4022 fn signature_algorithm_from_uri_rsa_sha1() {
4023 assert_eq!(
4024 SignatureAlgorithm::from_uri("http://www.w3.org/2000/09/xmldsig#rsa-sha1"),
4025 Some(SignatureAlgorithm::RsaSha1)
4026 );
4027 }
4028
4029 #[test]
4030 fn signature_algorithm_from_uri_ecdsa_sha256() {
4031 assert_eq!(
4032 SignatureAlgorithm::from_uri("http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"),
4033 Some(SignatureAlgorithm::EcdsaSha256)
4034 );
4035 }
4036
4037 #[test]
4038 fn signature_algorithm_from_uri_unknown() {
4039 assert_eq!(
4040 SignatureAlgorithm::from_uri("http://example.com/unknown"),
4041 None
4042 );
4043 }
4044
4045 #[test]
4046 fn signature_algorithm_uri_round_trip() {
4047 for algo in SignatureAlgorithm::ALL {
4048 assert_eq!(
4049 SignatureAlgorithm::from_uri(algo.uri()),
4050 Some(algo),
4051 "round-trip failed for {algo:?}"
4052 );
4053 }
4054 }
4055
4056 #[test]
4057 fn legacy_algorithms_are_verify_only() {
4058 assert!(!SignatureAlgorithm::DsaSha1.signing_allowed());
4059 assert!(!SignatureAlgorithm::HmacSha1.signing_allowed());
4060 assert!(!SignatureAlgorithm::RsaSha1.signing_allowed());
4061 assert!(SignatureAlgorithm::RsaSha256.signing_allowed());
4062 assert!(SignatureAlgorithm::EcdsaSha256.signing_allowed());
4063 }
4064
4065 #[test]
4068 fn find_signature_in_saml() {
4069 let xml = r#"<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
4070 <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
4071 <ds:SignedInfo/>
4072 </ds:Signature>
4073 </samlp:Response>"#;
4074 let doc = Document::parse(xml).unwrap();
4075 let sig = find_signature_node(&doc);
4076 assert!(sig.is_some());
4077 assert_eq!(sig.unwrap().tag_name().name(), "Signature");
4078 }
4079
4080 #[test]
4081 fn find_signature_missing() {
4082 let xml = "<root><child/></root>";
4083 let doc = Document::parse(xml).unwrap();
4084 assert!(find_signature_node(&doc).is_none());
4085 }
4086
4087 #[test]
4088 fn find_signature_ignores_wrong_namespace() {
4089 let xml = r#"<root><Signature xmlns="http://example.com/fake"/></root>"#;
4090 let doc = Document::parse(xml).unwrap();
4091 assert!(find_signature_node(&doc).is_none());
4092 }
4093
4094 #[test]
4097 fn key_info_session_applies_x509_byte_limits_before_decode() {
4098 let document = Document::parse("<KeyInfo xmlns='http://www.w3.org/2000/09/xmldsig#'><X509Data><X509SKI>AQID</X509SKI></X509Data></KeyInfo>").unwrap();
4100 for individual in [true, false] {
4101 let resources = crate::policy::ResourcePolicy {
4102 max_external_resource_bytes: if individual { 2 } else { 3 },
4103 max_external_resource_total_bytes: if individual { 100 } else { 5 },
4104 ..Default::default()
4105 };
4106 let mut session = KeyInfoParsingSession::new(&resources).unwrap();
4107 if !individual {
4108 session.parse(document.root_element()).unwrap();
4109 }
4110 assert!(matches!(
4111 session.parse(document.root_element()),
4112 Err(ParseError::Policy(_))
4113 ));
4114 }
4115 }
4116
4117 #[test]
4118 fn all_x509_binary_fields_preflight_active_byte_policy() {
4119 for element in ["X509Certificate", "X509SKI", "X509CRL", "X509Digest"] {
4122 let namespace = if element == "X509Digest" {
4123 XMLDSIG11_NS
4124 } else {
4125 XMLDSIG_NS
4126 };
4127 let xml = format!(
4128 "<KeyInfo xmlns='{XMLDSIG_NS}'><X509Data><v:{element} xmlns:v='{namespace}' Algorithm='test'>AQID</v:{element}></X509Data></KeyInfo>"
4129 );
4130 let document = Document::parse(&xml).unwrap();
4131 let resources = crate::policy::ResourcePolicy {
4132 max_external_resource_bytes: 2,
4133 ..Default::default()
4134 };
4135 let mut session = KeyInfoParsingSession::new(&resources).unwrap();
4136 assert!(
4137 matches!(
4138 session.parse(document.root_element()),
4139 Err(ParseError::Policy(_))
4140 ),
4141 "{element}"
4142 );
4143 }
4144 }
4145
4146 #[test]
4147 fn failed_x509_der_parse_keeps_session_byte_charge() {
4148 let invalid = Document::parse("<KeyInfo xmlns='http://www.w3.org/2000/09/xmldsig#'><X509Data><X509Certificate>AQID</X509Certificate></X509Data></KeyInfo>").unwrap();
4151 let valid = Document::parse("<KeyInfo xmlns='http://www.w3.org/2000/09/xmldsig#'><X509Data><X509SKI>AQID</X509SKI></X509Data></KeyInfo>").unwrap();
4152 let resources = crate::policy::ResourcePolicy {
4153 max_external_resource_bytes: 3,
4154 max_external_resource_total_bytes: 5,
4155 ..Default::default()
4156 };
4157 let mut session = KeyInfoParsingSession::new(&resources).unwrap();
4158 assert!(matches!(
4159 session.parse(invalid.root_element()),
4160 Err(ParseError::Certificate(error)) if !error.is_resource()
4161 ));
4162 assert!(matches!(
4163 session.parse(valid.root_element()),
4164 Err(ParseError::Policy(_))
4165 ));
4166 assert!(
4167 KeyInfoParsingSession::new(&resources)
4168 .unwrap()
4169 .parse(valid.root_element())
4170 .is_ok()
4171 );
4172 }
4173
4174 #[test]
4175 fn key_info_session_retains_failed_work_and_applies_active_policy() {
4176 let resources = crate::policy::ResourcePolicy {
4179 max_key_candidates: 1,
4180 ..Default::default()
4181 };
4182 let xml = "<KeyInfo xmlns='http://www.w3.org/2000/09/xmldsig#'><KeyValue/></KeyInfo>";
4183 let document = Document::parse(xml).unwrap();
4184 let mut session = KeyInfoParsingSession::new(&resources).unwrap();
4185 assert!(matches!(
4186 session.parse(document.root_element()),
4187 Err(ParseError::InvalidStructure(_))
4188 ));
4189 assert!(matches!(
4190 session.parse(document.root_element()),
4191 Err(ParseError::Policy(_))
4192 ));
4193 let zero = crate::policy::ResourcePolicy {
4194 max_key_candidates: 0,
4195 ..resources
4196 };
4197 assert!(matches!(
4198 KeyInfoParsingSession::new(&zero)
4199 .unwrap()
4200 .parse(document.root_element()),
4201 Err(ParseError::Policy(_))
4202 ));
4203 }
4204
4205 #[test]
4206 fn key_info_candidate_budget_precedes_key_value_parsing() {
4207 let document = Document::parse(
4210 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4211 <KeyValue><RSAKeyValue><Modulus>AQAB</Modulus></RSAKeyValue></KeyValue>
4212 </KeyInfo>"#,
4213 )
4214 .expect("fixed KeyInfo fixture must parse as XML");
4215 let resources = crate::policy::ResourcePolicy {
4216 max_key_candidates: 0,
4217 ..crate::policy::ResourcePolicy::default()
4218 };
4219
4220 let error = parse_key_info_with_policy_budgets(
4221 document.root_element(),
4222 crate::provider::default_provider(),
4223 &XmlBaseResolutionBudget::default(),
4224 &resources,
4225 None,
4226 )
4227 .expect_err("candidate policy must reject KeyValue before RSA parsing");
4228
4229 assert!(matches!(
4230 error,
4231 ParseError::Policy(crate::policy::PolicyViolation::ResourceLimit {
4232 resource: crate::policy::resource_name::KEY_CANDIDATES,
4233 maximum: 0,
4234 actual: 1,
4235 })
4236 ));
4237 }
4238
4239 #[test]
4240 fn key_info_candidate_budget_precedes_der_key_decoding() {
4241 let document = Document::parse(
4244 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
4245 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
4246 <dsig11:DEREncodedKeyValue>%%%invalid%%%</dsig11:DEREncodedKeyValue>
4247 </KeyInfo>"#,
4248 )
4249 .expect("fixed KeyInfo fixture must parse as XML");
4250 let resources = crate::policy::ResourcePolicy {
4251 max_key_candidates: 0,
4252 ..crate::policy::ResourcePolicy::default()
4253 };
4254
4255 let error = parse_key_info_with_policy_budgets(
4256 document.root_element(),
4257 crate::provider::default_provider(),
4258 &XmlBaseResolutionBudget::default(),
4259 &resources,
4260 None,
4261 )
4262 .expect_err("candidate policy must reject DEREncodedKeyValue before decoding");
4263
4264 assert!(matches!(
4265 error,
4266 ParseError::Policy(crate::policy::PolicyViolation::ResourceLimit {
4267 resource: crate::policy::resource_name::KEY_CANDIDATES,
4268 maximum: 0,
4269 actual: 1,
4270 })
4271 ));
4272 }
4273
4274 #[test]
4275 fn key_info_embedded_candidate_count_matches_materialized_key_kinds() {
4276 let key_info = KeyInfo {
4279 sources: vec![
4280 KeyInfoSource::KeyName("configured-key".into()),
4281 KeyInfoSource::KeyValue(KeyValueInfo::Unsupported {
4282 namespace: Some(XMLDSIG_NS.into()),
4283 local_name: "FutureKeyValue".into(),
4284 }),
4285 KeyInfoSource::DerEncodedKeyValue(vec![1]),
4286 KeyInfoSource::X509Data(X509DataInfo {
4287 certificates: vec![vec![2], vec![3]],
4288 ..X509DataInfo::default()
4289 }),
4290 KeyInfoSource::RetrievalMethod {
4291 uri: "urn:certificate".into(),
4292 resource_type: None,
4293 transforms: RetrievalMethodTransforms::None,
4294 },
4295 ],
4296 };
4297
4298 assert_eq!(key_info.embedded_candidate_count(), 4);
4299 }
4300
4301 #[test]
4302 fn parse_key_info_dispatches_supported_children() {
4303 let cert_base64 = fixture_rsa_cert_base64();
4304 let expected_cert = base64::engine::general_purpose::STANDARD
4305 .decode(&cert_base64)
4306 .expect("fixture PEM must contain valid base64");
4307 let cert_digest = base64::engine::general_purpose::STANDARD
4308 .encode(compute_digest(DigestAlgorithm::Sha256, &expected_cert));
4309 let xml = format!(
4310 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
4311 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
4312 <KeyName>idp-signing-key</KeyName>
4313 <KeyValue>
4314 <RSAKeyValue>
4315 <Modulus>AQAB</Modulus>
4316 <Exponent>AQAB</Exponent>
4317 </RSAKeyValue>
4318 </KeyValue>
4319 <X509Data>
4320 <X509Certificate>{cert_base64}</X509Certificate>
4321 <X509SubjectName>CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509SubjectName>
4322 <X509IssuerSerial>
4323 <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>
4324 <X509SerialNumber>680572598617295163017172295025714171905498632019</X509SerialNumber>
4325 </X509IssuerSerial>
4326 <X509SKI>bcOXN/nsVl8GatRbcKrPbzIbw0Y=</X509SKI>
4327 <X509CRL>BAUGBw==</X509CRL>
4328 <dsig11:X509Digest Algorithm="http://www.w3.org/2001/04/xmlenc#sha256">{cert_digest}</dsig11:X509Digest>
4329 </X509Data>
4330 <dsig11:DEREncodedKeyValue>AQIDBA==</dsig11:DEREncodedKeyValue>
4331 </KeyInfo>"#
4332 );
4333 let doc = Document::parse(&xml).unwrap();
4334
4335 let key_info = parse_key_info(doc.root_element()).unwrap();
4336 assert_eq!(key_info.sources.len(), 4);
4337
4338 assert_eq!(
4339 key_info.sources[0],
4340 KeyInfoSource::KeyName("idp-signing-key".to_string())
4341 );
4342 assert_eq!(
4343 key_info.sources[1],
4344 KeyInfoSource::KeyValue(KeyValueInfo::Rsa {
4345 modulus: vec![1, 0, 1],
4346 exponent: vec![1, 0, 1],
4347 })
4348 );
4349 let x509_info = match &key_info.sources[2] {
4350 KeyInfoSource::X509Data(x509) => x509,
4351 other => panic!("expected X509Data source, got {other:?}"),
4352 };
4353 assert_eq!(x509_info.certificates, vec![expected_cert]);
4354 assert_eq!(
4355 x509_info.subject_names,
4356 vec![
4357 "CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US"
4358 .to_string()
4359 ]
4360 );
4361 assert_eq!(
4362 x509_info.issuer_serials,
4363 vec![(
4364 "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(),
4365 "680572598617295163017172295025714171905498632019".to_string()
4366 )]
4367 );
4368 assert_eq!(
4369 x509_info.skis,
4370 vec![vec![
4371 109, 195, 151, 55, 249, 236, 86, 95, 6, 106, 212, 91, 112, 170, 207, 111, 50, 27,
4372 195, 70
4373 ]]
4374 );
4375 assert_eq!(x509_info.crls, vec![vec![4, 5, 6, 7]]);
4376 assert_eq!(
4377 x509_info.digests,
4378 vec![(
4379 "http://www.w3.org/2001/04/xmlenc#sha256".to_string(),
4380 compute_digest(DigestAlgorithm::Sha256, &x509_info.certificates[0])
4381 )]
4382 );
4383 assert_eq!(x509_info.parsed_certificates.len(), 1);
4384 assert_eq!(x509_info.certificate_chain, vec![0]);
4385 let parsed_cert = &x509_info.parsed_certificates[0];
4386 assert!(!parsed_cert.subject_dn.is_empty());
4387 assert!(!parsed_cert.issuer_dn.is_empty());
4388 assert_eq!(
4389 parsed_cert.serial_number_hex,
4390 "7735EE487F6862DAF1B3956D961CCB0FA6F34F53"
4391 );
4392 assert!(parsed_cert.subject_key_identifier.is_some());
4393 assert!(matches!(
4394 parsed_cert.public_key,
4395 X509PublicKeyInfo::Rsa { .. }
4396 ));
4397
4398 assert_eq!(
4399 key_info.sources[3],
4400 KeyInfoSource::DerEncodedKeyValue(vec![1, 2, 3, 4])
4401 );
4402 }
4403
4404 #[test]
4405 fn parse_rsa_key_value_preserves_wrapped_crypto_binary() {
4406 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4408 <KeyValue><RSAKeyValue>
4409 <Modulus> AQID
4410BA== </Modulus>
4411 <Exponent> AQAB </Exponent>
4412 </RSAKeyValue></KeyValue>
4413 </KeyInfo>"##;
4414 let doc = Document::parse(xml).unwrap();
4415
4416 assert_eq!(
4417 parse_key_info(doc.root_element()).unwrap().sources,
4418 vec![KeyInfoSource::KeyValue(KeyValueInfo::Rsa {
4419 modulus: vec![1, 2, 3, 4],
4420 exponent: vec![1, 0, 1],
4421 })]
4422 );
4423 }
4424
4425 #[test]
4426 fn parse_rsa_key_value_rejects_reordered_parameters() {
4427 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4429 <KeyValue><RSAKeyValue>
4430 <Exponent>AQAB</Exponent><Modulus>AQID</Modulus>
4431 </RSAKeyValue></KeyValue>
4432 </KeyInfo>"##;
4433 let doc = Document::parse(xml).unwrap();
4434
4435 assert!(matches!(
4436 parse_key_info(doc.root_element()),
4437 Err(ParseError::InvalidStructure(_))
4438 ));
4439 }
4440
4441 #[test]
4442 fn parse_rsa_key_value_rejects_missing_exponent() {
4443 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4445 <KeyValue><RSAKeyValue><Modulus>AQID</Modulus></RSAKeyValue></KeyValue>
4446 </KeyInfo>"##;
4447 let doc = Document::parse(xml).unwrap();
4448
4449 assert!(matches!(
4450 parse_key_info(doc.root_element()),
4451 Err(ParseError::InvalidStructure(_))
4452 ));
4453 }
4454
4455 #[test]
4456 fn parse_rsa_key_value_rejects_duplicate_exponent() {
4457 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4459 <KeyValue><RSAKeyValue>
4460 <Modulus>AQID</Modulus><Exponent>AQAB</Exponent><Exponent>AQAB</Exponent>
4461 </RSAKeyValue></KeyValue>
4462 </KeyInfo>"##;
4463 let doc = Document::parse(xml).unwrap();
4464
4465 assert!(matches!(
4466 parse_key_info(doc.root_element()),
4467 Err(ParseError::InvalidStructure(_))
4468 ));
4469 }
4470
4471 #[test]
4472 fn parse_rsa_key_value_rejects_wrong_parameter_namespace() {
4473 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#" xmlns:bad="urn:bad">
4475 <KeyValue><RSAKeyValue>
4476 <bad:Modulus>AQID</bad:Modulus><Exponent>AQAB</Exponent>
4477 </RSAKeyValue></KeyValue>
4478 </KeyInfo>"#;
4479 let doc = Document::parse(xml).unwrap();
4480
4481 assert!(matches!(
4482 parse_key_info(doc.root_element()),
4483 Err(ParseError::InvalidStructure(_))
4484 ));
4485 }
4486
4487 #[test]
4488 fn parse_rsa_key_value_rejects_nested_crypto_binary() {
4489 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4491 <KeyValue><RSAKeyValue>
4492 <Modulus><chunk>AQID</chunk></Modulus><Exponent>AQAB</Exponent>
4493 </RSAKeyValue></KeyValue>
4494 </KeyInfo>"#;
4495 let doc = Document::parse(xml).unwrap();
4496
4497 assert!(matches!(
4498 parse_key_info(doc.root_element()),
4499 Err(ParseError::InvalidStructure(_))
4500 ));
4501 }
4502
4503 #[test]
4504 fn parse_rsa_key_value_rejects_malformed_base64() {
4505 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4507 <KeyValue><RSAKeyValue>
4508 <Modulus>%%%%</Modulus><Exponent>AQAB</Exponent>
4509 </RSAKeyValue></KeyValue>
4510 </KeyInfo>"#;
4511 let doc = Document::parse(xml).unwrap();
4512
4513 assert!(matches!(
4514 parse_key_info(doc.root_element()),
4515 Err(ParseError::Base64(_))
4516 ));
4517 }
4518
4519 #[test]
4520 fn parse_rsa_key_value_rejects_oversized_exponent_before_decode() {
4521 let exponent = "A".repeat(MAX_RSA_EXPONENT_LEN.div_ceil(3) * 4 + 1);
4523 let xml = format!(
4524 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4525 <KeyValue><RSAKeyValue>
4526 <Modulus>AQID</Modulus><Exponent>{exponent}</Exponent>
4527 </RSAKeyValue></KeyValue>
4528 </KeyInfo>"#
4529 );
4530 let doc = Document::parse(&xml).unwrap();
4531
4532 assert!(matches!(
4533 parse_key_info(doc.root_element()),
4534 Err(ParseError::InvalidStructure(_))
4535 ));
4536 }
4537
4538 #[test]
4539 fn parse_key_info_ignores_unknown_children() {
4540 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4541 <Foo>bar</Foo>
4542 <KeyName>ok</KeyName>
4543 </KeyInfo>"#;
4544 let doc = Document::parse(xml).unwrap();
4545
4546 let key_info = parse_key_info(doc.root_element()).unwrap();
4547 assert_eq!(key_info.sources, vec![KeyInfoSource::KeyName("ok".into())]);
4548 }
4549
4550 #[test]
4551 fn parse_key_info_keyvalue_requires_single_child() {
4552 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4553 <KeyValue/>
4554 </KeyInfo>"#;
4555 let doc = Document::parse(xml).unwrap();
4556
4557 let err = parse_key_info(doc.root_element()).unwrap_err();
4558 assert!(matches!(err, ParseError::InvalidStructure(_)));
4559 }
4560
4561 #[test]
4562 fn parse_key_info_accepts_empty_x509data() {
4563 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4564 <X509Data/>
4565 </KeyInfo>"#;
4566 let doc = Document::parse(xml).unwrap();
4567
4568 let key_info = parse_key_info(doc.root_element()).unwrap();
4569 assert_eq!(
4570 key_info.sources,
4571 vec![KeyInfoSource::X509Data(X509DataInfo::default())]
4572 );
4573 }
4574
4575 #[test]
4576 fn parse_key_info_rejects_unknown_xmlsig_child_in_x509data() {
4577 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4578 <X509Data>
4579 <Foo/>
4580 </X509Data>
4581 </KeyInfo>"#;
4582 let doc = Document::parse(xml).unwrap();
4583
4584 let err = parse_key_info(doc.root_element()).unwrap_err();
4585 assert!(matches!(err, ParseError::InvalidStructure(_)));
4586 }
4587
4588 #[test]
4589 fn parse_key_info_rejects_unknown_xmlsig11_child_in_x509data() {
4590 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
4591 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
4592 <X509Data>
4593 <dsig11:Foo/>
4594 </X509Data>
4595 </KeyInfo>"#;
4596 let doc = Document::parse(xml).unwrap();
4597
4598 let err = parse_key_info(doc.root_element()).unwrap_err();
4599 assert!(matches!(err, ParseError::InvalidStructure(_)));
4600 }
4601
4602 #[test]
4603 fn parse_key_info_rejects_x509_issuer_serial_without_required_children() {
4604 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4605 <X509Data>
4606 <X509IssuerSerial>
4607 <X509IssuerName>CN=CA</X509IssuerName>
4608 </X509IssuerSerial>
4609 </X509Data>
4610 </KeyInfo>"#;
4611 let doc = Document::parse(xml).unwrap();
4612
4613 let err = parse_key_info(doc.root_element()).unwrap_err();
4614 assert!(matches!(err, ParseError::InvalidStructure(_)));
4615 }
4616
4617 #[test]
4618 fn parse_key_info_rejects_x509_issuer_serial_with_duplicate_issuer_name() {
4619 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4620 <X509Data>
4621 <X509IssuerSerial>
4622 <X509IssuerName>CN=CA-1</X509IssuerName>
4623 <X509IssuerName>CN=CA-2</X509IssuerName>
4624 <X509SerialNumber>42</X509SerialNumber>
4625 </X509IssuerSerial>
4626 </X509Data>
4627 </KeyInfo>"#;
4628 let doc = Document::parse(xml).unwrap();
4629
4630 let err = parse_key_info(doc.root_element()).unwrap_err();
4631 assert!(matches!(err, ParseError::InvalidStructure(_)));
4632 }
4633
4634 #[test]
4635 fn parse_key_info_rejects_x509_issuer_serial_with_duplicate_serial_number() {
4636 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4637 <X509Data>
4638 <X509IssuerSerial>
4639 <X509IssuerName>CN=CA</X509IssuerName>
4640 <X509SerialNumber>1</X509SerialNumber>
4641 <X509SerialNumber>2</X509SerialNumber>
4642 </X509IssuerSerial>
4643 </X509Data>
4644 </KeyInfo>"#;
4645 let doc = Document::parse(xml).unwrap();
4646
4647 let err = parse_key_info(doc.root_element()).unwrap_err();
4648 assert!(matches!(err, ParseError::InvalidStructure(_)));
4649 }
4650
4651 #[test]
4652 fn parse_key_info_rejects_x509_issuer_serial_with_whitespace_only_values() {
4653 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4654 <X509Data>
4655 <X509IssuerSerial>
4656 <X509IssuerName> </X509IssuerName>
4657 <X509SerialNumber>
4658
4659 </X509SerialNumber>
4660 </X509IssuerSerial>
4661 </X509Data>
4662 </KeyInfo>"#;
4663 let doc = Document::parse(xml).unwrap();
4664
4665 let err = parse_key_info(doc.root_element()).unwrap_err();
4666 assert!(matches!(err, ParseError::InvalidStructure(_)));
4667 }
4668
4669 #[test]
4670 fn parse_key_info_rejects_x509_issuer_serial_with_wrong_child_order() {
4671 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4672 <X509Data>
4673 <X509IssuerSerial>
4674 <X509SerialNumber>42</X509SerialNumber>
4675 <X509IssuerName>CN=CA</X509IssuerName>
4676 </X509IssuerSerial>
4677 </X509Data>
4678 </KeyInfo>"#;
4679 let doc = Document::parse(xml).unwrap();
4680
4681 let err = parse_key_info(doc.root_element()).unwrap_err();
4682 assert!(matches!(err, ParseError::InvalidStructure(_)));
4683 }
4684
4685 #[test]
4686 fn parse_key_info_rejects_x509_issuer_serial_with_extra_child_element() {
4687 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
4688 xmlns:foo="urn:example:foo">
4689 <X509Data>
4690 <X509IssuerSerial>
4691 <X509IssuerName>CN=CA</X509IssuerName>
4692 <X509SerialNumber>42</X509SerialNumber>
4693 <foo:Extra/>
4694 </X509IssuerSerial>
4695 </X509Data>
4696 </KeyInfo>"#;
4697 let doc = Document::parse(xml).unwrap();
4698
4699 let err = parse_key_info(doc.root_element()).unwrap_err();
4700 assert!(matches!(err, ParseError::InvalidStructure(_)));
4701 }
4702
4703 #[test]
4704 fn parse_key_info_rejects_x509_digest_without_algorithm() {
4705 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
4706 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
4707 <X509Data>
4708 <dsig11:X509Digest>AQID</dsig11:X509Digest>
4709 </X509Data>
4710 </KeyInfo>"#;
4711 let doc = Document::parse(xml).unwrap();
4712
4713 let err = parse_key_info(doc.root_element()).unwrap_err();
4714 assert!(matches!(err, ParseError::InvalidStructure(_)));
4715 }
4716
4717 #[test]
4718 fn parse_key_info_rejects_invalid_x509_certificate_base64() {
4719 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4720 <X509Data>
4721 <X509Certificate>%%%invalid%%%</X509Certificate>
4722 </X509Data>
4723 </KeyInfo>"#;
4724 let doc = Document::parse(xml).unwrap();
4725
4726 let err = parse_key_info(doc.root_element()).unwrap_err();
4727 assert!(matches!(err, ParseError::Base64(_)));
4728 }
4729
4730 #[test]
4731 fn parse_key_info_rejects_x509_data_exceeding_entry_budget() {
4732 let subjects = (0..(MAX_X509_DATA_ENTRY_COUNT + 1))
4733 .map(|idx| format!("<X509SubjectName>CN={idx}</X509SubjectName>"))
4734 .collect::<Vec<_>>()
4735 .join("");
4736 let xml = format!(
4737 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><X509Data>{subjects}</X509Data></KeyInfo>"
4738 );
4739 let doc = Document::parse(&xml).unwrap();
4740
4741 let err = parse_key_info(doc.root_element()).unwrap_err();
4742 assert!(matches!(err, ParseError::InvalidStructure(_)));
4743 }
4744
4745 #[test]
4746 fn parse_key_info_rejects_x509_data_exceeding_total_binary_budget() {
4747 let payload = base64::engine::general_purpose::STANDARD.encode(vec![0u8; 190_000]);
4748 let entries = (0..6)
4749 .map(|_| format!("<X509SKI>{payload}</X509SKI>"))
4750 .collect::<Vec<_>>()
4751 .join("");
4752 let xml = format!(
4753 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><X509Data>{entries}</X509Data></KeyInfo>"
4754 );
4755 let doc = Document::parse(&xml).unwrap();
4756
4757 let err = parse_key_info(doc.root_element()).unwrap_err();
4758 assert!(matches!(err, ParseError::InvalidStructure(_)));
4759 }
4760
4761 #[test]
4762 fn parse_key_info_rejects_x509_certificate_with_invalid_der() {
4763 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4765 <X509Data>
4766 <X509Certificate>AQID</X509Certificate>
4767 </X509Data>
4768 </KeyInfo>"#;
4769 let doc = Document::parse(xml).unwrap();
4770
4771 let err = parse_key_info(doc.root_element()).unwrap_err();
4772 assert!(
4773 matches!(err, ParseError::Certificate(error) if !error.is_resource() && error.field() == "certificate")
4774 );
4775 }
4776
4777 #[test]
4778 fn parse_key_info_rejects_x509_certificate_with_trailing_der_bytes() {
4779 let mut cert = base64::engine::general_purpose::STANDARD
4781 .decode(fixture_rsa_cert_base64())
4782 .unwrap();
4783 cert.extend_from_slice(&[0x00, 0x01]);
4784 let cert_base64 = base64::engine::general_purpose::STANDARD.encode(cert);
4785 let xml = format!(
4786 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4787 <X509Data>
4788 <X509Certificate>{cert_base64}</X509Certificate>
4789 </X509Data>
4790 </KeyInfo>"#
4791 );
4792 let doc = Document::parse(&xml).unwrap();
4793
4794 let err = parse_key_info(doc.root_element()).unwrap_err();
4795 assert!(
4796 matches!(err, ParseError::Certificate(error) if error.code() == structured_pkix::ErrorCode::TrailingData)
4797 );
4798 }
4799
4800 #[test]
4801 fn parse_key_info_marks_unsupported_spki_algorithm_as_unsupported() {
4802 let xml = include_str!(
4803 "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt.xml"
4804 );
4805 let doc = Document::parse(xml).unwrap();
4806 let key_info_node = doc
4807 .descendants()
4808 .find(|node| {
4809 node.is_element()
4810 && node.tag_name().namespace() == Some(XMLDSIG_NS)
4811 && node.tag_name().name() == "KeyInfo"
4812 })
4813 .expect("fixture must contain ds:KeyInfo");
4814
4815 let key_info = parse_key_info(key_info_node).expect("KeyInfo parse should succeed");
4816 let x509_info = match &key_info.sources[0] {
4817 KeyInfoSource::X509Data(x509) => x509,
4818 other => panic!("expected X509Data source, got {other:?}"),
4819 };
4820 assert_eq!(x509_info.certificates.len(), 1);
4821 assert_eq!(x509_info.parsed_certificates.len(), 1);
4822 assert_eq!(x509_info.certificate_chain, vec![0]);
4823 let parsed_cert = &x509_info.parsed_certificates[0];
4824 assert!(!parsed_cert.subject_dn.is_empty());
4825 assert!(!parsed_cert.issuer_dn.is_empty());
4826 assert!(parsed_cert.subject_key_identifier.is_some());
4827 assert!(matches!(
4828 parsed_cert.public_key,
4829 X509PublicKeyInfo::Unsupported { .. }
4830 ));
4831 }
4832
4833 #[test]
4834 fn parse_key_info_orders_x509_certificate_chain_from_signing_cert() {
4835 let root = fixture_cert_base64("../../tests/fixtures/keys/cacert.pem");
4836 let intermediate = fixture_cert_base64("../../tests/fixtures/keys/ca2cert.pem");
4837 let leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
4838 let xml = format!(
4839 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
4840 <X509Data>
4841 <X509Certificate>{root}</X509Certificate>
4842 <X509Certificate>{intermediate}</X509Certificate>
4843 <X509Certificate>{leaf}</X509Certificate>
4844 </X509Data>
4845 </KeyInfo>"#
4846 );
4847 let doc = Document::parse(&xml).unwrap();
4848
4849 let key_info = parse_key_info(doc.root_element()).unwrap();
4850 let x509_info = match &key_info.sources[0] {
4851 KeyInfoSource::X509Data(x509) => x509,
4852 other => panic!("expected X509Data source, got {other:?}"),
4853 };
4854
4855 assert_eq!(x509_info.certificate_chain, vec![2, 1, 0]);
4856 }
4857
4858 #[test]
4859 fn certificate_metadata_preserves_opaque_nonaligned_public_keys()
4860 -> Result<(), Box<dyn std::error::Error>> {
4861 let key = rcgen::KeyPair::generate()?;
4865 let certificate = rcgen::CertificateParams::new(Vec::<String>::new())?.self_signed(&key)?;
4866 let mut bytes = certificate.der().to_vec();
4867 let session =
4868 super::super::pkix::CertificateSession::new(&crate::policy::ResourcePolicy::default());
4869 let (start, length) = {
4870 let decoded = session.read(&bytes)?;
4871 let octets = decoded
4872 .tbs()
4873 .public_key()
4874 .key_octets()
4875 .ok_or("EC key octets")?;
4876 (
4877 octets.as_ptr() as usize - bytes.as_ptr() as usize,
4878 octets.len(),
4879 )
4880 };
4881 let ec_oid = [0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01];
4882 let oid_start = bytes
4883 .windows(ec_oid.len())
4884 .position(|value| value == ec_oid)
4885 .ok_or("EC algorithm identifier")?;
4886 bytes[oid_start + ec_oid.len() - 1] = 99;
4887 bytes[start - 1] = 1;
4888 bytes[start + length - 1] &= 0xfe;
4889 let metadata = parse_x509_certificate_in(&bytes, &session)?;
4890 assert!(
4891 matches!(metadata.public_key, X509PublicKeyInfo::Unsupported { algorithm_oid }
4892 if algorithm_oid == "1.2.840.10045.2.99")
4893 );
4894 Ok(())
4895 }
4896
4897 #[test]
4898 fn certificate_digest_selector_reserves_work_and_output_before_hashing() {
4899 let source = b"certificate identity bytes";
4902 let expected = super::super::compute_digest(DigestAlgorithm::Sha256, source);
4903 for (work, bytes, resource) in [
4904 (0, usize::MAX, crate::policy::resource_name::X509_WORK),
4905 (
4906 usize::MAX,
4907 expected.len() - 1,
4908 crate::policy::resource_name::X509_WORKSPACE_BYTES,
4909 ),
4910 ] {
4911 let resources = crate::policy::ResourcePolicy {
4912 max_x509_work: work,
4913 max_x509_workspace_bytes: bytes,
4914 ..Default::default()
4915 };
4916 let session = super::super::pkix::CertificateSession::new(&resources);
4917 assert!(
4918 matches!(source_digest_matches(crate::provider::default_provider(), DigestAlgorithm::Sha256, source, &expected, &session),
4919 Err(ParseError::Policy(crate::policy::PolicyViolation::ResourceLimitExceeded { resource: actual, .. })) if actual == resource)
4920 );
4921 assert_eq!(
4922 session.memory().used(),
4923 0,
4924 "failed admission retains no output"
4925 );
4926 }
4927 }
4928
4929 #[test]
4930 fn certificate_selectors_ignore_rewritten_display_metadata() {
4931 let der = base64::engine::general_purpose::STANDARD
4934 .decode(fixture_cert_base64(
4935 "../../tests/fixtures/keys/rsa/rsa-2048-cert.pem",
4936 ))
4937 .unwrap();
4938 let mut metadata = parse_x509_certificate(&der).unwrap();
4939 let session =
4940 super::super::pkix::CertificateSession::new(&crate::policy::ResourcePolicy::default());
4941 let mut serial = X509DataInfo {
4942 issuer_serials: vec![(metadata.issuer_dn.clone(), "1".into())],
4943 certificates: vec![der.clone()],
4944 ..Default::default()
4945 };
4946 metadata.serial_number_hex = "01".into();
4947 serial.parsed_certificates.push(metadata.clone());
4948 assert!(
4949 !x509_selector_categories_match_chain(
4950 &serial,
4951 crate::provider::default_provider(),
4952 &session
4953 )
4954 .unwrap()
4955 );
4956 let mut ski = X509DataInfo {
4957 skis: vec![vec![0x12, 0x34]],
4958 certificates: vec![der],
4959 ..Default::default()
4960 };
4961 metadata.subject_key_identifier = Some(vec![0x12, 0x34]);
4962 ski.parsed_certificates.push(metadata);
4963 assert!(
4964 !x509_selector_categories_match_chain(
4965 &ski,
4966 crate::provider::default_provider(),
4967 &session
4968 )
4969 .unwrap()
4970 );
4971 }
4972
4973 #[test]
4974 fn chain_discovery_uses_source_names_not_display_metadata() {
4975 let certificates = [
4978 fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem"),
4979 fixture_cert_base64("../../tests/fixtures/keys/ca2cert.pem"),
4980 fixture_cert_base64("../../tests/fixtures/keys/cacert.pem"),
4981 ]
4982 .map(|encoded| {
4983 base64::engine::general_purpose::STANDARD
4984 .decode(encoded)
4985 .unwrap()
4986 })
4987 .to_vec();
4988 let mut parsed_certificates = certificates
4989 .iter()
4990 .map(|certificate| parse_x509_certificate(certificate).unwrap())
4991 .collect::<Vec<_>>();
4992 parsed_certificates[0].subject_dn = parsed_certificates[0].issuer_dn.clone();
4993 let info = X509DataInfo {
4994 certificates,
4995 parsed_certificates,
4996 ..Default::default()
4997 };
4998 let session =
4999 super::super::pkix::CertificateSession::new(&crate::policy::ResourcePolicy::default());
5000 assert_eq!(
5001 build_x509_certificate_chain_from(
5002 &info,
5003 0,
5004 crate::provider::default_provider(),
5005 None,
5006 &session
5007 )
5008 .unwrap(),
5009 vec![0, 1, 2]
5010 );
5011 }
5012
5013 #[test]
5014 fn chain_builder_matches_x509_equivalent_distinguished_names() {
5015 let certificates = [
5019 fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem"),
5020 fixture_cert_base64("../../tests/fixtures/keys/ca2cert.pem"),
5021 fixture_cert_base64("../../tests/fixtures/keys/cacert.pem"),
5022 ]
5023 .map(|encoded| {
5024 base64::engine::general_purpose::STANDARD
5025 .decode(encoded)
5026 .unwrap()
5027 })
5028 .to_vec();
5029 let mut parsed_certificates = certificates
5030 .iter()
5031 .map(|certificate| parse_x509_certificate(certificate).unwrap())
5032 .collect::<Vec<_>>();
5033 parsed_certificates[0].issuer_dn = parsed_certificates[1].subject_dn.to_ascii_lowercase();
5034 parsed_certificates[1].issuer_dn = parsed_certificates[2].subject_dn.to_ascii_lowercase();
5035 let info = X509DataInfo {
5036 certificates,
5037 parsed_certificates,
5038 ..X509DataInfo::default()
5039 };
5040
5041 assert_eq!(
5042 select_x509_signing_certificate(
5043 &info,
5044 crate::provider::default_provider(),
5045 &super::super::pkix::CertificateSession::new(
5046 &crate::policy::ResourcePolicy::default()
5047 )
5048 )
5049 .unwrap(),
5050 0
5051 );
5052 assert_eq!(
5053 build_x509_certificate_chain_from(
5054 &info,
5055 0,
5056 crate::provider::default_provider(),
5057 None,
5058 &super::super::pkix::CertificateSession::new(
5059 &crate::policy::ResourcePolicy::default()
5060 ),
5061 )
5062 .unwrap(),
5063 vec![0, 1, 2]
5064 );
5065 }
5066
5067 #[test]
5068 fn parse_key_info_uses_issuer_serial_to_select_x509_signing_certificate() {
5069 let root = fixture_cert_base64("../../tests/fixtures/keys/cacert.pem");
5070 let intermediate = fixture_cert_base64("../../tests/fixtures/keys/ca2cert.pem");
5071 let leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
5072 let xml = format!(
5073 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5074 <X509Data>
5075 <X509IssuerSerial>
5076 <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>
5077 <X509SerialNumber>680572598617295163017172295025714171905498632019</X509SerialNumber>
5078 </X509IssuerSerial>
5079 <X509Certificate>{root}</X509Certificate>
5080 <X509Certificate>{intermediate}</X509Certificate>
5081 <X509Certificate>{leaf}</X509Certificate>
5082 </X509Data>
5083 </KeyInfo>"#
5084 );
5085 let doc = Document::parse(&xml).unwrap();
5086
5087 let key_info = parse_key_info(doc.root_element()).unwrap();
5088 let x509_info = match &key_info.sources[0] {
5089 KeyInfoSource::X509Data(x509) => x509,
5090 other => panic!("expected X509Data source, got {other:?}"),
5091 };
5092
5093 assert_eq!(x509_info.certificate_chain, vec![2, 1, 0]);
5094 }
5095
5096 #[test]
5097 fn parse_key_info_allows_selectors_for_multiple_chain_members() {
5098 let root = fixture_cert_base64("../../tests/fixtures/keys/cacert.pem");
5101 let intermediate = fixture_cert_base64("../../tests/fixtures/keys/ca2cert.pem");
5102 let leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
5103 let xml = format!(
5104 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5105 <X509Data>
5106 <X509SubjectName>CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509SubjectName>
5107 <X509SKI>0X0XrEVCio75sBcl1TxymJ2IOiU=</X509SKI>
5108 <X509Certificate>{root}</X509Certificate>
5109 <X509Certificate>{intermediate}</X509Certificate>
5110 <X509Certificate>{leaf}</X509Certificate>
5111 </X509Data>
5112 </KeyInfo>"#
5113 );
5114 let doc = Document::parse(&xml).unwrap();
5115
5116 let key_info = parse_key_info(doc.root_element()).unwrap();
5117 let x509_info = match &key_info.sources[0] {
5118 KeyInfoSource::X509Data(x509) => x509,
5119 other => panic!("expected X509Data source, got {other:?}"),
5120 };
5121
5122 assert_eq!(x509_info.certificate_chain, vec![2, 1, 0]);
5123 }
5124
5125 #[test]
5126 fn parse_key_info_uses_decimal_issuer_serial_to_select_x509_signing_certificate() {
5127 let serial = "680572598617295163017172295025714171905498632019";
5128 let padded_serial = format!("{}{}", "0".repeat(64), serial);
5129 assert_eq!(
5130 x509_serial_decimal_to_hex(&padded_serial).as_deref(),
5131 Some("7735EE487F6862DAF1B3956D961CCB0FA6F34F53")
5132 );
5133 let root = fixture_cert_base64("../../tests/fixtures/keys/cacert.pem");
5134 let intermediate = fixture_cert_base64("../../tests/fixtures/keys/ca2cert.pem");
5135 let leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
5136 let other_leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-4096-cert.pem");
5137 let xml = format!(
5138 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5139 <X509Data>
5140 <X509IssuerSerial>
5141 <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>
5142 <X509SerialNumber>{padded_serial}</X509SerialNumber>
5143 </X509IssuerSerial>
5144 <X509Certificate>{root}</X509Certificate>
5145 <X509Certificate>{intermediate}</X509Certificate>
5146 <X509Certificate>{leaf}</X509Certificate>
5147 <X509Certificate>{other_leaf}</X509Certificate>
5148 </X509Data>
5149 </KeyInfo>"#
5150 );
5151 let doc = Document::parse(&xml).unwrap();
5152
5153 let key_info = parse_key_info(doc.root_element()).unwrap();
5154 let x509_info = match &key_info.sources[0] {
5155 KeyInfoSource::X509Data(x509) => x509,
5156 other => panic!("expected X509Data source, got {other:?}"),
5157 };
5158
5159 assert_eq!(x509_info.certificate_chain, vec![2, 1, 0]);
5160 }
5161
5162 #[test]
5163 fn parse_key_info_rejects_ambiguous_x509_signing_certificate_candidates() {
5164 let first_leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
5165 let second_leaf = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-4096-cert.pem");
5166 let xml = format!(
5167 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5168 <X509Data>
5169 <X509Certificate>{first_leaf}</X509Certificate>
5170 <X509Certificate>{second_leaf}</X509Certificate>
5171 </X509Data>
5172 </KeyInfo>"#
5173 );
5174 let doc = Document::parse(&xml).unwrap();
5175
5176 let err = parse_key_info(doc.root_element()).unwrap_err();
5177 assert!(matches!(err, ParseError::InvalidStructure(_)));
5178 }
5179
5180 #[test]
5181 fn parse_key_info_rejects_unmatched_x509_lookup_identifier() {
5182 let cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
5183 let xml = format!(
5184 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5185 <X509Data>
5186 <X509SubjectName>CN=Not The Embedded Certificate</X509SubjectName>
5187 <X509Certificate>{cert}</X509Certificate>
5188 </X509Data>
5189 </KeyInfo>"#
5190 );
5191 let doc = Document::parse(&xml).unwrap();
5192
5193 let err = parse_key_info(doc.root_element()).unwrap_err();
5194 assert!(
5195 matches!(err, ParseError::InvalidStructure(message) if message.contains("lookup identifiers"))
5196 );
5197 }
5198
5199 #[test]
5200 fn parse_key_info_rejects_partially_matched_selector_category() {
5201 let cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
5204 let xml = format!(
5205 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5206 <X509Data>
5207 <X509SubjectName>CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509SubjectName>
5208 <X509SubjectName>CN=Not In The Embedded Chain</X509SubjectName>
5209 <X509Certificate>{cert}</X509Certificate>
5210 </X509Data>
5211 </KeyInfo>"#
5212 );
5213 let doc = Document::parse(&xml).unwrap();
5214
5215 let err = parse_key_info(doc.root_element()).unwrap_err();
5216 assert!(
5217 matches!(err, ParseError::InvalidStructure(message) if message.contains("lookup identifiers"))
5218 );
5219 }
5220
5221 #[test]
5222 fn parse_key_info_rejects_malformed_issuer_serial_even_with_matching_subject() {
5223 let cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
5226 let xml = format!(
5227 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5228 <X509Data>
5229 <X509SubjectName>CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509SubjectName>
5230 <X509IssuerSerial>
5231 <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>
5232 <X509SerialNumber>not-a-decimal-serial</X509SerialNumber>
5233 </X509IssuerSerial>
5234 <X509Certificate>{cert}</X509Certificate>
5235 </X509Data>
5236 </KeyInfo>"#
5237 );
5238 let doc = Document::parse(&xml).unwrap();
5239
5240 let err = parse_key_info(doc.root_element()).unwrap_err();
5241 assert!(
5242 matches!(err, ParseError::InvalidStructure(message) if message.contains("invalid X509SerialNumber"))
5243 );
5244 }
5245
5246 #[test]
5247 fn parse_key_info_rejects_unmatched_ski_even_with_matching_subject() {
5248 let cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
5249 let xml = format!(
5250 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5251 <X509Data>
5252 <X509SubjectName>CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509SubjectName>
5253 <X509SKI>AQIDBA==</X509SKI>
5254 <X509Certificate>{cert}</X509Certificate>
5255 </X509Data>
5256 </KeyInfo>"#
5257 );
5258 let doc = Document::parse(&xml).unwrap();
5259
5260 let err = parse_key_info(doc.root_element()).unwrap_err();
5261 assert!(
5262 matches!(err, ParseError::InvalidStructure(message) if message.contains("lookup identifiers"))
5263 );
5264 }
5265
5266 #[test]
5267 fn parse_key_info_rejects_lookup_hints_for_different_certificates() {
5268 let first_cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-2048-cert.pem");
5269 let second_cert = fixture_cert_base64("../../tests/fixtures/keys/rsa/rsa-4096-cert.pem");
5270 let xml = format!(
5271 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5272 <X509Data>
5273 <X509SubjectName>CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509SubjectName>
5274 <X509SKI>60zMLKCfzQ3qnXAzABzRNpdgQ8Q=</X509SKI>
5275 <X509Certificate>{first_cert}</X509Certificate>
5276 <X509Certificate>{second_cert}</X509Certificate>
5277 </X509Data>
5278 </KeyInfo>"#
5279 );
5280 let doc = Document::parse(&xml).unwrap();
5281
5282 let err = parse_key_info(doc.root_element()).unwrap_err();
5283 assert!(
5284 matches!(err, ParseError::InvalidStructure(message) if message.contains("lookup identifiers match multiple certificates"))
5285 );
5286 }
5287
5288 #[test]
5289 fn configured_certificate_matching_requires_every_x509_selector_category() {
5290 let certificate = base64::engine::general_purpose::STANDARD
5293 .decode(fixture_rsa_cert_base64())
5294 .unwrap();
5295 let parsed = parse_x509_certificate(&certificate).unwrap();
5296 let digest = compute_digest_with_provider(
5297 crate::provider::default_provider(),
5298 DigestAlgorithm::Sha256,
5299 &certificate,
5300 )
5301 .unwrap();
5302 let matching = X509DataInfo {
5303 subject_names: vec![parsed.subject_dn.clone()],
5304 issuer_serials: vec![(
5305 parsed.issuer_dn.clone(),
5306 "680572598617295163017172295025714171905498632019".into(),
5307 )],
5308 skis: vec![parsed.subject_key_identifier.clone().unwrap()],
5309 digests: vec![(DigestAlgorithm::Sha256.uri().into(), digest)],
5310 ..X509DataInfo::default()
5311 };
5312
5313 assert!(
5314 x509_certificate_matches_selectors(
5315 &matching,
5316 &certificate,
5317 crate::provider::default_provider()
5318 )
5319 .unwrap()
5320 );
5321 for mismatching in [
5322 X509DataInfo {
5323 subject_names: vec!["CN=other".into()],
5324 ..matching.clone()
5325 },
5326 X509DataInfo {
5327 issuer_serials: vec![(parsed.issuer_dn.clone(), "1".into())],
5328 ..matching.clone()
5329 },
5330 X509DataInfo {
5331 skis: vec![vec![0]],
5332 ..matching.clone()
5333 },
5334 X509DataInfo {
5335 digests: vec![(DigestAlgorithm::Sha256.uri().into(), vec![0; 32])],
5336 ..matching.clone()
5337 },
5338 ] {
5339 assert!(
5340 !x509_certificate_matches_selectors(
5341 &mismatching,
5342 &certificate,
5343 crate::provider::default_provider()
5344 )
5345 .unwrap()
5346 );
5347 }
5348 }
5349
5350 #[test]
5351 fn build_x509_certificate_chain_rejects_chain_exceeding_max_depth() {
5352 let parameters = (0..=MAX_X509_CHAIN_DEPTH)
5355 .map(|index| {
5356 let mut params = rcgen::CertificateParams::new(Vec::new()).unwrap();
5357 params
5358 .distinguished_name
5359 .push(rcgen::DnType::CommonName, format!("cert-{index}"));
5360 params
5361 })
5362 .collect::<Vec<_>>();
5363 let key = rcgen::KeyPair::generate().unwrap();
5364 let certificates = parameters
5365 .iter()
5366 .enumerate()
5367 .map(|(index, params)| {
5368 let issuer = rcgen::Issuer::from_params(
5369 ¶meters[(index + 1).min(MAX_X509_CHAIN_DEPTH)],
5370 &key,
5371 );
5372 params
5373 .clone()
5374 .signed_by(&key, &issuer)
5375 .unwrap()
5376 .der()
5377 .to_vec()
5378 })
5379 .collect::<Vec<_>>();
5380 let parsed_certificates = certificates
5381 .iter()
5382 .map(|certificate| parse_x509_certificate(certificate).unwrap())
5383 .collect();
5384 let info = X509DataInfo {
5385 certificates,
5386 parsed_certificates,
5387 ..X509DataInfo::default()
5388 };
5389
5390 let err = build_x509_certificate_chain(
5391 &info,
5392 crate::provider::default_provider(),
5393 None,
5394 &super::super::pkix::CertificateSession::new(&crate::policy::ResourcePolicy::default()),
5395 )
5396 .unwrap_err();
5397 assert!(
5398 matches!(err, ParseError::InvalidStructure(message) if message.contains("maximum depth"))
5399 );
5400 }
5401
5402 #[test]
5403 fn x509_serial_hex_strips_der_sign_extension_zeroes() {
5404 assert_eq!(format_x509_serial_value_hex(&[0x00, 0xFF]), "FF");
5405 assert_eq!(format_x509_serial_value_hex(&[0x00, 0x7F]), "7F");
5406 assert_eq!(format_x509_serial_value_hex(&[0x00, 0x00]), "00");
5407 }
5408
5409 #[test]
5410 fn x509_serial_decimal_parser_enforces_rfc5280_positive_range() {
5411 let max_serial = "1461501637330902918203684832716283019655932542975";
5414 assert_eq!(
5415 x509_serial_decimal_to_hex(max_serial),
5416 Some("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".into())
5417 );
5418 assert_eq!(
5419 x509_serial_decimal_to_hex("730750818665451459101842416358141509827966271488"),
5420 Some("8000000000000000000000000000000000000000".into())
5421 );
5422 assert_eq!(
5423 x509_serial_decimal_to_hex("0000000000000000000000000000000000000000000000001"),
5424 Some("01".into())
5425 );
5426 assert_eq!(
5427 x509_serial_decimal_to_hex("00000000000000000000000000000000000000000000000001"),
5428 Some("01".into())
5429 );
5430 assert_eq!(x509_serial_decimal_to_hex("+1"), Some("01".into()));
5431
5432 for invalid in [
5433 "",
5434 "0",
5435 "000",
5436 "+0",
5437 "++1",
5438 "-1",
5439 "1a",
5440 "1461501637330902918203684832716283019655932542976",
5441 ] {
5442 assert_eq!(
5443 x509_serial_decimal_to_hex(invalid),
5444 None,
5445 "invalid serial {invalid:?} must be rejected"
5446 );
5447 }
5448 }
5449
5450 #[test]
5451 fn parse_x509_serial_normalizes_boundary_whitespace_and_rejects_overflow() {
5452 let max_serial = "1461501637330902918203684832716283019655932542975";
5455 let valid = format!(
5456 "<KeyInfo xmlns=\"{XMLDSIG_NS}\"><X509Data><X509IssuerSerial><X509IssuerName>CN=issuer</X509IssuerName><X509SerialNumber>\n {max_serial}\t</X509SerialNumber></X509IssuerSerial></X509Data></KeyInfo>"
5457 );
5458 let doc = Document::parse(&valid).unwrap();
5459 let parsed = parse_key_info(doc.root_element()).unwrap();
5460 let KeyInfoSource::X509Data(x509) = &parsed.sources[0] else {
5461 panic!("expected X509Data source");
5462 };
5463 assert_eq!(x509.issuer_serials[0].1, max_serial);
5464
5465 let explicit_positive = valid.replace(max_serial, "+42");
5466 let doc = Document::parse(&explicit_positive).unwrap();
5467 let parsed = parse_key_info(doc.root_element()).unwrap();
5468 let KeyInfoSource::X509Data(x509) = &parsed.sources[0] else {
5469 panic!("expected X509Data source");
5470 };
5471 assert_eq!(x509.issuer_serials[0].1, "42");
5472
5473 let overflow = valid.replace(
5474 max_serial,
5475 "1461501637330902918203684832716283019655932542976",
5476 );
5477 let doc = Document::parse(&overflow).unwrap();
5478 assert!(matches!(
5479 parse_key_info(doc.root_element()),
5480 Err(ParseError::InvalidStructure(message))
5481 if message.contains("invalid X509SerialNumber")
5482 ));
5483 }
5484
5485 #[test]
5486 fn issuer_selector_matches_a_sign_padded_twenty_octet_serial() {
5487 let serial_hex = "8000000000000000000000000000000000000000";
5490 let mut magnitude = [0; 20];
5491 magnitude[0] = 0x80;
5492 let mut parameters = rcgen::CertificateParams::new(Vec::new()).unwrap();
5493 parameters
5494 .distinguished_name
5495 .push(rcgen::DnType::CommonName, "issuer");
5496 parameters.serial_number = Some(rcgen::SerialNumber::from_slice(&magnitude));
5497 let key = rcgen::KeyPair::generate().unwrap();
5498 let certificate = parameters.self_signed(&key).unwrap();
5499 let metadata = parse_x509_certificate(certificate.der()).unwrap();
5500 assert_eq!(
5501 metadata.serial_number,
5502 [vec![0], magnitude.to_vec()].concat()
5503 );
5504 assert_eq!(metadata.serial_number_hex, serial_hex);
5505 let info = X509DataInfo {
5506 issuer_serials: vec![(
5507 "CN=issuer".into(),
5508 "730750818665451459101842416358141509827966271488".into(),
5509 )],
5510 certificates: vec![certificate.der().to_vec()],
5511 parsed_certificates: vec![metadata],
5512 ..X509DataInfo::default()
5513 };
5514
5515 assert!(
5516 x509_selector_categories_match_chain(
5517 &info,
5518 crate::provider::default_provider(),
5519 &super::super::pkix::CertificateSession::new(
5520 &crate::policy::ResourcePolicy::default()
5521 )
5522 )
5523 .unwrap()
5524 );
5525 }
5526
5527 #[test]
5528 fn distinguished_name_matching_preserves_rdn_order() {
5529 assert!(distinguished_names_equal(
5532 "CN=leaf, O=example",
5533 "CN=leaf,O=example"
5534 ));
5535 assert!(!distinguished_names_equal(
5536 "CN=leaf,O=example",
5537 "O=example,CN=leaf"
5538 ));
5539 }
5540
5541 #[test]
5542 fn distinguished_name_matching_applies_x520_string_preparation() {
5543 assert!(distinguished_names_equal(
5546 "CN= TEST key ,O=Example",
5547 "CN=test key,O=example"
5548 ));
5549 assert!(distinguished_names_equal(
5550 "CN=Straße,O=Example",
5551 "CN=STRASSE,O=EXAMPLE"
5552 ));
5553 assert!(distinguished_names_equal(
5554 "CN=test+OU=security,O=example",
5555 "OU=SECURITY+CN=TEST,O=EXAMPLE"
5556 ));
5557 assert!(!distinguished_names_equal(
5558 "1.2.3.4=#040141,O=example",
5559 "1.2.3.4=#040142,O=example"
5560 ));
5561 }
5562
5563 #[test]
5564 fn distinguished_name_matching_applies_ia5_matching_rules() {
5565 assert!(distinguished_names_equal(
5569 "EMAIL=ops@EXAMPLE.COM,DC=EXAMPLE,DC=COM",
5570 "EMAIL=ops@example.com,DC=example,DC=com"
5571 ));
5572 assert!(distinguished_names_equal(
5573 "EMAIL=OPS@example.com,DC=example,DC=com",
5574 "EMAIL=ops@example.com,DC=example,DC=com"
5575 ));
5576 }
5577
5578 #[test]
5579 fn distinguished_name_matching_handles_rfc4514_escaped_values() {
5580 let value = " leading,plus+equals=slash\\trailing ";
5583 let mut params = rcgen::CertificateParams::new(Vec::new()).unwrap();
5584 params
5585 .distinguished_name
5586 .push(rcgen::DnType::CommonName, value);
5587 let key = rcgen::KeyPair::generate().unwrap();
5588 let certificate = params.self_signed(&key).unwrap();
5589 let parsed = parse_x509_certificate(certificate.der()).unwrap();
5590
5591 assert_eq!(
5592 parsed.subject_dn,
5593 r"CN=\ leading\,plus\+equals\=slash\\trailing\ "
5594 );
5595 assert!(distinguished_names_equal(
5596 r"CN=\ leading\,plus\+equals=slash\\trailing\ ",
5597 &parsed.subject_dn
5598 ));
5599 assert!(distinguished_names_equal(
5600 "\n CN=\\ leading\\,plus\\+equals=slash\\\\trailing\\ \n",
5601 &parsed.subject_dn
5602 ));
5603 }
5604
5605 #[test]
5606 fn distinguished_name_trailing_escape_covers_all_xml_whitespace() {
5607 for whitespace in [' ', '\t', '\r', '\n'] {
5610 let session = super::super::pkix::CertificateSession::new(
5611 &crate::policy::ResourcePolicy::default(),
5612 );
5613 let escaped = format!("CN=value\\{whitespace}");
5617 if whitespace == ' ' {
5618 assert!(session.names_equal(&escaped, &escaped).unwrap());
5619 } else {
5620 assert!(
5621 matches!(session.selector(&escaped), Err(super::super::X509ChainError::Processing(error))
5622 if error.code() == structured_pkix::ErrorCode::MalformedValue)
5623 );
5624 }
5625 assert!(
5626 session
5627 .names_equal(&format!("CN=value{whitespace}"), "CN=value")
5628 .unwrap()
5629 );
5630 }
5631 }
5632
5633 #[test]
5634 fn parse_key_info_accepts_large_textual_x509_entries_within_entry_budget() {
5635 let issuer_name = "C".repeat(MAX_X509_ISSUER_NAME_TEXT_LEN);
5636 let serial_number = "0".repeat(MAX_X509_SERIAL_NUMBER_VALUE_DIGITS - 1) + "1";
5637 let issuer_serials = (0..52)
5638 .map(|_| {
5639 format!(
5640 "<X509IssuerSerial><X509IssuerName>{issuer_name}</X509IssuerName><X509SerialNumber>{serial_number}</X509SerialNumber></X509IssuerSerial>"
5641 )
5642 })
5643 .collect::<Vec<_>>()
5644 .join("");
5645 let xml = format!(
5646 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><X509Data>{issuer_serials}</X509Data></KeyInfo>"
5647 );
5648 let doc = Document::parse(&xml).unwrap();
5649
5650 let key_info = parse_key_info(doc.root_element()).unwrap();
5651 let parsed = match &key_info.sources[0] {
5652 KeyInfoSource::X509Data(x509) => x509,
5653 _ => panic!("expected X509Data source"),
5654 };
5655 assert_eq!(parsed.issuer_serials.len(), 52);
5656 }
5657
5658 #[test]
5659 fn parse_key_info_bounds_raw_x509_serial_text() {
5660 let serial = "0".repeat(MAX_X509_SERIAL_NUMBER_RAW_TEXT_LEN + 1);
5663 let xml = format!(
5664 "<KeyInfo xmlns=\"{XMLDSIG_NS}\"><X509Data><X509IssuerSerial><X509IssuerName>CN=issuer</X509IssuerName><X509SerialNumber>{serial}</X509SerialNumber></X509IssuerSerial></X509Data></KeyInfo>"
5665 );
5666 let doc = Document::parse(&xml).unwrap();
5667
5668 let error = parse_key_info(doc.root_element()).unwrap_err();
5669
5670 assert!(matches!(
5671 error,
5672 ParseError::InvalidStructure(reason)
5673 if reason == "X509SerialNumber exceeds maximum allowed text length"
5674 ));
5675 }
5676
5677 #[test]
5678 fn parse_key_info_accepts_x509data_with_only_foreign_namespace_children() {
5679 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
5680 xmlns:foo="urn:example:foo">
5681 <X509Data>
5682 <foo:Bar/>
5683 </X509Data>
5684 </KeyInfo>"#;
5685 let doc = Document::parse(xml).unwrap();
5686
5687 let key_info = parse_key_info(doc.root_element()).unwrap();
5688 assert_eq!(
5689 key_info.sources,
5690 vec![KeyInfoSource::X509Data(X509DataInfo::default())]
5691 );
5692 }
5693
5694 #[test]
5695 fn parse_key_info_der_encoded_key_value_rejects_invalid_base64() {
5696 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
5697 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
5698 <dsig11:DEREncodedKeyValue>%%%invalid%%%</dsig11:DEREncodedKeyValue>
5699 </KeyInfo>"#;
5700 let doc = Document::parse(xml).unwrap();
5701
5702 let err = parse_key_info(doc.root_element()).unwrap_err();
5703 assert!(matches!(err, ParseError::Base64(_)));
5704 }
5705
5706 #[test]
5707 fn parse_key_info_der_encoded_key_value_accepts_xml_whitespace() {
5708 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
5709 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
5710 <dsig11:DEREncodedKeyValue>
5711 AQID
5712 BA==
5713 </dsig11:DEREncodedKeyValue>
5714 </KeyInfo>"#;
5715 let doc = Document::parse(xml).unwrap();
5716
5717 let key_info = parse_key_info(doc.root_element()).unwrap();
5718 assert_eq!(
5719 key_info.sources,
5720 vec![KeyInfoSource::DerEncodedKeyValue(vec![1, 2, 3, 4])]
5721 );
5722 }
5723
5724 #[test]
5725 fn parse_key_info_dispatches_dsig11_ec_keyvalue() {
5726 let public_key = "BJ/yaXNlq4FRObyJCBhb5jAz8GVzinK3bBGLjSDfjbJwNfydtgjnlS4EsDmxSRhWyJWq6GIqy5wvnaiARK04uB4=";
5727 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
5728 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
5729 <KeyValue>
5730 <dsig11:ECKeyValue>
5731 <dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/>
5732 <dsig11:PublicKey>BJ/yaXNlq4FRObyJCBhb5jAz8GVzinK3bBGLjSDfjbJwNfydtgjnlS4EsDmxSRhWyJWq6GIqy5wvnaiARK04uB4=</dsig11:PublicKey>
5733 </dsig11:ECKeyValue>
5734 </KeyValue>
5735 </KeyInfo>"#;
5736 let doc = Document::parse(xml).unwrap();
5737 let expected_public_key = base64::engine::general_purpose::STANDARD
5738 .decode(public_key)
5739 .expect("fixture EC point must be valid base64");
5740
5741 let key_info = parse_key_info(doc.root_element()).unwrap();
5742 assert_eq!(
5743 key_info.sources,
5744 vec![KeyInfoSource::KeyValue(KeyValueInfo::Ec {
5745 curve_oid: "1.2.840.10045.3.1.7".into(),
5746 public_key: expected_public_key,
5747 })]
5748 );
5749 }
5750
5751 #[test]
5752 fn parse_ec_key_value_accepts_bare_curve_oid() {
5753 use base64::Engine;
5754
5755 let encoded_public_key = "BO/yd/OZzDfjX4qivDY/vsUIuh6KWAxoxW5P4ukvwd+T6pVljWsX2UBJNNy5MdhTwB8e2YwB8kUbJwdsAS/XGi/fz8unFrs+lVlAgIs6s/xBYFbfUoRiAacD2SpVDe6XBA==";
5756 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
5757 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
5758 <KeyValue>
5759 <dsig11:ECKeyValue>
5760 <dsig11:NamedCurve URI="1.3.132.0.34"/>
5761 <dsig11:PublicKey>BO/yd/OZzDfjX4qivDY/vsUIuh6KWAxoxW5P4ukvwd+T6pVljWsX2UBJNNy5MdhTwB8e2YwB8kUbJwdsAS/XGi/fz8unFrs+lVlAgIs6s/xBYFbfUoRiAacD2SpVDe6XBA==</dsig11:PublicKey>
5762 </dsig11:ECKeyValue>
5763 </KeyValue>
5764 </KeyInfo>"#;
5765 let doc = Document::parse(xml).unwrap();
5766 let expected_public_key = base64::engine::general_purpose::STANDARD
5767 .decode(encoded_public_key)
5768 .unwrap();
5769
5770 let sources = parse_key_info(doc.root_element()).unwrap().sources;
5771
5772 assert!(matches!(
5773 &sources[0],
5774 KeyInfoSource::KeyValue(KeyValueInfo::Ec { curve_oid, public_key })
5775 if curve_oid == EC_P384_OID && public_key == &expected_public_key
5776 ));
5777 }
5778
5779 #[test]
5780 fn parse_ec_key_value_marks_ec_parameters_as_unsupported() {
5781 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
5782 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
5783 <KeyValue>
5784 <dsig11:ECKeyValue>
5785 <dsig11:ECParameters/>
5786 <dsig11:PublicKey>BA==</dsig11:PublicKey>
5787 </dsig11:ECKeyValue>
5788 </KeyValue>
5789 </KeyInfo>"#;
5790 let doc = Document::parse(xml).unwrap();
5791
5792 let key_info = parse_key_info(doc.root_element()).unwrap();
5793 assert_eq!(
5794 key_info.sources,
5795 vec![KeyInfoSource::KeyValue(KeyValueInfo::Unsupported {
5796 namespace: Some(XMLDSIG11_NS.to_string()),
5797 local_name: "ECKeyValue".into(),
5798 })]
5799 );
5800 }
5801
5802 #[test]
5803 fn parse_ec_key_value_marks_unsupported_curve_as_unsupported() {
5804 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
5805 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
5806 <KeyValue>
5807 <dsig11:ECKeyValue>
5808 <dsig11:NamedCurve URI="urn:oid:1.3.132.0.36"/>
5809 <dsig11:PublicKey>BA==</dsig11:PublicKey>
5810 </dsig11:ECKeyValue>
5811 </KeyValue>
5812 </KeyInfo>"#;
5813 let doc = Document::parse(xml).unwrap();
5814
5815 let key_info = parse_key_info(doc.root_element()).unwrap();
5816 assert_eq!(
5817 key_info.sources,
5818 vec![KeyInfoSource::KeyValue(KeyValueInfo::Unsupported {
5819 namespace: Some(XMLDSIG11_NS.to_string()),
5820 local_name: "ECKeyValue".into(),
5821 })]
5822 );
5823 }
5824
5825 #[test]
5826 fn parse_ec_key_value_marks_missing_named_curve_uri_invalid() {
5827 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
5828 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
5829 <KeyValue>
5830 <dsig11:ECKeyValue>
5831 <dsig11:NamedCurve/>
5832 <dsig11:PublicKey>BA==</dsig11:PublicKey>
5833 </dsig11:ECKeyValue>
5834 </KeyValue>
5835 </KeyInfo>"#;
5836 let doc = Document::parse(xml).unwrap();
5837
5838 let key_info = parse_key_info(doc.root_element()).unwrap();
5839 assert_eq!(
5840 key_info.sources,
5841 vec![KeyInfoSource::KeyValue(KeyValueInfo::InvalidEcKeyValue)]
5842 );
5843 }
5844
5845 #[test]
5846 fn parse_ec_key_value_marks_reordered_children_invalid() {
5847 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
5848 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
5849 <KeyValue>
5850 <dsig11:ECKeyValue>
5851 <dsig11:PublicKey>BJ/yaXNlq4FRObyJCBhb5jAz8GVzinK3bBGLjSDfjbJwNfydtgjnlS4EsDmxSRhWyJWq6GIqy5wvnaiARK04uB4=</dsig11:PublicKey>
5852 <dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/>
5853 </dsig11:ECKeyValue>
5854 </KeyValue>
5855 </KeyInfo>"#;
5856 let doc = Document::parse(xml).unwrap();
5857
5858 let key_info = parse_key_info(doc.root_element()).unwrap();
5859 assert_eq!(
5860 key_info.sources,
5861 vec![KeyInfoSource::KeyValue(KeyValueInfo::InvalidEcKeyValue)]
5862 );
5863 }
5864
5865 #[test]
5866 fn parse_ec_key_value_marks_non_uncompressed_point_invalid() {
5867 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
5868 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
5869 <KeyValue>
5870 <dsig11:ECKeyValue>
5871 <dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/>
5872 <dsig11:PublicKey>Ap/yaXNlq4FRObyJCBhb5jAz8GVzinK3bBGLjSDfjbJwNfydtgjnlS4EsDmxSRhWyJWq6GIqy5wvnaiARK04uB4=</dsig11:PublicKey>
5873 </dsig11:ECKeyValue>
5874 </KeyValue>
5875 </KeyInfo>"#;
5876 let doc = Document::parse(xml).unwrap();
5877
5878 let key_info = parse_key_info(doc.root_element()).unwrap();
5879 assert_eq!(
5880 key_info.sources,
5881 vec![KeyInfoSource::KeyValue(KeyValueInfo::InvalidEcKeyValue)]
5882 );
5883 }
5884
5885 #[test]
5886 fn parse_key_info_marks_ds_namespace_ec_keyvalue_as_unsupported() {
5887 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5888 <KeyValue>
5889 <ECKeyValue/>
5890 </KeyValue>
5891 </KeyInfo>"#;
5892 let doc = Document::parse(xml).unwrap();
5893
5894 let key_info = parse_key_info(doc.root_element()).unwrap();
5895 assert_eq!(
5896 key_info.sources,
5897 vec![KeyInfoSource::KeyValue(KeyValueInfo::Unsupported {
5898 namespace: Some(XMLDSIG_NS.to_string()),
5899 local_name: "ECKeyValue".into(),
5900 })]
5901 );
5902 }
5903
5904 #[test]
5905 fn parse_key_info_keeps_unsupported_keyvalue_child_as_marker() {
5906 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5907 <KeyValue>
5908 <FutureKeyValue/>
5909 </KeyValue>
5910 </KeyInfo>"#;
5911 let doc = Document::parse(xml).unwrap();
5912
5913 let key_info = parse_key_info(doc.root_element()).unwrap();
5914 assert_eq!(
5915 key_info.sources,
5916 vec![KeyInfoSource::KeyValue(KeyValueInfo::Unsupported {
5917 namespace: Some(XMLDSIG_NS.to_string()),
5918 local_name: "FutureKeyValue".into(),
5919 })]
5920 );
5921 }
5922
5923 #[test]
5924 fn parse_key_info_accepts_supported_x509_retrieval_xpath() {
5925 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5927 <RetrievalMethod Type="http://www.w3.org/2000/09/xmldsig#X509Data" URI="#keys">
5928 <Transforms><Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">
5929 <XPath xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">ancestor-or-self::dsig:X509Data</XPath>
5930 </Transform></Transforms>
5931 </RetrievalMethod>
5932 </KeyInfo>"##;
5933 let doc = Document::parse(xml).unwrap();
5934
5935 let key_info = parse_key_info(doc.root_element()).unwrap();
5936 assert!(matches!(
5937 key_info.sources.as_slice(),
5938 [KeyInfoSource::RetrievalMethod {
5939 uri,
5940 resource_type: Some(resource_type),
5941 transforms: RetrievalMethodTransforms::X509DataNodeSetFilter { .. },
5942 }] if uri == "#keys"
5943 && resource_type == "http://www.w3.org/2000/09/xmldsig#X509Data"
5944 ));
5945 }
5946
5947 #[test]
5948 fn retrieval_xpath_namespace_binding_policy_precedes_materialization() {
5949 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5952 <RetrievalMethod Type="http://www.w3.org/2000/09/xmldsig#X509Data" URI="#keys">
5953 <Transforms><Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">
5954 <XPath xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">ancestor-or-self::dsig:X509Data</XPath>
5955 </Transform></Transforms>
5956 </RetrievalMethod>
5957 </KeyInfo>"##;
5958 let document = Document::parse(xml).expect("fixed KeyInfo fixture must parse");
5959 let resources = crate::policy::ResourcePolicy {
5960 max_xpath_namespace_bindings: 0,
5961 ..crate::policy::ResourcePolicy::default()
5962 };
5963
5964 let error = parse_key_info_with_policy_budgets(
5965 document.root_element(),
5966 crate::provider::default_provider(),
5967 &XmlBaseResolutionBudget::default(),
5968 &resources,
5969 None,
5970 )
5971 .expect_err("zero namespace bindings must reject RetrievalMethod XPath");
5972
5973 assert!(matches!(
5974 error,
5975 ParseError::Transform(TransformError::Policy(
5976 crate::policy::PolicyViolation::ResourceLimit {
5977 resource: crate::policy::resource_name::XPATH_NAMESPACE_BINDINGS,
5978 maximum: 0,
5979 actual: 1,
5980 }
5981 ))
5982 ));
5983 }
5984
5985 #[test]
5986 fn retrieval_xpath_namespace_byte_policy_precedes_materialization() {
5987 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
5990 <RetrievalMethod Type="http://www.w3.org/2000/09/xmldsig#X509Data" URI="#keys">
5991 <Transforms><Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">
5992 <XPath xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">ancestor-or-self::dsig:X509Data</XPath>
5993 </Transform></Transforms>
5994 </RetrievalMethod>
5995 </KeyInfo>"##;
5996 let document = Document::parse(xml).expect("fixed KeyInfo fixture must parse");
5997 let resources = crate::policy::ResourcePolicy {
5998 max_xpath_namespace_bytes: 0,
5999 ..crate::policy::ResourcePolicy::default()
6000 };
6001
6002 let error = parse_key_info_with_policy_budgets(
6003 document.root_element(),
6004 crate::provider::default_provider(),
6005 &XmlBaseResolutionBudget::default(),
6006 &resources,
6007 None,
6008 )
6009 .expect_err("zero namespace bytes must reject RetrievalMethod XPath");
6010
6011 assert!(matches!(
6012 error,
6013 ParseError::Transform(TransformError::Policy(
6014 crate::policy::PolicyViolation::ResourceLimit {
6015 resource: crate::policy::resource_name::XPATH_NAMESPACE_BYTES,
6016 maximum: 0,
6017 actual,
6018 }
6019 )) if actual == "dsig".len() + XMLDSIG_NS.len()
6020 ));
6021 }
6022
6023 #[test]
6024 fn parse_key_info_rejects_oversized_retrieval_method_type() {
6025 let oversized_type = "x".repeat(MAX_KEY_NAME_TEXT_LEN + 1);
6028 let xml = format!(
6029 r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"><RetrievalMethod URI="#key" Type="{oversized_type}"/></KeyInfo>"##
6030 );
6031 let document = Document::parse(&xml).unwrap();
6032
6033 assert!(matches!(
6034 parse_key_info(document.root_element()),
6035 Err(ParseError::InvalidStructure(reason))
6036 if reason == "RetrievalMethod Type exceeds maximum length"
6037 ));
6038 }
6039
6040 #[test]
6041 fn parse_key_info_bounds_retrieval_method_xml_base_chain() {
6042 let mut xml =
6045 format!(r#"<KeyInfo xmlns="{XMLDSIG_NS}"><RetrievalMethod URI="key.der"/></KeyInfo>"#);
6046 for _ in 0..65 {
6047 xml = format!(r#"<n xml:base="segment/">{xml}</n>"#);
6048 }
6049 let document = Document::parse(&xml).unwrap();
6050 let key_info = document
6051 .descendants()
6052 .find(|node| node.has_tag_name((XMLDSIG_NS, "KeyInfo")))
6053 .unwrap();
6054
6055 assert!(matches!(
6056 parse_key_info(key_info),
6057 Err(ParseError::InvalidStructure(reason))
6058 if reason.contains("XML Base resolution")
6059 ));
6060 }
6061
6062 #[test]
6063 fn parse_key_info_normalizes_external_retrieval_without_xml_base() {
6064 let xml = format!(
6067 r#"<KeyInfo xmlns="{XMLDSIG_NS}"><RetrievalMethod URI="https://example.test/a/../key.der"/></KeyInfo>"#
6068 );
6069 let document = Document::parse(&xml).unwrap();
6070 let key_info = parse_key_info(document.root_element()).unwrap();
6071
6072 assert!(matches!(
6073 key_info.sources.as_slice(),
6074 [KeyInfoSource::RetrievalMethod { uri, .. }]
6075 if uri == "https://example.test/key.der"
6076 ));
6077 }
6078
6079 #[test]
6080 fn parse_key_info_absolute_retrieval_bypasses_xml_base_chain() {
6081 let mut xml = format!(
6084 r#"<KeyInfo xmlns="{XMLDSIG_NS}"><RetrievalMethod URI="https://example.test/a/../key.der"/></KeyInfo>"#
6085 );
6086 for _ in 0..65 {
6087 xml = format!(r#"<n xml:base="segment/">{xml}</n>"#);
6088 }
6089 let document = Document::parse(&xml).unwrap();
6090 let key_info_node = document
6091 .descendants()
6092 .find(|node| node.has_tag_name((XMLDSIG_NS, "KeyInfo")))
6093 .unwrap();
6094 let key_info = parse_key_info(key_info_node)
6095 .expect("absolute RetrievalMethod must not consume ancestor-base budget");
6096
6097 assert!(matches!(
6098 key_info.sources.as_slice(),
6099 [KeyInfoSource::RetrievalMethod { uri, .. }]
6100 if uri == "https://example.test/key.der"
6101 ));
6102 }
6103
6104 #[test]
6105 fn parse_key_info_accepts_namespace_equivalent_retrieval_xpath_prefix() {
6106 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
6107 <RetrievalMethod Type="http://www.w3.org/2000/09/xmldsig#X509Data" URI="#keys">
6108 <Transforms><Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">
6109 <XPath xmlns:ds="http://www.w3.org/2000/09/xmldsig#">ancestor-or-self::ds:X509Data</XPath>
6110 </Transform></Transforms>
6111 </RetrievalMethod>
6112 </KeyInfo>"##;
6113 let doc = Document::parse(xml).unwrap();
6114
6115 assert!(matches!(
6116 parse_key_info(doc.root_element())
6117 .unwrap()
6118 .sources
6119 .as_slice(),
6120 [KeyInfoSource::RetrievalMethod {
6121 transforms: RetrievalMethodTransforms::X509DataNodeSetFilter { .. },
6122 ..
6123 }]
6124 ));
6125 }
6126
6127 #[test]
6128 fn parse_key_info_reads_complete_retrieval_xpath_text() {
6129 let valid = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
6132 <RetrievalMethod Type="http://www.w3.org/2000/09/xmldsig#X509Data" URI="#keys">
6133 <Transforms><Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">
6134 <XPath xmlns:ds="http://www.w3.org/2000/09/xmldsig#">ancestor-or-self::ds:X509<!-- split -->Data</XPath>
6135 </Transform></Transforms>
6136 </RetrievalMethod>
6137 </KeyInfo>"##;
6138 let document = Document::parse(valid).unwrap();
6139 assert!(parse_key_info(document.root_element()).is_ok());
6140
6141 let unsupported =
6142 valid.replace("X509<!-- split -->Data", "X509Data<!-- split -->[false()]");
6143 let document = Document::parse(&unsupported).unwrap();
6144 assert!(matches!(
6145 parse_key_info(document.root_element()),
6146 Err(ParseError::InvalidStructure(reason))
6147 if reason == "unsupported RetrievalMethod XPath selection"
6148 ));
6149 }
6150
6151 #[test]
6152 fn parse_dsa_key_value_accepts_schema_optional_parameters_and_rejects_half_pair() {
6153 let key_info = |parameters: &str| {
6154 format!(
6155 r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"><KeyValue><DSAKeyValue>
6156 {parameters}
6157 </DSAKeyValue></KeyValue></KeyInfo>"#
6158 )
6159 };
6160 for parameters in [
6161 "<Y>AQ==</Y>",
6162 "<G>AQ==</G><Y>AQ==</Y>",
6163 "<P>AQ==</P><Q>AQ==</Q><Y>AQ==</Y>",
6164 "<P>AQ==</P><Q>AQ==</Q><G>AQ==</G><Y>AQ==</Y><J>AQ==</J>",
6165 "<Y>AQ==</Y><Seed>AQ==</Seed><PgenCounter>AQ==</PgenCounter>",
6166 "<Y>AQ==</Y><J>AQ==</J><Seed>AQ==</Seed><PgenCounter>AQ==</PgenCounter>",
6167 ] {
6168 let xml = key_info(parameters);
6169 let doc = Document::parse(&xml).unwrap();
6170 assert!(matches!(
6171 parse_key_info(doc.root_element())
6172 .unwrap()
6173 .sources
6174 .as_slice(),
6175 [KeyInfoSource::KeyValue(KeyValueInfo::Dsa { .. })]
6176 ));
6177 }
6178
6179 for invalid_parameters in [
6180 "<P>AQ==</P><Y>AQ==</Y>",
6181 "<Q>AQ==</Q><Y>AQ==</Y>",
6182 "<Y>AQ==</Y><Seed>AQ==</Seed>",
6183 "<Y>AQ==</Y><PgenCounter>AQ==</PgenCounter>",
6184 ] {
6185 let xml = key_info(invalid_parameters);
6186 let doc = Document::parse(&xml).unwrap();
6187 assert!(matches!(
6188 parse_key_info(doc.root_element()),
6189 Err(ParseError::InvalidStructure(_))
6190 ));
6191 }
6192 }
6193
6194 #[test]
6195 fn parse_dsa_crypto_binary_ignores_comment_nodes() {
6196 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
6198 <KeyValue><DSAKeyValue><Y>AQ<!-- split -->ID</Y></DSAKeyValue></KeyValue>
6199 </KeyInfo>"#;
6200 let doc = Document::parse(xml).unwrap();
6201
6202 assert!(matches!(
6203 parse_key_info(doc.root_element())
6204 .unwrap()
6205 .sources
6206 .as_slice(),
6207 [KeyInfoSource::KeyValue(KeyValueInfo::Dsa { y, .. })] if y == &[1, 2, 3]
6208 ));
6209 }
6210
6211 #[test]
6212 fn parse_rsa_crypto_binary_ignores_comment_nodes() {
6213 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
6215 <KeyValue><RSAKeyValue>
6216 <Modulus>AQ<!-- split -->ID</Modulus><Exponent>Aw==</Exponent>
6217 </RSAKeyValue></KeyValue>
6218 </KeyInfo>"#;
6219 let doc = Document::parse(xml).unwrap();
6220
6221 assert!(matches!(
6222 parse_key_info(doc.root_element())
6223 .unwrap()
6224 .sources
6225 .as_slice(),
6226 [KeyInfoSource::KeyValue(KeyValueInfo::Rsa { modulus, exponent })]
6227 if modulus == &[1, 2, 3] && exponent == &[3]
6228 ));
6229 }
6230
6231 #[test]
6232 fn parse_key_info_preserves_advisory_unsupported_retrieval_transform() {
6233 let xml = r##"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
6236 <RetrievalMethod URI="#keys" Type="urn:vendor:key"><Transforms>
6237 <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#base64"/>
6238 </Transforms></RetrievalMethod>
6239 <KeyName>fallback</KeyName>
6240 </KeyInfo>"##;
6241 let doc = Document::parse(xml).unwrap();
6242
6243 let key_info = parse_key_info(doc.root_element())
6244 .expect("unsupported advisory retrieval must not reject all KeyInfo sources");
6245 assert!(matches!(
6246 key_info.sources.as_slice(),
6247 [
6248 KeyInfoSource::RetrievalMethod { resource_type: Some(resource_type), .. },
6249 KeyInfoSource::KeyName(name),
6250 ] if resource_type == "urn:vendor:key" && name == "fallback"
6251 ));
6252 }
6253
6254 #[test]
6255 fn parse_key_info_preserves_empty_same_document_reference_uri() {
6256 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
6259 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
6260 <dsig11:KeyInfoReference URI=""/>
6261 </KeyInfo>"#;
6262 let document = Document::parse(xml).unwrap();
6263
6264 assert!(matches!(
6265 parse_key_info(document.root_element())
6266 .unwrap()
6267 .sources
6268 .as_slice(),
6269 [KeyInfoSource::KeyInfoReference { uri }] if uri.is_empty()
6270 ));
6271 }
6272
6273 #[test]
6274 fn parse_key_info_rejects_excessive_child_sources() {
6275 let children = (0..=64)
6277 .map(|index| format!(r#"<extension xmlns="urn:test" index="{index}"/>"#))
6278 .collect::<String>();
6279 let xml =
6280 format!(r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">{children}</KeyInfo>"#);
6281 let document = Document::parse(&xml).unwrap();
6282
6283 assert!(matches!(
6284 parse_key_info(document.root_element()),
6285 Err(ParseError::InvalidStructure(reason))
6286 if reason == "KeyInfo contains too many child elements"
6287 ));
6288 }
6289
6290 #[test]
6291 fn parse_key_info_rejects_keyname_with_child_elements() {
6292 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
6293 <KeyName>ok<foo/></KeyName>
6294 </KeyInfo>"#;
6295 let doc = Document::parse(xml).unwrap();
6296
6297 let err = parse_key_info(doc.root_element()).unwrap_err();
6298 assert!(matches!(err, ParseError::InvalidStructure(_)));
6299 }
6300
6301 #[test]
6302 fn parse_key_info_preserves_keyname_text_without_trimming() {
6303 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
6304 <KeyName> signing key </KeyName>
6305 </KeyInfo>"#;
6306 let doc = Document::parse(xml).unwrap();
6307
6308 let key_info = parse_key_info(doc.root_element()).unwrap();
6309 assert_eq!(
6310 key_info.sources,
6311 vec![KeyInfoSource::KeyName(" signing key ".into())]
6312 );
6313 }
6314
6315 #[test]
6316 fn parse_key_info_rejects_oversized_keyname_text() {
6317 let oversized = "A".repeat(4097);
6318 let xml = format!(
6319 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><KeyName>{oversized}</KeyName></KeyInfo>"
6320 );
6321 let doc = Document::parse(&xml).unwrap();
6322
6323 let err = parse_key_info(doc.root_element()).unwrap_err();
6324 assert!(matches!(err, ParseError::InvalidStructure(_)));
6325 }
6326
6327 #[test]
6328 fn parse_key_info_rejects_non_whitespace_mixed_content() {
6329 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">oops<KeyName>k</KeyName></KeyInfo>"#;
6330 let doc = Document::parse(xml).unwrap();
6331
6332 let err = parse_key_info(doc.root_element()).unwrap_err();
6333 assert!(matches!(err, ParseError::InvalidStructure(_)));
6334 }
6335
6336 #[test]
6337 fn parse_key_info_rejects_nbsp_as_non_xml_whitespace_mixed_content() {
6338 let xml = "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\u{00A0}<KeyName>k</KeyName></KeyInfo>";
6339 let doc = Document::parse(xml).unwrap();
6340
6341 let err = parse_key_info(doc.root_element()).unwrap_err();
6342 assert!(matches!(err, ParseError::InvalidStructure(_)));
6343 }
6344
6345 #[test]
6346 fn parse_key_info_der_encoded_key_value_rejects_oversized_payload() {
6347 let oversized =
6348 base64::engine::general_purpose::STANDARD
6349 .encode(vec![0u8; MAX_DER_ENCODED_KEY_VALUE_LEN + 1]);
6350 let xml = format!(
6351 "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\" xmlns:dsig11=\"http://www.w3.org/2009/xmldsig11#\"><dsig11:DEREncodedKeyValue>{oversized}</dsig11:DEREncodedKeyValue></KeyInfo>"
6352 );
6353 let doc = Document::parse(&xml).unwrap();
6354
6355 let err = parse_key_info(doc.root_element()).unwrap_err();
6356 assert!(matches!(err, ParseError::InvalidStructure(_)));
6357 }
6358
6359 #[test]
6360 fn parse_key_info_der_encoded_key_value_rejects_empty_payload() {
6361 let xml = r#"<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
6362 xmlns:dsig11="http://www.w3.org/2009/xmldsig11#">
6363 <dsig11:DEREncodedKeyValue>
6364
6365 </dsig11:DEREncodedKeyValue>
6366 </KeyInfo>"#;
6367 let doc = Document::parse(xml).unwrap();
6368
6369 let err = parse_key_info(doc.root_element()).unwrap_err();
6370 assert!(matches!(err, ParseError::InvalidStructure(_)));
6371 }
6372
6373 #[test]
6374 fn parse_key_info_der_encoded_key_value_non_xml_ascii_whitespace_is_not_parseable_xml() {
6375 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>";
6376 assert!(Document::parse(xml).is_err());
6377 }
6378
6379 #[test]
6382 fn parse_hmac_output_length_reads_all_text_nodes() {
6383 let xml = r#"<SignatureMethod xmlns="http://www.w3.org/2000/09/xmldsig#">
6385 <HMACOutputLength>8<!-- split -->0</HMACOutputLength>
6386 </SignatureMethod>"#;
6387 let document = Document::parse(xml).unwrap();
6388
6389 assert_eq!(
6390 parse_hmac_output_length(document.root_element(), SignatureAlgorithm::HmacSha1)
6391 .unwrap(),
6392 Some(80)
6393 );
6394 }
6395
6396 #[test]
6397 fn hmac_sha256_accepts_byte_aligned_output_length() {
6398 let document = Document::parse(
6399 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>"#,
6400 )
6401 .unwrap();
6402 let algorithm =
6403 SignatureAlgorithm::from_uri("http://www.w3.org/2001/04/xmldsig-more#hmac-sha256")
6404 .expect("HMAC-SHA256 must be recognized");
6405
6406 assert_eq!(
6407 parse_hmac_output_length(document.root_element(), algorithm).unwrap(),
6408 Some(128)
6409 );
6410 }
6411
6412 #[test]
6413 fn parse_hmac_output_length_rejects_hidden_suffix_text() {
6414 let xml = r#"<SignatureMethod xmlns="http://www.w3.org/2000/09/xmldsig#">
6416 <HMACOutputLength>80<!-- split -->0</HMACOutputLength>
6417 </SignatureMethod>"#;
6418 let document = Document::parse(xml).unwrap();
6419
6420 assert!(matches!(
6421 parse_hmac_output_length(document.root_element(), SignatureAlgorithm::HmacSha1),
6422 Err(ParseError::InvalidStructure(reason))
6423 if reason == "HMACOutputLength must be a positive byte-aligned value no greater than 160"
6424 ));
6425 }
6426
6427 #[test]
6428 fn hmac_output_length_rejects_non_xml_whitespace() {
6429 for separator in ['\u{a0}', '\u{2003}', '\u{85}'] {
6432 let text = format!("{separator}128{separator}");
6433 assert!(ByteAlignedHmacOutputLength::parse(&text, 256).is_err());
6434 }
6435 assert_eq!(
6436 ByteAlignedHmacOutputLength::parse(" \t\r\n+00128 \t", 256)
6437 .unwrap()
6438 .bits(),
6439 128
6440 );
6441 }
6442
6443 #[test]
6444 fn xmldsig_1_1_rejects_non_octet_hmac_output_length() {
6445 let xml = r#"<SignatureMethod xmlns="http://www.w3.org/2000/09/xmldsig#">
6448 <HMACOutputLength>129</HMACOutputLength>
6449 </SignatureMethod>"#;
6450 let document = Document::parse(xml).unwrap();
6451
6452 assert!(matches!(
6453 parse_hmac_output_length(document.root_element(), SignatureAlgorithm::HmacSha256),
6454 Err(ParseError::InvalidStructure(reason))
6455 if reason == "HMACOutputLength must be a positive byte-aligned value no greater than 256"
6456 ));
6457 }
6458
6459 #[test]
6460 fn parse_signed_info_rsa_sha256_with_reference() {
6461 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
6462 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
6463 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
6464 <Reference URI="">
6465 <Transforms>
6466 <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
6467 <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
6468 </Transforms>
6469 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
6470 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
6471 </Reference>
6472 </SignedInfo>"#;
6473 let doc = Document::parse(xml).unwrap();
6474 let si = parse_signed_info(doc.root_element()).unwrap();
6475
6476 assert_eq!(si.signature_method, SignatureAlgorithm::RsaSha256);
6477 assert_eq!(si.references.len(), 1);
6478
6479 let r = &si.references[0];
6480 assert_eq!(r.uri.as_deref(), Some(""));
6481 assert_eq!(r.digest_method, DigestAlgorithm::Sha256);
6482 assert_eq!(r.digest_value, vec![0u8; 32]);
6483 assert_eq!(r.transforms.len(), 2);
6484 }
6485
6486 #[test]
6487 fn parse_signed_info_multiple_references() {
6488 let xml = r##"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
6489 <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
6490 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"/>
6491 <Reference URI="#a">
6492 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
6493 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
6494 </Reference>
6495 <Reference URI="#b">
6496 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
6497 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
6498 </Reference>
6499 </SignedInfo>"##;
6500 let doc = Document::parse(xml).unwrap();
6501 let si = parse_signed_info(doc.root_element()).unwrap();
6502
6503 assert_eq!(si.signature_method, SignatureAlgorithm::EcdsaSha256);
6504 assert_eq!(si.references.len(), 2);
6505 assert_eq!(si.references[0].uri.as_deref(), Some("#a"));
6506 assert_eq!(si.references[0].digest_method, DigestAlgorithm::Sha256);
6507 assert_eq!(si.references[1].uri.as_deref(), Some("#b"));
6508 assert_eq!(si.references[1].digest_method, DigestAlgorithm::Sha1);
6509 }
6510
6511 #[test]
6512 fn parse_signed_info_rejects_too_many_references() {
6513 let references = (0..=MAX_REFERENCES_PER_SIGNATURE)
6516 .map(|index| {
6517 format!(
6518 r##"<Reference URI="#item-{index}">
6519 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
6520 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
6521 </Reference>"##
6522 )
6523 })
6524 .collect::<String>();
6525 let xml = format!(
6526 r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
6527 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
6528 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
6529 {references}
6530 </SignedInfo>"#
6531 );
6532 let document = Document::parse(&xml).expect("fixed oversized fixture must parse");
6533
6534 let error = parse_signed_info(document.root_element())
6535 .expect_err("the parser must reject the 65th Reference");
6536
6537 assert!(matches!(
6538 error,
6539 ParseError::Policy(crate::policy::PolicyViolation::ResourceLimit {
6540 resource: crate::policy::resource_name::SIGNATURE_REFERENCES,
6541 maximum: MAX_REFERENCES_PER_SIGNATURE,
6542 actual: 65,
6543 })
6544 ));
6545 }
6546
6547 #[test]
6548 fn parse_signed_info_bounds_xpath_expressions_across_references() {
6549 let filters = r#"<XPath xmlns="http://www.w3.org/2002/06/xmldsig-filter2" Filter="intersect">true()</XPath>"#
6552 .repeat(64);
6553 let filter_transform = format!(
6554 r#"<Transform Algorithm="http://www.w3.org/2002/06/xmldsig-filter2">{filters}</Transform>"#
6555 );
6556 let reference = |index, transforms: &str| {
6557 format!(
6558 r##"<Reference URI="#item-{index}">
6559 <Transforms>{transforms}</Transforms>
6560 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
6561 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
6562 </Reference>"##
6563 )
6564 };
6565 let signed_info = |references: &str| {
6566 format!(
6567 r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
6568 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
6569 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
6570 {references}
6571 </SignedInfo>"#
6572 )
6573 };
6574
6575 let max_reference = reference(0, &filter_transform.repeat(64));
6576 let boundary_xml = signed_info(&max_reference);
6577 let boundary_document =
6578 Document::parse(&boundary_xml).expect("fixed boundary fixture must parse");
6579 parse_signed_info(boundary_document.root_element())
6580 .expect("one maximum-shaped Reference must remain accepted");
6581
6582 let extra_transform = r#"<Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116"><XPath>true()</XPath></Transform>"#;
6583 let xml = signed_info(&format!("{max_reference}{}", reference(1, extra_transform)));
6584 let document = Document::parse(&xml).expect("fixed aggregate fixture must parse");
6585
6586 let error = parse_signed_info(document.root_element())
6587 .expect_err("signature-wide XPath expression count must be bounded");
6588
6589 assert!(matches!(
6590 error,
6591 ParseError::Transform(TransformError::Policy(
6592 crate::policy::PolicyViolation::ResourceLimit {
6593 resource: "XPath expressions",
6594 ..
6595 }
6596 ))
6597 ));
6598 }
6599
6600 #[test]
6601 fn parse_reference_without_transforms() {
6602 let xml = r##"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
6604 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
6605 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
6606 <Reference URI="#obj">
6607 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
6608 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
6609 </Reference>
6610 </SignedInfo>"##;
6611 let doc = Document::parse(xml).unwrap();
6612 let si = parse_signed_info(doc.root_element()).unwrap();
6613
6614 assert!(si.references[0].transforms.is_empty());
6615 }
6616
6617 #[test]
6618 fn parse_reference_with_all_attributes() {
6619 let xml = r##"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
6620 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
6621 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
6622 <Reference URI="#data" Id="ref1" Type="http://www.w3.org/2000/09/xmldsig#Object">
6623 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
6624 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
6625 </Reference>
6626 </SignedInfo>"##;
6627 let doc = Document::parse(xml).unwrap();
6628 let si = parse_signed_info(doc.root_element()).unwrap();
6629 let r = &si.references[0];
6630
6631 assert_eq!(r.uri.as_deref(), Some("#data"));
6632 assert_eq!(r.id.as_deref(), Some("ref1"));
6633 assert_eq!(
6634 r.ref_type.as_deref(),
6635 Some("http://www.w3.org/2000/09/xmldsig#Object")
6636 );
6637 }
6638
6639 #[test]
6640 fn parse_reference_absent_uri() {
6641 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
6643 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
6644 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
6645 <Reference>
6646 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
6647 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
6648 </Reference>
6649 </SignedInfo>"#;
6650 let doc = Document::parse(xml).unwrap();
6651 let si = parse_signed_info(doc.root_element()).unwrap();
6652 assert!(si.references[0].uri.is_none());
6653 }
6654
6655 #[test]
6656 fn parse_signed_info_preserves_inclusive_prefixes() {
6657 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
6658 xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#">
6659 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
6660 <ec:InclusiveNamespaces PrefixList="ds saml #default"/>
6661 </CanonicalizationMethod>
6662 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
6663 <Reference URI="">
6664 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
6665 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
6666 </Reference>
6667 </SignedInfo>"#;
6668 let doc = Document::parse(xml).unwrap();
6669
6670 let si = parse_signed_info(doc.root_element()).unwrap();
6671 assert!(si.c14n_method.inclusive_prefixes().contains("ds"));
6672 assert!(si.c14n_method.inclusive_prefixes().contains("saml"));
6673 assert!(si.c14n_method.inclusive_prefixes().contains(""));
6674 }
6675
6676 #[test]
6679 fn missing_canonicalization_method() {
6680 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
6681 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
6682 <Reference URI="">
6683 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
6684 <DigestValue>dGVzdA==</DigestValue>
6685 </Reference>
6686 </SignedInfo>"#;
6687 let doc = Document::parse(xml).unwrap();
6688 let result = parse_signed_info(doc.root_element());
6689 assert!(result.is_err());
6690 assert!(matches!(
6692 result.unwrap_err(),
6693 ParseError::InvalidStructure(_)
6694 ));
6695 }
6696
6697 #[test]
6698 fn missing_signature_method() {
6699 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
6700 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
6701 <Reference URI="">
6702 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
6703 <DigestValue>dGVzdA==</DigestValue>
6704 </Reference>
6705 </SignedInfo>"#;
6706 let doc = Document::parse(xml).unwrap();
6707 let result = parse_signed_info(doc.root_element());
6708 assert!(result.is_err());
6709 assert!(matches!(
6711 result.unwrap_err(),
6712 ParseError::InvalidStructure(_)
6713 ));
6714 }
6715
6716 #[test]
6717 fn no_references() {
6718 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
6719 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
6720 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
6721 </SignedInfo>"#;
6722 let doc = Document::parse(xml).unwrap();
6723 let result = parse_signed_info(doc.root_element());
6724 assert!(matches!(
6725 result.unwrap_err(),
6726 ParseError::MissingElement {
6727 element: "Reference"
6728 }
6729 ));
6730 }
6731
6732 #[test]
6733 fn unsupported_c14n_algorithm() {
6734 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
6735 <CanonicalizationMethod Algorithm="http://example.com/bogus-c14n"/>
6736 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
6737 <Reference URI="">
6738 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
6739 <DigestValue>dGVzdA==</DigestValue>
6740 </Reference>
6741 </SignedInfo>"#;
6742 let doc = Document::parse(xml).unwrap();
6743 let result = parse_signed_info(doc.root_element());
6744 assert!(matches!(
6745 result.unwrap_err(),
6746 ParseError::UnsupportedAlgorithm { .. }
6747 ));
6748 }
6749
6750 #[test]
6751 fn unsupported_signature_algorithm() {
6752 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
6753 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
6754 <SignatureMethod Algorithm="http://example.com/bogus-sign"/>
6755 <Reference URI="">
6756 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
6757 <DigestValue>dGVzdA==</DigestValue>
6758 </Reference>
6759 </SignedInfo>"#;
6760 let doc = Document::parse(xml).unwrap();
6761 let result = parse_signed_info(doc.root_element());
6762 assert!(matches!(
6763 result.unwrap_err(),
6764 ParseError::UnsupportedAlgorithm { .. }
6765 ));
6766 }
6767
6768 #[test]
6769 fn unsupported_digest_algorithm() {
6770 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
6771 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
6772 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
6773 <Reference URI="">
6774 <DigestMethod Algorithm="http://example.com/bogus-digest"/>
6775 <DigestValue>dGVzdA==</DigestValue>
6776 </Reference>
6777 </SignedInfo>"#;
6778 let doc = Document::parse(xml).unwrap();
6779 let result = parse_signed_info(doc.root_element());
6780 assert!(matches!(
6781 result.unwrap_err(),
6782 ParseError::UnsupportedAlgorithm { .. }
6783 ));
6784 }
6785
6786 #[test]
6787 fn missing_digest_method() {
6788 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
6789 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
6790 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
6791 <Reference URI="">
6792 <DigestValue>dGVzdA==</DigestValue>
6793 </Reference>
6794 </SignedInfo>"#;
6795 let doc = Document::parse(xml).unwrap();
6796 let result = parse_signed_info(doc.root_element());
6797 assert!(result.is_err());
6799 }
6800
6801 #[test]
6802 fn missing_digest_value() {
6803 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
6804 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
6805 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
6806 <Reference URI="">
6807 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
6808 </Reference>
6809 </SignedInfo>"#;
6810 let doc = Document::parse(xml).unwrap();
6811 let result = parse_signed_info(doc.root_element());
6812 assert!(matches!(
6813 result.unwrap_err(),
6814 ParseError::MissingElement {
6815 element: "DigestValue"
6816 }
6817 ));
6818 }
6819
6820 #[test]
6821 fn invalid_base64_digest_value() {
6822 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
6823 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
6824 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
6825 <Reference URI="">
6826 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
6827 <DigestValue>!!!not-base64!!!</DigestValue>
6828 </Reference>
6829 </SignedInfo>"#;
6830 let doc = Document::parse(xml).unwrap();
6831 let result = parse_signed_info(doc.root_element());
6832 assert!(matches!(result.unwrap_err(), ParseError::Base64(_)));
6833 }
6834
6835 #[test]
6836 fn digest_value_length_must_match_digest_method() {
6837 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
6838 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
6839 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
6840 <Reference URI="">
6841 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
6842 <DigestValue>dGVzdA==</DigestValue>
6843 </Reference>
6844 </SignedInfo>"#;
6845 let doc = Document::parse(xml).unwrap();
6846
6847 let result = parse_signed_info(doc.root_element());
6848 assert!(matches!(
6849 result.unwrap_err(),
6850 ParseError::DigestLengthMismatch {
6851 algorithm: "http://www.w3.org/2001/04/xmlenc#sha256",
6852 expected: 32,
6853 actual: 4,
6854 }
6855 ));
6856 }
6857
6858 #[test]
6859 fn inclusive_prefixes_on_inclusive_c14n_is_rejected() {
6860 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#"
6861 xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#">
6862 <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315">
6863 <ec:InclusiveNamespaces PrefixList="ds"/>
6864 </CanonicalizationMethod>
6865 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
6866 <Reference URI="">
6867 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
6868 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
6869 </Reference>
6870 </SignedInfo>"#;
6871 let doc = Document::parse(xml).unwrap();
6872
6873 let result = parse_signed_info(doc.root_element());
6874 assert!(matches!(
6875 result.unwrap_err(),
6876 ParseError::UnsupportedAlgorithm { .. }
6877 ));
6878 }
6879
6880 #[test]
6881 fn extra_element_after_digest_value() {
6882 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
6883 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
6884 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
6885 <Reference URI="">
6886 <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
6887 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</DigestValue>
6888 <Unexpected/>
6889 </Reference>
6890 </SignedInfo>"#;
6891 let doc = Document::parse(xml).unwrap();
6892 let result = parse_signed_info(doc.root_element());
6893 assert!(matches!(
6894 result.unwrap_err(),
6895 ParseError::InvalidStructure(_)
6896 ));
6897 }
6898
6899 #[test]
6900 fn digest_value_with_element_child_is_rejected() {
6901 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
6902 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
6903 <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
6904 <Reference URI="">
6905 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
6906 <DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAA=<Junk/>AAAA</DigestValue>
6907 </Reference>
6908 </SignedInfo>"#;
6909 let doc = Document::parse(xml).unwrap();
6910
6911 let result = parse_signed_info(doc.root_element());
6912 assert!(matches!(
6913 result.unwrap_err(),
6914 ParseError::InvalidStructure(_)
6915 ));
6916 }
6917
6918 #[test]
6919 fn wrong_namespace_on_signed_info() {
6920 let xml = r#"<SignedInfo xmlns="http://example.com/fake">
6921 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
6922 </SignedInfo>"#;
6923 let doc = Document::parse(xml).unwrap();
6924 let result = parse_signed_info(doc.root_element());
6925 assert!(matches!(
6926 result.unwrap_err(),
6927 ParseError::InvalidStructure(_)
6928 ));
6929 }
6930
6931 #[test]
6934 fn base64_with_whitespace() {
6935 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
6936 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
6937 <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
6938 <Reference URI="">
6939 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
6940 <DigestValue>
6941 AAAAAAAA
6942 AAAAAAAAAAAAAAAAAAA=
6943 </DigestValue>
6944 </Reference>
6945 </SignedInfo>"#;
6946 let doc = Document::parse(xml).unwrap();
6947 let si = parse_signed_info(doc.root_element()).unwrap();
6948 assert_eq!(si.references[0].digest_value, vec![0u8; 20]);
6949 }
6950
6951 #[test]
6952 fn digest_value_ignores_processing_instruction_data() {
6953 let xml = r#"<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
6956 <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
6957 <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
6958 <Reference URI="">
6959 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
6960 <DigestValue>AAAAAAAA<?ignored not-base64?>AAAAAAAAAAAAAAAAAAA=</DigestValue>
6961 </Reference>
6962 </SignedInfo>"#;
6963 let document = Document::parse(xml).expect("fixture must parse");
6964 let signed_info =
6965 parse_signed_info(document.root_element()).expect("PI must not alter digest data");
6966
6967 assert_eq!(signed_info.references[0].digest_value, vec![0_u8; 20]);
6968 }
6969
6970 #[test]
6971 fn base64_decode_digest_accepts_xml_whitespace_chars() {
6972 let digest =
6973 base64_decode_digest("AAAA\tAAAA\rAAAA\nAAAA AAAAAAAAAAA=", DigestAlgorithm::Sha1)
6974 .expect("XML whitespace in DigestValue must be accepted");
6975 assert_eq!(digest, vec![0u8; 20]);
6976 }
6977
6978 #[test]
6979 fn base64_decode_digest_rejects_non_xml_ascii_whitespace() {
6980 let err = base64_decode_digest(
6981 "AAAA\u{000C}AAAAAAAAAAAAAAAAAAAAAAA=",
6982 DigestAlgorithm::Sha1,
6983 )
6984 .expect_err("form-feed/vertical-tab in DigestValue must be rejected");
6985 assert!(matches!(err, ParseError::Base64(_)));
6986 }
6987
6988 #[test]
6989 fn base64_decode_digest_rejects_oversized_base64_before_decode() {
6990 let err = base64_decode_digest("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", DigestAlgorithm::Sha1)
6991 .expect_err("oversized DigestValue base64 must fail before decode");
6992 match err {
6993 ParseError::Base64(message) => {
6994 assert!(
6995 message.contains("DigestValue exceeds maximum allowed base64 length"),
6996 "unexpected message: {message}"
6997 );
6998 }
6999 other => panic!("expected ParseError::Base64, got {other:?}"),
7000 }
7001 }
7002
7003 #[test]
7006 fn saml_response_signed_info() {
7007 let xml = r##"<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
7008 <ds:SignedInfo>
7009 <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
7010 <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
7011 <ds:Reference URI="#_resp1">
7012 <ds:Transforms>
7013 <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
7014 <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
7015 </ds:Transforms>
7016 <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
7017 <ds:DigestValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</ds:DigestValue>
7018 </ds:Reference>
7019 </ds:SignedInfo>
7020 <ds:SignatureValue>ZmFrZQ==</ds:SignatureValue>
7021 </ds:Signature>"##;
7022 let doc = Document::parse(xml).unwrap();
7023
7024 let sig_node = doc.root_element();
7026 let signed_info_node = sig_node
7027 .children()
7028 .find(|n| n.is_element() && n.tag_name().name() == "SignedInfo")
7029 .unwrap();
7030
7031 let si = parse_signed_info(signed_info_node).unwrap();
7032 assert_eq!(si.signature_method, SignatureAlgorithm::RsaSha256);
7033 assert_eq!(si.references.len(), 1);
7034 assert_eq!(si.references[0].uri.as_deref(), Some("#_resp1"));
7035 assert_eq!(si.references[0].transforms.len(), 2);
7036 assert_eq!(si.references[0].digest_value, vec![0u8; 32]);
7037 }
7038}