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