Skip to main content

xml_sec/xmldsig/
keys.rs

1//! Configuration and key material for XMLDSig key resolution.
2
3use std::{collections::HashMap, fmt, time::SystemTime};
4
5use crate::rsa_encoding::RsaPublicKeyEncoding as _;
6use crypto_bigint::BoxedUint;
7use dsa::pkcs8::EncodePublicKey as DsaEncodePublicKey;
8use hmac::{KeyInit, Mac};
9use x509_parser::{
10    prelude::{FromDer, X509Certificate},
11    public_key::PublicKey,
12    x509::SubjectPublicKeyInfo,
13};
14use zeroize::Zeroizing;
15
16use super::signature::{
17    decode_dsa_verifying_key, signature_value_matches_spki,
18    signature_value_matches_spki_with_encoding, validate_dsa_signature_spki_with_minimum,
19    validate_ec_public_key_encoding, validate_rsa_signature_spki_with_minimum,
20    verify_dsa_signature_spki_primitive, verify_dsa_signature_spki_with_minimum,
21    verify_rsa_signature_spki_primitive, verify_rsa_signature_spki_with_minimum,
22};
23use super::{
24    DsigError, KeyInfo, KeyInfoSource, KeyResolver, KeyValueInfo, SignatureAlgorithm, VerifyingKey,
25    X509ChainOptions, X509DataInfo,
26    parse::{
27        EC_P256_OID, EC_P384_OID, EC_P521_OID, ParseError, X509ChainBuildError,
28        build_x509_certificate_paths_to_selector_targets,
29        build_x509_certificate_paths_to_trusted_prefix, distinguished_names_equal,
30        parse_x509_certificate, x509_certificate_matches_any_selector,
31        x509_data_has_lookup_identifiers, x509_selector_categories_match_chain,
32    },
33    verify_ecdsa_signature_spki, verify_ecdsa_signature_spki_with_encoding,
34    x509::verify_x509_certificate_chain_with_provider_and_crls,
35};
36use super::{ResolvedVerificationKey, TrustedPublicKey, X509TrustEvidence};
37
38/// Caller-owned HMAC verification key.
39///
40/// Policy-free [`VerifyingKey`] calls enforce [`crate::policy::HmacPolicy::default`].
41/// [`super::VerifyContext`] supplies its immutable operation policy through the
42/// policy-aware hooks, so legacy truncation always requires an explicit opt-in.
43/// Owned secret bytes are zeroized when the key is dropped.
44#[derive(Clone)]
45pub struct HmacVerificationKey {
46    secret: Zeroizing<Vec<u8>>,
47}
48
49impl fmt::Debug for HmacVerificationKey {
50    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
51        formatter
52            .debug_struct("HmacVerificationKey")
53            .finish_non_exhaustive()
54    }
55}
56
57impl HmacVerificationKey {
58    /// Construct a key from non-empty secret bytes.
59    pub fn new(secret: impl Into<Vec<u8>>) -> Result<Self, KeyResolutionError> {
60        let secret = secret.into();
61        if secret.is_empty() {
62            return Err(KeyResolutionError::InvalidPublicKey);
63        }
64        Ok(Self {
65            secret: Zeroizing::new(secret),
66        })
67    }
68
69    fn validate_output(
70        &self,
71        policy: crate::policy::HmacPolicy,
72        algorithm: SignatureAlgorithm,
73        signature_value: &[u8],
74    ) -> Result<(), DsigError> {
75        if algorithm.hmac_output_bits().is_none() {
76            return Err(KeyResolutionError::AlgorithmMismatch.into());
77        }
78        policy.validate_key_bytes(self.secret.len())?;
79        policy.validate_output(algorithm, signature_value.len().saturating_mul(8))?;
80        Ok(())
81    }
82
83    fn verify_with_hmac_policy(
84        &self,
85        policy: crate::policy::HmacPolicy,
86        algorithm: SignatureAlgorithm,
87        signed_data: &[u8],
88        signature_value: &[u8],
89    ) -> Result<bool, DsigError> {
90        self.validate_output(policy, algorithm, signature_value)?;
91        macro_rules! verify_hmac {
92            ($digest:ty) => {{
93                let mut mac = hmac::Hmac::<$digest>::new_from_slice(&self.secret)
94                    .map_err(|_| KeyResolutionError::InvalidPublicKey)?;
95                mac.update(signed_data);
96                let expected = mac.finalize().into_bytes();
97                subtle::ConstantTimeEq::ct_eq(&expected[..signature_value.len()], signature_value)
98                    .into()
99            }};
100        }
101        Ok(match algorithm {
102            #[cfg(feature = "legacy-algorithms")]
103            SignatureAlgorithm::HmacMd5 => verify_hmac!(md5::Md5),
104            #[cfg(feature = "legacy-algorithms")]
105            SignatureAlgorithm::HmacRipemd160 => verify_hmac!(ripemd::Ripemd160),
106            SignatureAlgorithm::HmacSha1 => verify_hmac!(sha1::Sha1),
107            SignatureAlgorithm::HmacSha224 => verify_hmac!(sha2::Sha224),
108            SignatureAlgorithm::HmacSha256 => verify_hmac!(sha2::Sha256),
109            SignatureAlgorithm::HmacSha384 => verify_hmac!(sha2::Sha384),
110            SignatureAlgorithm::HmacSha512 => verify_hmac!(sha2::Sha512),
111            _ => return Err(KeyResolutionError::AlgorithmMismatch.into()),
112        })
113    }
114}
115
116impl VerifyingKey for HmacVerificationKey {
117    fn validate_policy(&self, policy: &crate::policy::VerificationPolicy) -> Result<(), DsigError> {
118        policy
119            .hmac
120            .validate_key_bytes(self.secret.len())
121            .map_err(Into::into)
122    }
123
124    fn validate_signature_value(
125        &self,
126        algorithm: SignatureAlgorithm,
127        signature_value: &[u8],
128    ) -> Result<bool, DsigError> {
129        self.validate_output(
130            crate::policy::HmacPolicy::default(),
131            algorithm,
132            signature_value,
133        )?;
134        Ok(true)
135    }
136
137    fn validate_signature_value_with_policy(
138        &self,
139        policy: &crate::policy::VerificationPolicy,
140        algorithm: SignatureAlgorithm,
141        signature_value: &[u8],
142    ) -> Result<bool, DsigError> {
143        self.validate_output(policy.hmac, algorithm, signature_value)?;
144        Ok(true)
145    }
146
147    fn verify(
148        &self,
149        algorithm: SignatureAlgorithm,
150        signed_data: &[u8],
151        signature_value: &[u8],
152    ) -> Result<bool, DsigError> {
153        self.verify_with_hmac_policy(
154            crate::policy::HmacPolicy::default(),
155            algorithm,
156            signed_data,
157            signature_value,
158        )
159    }
160
161    fn verify_with_policy(
162        &self,
163        policy: &crate::policy::VerificationPolicy,
164        algorithm: SignatureAlgorithm,
165        signed_data: &[u8],
166        signature_value: &[u8],
167    ) -> Result<bool, DsigError> {
168        self.verify_with_hmac_policy(policy.hmac, algorithm, signed_data, signature_value)
169    }
170}
171
172/// Compatibility name for the verification key originally limited to HMAC-SHA1.
173pub type HmacSha1VerificationKey = HmacVerificationKey;
174
175/// A public verification key available to key resolvers.
176#[derive(Debug, Clone, PartialEq, Eq)]
177pub struct VerificationKey {
178    /// Signature algorithm this key is configured to verify.
179    pub algorithm: SignatureAlgorithm,
180    /// DER-encoded SubjectPublicKeyInfo bytes.
181    pub public_key_bytes: Vec<u8>,
182    /// DER certificate from which the key was extracted, when applicable.
183    pub certificate_der: Option<Vec<u8>>,
184    /// Name used to register this key for `<KeyName>` resolution.
185    pub name: Option<String>,
186}
187
188impl VerifyingKey for VerificationKey {
189    fn verification_spki(&self, algorithm: SignatureAlgorithm) -> Result<Option<&[u8]>, DsigError> {
190        if algorithm != self.algorithm {
191            return Err(KeyResolutionError::AlgorithmMismatch.into());
192        }
193        Ok(Some(&self.public_key_bytes))
194    }
195    fn verify_with_context(
196        &self,
197        algorithm: SignatureAlgorithm,
198        context: &super::SignatureContext,
199        data: &[u8],
200        signature: &[u8],
201    ) -> Result<bool, DsigError> {
202        if algorithm != self.algorithm {
203            return Err(KeyResolutionError::AlgorithmMismatch.into());
204        }
205        if algorithm.eddsa_signature_len().is_some()
206            || matches!(algorithm, SignatureAlgorithm::PostQuantum(_))
207        {
208            return super::modern::verify_with_context(
209                algorithm,
210                &self.public_key_bytes,
211                context,
212                data,
213                signature,
214            )
215            .map_err(DsigError::Crypto);
216        }
217        if !context.as_bytes().is_empty() {
218            return Err(KeyResolutionError::AlgorithmMismatch.into());
219        }
220        self.verify(algorithm, data, signature)
221    }
222    fn validate_policy(&self, policy: &crate::policy::VerificationPolicy) -> Result<(), DsigError> {
223        let result = match self.algorithm {
224            #[cfg(feature = "legacy-algorithms")]
225            SignatureAlgorithm::RsaMd5 | SignatureAlgorithm::RsaRipemd160 => {
226                validate_rsa_signature_spki_with_minimum(
227                    self.algorithm,
228                    &self.public_key_bytes,
229                    policy.key_trust.rsa_keys.minimum_modulus_bits,
230                )
231            }
232            #[cfg(feature = "legacy-algorithms")]
233            SignatureAlgorithm::HmacMd5
234            | SignatureAlgorithm::HmacRipemd160
235            | SignatureAlgorithm::EcdsaRipemd160 => Ok(()),
236            SignatureAlgorithm::Ed25519
237            | SignatureAlgorithm::Ed25519Ctx
238            | SignatureAlgorithm::Ed25519Ph
239            | SignatureAlgorithm::Ed448
240            | SignatureAlgorithm::Ed448Ph
241            | SignatureAlgorithm::PostQuantum(_) => {
242                super::modern::validate_public_key(self.algorithm, &self.public_key_bytes)
243            }
244            SignatureAlgorithm::DsaSha1 | SignatureAlgorithm::DsaSha256 => {
245                validate_dsa_signature_spki_with_minimum(
246                    &self.public_key_bytes,
247                    policy.key_trust.dsa_keys.minimum_modulus_bits,
248                )
249            }
250            SignatureAlgorithm::RsaSha1
251            | SignatureAlgorithm::RsaSha224
252            | SignatureAlgorithm::RsaSha256
253            | SignatureAlgorithm::RsaSha384
254            | SignatureAlgorithm::RsaSha512
255            | SignatureAlgorithm::RsaPssSha1
256            | SignatureAlgorithm::RsaPssSha224
257            | SignatureAlgorithm::RsaPssSha256
258            | SignatureAlgorithm::RsaPssSha384
259            | SignatureAlgorithm::RsaPssSha512
260            | SignatureAlgorithm::RsaPssSha3_224
261            | SignatureAlgorithm::RsaPssSha3_256
262            | SignatureAlgorithm::RsaPssSha3_384
263            | SignatureAlgorithm::RsaPssSha3_512
264            | SignatureAlgorithm::RsaPss(_) => validate_rsa_signature_spki_with_minimum(
265                self.algorithm,
266                &self.public_key_bytes,
267                policy.key_trust.rsa_keys.minimum_modulus_bits,
268            ),
269            SignatureAlgorithm::HmacSha1
270            | SignatureAlgorithm::HmacSha224
271            | SignatureAlgorithm::HmacSha256
272            | SignatureAlgorithm::HmacSha384
273            | SignatureAlgorithm::HmacSha512
274            | SignatureAlgorithm::EcdsaSha1
275            | SignatureAlgorithm::EcdsaSha224
276            | SignatureAlgorithm::EcdsaSha256
277            | SignatureAlgorithm::EcdsaSha384
278            | SignatureAlgorithm::EcdsaSha512
279            | SignatureAlgorithm::EcdsaSha3_224
280            | SignatureAlgorithm::EcdsaSha3_256
281            | SignatureAlgorithm::EcdsaSha3_384
282            | SignatureAlgorithm::EcdsaSha3_512 => Ok(()),
283        };
284        result.map_err(DsigError::Crypto)
285    }
286
287    fn validate_signature_value(
288        &self,
289        algorithm: SignatureAlgorithm,
290        signature_value: &[u8],
291    ) -> Result<bool, DsigError> {
292        if algorithm != self.algorithm {
293            return Err(KeyResolutionError::AlgorithmMismatch.into());
294        }
295        signature_value_matches_spki(algorithm, &self.public_key_bytes, signature_value)
296            .map_err(DsigError::Crypto)
297    }
298
299    fn validate_signature_value_with_policy(
300        &self,
301        policy: &crate::policy::VerificationPolicy,
302        algorithm: SignatureAlgorithm,
303        signature_value: &[u8],
304    ) -> Result<bool, DsigError> {
305        if algorithm != self.algorithm {
306            return Err(KeyResolutionError::AlgorithmMismatch.into());
307        }
308        signature_value_matches_spki_with_encoding(
309            algorithm,
310            &self.public_key_bytes,
311            signature_value,
312            policy.ecdsa_signature_value_encoding,
313        )
314        .map_err(DsigError::Crypto)
315    }
316
317    fn verify(
318        &self,
319        algorithm: SignatureAlgorithm,
320        signed_data: &[u8],
321        signature_value: &[u8],
322    ) -> Result<bool, DsigError> {
323        if algorithm != self.algorithm {
324            return Err(KeyResolutionError::AlgorithmMismatch.into());
325        }
326        let result = match algorithm {
327            #[cfg(feature = "legacy-algorithms")]
328            SignatureAlgorithm::RsaMd5 | SignatureAlgorithm::RsaRipemd160 => {
329                verify_rsa_signature_spki_primitive(
330                    algorithm,
331                    &self.public_key_bytes,
332                    signed_data,
333                    signature_value,
334                )
335            }
336            #[cfg(feature = "legacy-algorithms")]
337            SignatureAlgorithm::EcdsaRipemd160 => verify_ecdsa_signature_spki(
338                algorithm,
339                &self.public_key_bytes,
340                signed_data,
341                signature_value,
342            ),
343            #[cfg(feature = "legacy-algorithms")]
344            SignatureAlgorithm::HmacMd5 | SignatureAlgorithm::HmacRipemd160 => {
345                return Err(KeyResolutionError::AlgorithmMismatch.into());
346            }
347            SignatureAlgorithm::Ed25519
348            | SignatureAlgorithm::Ed25519Ctx
349            | SignatureAlgorithm::Ed25519Ph
350            | SignatureAlgorithm::Ed448
351            | SignatureAlgorithm::Ed448Ph
352            | SignatureAlgorithm::PostQuantum(_) => super::modern::verify(
353                algorithm,
354                &self.public_key_bytes,
355                signed_data,
356                signature_value,
357            ),
358            SignatureAlgorithm::DsaSha1 | SignatureAlgorithm::DsaSha256 => {
359                verify_dsa_signature_spki_primitive(
360                    algorithm,
361                    &self.public_key_bytes,
362                    signed_data,
363                    signature_value,
364                )
365            }
366            SignatureAlgorithm::HmacSha1
367            | SignatureAlgorithm::HmacSha224
368            | SignatureAlgorithm::HmacSha256
369            | SignatureAlgorithm::HmacSha384
370            | SignatureAlgorithm::HmacSha512 => {
371                return Err(KeyResolutionError::AlgorithmMismatch.into());
372            }
373            SignatureAlgorithm::RsaSha1
374            | SignatureAlgorithm::RsaSha224
375            | SignatureAlgorithm::RsaSha256
376            | SignatureAlgorithm::RsaSha384
377            | SignatureAlgorithm::RsaSha512
378            | SignatureAlgorithm::RsaPssSha1
379            | SignatureAlgorithm::RsaPssSha224
380            | SignatureAlgorithm::RsaPssSha256
381            | SignatureAlgorithm::RsaPssSha384
382            | SignatureAlgorithm::RsaPssSha512
383            | SignatureAlgorithm::RsaPssSha3_224
384            | SignatureAlgorithm::RsaPssSha3_256
385            | SignatureAlgorithm::RsaPssSha3_384
386            | SignatureAlgorithm::RsaPssSha3_512
387            | SignatureAlgorithm::RsaPss(_) => verify_rsa_signature_spki_primitive(
388                algorithm,
389                &self.public_key_bytes,
390                signed_data,
391                signature_value,
392            ),
393            SignatureAlgorithm::EcdsaSha1
394            | SignatureAlgorithm::EcdsaSha224
395            | SignatureAlgorithm::EcdsaSha256
396            | SignatureAlgorithm::EcdsaSha384
397            | SignatureAlgorithm::EcdsaSha512
398            | SignatureAlgorithm::EcdsaSha3_224
399            | SignatureAlgorithm::EcdsaSha3_256
400            | SignatureAlgorithm::EcdsaSha3_384
401            | SignatureAlgorithm::EcdsaSha3_512 => verify_ecdsa_signature_spki(
402                algorithm,
403                &self.public_key_bytes,
404                signed_data,
405                signature_value,
406            ),
407        };
408        result.map_err(DsigError::Crypto)
409    }
410
411    fn verify_with_policy(
412        &self,
413        policy: &crate::policy::VerificationPolicy,
414        algorithm: SignatureAlgorithm,
415        signed_data: &[u8],
416        signature_value: &[u8],
417    ) -> Result<bool, DsigError> {
418        if algorithm != self.algorithm {
419            return Err(KeyResolutionError::AlgorithmMismatch.into());
420        }
421        if algorithm.ecdsa_digest().is_some() {
422            return verify_ecdsa_signature_spki_with_encoding(
423                algorithm,
424                &self.public_key_bytes,
425                signed_data,
426                signature_value,
427                policy.ecdsa_signature_value_encoding,
428            )
429            .map_err(DsigError::Crypto);
430        }
431        self.verify(algorithm, signed_data, signature_value)
432    }
433}
434
435struct PolicyBoundVerificationKey {
436    key: VerificationKey,
437    rsa_minimum_bits: usize,
438    dsa_minimum_bits: usize,
439}
440
441impl VerifyingKey for PolicyBoundVerificationKey {
442    fn verification_spki(&self, algorithm: SignatureAlgorithm) -> Result<Option<&[u8]>, DsigError> {
443        // Preserve strength checks carried by this resolver-produced handle.
444        if algorithm.is_rsa() {
445            validate_rsa_signature_spki_with_minimum(
446                algorithm,
447                &self.key.public_key_bytes,
448                self.rsa_minimum_bits,
449            )
450            .map_err(DsigError::Crypto)?;
451        }
452        self.key.verification_spki(algorithm)
453    }
454    fn verify_with_context(
455        &self,
456        algorithm: SignatureAlgorithm,
457        context: &super::SignatureContext,
458        data: &[u8],
459        signature: &[u8],
460    ) -> Result<bool, DsigError> {
461        if context.as_bytes().is_empty() {
462            return self.verify(algorithm, data, signature);
463        }
464        self.key
465            .verify_with_context(algorithm, context, data, signature)
466    }
467    fn validate_signature_value(
468        &self,
469        algorithm: SignatureAlgorithm,
470        signature_value: &[u8],
471    ) -> Result<bool, DsigError> {
472        self.key
473            .validate_signature_value(algorithm, signature_value)
474    }
475
476    fn validate_signature_value_with_policy(
477        &self,
478        policy: &crate::policy::VerificationPolicy,
479        algorithm: SignatureAlgorithm,
480        signature_value: &[u8],
481    ) -> Result<bool, DsigError> {
482        self.key
483            .validate_signature_value_with_policy(policy, algorithm, signature_value)
484    }
485
486    fn verify(
487        &self,
488        algorithm: SignatureAlgorithm,
489        signed_data: &[u8],
490        signature_value: &[u8],
491    ) -> Result<bool, DsigError> {
492        if algorithm != self.key.algorithm {
493            return Err(KeyResolutionError::AlgorithmMismatch.into());
494        }
495        let result = match algorithm {
496            SignatureAlgorithm::DsaSha1 | SignatureAlgorithm::DsaSha256 => {
497                verify_dsa_signature_spki_with_minimum(
498                    algorithm,
499                    &self.key.public_key_bytes,
500                    signed_data,
501                    signature_value,
502                    self.dsa_minimum_bits,
503                )
504            }
505            algorithm if algorithm.is_rsa() => verify_rsa_signature_spki_with_minimum(
506                algorithm,
507                &self.key.public_key_bytes,
508                signed_data,
509                signature_value,
510                self.rsa_minimum_bits,
511            ),
512            _ => return self.key.verify(algorithm, signed_data, signature_value),
513        };
514        result.map_err(DsigError::Crypto)
515    }
516
517    fn verify_with_policy(
518        &self,
519        policy: &crate::policy::VerificationPolicy,
520        algorithm: SignatureAlgorithm,
521        signed_data: &[u8],
522        signature_value: &[u8],
523    ) -> Result<bool, DsigError> {
524        if algorithm.ecdsa_digest().is_some() {
525            return self
526                .key
527                .verify_with_policy(policy, algorithm, signed_data, signature_value);
528        }
529        self.verify(algorithm, signed_data, signature_value)
530    }
531}
532
533/// Failures while applying [`KeyResolverConfig`] to parsed key material.
534#[derive(Debug, thiserror::Error)]
535#[non_exhaustive]
536pub enum KeyResolutionError {
537    /// A configured or embedded key does not match the signature method.
538    #[error("verification key does not match the signature algorithm")]
539    AlgorithmMismatch,
540    /// An embedded certificate could not be parsed completely.
541    #[error("invalid embedded certificate DER")]
542    InvalidCertificate,
543    /// Configured or embedded public key DER could not be parsed completely.
544    #[error("invalid public key DER")]
545    InvalidPublicKey,
546    /// More than one configured certificate satisfies all X.509 selectors.
547    #[error("X.509 lookup selectors match multiple configured certificates")]
548    AmbiguousCertificate,
549    /// An X.509 selector uses a digest algorithm unsupported by this crate.
550    #[error("unsupported X.509 digest algorithm: {0}")]
551    UnsupportedDigestAlgorithm(String),
552    /// Embedded certificate path validation failed.
553    #[error("certificate chain validation failed: {0}")]
554    Chain(#[from] super::X509ChainError),
555    /// System time was unavailable for certificate validation.
556    #[error("system time is unavailable")]
557    SystemTime,
558}
559
560/// Configuration for the default XMLDSig key resolver.
561///
562/// The configuration owns all key material and has no global registry. Chain
563/// verification is opt-in so callers that pin an embedded certificate can use
564/// the documented TOFU model without constructing a certificate path.
565#[derive(Debug, Clone, Default, PartialEq, Eq)]
566pub struct KeyResolverConfig {
567    /// DER-encoded certificates available to X.509 selectors and as untrusted
568    /// path intermediates. They establish trust only by chaining to an entry in
569    /// [`Self::trusted_certs`].
570    pub lookup_certs: Vec<Vec<u8>>,
571    /// DER-encoded certificates accepted as trust anchors.
572    pub trusted_certs: Vec<Vec<u8>>,
573    /// Caller-owned revocation evidence applied without copying it into each XML source.
574    pub crls: Vec<Vec<u8>>,
575    /// Verification keys addressable by `<KeyName>` content.
576    pub named_keys: HashMap<String, VerificationKey>,
577}
578
579/// Configuration-driven resolver for embedded certificates, DER keys, and key names.
580#[derive(Debug, Clone, Default)]
581pub struct DefaultKeyResolver {
582    config: KeyResolverConfig,
583}
584
585#[derive(Clone, Copy)]
586pub(crate) enum ResolutionScope {
587    Document,
588    Trusted,
589    DocumentPrefix(usize),
590    TrustedPrefix(usize),
591    DocumentSuffix(usize),
592    TrustedSuffix(usize),
593}
594
595/// A partial source scan separates a deferred mismatch from terminal errors.
596/// Continuations may retain the former, but cannot retry the latter.
597pub(crate) struct SourceResolution {
598    pub(crate) key: Option<ResolvedVerificationKey<'static>>,
599    pub(crate) deferred_error: Option<KeyResolutionError>,
600}
601
602impl SourceResolution {
603    pub(crate) fn finish(self) -> Result<Option<ResolvedVerificationKey<'static>>, DsigError> {
604        if let Some(error) = self.deferred_error {
605            return Err(error.into());
606        }
607        Ok(self.key)
608    }
609}
610
611/// Counts candidates actually inspected by one resolver invocation.
612///
613/// Parser cardinality preflights prevent expensive materialization, but do not
614/// replace this runtime accounting: embedded and indirect candidates both
615/// consume resolver work when inspected.
616/// Cumulative source-inspection work shared across one key resolution operation.
617/// Reuse this budget when resolving multiple caller-owned candidate records.
618#[derive(Debug)]
619pub struct InspectedKeyCandidateBudget {
620    maximum: usize,
621    attempted: usize,
622}
623
624impl InspectedKeyCandidateBudget {
625    /// Start shared accounting derived solely from the operation policy.
626    #[must_use]
627    pub fn new(policy: &crate::policy::VerificationPolicy) -> Self {
628        Self {
629            maximum: policy.resources.max_key_candidates,
630            attempted: 0,
631        }
632    }
633
634    /// Reserve one direct-key inspection before copying or decoding it.
635    pub fn charge(&mut self) -> Result<(), DsigError> {
636        self.charge_many(1)
637    }
638
639    pub(crate) fn charge_many(&mut self, count: usize) -> Result<(), DsigError> {
640        if self.attempted > self.maximum || count > self.maximum - self.attempted {
641            return Err(crate::policy::PolicyViolation::ResourceLimit {
642                resource: crate::policy::resource_name::KEY_CANDIDATES,
643                maximum: self.maximum,
644                actual: self.attempted.saturating_add(count),
645            }
646            .into());
647        }
648        debug_assert!(self.attempted <= self.maximum);
649        self.attempted += count;
650        Ok(())
651    }
652}
653
654pub(crate) fn validate_key_info_source_permissions(
655    key_info: &KeyInfo,
656    allowed: crate::policy::KeySourcePolicy,
657) -> Result<(), crate::policy::PolicyViolation> {
658    for source in &key_info.sources {
659        let disabled_reason = match source {
660            KeyInfoSource::X509Data(_) if !allowed.x509_data => {
661                Some("X509Data key sources are disabled")
662            }
663            KeyInfoSource::DerEncodedKeyValue(_) if !allowed.der_encoded_key_value => {
664                Some("DEREncodedKeyValue key sources are disabled")
665            }
666            KeyInfoSource::KeyName(_) if !allowed.key_name => {
667                Some("KeyName key sources are disabled")
668            }
669            KeyInfoSource::KeyValue(_) if !allowed.key_value => {
670                Some("KeyValue key sources are disabled")
671            }
672            KeyInfoSource::KeyInfoReference { .. } if !allowed.key_info_reference => {
673                Some("KeyInfoReference key sources are disabled")
674            }
675            KeyInfoSource::X509Data(_)
676            | KeyInfoSource::DerEncodedKeyValue(_)
677            | KeyInfoSource::KeyName(_)
678            | KeyInfoSource::KeyValue(_)
679            | KeyInfoSource::RetrievalMethod { .. }
680            | KeyInfoSource::KeyInfoReference { .. } => None,
681        };
682        if let Some(reason) = disabled_reason {
683            return Err(crate::policy::PolicyViolation::KeyTrust { reason });
684        }
685    }
686    Ok(())
687}
688
689impl DefaultKeyResolver {
690    /// Resolve another candidate without resetting aggregate inspection work.
691    /// The caller must keep the same budget throughout an operation, including
692    /// failed attempts; policy denials must not be treated as candidate misses.
693    pub fn resolve_with_candidate_budget(
694        &self,
695        key_info: Option<&KeyInfo>,
696        algorithm: SignatureAlgorithm,
697        policy: &crate::policy::VerificationPolicy,
698        provider: &dyn crate::provider::CryptoProvider,
699        candidate_budget: &mut InspectedKeyCandidateBudget,
700    ) -> Result<Option<ResolvedVerificationKey<'static>>, DsigError> {
701        candidate_budget.maximum = candidate_budget
702            .maximum
703            .min(policy.resources.max_key_candidates);
704        candidate_budget.charge_many(0)?;
705        self.resolve_with_trust(
706            key_info,
707            algorithm,
708            policy,
709            provider,
710            candidate_budget,
711            ResolutionScope::Document,
712        )
713    }
714
715    pub(crate) fn resolve_trusted_material_with_candidate_budget(
716        &self,
717        key_info: &KeyInfo,
718        algorithm: SignatureAlgorithm,
719        policy: &crate::policy::VerificationPolicy,
720        provider: &dyn crate::provider::CryptoProvider,
721        candidate_budget: &mut InspectedKeyCandidateBudget,
722    ) -> Result<Option<ResolvedVerificationKey<'static>>, DsigError> {
723        self.resolve_with_trust(
724            Some(key_info),
725            algorithm,
726            policy,
727            provider,
728            candidate_budget,
729            ResolutionScope::Trusted,
730        )
731    }
732
733    pub(crate) fn resolve_sources_with_candidate_budget(
734        &self,
735        key_info: &KeyInfo,
736        algorithm: SignatureAlgorithm,
737        policy: &crate::policy::VerificationPolicy,
738        provider: &dyn crate::provider::CryptoProvider,
739        candidate_budget: &mut InspectedKeyCandidateBudget,
740        scope: ResolutionScope,
741    ) -> Result<SourceResolution, DsigError> {
742        self.resolve_source_range(
743            Some(key_info),
744            algorithm,
745            policy,
746            provider,
747            candidate_budget,
748            scope,
749        )
750    }
751    /// Construct a resolver from explicit caller-owned key and certificate stores.
752    #[must_use]
753    pub fn new(config: KeyResolverConfig) -> Self {
754        Self { config }
755    }
756
757    /// Borrow the active resolver configuration.
758    #[must_use]
759    pub fn config(&self) -> &KeyResolverConfig {
760        &self.config
761    }
762
763    fn check_configured_x509_material(
764        &self,
765        info: &X509DataInfo,
766        resources: &crate::policy::ResourcePolicy,
767        trust: &crate::policy::KeyTrustPolicy,
768        budget: &mut InspectedKeyCandidateBudget,
769        charge_crls: bool,
770    ) -> Result<(), DsigError> {
771        if charge_crls && trust.check_crls && trust.verify_x509_chains {
772            budget.charge_many(self.config.crls.len())?;
773        }
774        let certificates = self
775            .config
776            .trusted_certs
777            .iter()
778            .chain(&self.config.lookup_certs);
779        let crls = self
780            .config
781            .crls
782            .iter()
783            .filter(|_| trust.check_crls && trust.verify_x509_chains);
784        let mut total = 0_usize;
785        // Embedded and configured bytes coexist during chain assembly; this
786        // combined preflight precedes certificate parsing and cloning.
787        for material in info
788            .certificates
789            .iter()
790            .chain(&info.crls)
791            .chain(certificates)
792            .chain(crls)
793        {
794            if material.len() > resources.max_external_resource_bytes {
795                return Err(crate::policy::PolicyViolation::ResourceLimit {
796                    resource: crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES,
797                    maximum: resources.max_external_resource_bytes,
798                    actual: material.len(),
799                }
800                .into());
801            }
802            debug_assert!(total <= resources.max_external_resource_total_bytes);
803            if material.len() > resources.max_external_resource_total_bytes - total {
804                return Err(crate::policy::PolicyViolation::ResourceLimit {
805                    resource: crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
806                    maximum: resources.max_external_resource_total_bytes,
807                    actual: total.saturating_add(material.len()),
808                }
809                .into());
810            }
811            total += material.len();
812        }
813        Ok(())
814    }
815
816    fn resolve_x509(
817        &self,
818        info: &X509DataInfo,
819        algorithm: SignatureAlgorithm,
820        trust: &crate::policy::KeyTrustPolicy,
821        provider: &dyn crate::provider::CryptoProvider,
822        budget: &mut InspectedKeyCandidateBudget,
823    ) -> Result<Option<(VerificationKey, Option<X509TrustEvidence>)>, DsigError> {
824        let mut evidence = None;
825        let effective_trust;
826        let trust = if trust.verify_x509_chains && trust.verification_time.is_none() {
827            effective_trust = {
828                let mut captured = trust.clone();
829                captured.verification_time = Some(SystemTime::now());
830                captured
831            };
832            &effective_trust
833        } else {
834            trust
835        };
836        let certificate_der = if let Some(&signing_index) = info.certificate_chain.first() {
837            if trust.verify_x509_chains {
838                let path =
839                    self.prepare_embedded_x509(info, signing_index, trust, provider, budget)?;
840                evidence = Some(Self::x509_trust_evidence(&path, trust)?);
841            } else {
842                budget.charge_many(info.certificates.len())?;
843            }
844            info.certificates
845                .get(signing_index)
846                .ok_or(KeyResolutionError::InvalidCertificate)?
847                .clone()
848        } else {
849            let Some(selected) = self.resolve_configured_x509(info, trust, provider, budget)?
850            else {
851                return Ok(None);
852            };
853            if trust.verify_x509_chains {
854                evidence = Some(Self::x509_trust_evidence(&selected, trust)?);
855            }
856            selected
857                .certificate_chain
858                .first()
859                .and_then(|index| selected.certificates.get(*index))
860                .ok_or(KeyResolutionError::InvalidCertificate)?
861                .clone()
862        };
863
864        let (rest, certificate) = X509Certificate::from_der(&certificate_der)
865            .map_err(|_| KeyResolutionError::InvalidCertificate)?;
866        if !rest.is_empty() {
867            return Err(KeyResolutionError::InvalidCertificate.into());
868        }
869        let public_key_bytes = certificate.public_key().raw.to_vec();
870        validate_spki_algorithm(&public_key_bytes, algorithm)?;
871        Ok(Some((
872            VerificationKey {
873                algorithm,
874                public_key_bytes,
875                certificate_der: Some(certificate_der),
876                name: None,
877            },
878            evidence,
879        )))
880    }
881
882    fn x509_trust_evidence(
883        info: &X509DataInfo,
884        trust: &crate::policy::KeyTrustPolicy,
885    ) -> Result<X509TrustEvidence, DsigError> {
886        let time = trust
887            .verification_time
888            .ok_or(KeyResolutionError::SystemTime)?;
889        X509TrustEvidence::new(
890            info.certificate_chain
891                .iter()
892                .map(|index| info.certificates[*index].as_slice()),
893            time,
894            trust.check_crls,
895        )
896    }
897
898    fn verify_x509_policy(
899        &self,
900        info: &X509DataInfo,
901        trust: &crate::policy::KeyTrustPolicy,
902        provider: &dyn crate::provider::CryptoProvider,
903    ) -> Result<(), KeyResolutionError> {
904        let options = X509ChainOptions {
905            trusted_certs: &self.config.trusted_certs,
906            verification_time: trust.verification_time.unwrap_or_else(SystemTime::now),
907            max_chain_depth: trust.max_x509_chain_depth,
908            check_crls: trust.check_crls,
909            allowed_extended_key_usages: Some(&trust.allowed_extended_key_usages),
910            rsa_keys: trust.rsa_keys,
911            dsa_keys: trust.dsa_keys,
912            certificate_signature_algorithms: trust.certificate_signature_algorithms.as_ref(),
913        };
914        verify_x509_certificate_chain_with_provider_and_crls(
915            info,
916            &options,
917            provider,
918            &self.config.crls,
919        )?;
920        Ok(())
921    }
922
923    fn prepare_embedded_x509(
924        &self,
925        info: &X509DataInfo,
926        signing_index: usize,
927        trust: &crate::policy::KeyTrustPolicy,
928        provider: &dyn crate::provider::CryptoProvider,
929        budget: &mut InspectedKeyCandidateBudget,
930    ) -> Result<X509DataInfo, DsigError> {
931        let signing_der = info
932            .certificates
933            .get(signing_index)
934            .ok_or(KeyResolutionError::InvalidCertificate)?;
935        let mut available = X509DataInfo {
936            crls: info.crls.clone(),
937            ..X509DataInfo::default()
938        };
939        let mut trusted_prefix_len = 0;
940        for certificate in &self.config.trusted_certs {
941            budget.charge()?;
942            if available
943                .certificates
944                .iter()
945                .any(|known| known == certificate)
946            {
947                continue;
948            }
949            available.parsed_certificates.push(
950                parse_x509_certificate(certificate)
951                    .map_err(|_| KeyResolutionError::InvalidCertificate)?,
952            );
953            available.certificates.push(certificate.clone());
954            trusted_prefix_len += 1;
955        }
956        for certificate in self.config.lookup_certs.iter().chain(&info.certificates) {
957            budget.charge()?;
958            if available
959                .certificates
960                .iter()
961                .any(|known| known == certificate)
962            {
963                continue;
964            }
965            available.parsed_certificates.push(
966                parse_x509_certificate(certificate)
967                    .map_err(|_| KeyResolutionError::InvalidCertificate)?,
968            );
969            available.certificates.push(certificate.clone());
970        }
971        let signing_index = available
972            .certificates
973            .iter()
974            .position(|certificate| certificate == signing_der)
975            .ok_or(KeyResolutionError::InvalidCertificate)?;
976        self.select_valid_x509_path(
977            &mut available,
978            signing_index,
979            trusted_prefix_len,
980            trust,
981            provider,
982            None,
983        )?;
984        Ok(available)
985    }
986
987    fn select_valid_x509_path(
988        &self,
989        available: &mut X509DataInfo,
990        signing_index: usize,
991        trusted_prefix_len: usize,
992        trust: &crate::policy::KeyTrustPolicy,
993        provider: &dyn crate::provider::CryptoProvider,
994        selectors: Option<&X509DataInfo>,
995    ) -> Result<bool, KeyResolutionError> {
996        let candidates = build_x509_certificate_paths_to_trusted_prefix(
997            available,
998            signing_index,
999            trusted_prefix_len,
1000            trust.max_x509_chain_depth,
1001            trust.max_x509_candidate_paths,
1002            provider,
1003            trust.certificate_signature_algorithms.as_ref(),
1004        )
1005        .map_err(|error| match error {
1006            X509ChainBuildError::AmbiguousIssuer => KeyResolutionError::AmbiguousCertificate,
1007            X509ChainBuildError::Policy(error) => {
1008                KeyResolutionError::Chain(super::X509ChainError::Policy(error))
1009            }
1010            X509ChainBuildError::Provider(error) => {
1011                KeyResolutionError::Chain(super::X509ChainError::Provider(error))
1012            }
1013            X509ChainBuildError::UnsupportedSignatureAlgorithm { oid } => {
1014                KeyResolutionError::Chain(super::X509ChainError::UnsupportedSignatureAlgorithm {
1015                    oid,
1016                })
1017            }
1018            _ => KeyResolutionError::InvalidCertificate,
1019        })?;
1020        let mut first_error = None;
1021        let mut valid_path_without_selector_match = false;
1022        for candidate in candidates {
1023            available.certificate_chain = candidate;
1024            match self.verify_x509_policy(available, trust, provider) {
1025                Ok(()) => {
1026                    if match selectors {
1027                        Some(selectors) => {
1028                            selected_x509_path_matches_selectors(available, selectors, provider)?
1029                        }
1030                        None => true,
1031                    } {
1032                        return Ok(true);
1033                    }
1034                    valid_path_without_selector_match = true;
1035                }
1036                Err(error) => {
1037                    first_error.get_or_insert(error);
1038                }
1039            }
1040        }
1041        if valid_path_without_selector_match {
1042            return Ok(false);
1043        }
1044        Err(first_error.unwrap_or(KeyResolutionError::Chain(
1045            super::X509ChainError::UntrustedRoot,
1046        )))
1047    }
1048
1049    fn select_x509_selector_path(
1050        &self,
1051        available: &mut X509DataInfo,
1052        signing_index: usize,
1053        matching_indices: &[usize],
1054        trust: &crate::policy::KeyTrustPolicy,
1055        provider: &dyn crate::provider::CryptoProvider,
1056        selectors: &X509DataInfo,
1057    ) -> Result<bool, KeyResolutionError> {
1058        let targets = matching_indices
1059            .iter()
1060            .copied()
1061            .filter(|index| *index != signing_index)
1062            .collect::<Vec<_>>();
1063        if targets.is_empty() {
1064            return Ok(false);
1065        }
1066        let candidates = build_x509_certificate_paths_to_selector_targets(
1067            available,
1068            signing_index,
1069            &targets,
1070            trust.max_x509_chain_depth,
1071            trust.max_x509_candidate_paths,
1072            provider,
1073            trust.certificate_signature_algorithms.as_ref(),
1074        )
1075        .map_err(|error| match error {
1076            X509ChainBuildError::AmbiguousIssuer => KeyResolutionError::AmbiguousCertificate,
1077            X509ChainBuildError::Policy(error) => {
1078                KeyResolutionError::Chain(super::X509ChainError::Policy(error))
1079            }
1080            X509ChainBuildError::Provider(error) => {
1081                KeyResolutionError::Chain(super::X509ChainError::Provider(error))
1082            }
1083            X509ChainBuildError::UnsupportedSignatureAlgorithm { oid } => {
1084                KeyResolutionError::Chain(super::X509ChainError::UnsupportedSignatureAlgorithm {
1085                    oid,
1086                })
1087            }
1088            _ => KeyResolutionError::InvalidCertificate,
1089        })?;
1090        for candidate in candidates {
1091            available.certificate_chain = candidate;
1092            if selected_x509_path_matches_selectors(available, selectors, provider)? {
1093                return Ok(true);
1094            }
1095        }
1096        Ok(false)
1097    }
1098
1099    fn resolve_configured_x509(
1100        &self,
1101        info: &X509DataInfo,
1102        trust: &crate::policy::KeyTrustPolicy,
1103        provider: &dyn crate::provider::CryptoProvider,
1104        budget: &mut InspectedKeyCandidateBudget,
1105    ) -> Result<Option<X509DataInfo>, DsigError> {
1106        if !x509_data_has_lookup_identifiers(info) {
1107            return Ok(None);
1108        }
1109
1110        let mut available = X509DataInfo {
1111            subject_names: info.subject_names.clone(),
1112            issuer_serials: info.issuer_serials.clone(),
1113            skis: info.skis.clone(),
1114            crls: info.crls.clone(),
1115            digests: info.digests.clone(),
1116            ..X509DataInfo::default()
1117        };
1118        let mut matches = Vec::new();
1119        let mut trusted_prefix_len = 0usize;
1120        for (trusted, certificate_der) in self
1121            .config
1122            .trusted_certs
1123            .iter()
1124            .map(|certificate| (true, certificate))
1125            .chain(
1126                self.config
1127                    .lookup_certs
1128                    .iter()
1129                    .map(|certificate| (false, certificate)),
1130            )
1131        {
1132            budget.charge()?;
1133            if available
1134                .certificates
1135                .iter()
1136                .any(|available_der| available_der == certificate_der)
1137            {
1138                continue;
1139            }
1140            let parsed = parse_x509_certificate(certificate_der)
1141                .map_err(|_| KeyResolutionError::InvalidCertificate)?;
1142            let is_match =
1143                x509_certificate_matches_any_selector(info, &parsed, certificate_der, provider)
1144                    .map_err(map_x509_selector_error)?;
1145            if is_match {
1146                matches.push((available.certificates.len(), parsed.clone()));
1147            }
1148            available.certificates.push(certificate_der.clone());
1149            available.parsed_certificates.push(parsed);
1150            if trusted {
1151                trusted_prefix_len += 1;
1152            }
1153        }
1154
1155        let matched_chain = X509DataInfo {
1156            certificates: matches
1157                .iter()
1158                .map(|(index, _)| available.certificates[*index].clone())
1159                .collect(),
1160            parsed_certificates: matches.iter().map(|(_, parsed)| parsed.clone()).collect(),
1161            ..X509DataInfo::default()
1162        };
1163        if !x509_selector_categories_match_chain(
1164            &X509DataInfo {
1165                subject_names: info.subject_names.clone(),
1166                issuer_serials: info.issuer_serials.clone(),
1167                skis: info.skis.clone(),
1168                digests: info.digests.clone(),
1169                ..matched_chain
1170            },
1171            provider,
1172        )
1173        .map_err(map_x509_selector_error)?
1174        {
1175            return Ok(None);
1176        }
1177
1178        let signing_index = match matches.as_slice() {
1179            [] => return Ok(None),
1180            [(index, _)] => *index,
1181            _ => {
1182                let leaves = matches
1183                    .iter()
1184                    .filter(|(_, candidate)| {
1185                        !distinguished_names_equal(&candidate.subject_dn, &candidate.issuer_dn)
1186                            && !matches.iter().any(|(_, other)| {
1187                                distinguished_names_equal(&other.issuer_dn, &candidate.subject_dn)
1188                            })
1189                    })
1190                    .collect::<Vec<_>>();
1191                match leaves.as_slice() {
1192                    [(index, _)] => *index,
1193                    _ => return Err(KeyResolutionError::AmbiguousCertificate.into()),
1194                }
1195            }
1196        };
1197        let matching_indices = matches.iter().map(|(index, _)| *index).collect::<Vec<_>>();
1198        // `available` preserves trusted certificates as a prefix. Selecting
1199        // one of those exact certificates is already a terminal trust
1200        // decision, even when the certificate is not self-signed.
1201        available.certificate_chain =
1202            if signing_index < trusted_prefix_len || !trust.verify_x509_chains {
1203                vec![signing_index]
1204            } else {
1205                if !self.select_valid_x509_path(
1206                    &mut available,
1207                    signing_index,
1208                    trusted_prefix_len,
1209                    trust,
1210                    provider,
1211                    Some(info),
1212                )? {
1213                    return Ok(None);
1214                }
1215                available.certificate_chain.clone()
1216            };
1217        if trust.verify_x509_chains && signing_index < trusted_prefix_len {
1218            self.verify_x509_policy(&available, trust, provider)?;
1219        }
1220        if !trust.verify_x509_chains || signing_index < trusted_prefix_len {
1221            let direct_match = selected_x509_path_matches_selectors(&available, info, provider)?;
1222            if !direct_match
1223                && (signing_index < trusted_prefix_len
1224                    || !self.select_x509_selector_path(
1225                        &mut available,
1226                        signing_index,
1227                        &matching_indices,
1228                        trust,
1229                        provider,
1230                        info,
1231                    )?)
1232            {
1233                return Ok(None);
1234            }
1235        }
1236        Ok(Some(available))
1237    }
1238
1239    fn resolve_key_value(
1240        key_value: &KeyValueInfo,
1241        algorithm: SignatureAlgorithm,
1242    ) -> Result<Option<VerificationKey>, KeyResolutionError> {
1243        let public_key_bytes = match key_value {
1244            KeyValueInfo::Dsa { p, q, g, y } => {
1245                if !matches!(
1246                    algorithm,
1247                    SignatureAlgorithm::DsaSha1 | SignatureAlgorithm::DsaSha256
1248                ) {
1249                    return Err(KeyResolutionError::AlgorithmMismatch);
1250                }
1251                let (Some(p), Some(q), Some(g)) = (p.as_deref(), q.as_deref(), g.as_deref()) else {
1252                    return Err(KeyResolutionError::InvalidPublicKey);
1253                };
1254                dsa_key_value_to_spki_der(p, q, g, y)?
1255            }
1256            KeyValueInfo::Rsa { modulus, exponent } => {
1257                if !algorithm.is_rsa() {
1258                    return Err(KeyResolutionError::AlgorithmMismatch);
1259                }
1260                rsa_key_value_to_spki_der(modulus, exponent)?
1261            }
1262            KeyValueInfo::Ec {
1263                curve_oid,
1264                public_key,
1265            } => {
1266                if algorithm.ecdsa_digest().is_none() {
1267                    return Ok(None);
1268                }
1269                ec_key_value_to_spki_der(curve_oid, public_key)?
1270            }
1271            KeyValueInfo::InvalidEcKeyValue => return Err(KeyResolutionError::InvalidPublicKey),
1272            // DH key agreement keys cannot verify an XML signature.
1273            KeyValueInfo::Dh { .. } | KeyValueInfo::Unsupported { .. } => return Ok(None),
1274        };
1275        validate_spki_algorithm(&public_key_bytes, algorithm)?;
1276
1277        Ok(Some(VerificationKey {
1278            algorithm,
1279            public_key_bytes,
1280            certificate_der: None,
1281            name: None,
1282        }))
1283    }
1284
1285    fn resolve_with_trust(
1286        &self,
1287        key_info: Option<&KeyInfo>,
1288        algorithm: SignatureAlgorithm,
1289        policy: &crate::policy::VerificationPolicy,
1290        provider: &dyn crate::provider::CryptoProvider,
1291        candidate_budget: &mut InspectedKeyCandidateBudget,
1292        scope: ResolutionScope,
1293    ) -> Result<Option<ResolvedVerificationKey<'static>>, DsigError> {
1294        self.resolve_source_range(
1295            key_info,
1296            algorithm,
1297            policy,
1298            provider,
1299            candidate_budget,
1300            scope,
1301        )?
1302        .finish()
1303    }
1304
1305    fn resolve_source_range(
1306        &self,
1307        key_info: Option<&KeyInfo>,
1308        algorithm: SignatureAlgorithm,
1309        policy: &crate::policy::VerificationPolicy,
1310        provider: &dyn crate::provider::CryptoProvider,
1311        candidate_budget: &mut InspectedKeyCandidateBudget,
1312        scope: ResolutionScope,
1313    ) -> Result<SourceResolution, DsigError> {
1314        let trust = &policy.key_trust;
1315        let resources = &policy.resources;
1316        trust.validate()?;
1317        resources.validate()?;
1318        let Some(key_info) = key_info else {
1319            return Ok(SourceResolution {
1320                key: None,
1321                deferred_error: None,
1322            });
1323        };
1324        let document_sources = matches!(
1325            scope,
1326            ResolutionScope::Document
1327                | ResolutionScope::DocumentPrefix(_)
1328                | ResolutionScope::DocumentSuffix(_)
1329        );
1330        if document_sources {
1331            validate_key_info_source_permissions(key_info, policy.key_sources)?;
1332        }
1333        let source_end = match scope {
1334            ResolutionScope::DocumentPrefix(end) | ResolutionScope::TrustedPrefix(end) => end,
1335            _ => key_info.sources.len(),
1336        };
1337        let source_start = match scope {
1338            ResolutionScope::DocumentSuffix(start) | ResolutionScope::TrustedSuffix(start) => start,
1339            _ => 0,
1340        };
1341        let mut deferred_key_value_error = None;
1342        let mut configured_material_checked = false;
1343        for source in &key_info.sources[source_start..source_end] {
1344            if !document_sources && matches!(source, KeyInfoSource::KeyName(_)) {
1345                continue;
1346            }
1347            let mut path_evidence = None;
1348            let mut caller_trusted =
1349                !document_sources || matches!(source, KeyInfoSource::KeyName(_));
1350            let resolved = match source {
1351                KeyInfoSource::X509Data(info) => {
1352                    // Enforce selector permissions before candidate work or
1353                    // hashing, including direct callers of this resolver.
1354                    for (uri, _) in &info.digests {
1355                        let algorithm = super::DigestAlgorithm::from_uri(uri).ok_or_else(|| {
1356                            KeyResolutionError::UnsupportedDigestAlgorithm(uri.clone())
1357                        })?;
1358                        policy.check_digest_algorithm(algorithm)?;
1359                    }
1360                    if if info.certificate_chain.is_empty() {
1361                        x509_data_has_lookup_identifiers(info)
1362                    } else {
1363                        trust.verify_x509_chains
1364                    } {
1365                        self.check_configured_x509_material(
1366                            info,
1367                            resources,
1368                            trust,
1369                            candidate_budget,
1370                            !configured_material_checked,
1371                        )?;
1372                        configured_material_checked = true;
1373                    }
1374                    match self.resolve_x509(info, algorithm, trust, provider, candidate_budget)? {
1375                        Some((key, evidence)) => {
1376                            if evidence.is_none()
1377                                && !caller_trusted
1378                                && trust.mode
1379                                    == crate::policy::VerificationTrustMode::RequireTrustedKey
1380                                && let Some(der) = key.certificate_der.as_ref()
1381                            {
1382                                // Pin lookup shares the same work allowance as
1383                                // discovery; charge before each DER comparison.
1384                                for anchor in &self.config.trusted_certs {
1385                                    candidate_budget.charge()?;
1386                                    if anchor == der {
1387                                        caller_trusted = true;
1388                                        break;
1389                                    }
1390                                }
1391                            }
1392                            path_evidence = evidence;
1393                            Some(key)
1394                        }
1395                        None => None,
1396                    }
1397                }
1398                KeyInfoSource::DerEncodedKeyValue(public_key_bytes) => {
1399                    candidate_budget.charge()?;
1400                    validate_spki_algorithm(public_key_bytes, algorithm)?;
1401                    Some(VerificationKey {
1402                        algorithm,
1403                        public_key_bytes: public_key_bytes.clone(),
1404                        certificate_der: None,
1405                        name: None,
1406                    })
1407                }
1408                KeyInfoSource::KeyName(name) => {
1409                    candidate_budget.charge()?;
1410                    self.config
1411                        .named_keys
1412                        .get(name)
1413                        .map(|key| {
1414                            if key.algorithm != algorithm {
1415                                return Err(KeyResolutionError::AlgorithmMismatch);
1416                            }
1417                            validate_spki_algorithm(&key.public_key_bytes, algorithm)?;
1418                            Ok(key.clone())
1419                        })
1420                        .transpose()?
1421                }
1422                KeyInfoSource::KeyValue(key_value) => {
1423                    candidate_budget.charge()?;
1424                    match Self::resolve_key_value(key_value, algorithm) {
1425                        Ok(resolved) => resolved,
1426                        Err(error) if key_value_error_allows_fallback(key_value, &error) => {
1427                            deferred_key_value_error.get_or_insert(error);
1428                            None
1429                        }
1430                        Err(error) => return Err(error.into()),
1431                    }
1432                }
1433                KeyInfoSource::RetrievalMethod { .. } => {
1434                    candidate_budget.charge()?;
1435                    None
1436                }
1437                KeyInfoSource::KeyInfoReference { .. } => {
1438                    candidate_budget.charge()?;
1439                    None
1440                }
1441            };
1442            if let Some(key) = resolved {
1443                let key = Box::new(PolicyBoundVerificationKey {
1444                    key,
1445                    rsa_minimum_bits: trust.rsa_keys.minimum_modulus_bits,
1446                    dsa_minimum_bits: trust.dsa_keys.minimum_modulus_bits,
1447                });
1448                let key = if policy.key_trust.mode
1449                    == crate::policy::VerificationTrustMode::CryptographicOnly
1450                {
1451                    ResolvedVerificationKey::Candidate(key)
1452                } else if let Some(evidence) = path_evidence {
1453                    ResolvedVerificationKey::Validated(TrustedPublicKey::new(
1454                        key,
1455                        trust.clone(),
1456                        evidence,
1457                    ))
1458                } else if caller_trusted {
1459                    ResolvedVerificationKey::CallerTrusted(key)
1460                } else {
1461                    ResolvedVerificationKey::Candidate(key)
1462                };
1463                return Ok(SourceResolution {
1464                    key: Some(key),
1465                    deferred_error: None,
1466                });
1467            }
1468        }
1469        Ok(SourceResolution {
1470            key: None,
1471            deferred_error: deferred_key_value_error,
1472        })
1473    }
1474}
1475
1476impl KeyResolver for DefaultKeyResolver {
1477    fn resolve_for_verification<'a>(
1478        &'a self,
1479        key_info: Option<&KeyInfo>,
1480        algorithm: SignatureAlgorithm,
1481        policy: &crate::policy::VerificationPolicy,
1482        provider: &dyn crate::provider::CryptoProvider,
1483    ) -> Result<Option<ResolvedVerificationKey<'a>>, DsigError> {
1484        let mut budget = InspectedKeyCandidateBudget::new(policy);
1485        self.resolve_with_candidate_budget(key_info, algorithm, policy, provider, &mut budget)
1486    }
1487    fn resolve<'a>(
1488        &'a self,
1489        key_info: Option<&KeyInfo>,
1490        algorithm: SignatureAlgorithm,
1491    ) -> Result<Option<Box<dyn VerifyingKey + 'a>>, DsigError> {
1492        let policy = crate::policy::VerificationPolicy::default();
1493        let mut candidate_budget = InspectedKeyCandidateBudget::new(&policy);
1494        self.resolve_with_trust(
1495            key_info,
1496            algorithm,
1497            &policy,
1498            crate::provider::default_provider(),
1499            &mut candidate_budget,
1500            ResolutionScope::Document,
1501        )
1502        .map(|key| key.map(ResolvedVerificationKey::into_key))
1503    }
1504
1505    fn resolve_with_policy<'a>(
1506        &'a self,
1507        key_info: Option<&KeyInfo>,
1508        algorithm: SignatureAlgorithm,
1509        policy: &crate::policy::VerificationPolicy,
1510    ) -> Result<Option<Box<dyn VerifyingKey + 'a>>, DsigError> {
1511        self.resolve_with_policy_and_provider(
1512            key_info,
1513            algorithm,
1514            policy,
1515            crate::provider::default_provider(),
1516        )
1517    }
1518
1519    fn resolve_with_policy_and_provider<'a>(
1520        &'a self,
1521        key_info: Option<&KeyInfo>,
1522        algorithm: SignatureAlgorithm,
1523        policy: &crate::policy::VerificationPolicy,
1524        provider: &dyn crate::provider::CryptoProvider,
1525    ) -> Result<Option<Box<dyn VerifyingKey + 'a>>, DsigError> {
1526        let mut candidate_budget = InspectedKeyCandidateBudget::new(policy);
1527        self.resolve_with_candidate_budget(
1528            key_info,
1529            algorithm,
1530            policy,
1531            provider,
1532            &mut candidate_budget,
1533        )
1534        .map(|key| key.map(ResolvedVerificationKey::into_key))
1535    }
1536
1537    fn consumes_document_key_info(&self) -> bool {
1538        true
1539    }
1540}
1541
1542fn map_x509_selector_error(error: ParseError) -> DsigError {
1543    match error {
1544        ParseError::Provider(error) => DsigError::Provider(error),
1545        ParseError::UnsupportedAlgorithm { uri } => {
1546            KeyResolutionError::UnsupportedDigestAlgorithm(uri).into()
1547        }
1548        _ => KeyResolutionError::InvalidCertificate.into(),
1549    }
1550}
1551
1552fn selected_x509_path_matches_selectors(
1553    available: &X509DataInfo,
1554    selectors: &X509DataInfo,
1555    provider: &dyn crate::provider::CryptoProvider,
1556) -> Result<bool, KeyResolutionError> {
1557    let selected = X509DataInfo {
1558        subject_names: selectors.subject_names.clone(),
1559        issuer_serials: selectors.issuer_serials.clone(),
1560        skis: selectors.skis.clone(),
1561        digests: selectors.digests.clone(),
1562        certificates: available
1563            .certificate_chain
1564            .iter()
1565            .map(|index| available.certificates[*index].clone())
1566            .collect(),
1567        parsed_certificates: available
1568            .certificate_chain
1569            .iter()
1570            .map(|index| available.parsed_certificates[*index].clone())
1571            .collect(),
1572        ..X509DataInfo::default()
1573    };
1574    x509_selector_categories_match_chain(&selected, provider).map_err(|error| match error {
1575        ParseError::Provider(error) => {
1576            KeyResolutionError::Chain(super::X509ChainError::Provider(error))
1577        }
1578        ParseError::UnsupportedAlgorithm { uri } => {
1579            KeyResolutionError::UnsupportedDigestAlgorithm(uri)
1580        }
1581        _ => KeyResolutionError::InvalidCertificate,
1582    })
1583}
1584
1585fn rsa_key_value_to_spki_der(
1586    modulus: &[u8],
1587    exponent: &[u8],
1588) -> Result<Vec<u8>, KeyResolutionError> {
1589    let (modulus, exponent) = bounded_rsa_public_components(modulus, exponent)?;
1590    let key = rsa::RsaPublicKey::new(
1591        BoxedUint::from_be_slice_vartime(modulus),
1592        BoxedUint::from_be_slice_vartime(exponent),
1593    )
1594    .map_err(|_| KeyResolutionError::InvalidPublicKey)?;
1595    key.to_public_key_der()
1596        .map_err(|_| KeyResolutionError::InvalidPublicKey)
1597        .map(|der| der.as_bytes().to_vec())
1598}
1599
1600pub(crate) fn bounded_rsa_public_components<'a>(
1601    modulus: &'a [u8],
1602    exponent: &'a [u8],
1603) -> Result<(&'a [u8], &'a [u8]), KeyResolutionError> {
1604    let modulus = &modulus[modulus
1605        .iter()
1606        .position(|byte| *byte != 0)
1607        .unwrap_or(modulus.len())..];
1608    let exponent = &exponent[exponent
1609        .iter()
1610        .position(|byte| *byte != 0)
1611        .unwrap_or(exponent.len())..];
1612    let maximum = crate::hard_limits::RSA_MODULUS_BIT_CEILING;
1613    if modulus.is_empty()
1614        || exponent.is_empty()
1615        || modulus.len() > maximum.div_ceil(8)
1616        || exponent.len() > maximum.div_ceil(8)
1617        || (modulus.len() * 8 - modulus[0].leading_zeros() as usize) > maximum
1618    {
1619        return Err(KeyResolutionError::InvalidPublicKey);
1620    }
1621    Ok((modulus, exponent))
1622}
1623
1624fn dsa_key_value_to_spki_der(
1625    p: &[u8],
1626    q: &[u8],
1627    g: &[u8],
1628    y: &[u8],
1629) -> Result<Vec<u8>, KeyResolutionError> {
1630    // Bound borrowed unsigned components before any bigint allocation or
1631    // subgroup exponentiation, not only after the SPKI has been produced.
1632    let trim = |bytes: &[u8]| bytes.iter().take_while(|byte| **byte == 0).count();
1633    let p = &p[trim(p)..];
1634    let q = &q[trim(q)..];
1635    let g = &g[trim(g)..];
1636    let y = &y[trim(y)..];
1637    for value in [p, q, g, y] {
1638        if value.is_empty() || value.len() > crate::hard_limits::DSA_KEY_COMPONENT_BYTE_CEILING {
1639            return Err(KeyResolutionError::InvalidPublicKey);
1640        }
1641    }
1642    let components = dsa::Components::from_components(
1643        BoxedUint::from_be_slice_vartime(p),
1644        BoxedUint::from_be_slice_vartime(q),
1645        BoxedUint::from_be_slice_vartime(g),
1646    )
1647    .map_err(|_| KeyResolutionError::InvalidPublicKey)?;
1648    dsa::VerifyingKey::from_components(components, BoxedUint::from_be_slice_vartime(y))
1649        .map_err(|_| KeyResolutionError::InvalidPublicKey)?
1650        .to_public_key_der()
1651        .map_err(|_| KeyResolutionError::InvalidPublicKey)
1652        .map(|der| der.as_bytes().to_vec())
1653}
1654
1655fn ec_key_value_to_spki_der(
1656    curve_oid: &str,
1657    public_key: &[u8],
1658) -> Result<Vec<u8>, KeyResolutionError> {
1659    match curve_oid {
1660        EC_P256_OID => p256::PublicKey::from_sec1_bytes(public_key)
1661            .map_err(|_| KeyResolutionError::InvalidPublicKey)?
1662            .to_public_key_der()
1663            .map_err(|_| KeyResolutionError::InvalidPublicKey)
1664            .map(|der| der.as_bytes().to_vec()),
1665        EC_P384_OID => p384::PublicKey::from_sec1_bytes(public_key)
1666            .map_err(|_| KeyResolutionError::InvalidPublicKey)?
1667            .to_public_key_der()
1668            .map_err(|_| KeyResolutionError::InvalidPublicKey)
1669            .map(|der| der.as_bytes().to_vec()),
1670        EC_P521_OID => p521::PublicKey::from_sec1_bytes(public_key)
1671            .map_err(|_| KeyResolutionError::InvalidPublicKey)?
1672            .to_public_key_der()
1673            .map_err(|_| KeyResolutionError::InvalidPublicKey)
1674            .map(|der| der.as_bytes().to_vec()),
1675        _ => Err(KeyResolutionError::InvalidPublicKey),
1676    }
1677}
1678
1679fn key_value_error_allows_fallback(key_value: &KeyValueInfo, error: &KeyResolutionError) -> bool {
1680    matches!(
1681        key_value,
1682        KeyValueInfo::Dsa { .. } | KeyValueInfo::Ec { .. } | KeyValueInfo::InvalidEcKeyValue
1683    ) && matches!(
1684        error,
1685        KeyResolutionError::InvalidPublicKey | KeyResolutionError::AlgorithmMismatch
1686    )
1687}
1688
1689fn validate_spki_algorithm(
1690    public_key_bytes: &[u8],
1691    algorithm: SignatureAlgorithm,
1692) -> Result<(), KeyResolutionError> {
1693    if algorithm.rsa_pss_parameters().is_some() {
1694        // RFC 4055 section 1.2 requires id-RSASSA-PSS for PSS-only keys.
1695        // Share the borrowed RSA validator so resolver and provider enforce the
1696        // same parameter restrictions; the generic parser recognizes only rsaEncryption.
1697        // https://www.rfc-editor.org/rfc/rfc4055.html#section-1.2
1698        // Eight bits is structural validation only; the selected policy's
1699        // strength minimum is enforced on the resolved key before execution.
1700        return validate_rsa_signature_spki_with_minimum(algorithm, public_key_bytes, 8)
1701            .map_err(|_| KeyResolutionError::InvalidPublicKey);
1702    }
1703    if algorithm.eddsa_signature_len().is_some()
1704        || matches!(algorithm, SignatureAlgorithm::PostQuantum(_))
1705    {
1706        return super::modern::validate_public_key(algorithm, public_key_bytes)
1707            .map_err(|_| KeyResolutionError::InvalidPublicKey);
1708    }
1709    let (rest, spki) = SubjectPublicKeyInfo::from_der(public_key_bytes)
1710        .map_err(|_| KeyResolutionError::InvalidPublicKey)?;
1711    if !rest.is_empty() {
1712        return Err(KeyResolutionError::InvalidPublicKey);
1713    }
1714    let parsed = spki
1715        .parsed()
1716        .map_err(|_| KeyResolutionError::InvalidPublicKey)?;
1717    let curve_oid = spki
1718        .algorithm
1719        .parameters
1720        .as_ref()
1721        .and_then(|value| value.as_oid().ok())
1722        .map(|oid| oid.to_id_string());
1723    match (algorithm, parsed) {
1724        (SignatureAlgorithm::DsaSha1 | SignatureAlgorithm::DsaSha256, PublicKey::DSA(_)) => {
1725            let _ = decode_dsa_verifying_key(public_key_bytes)
1726                .map_err(|_| KeyResolutionError::InvalidPublicKey)?;
1727            Ok(())
1728        }
1729        (method, PublicKey::RSA(_)) if method.is_rsa() => Ok(()),
1730        (method, PublicKey::EC(ec))
1731            if method.ecdsa_digest().is_some()
1732                && matches!(
1733                    curve_oid.as_deref(),
1734                    Some(EC_P256_OID | EC_P384_OID | EC_P521_OID)
1735                ) =>
1736        {
1737            validate_ec_public_key_encoding(&ec, spki.subject_public_key.data.as_ref())
1738                .map_err(|_| KeyResolutionError::InvalidPublicKey)?;
1739            validate_ec_point(curve_oid.as_deref(), spki.subject_public_key.data.as_ref())?;
1740            Ok(())
1741        }
1742        _ => Err(KeyResolutionError::AlgorithmMismatch),
1743    }
1744}
1745
1746pub(crate) fn supported_parsed_spki_is_rsa(
1747    spki: &SubjectPublicKeyInfo<'_>,
1748    public_key_bytes: &[u8],
1749) -> Result<bool, KeyResolutionError> {
1750    let borrowed = pkcs8::SubjectPublicKeyInfoRef::try_from(public_key_bytes)
1751        .map_err(|_| KeyResolutionError::InvalidPublicKey)?;
1752    if let Some(parameter) = super::PqAlgorithm::from_oid(borrowed.algorithm.oid) {
1753        super::modern::validate_public_key(
1754            SignatureAlgorithm::PostQuantum(parameter),
1755            public_key_bytes,
1756        )
1757        .map_err(|_| KeyResolutionError::InvalidPublicKey)?;
1758        return Ok(false);
1759    }
1760    let eddsa = match spki.algorithm.algorithm.to_id_string().as_str() {
1761        "1.3.101.112" => Some(SignatureAlgorithm::Ed25519),
1762        "1.3.101.113" => Some(SignatureAlgorithm::Ed448),
1763        _ => None,
1764    };
1765    if let Some(algorithm) = eddsa {
1766        super::modern::validate_public_key(algorithm, public_key_bytes)
1767            .map_err(|_| KeyResolutionError::InvalidPublicKey)?;
1768        return Ok(false);
1769    }
1770    let parsed = spki
1771        .parsed()
1772        .map_err(|_| KeyResolutionError::InvalidPublicKey)?;
1773    match parsed {
1774        PublicKey::RSA(key) => {
1775            bounded_rsa_public_components(key.modulus, key.exponent)?;
1776            rsa::RsaPublicKey::from_public_key_der(public_key_bytes)
1777                .map_err(|_| KeyResolutionError::InvalidPublicKey)?;
1778            Ok(true)
1779        }
1780        PublicKey::DSA(_) => {
1781            let _ = decode_dsa_verifying_key(public_key_bytes)
1782                .map_err(|_| KeyResolutionError::InvalidPublicKey)?;
1783            Ok(false)
1784        }
1785        PublicKey::EC(ec) => {
1786            validate_ec_public_key_encoding(&ec, spki.subject_public_key.data.as_ref())
1787                .map_err(|_| KeyResolutionError::InvalidPublicKey)?;
1788            let curve_oid = spki
1789                .algorithm
1790                .parameters
1791                .as_ref()
1792                .and_then(|value| value.as_oid().ok())
1793                .map(|oid| oid.to_id_string());
1794            validate_ec_point(curve_oid.as_deref(), spki.subject_public_key.data.as_ref())?;
1795            Ok(false)
1796        }
1797        _ => Err(KeyResolutionError::AlgorithmMismatch),
1798    }
1799}
1800
1801fn validate_ec_point(curve_oid: Option<&str>, point: &[u8]) -> Result<(), KeyResolutionError> {
1802    match curve_oid {
1803        Some(EC_P256_OID) => p256::PublicKey::from_sec1_bytes(point).map(|_| ()),
1804        Some(EC_P384_OID) => p384::PublicKey::from_sec1_bytes(point).map(|_| ()),
1805        Some(EC_P521_OID) => p521::PublicKey::from_sec1_bytes(point).map(|_| ()),
1806        _ => return Err(KeyResolutionError::AlgorithmMismatch),
1807    }
1808    .map_err(|_| KeyResolutionError::InvalidPublicKey)
1809}
1810
1811pub(crate) fn supported_key_value_is_rsa(value: &KeyValueInfo) -> Result<bool, KeyResolutionError> {
1812    let (spki, is_rsa) = match value {
1813        KeyValueInfo::Rsa { modulus, exponent } => {
1814            (rsa_key_value_to_spki_der(modulus, exponent)?, true)
1815        }
1816        KeyValueInfo::Dsa {
1817            p: Some(p),
1818            q: Some(q),
1819            g: Some(g),
1820            y,
1821        } => (dsa_key_value_to_spki_der(p, q, g, y)?, false),
1822        KeyValueInfo::Ec {
1823            curve_oid,
1824            public_key,
1825        } => (ec_key_value_to_spki_der(curve_oid, public_key)?, false),
1826        _ => return Err(KeyResolutionError::InvalidPublicKey),
1827    };
1828    let algorithm = if is_rsa {
1829        SignatureAlgorithm::RsaSha256
1830    } else if matches!(value, KeyValueInfo::Dsa { .. }) {
1831        SignatureAlgorithm::DsaSha256
1832    } else {
1833        SignatureAlgorithm::EcdsaSha256
1834    };
1835    validate_spki_algorithm(&spki, algorithm)?;
1836    Ok(is_rsa)
1837}
1838
1839#[cfg(test)]
1840mod tests {
1841    use crate::xml::dom as roxmltree;
1842    use std::sync::atomic::{AtomicUsize, Ordering};
1843
1844    use base64::{Engine, engine::general_purpose::STANDARD};
1845    use der::Decode as _;
1846    use pkcs8::DecodePublicKey as _;
1847    use rcgen::{
1848        CertificateRevocationListParams, Issuer, KeyIdMethod, KeyPair, KeyUsagePurpose,
1849        RevokedCertParams, SerialNumber, date_time_ymd,
1850    };
1851    use rsa::traits::PublicKeyParts;
1852
1853    use super::*;
1854
1855    #[test]
1856    fn shared_candidate_budget_cannot_relax_active_policy() {
1857        // A policy-derived budget cannot override policy, nor can a
1858        // later tighter snapshot forget work already performed.
1859        let resolver = DefaultKeyResolver::new(KeyResolverConfig::default());
1860        let info = KeyInfo {
1861            sources: vec![KeyInfoSource::KeyName("missing".into())],
1862        };
1863        let mut policy = crate::policy::VerificationPolicy::default();
1864        policy.resources.max_key_candidates = 1;
1865        let mut budget = InspectedKeyCandidateBudget::new(&policy);
1866        assert!(
1867            resolver
1868                .resolve_with_candidate_budget(
1869                    Some(&info),
1870                    SignatureAlgorithm::RsaSha256,
1871                    &policy,
1872                    crate::provider::default_provider(),
1873                    &mut budget
1874                )
1875                .expect("first candidate fits the active policy")
1876                .is_none()
1877        );
1878        assert!(matches!(
1879            resolver.resolve_with_candidate_budget(
1880                Some(&info),
1881                SignatureAlgorithm::RsaSha256,
1882                &policy,
1883                crate::provider::default_provider(),
1884                &mut budget
1885            ),
1886            Err(DsigError::Policy(
1887                crate::policy::PolicyViolation::ResourceLimit {
1888                    maximum: 1,
1889                    actual: 2,
1890                    ..
1891                }
1892            ))
1893        ));
1894        let mut spent =
1895            InspectedKeyCandidateBudget::new(&crate::policy::VerificationPolicy::default());
1896        spent
1897            .charge_many(2)
1898            .expect("initial budget admits two candidates");
1899        assert!(matches!(
1900            resolver.resolve_with_candidate_budget(
1901                None,
1902                SignatureAlgorithm::RsaSha256,
1903                &policy,
1904                crate::provider::default_provider(),
1905                &mut spent
1906            ),
1907            Err(DsigError::Policy(
1908                crate::policy::PolicyViolation::ResourceLimit {
1909                    maximum: 1,
1910                    actual: 2,
1911                    ..
1912                }
1913            ))
1914        ));
1915    }
1916
1917    #[test]
1918    fn dsa_key_value_components_are_bounded_before_bigint_decode() {
1919        // Every unsigned XML component uses the same pre-conversion ceiling;
1920        // redundant zero padding must not inflate bigint precision or change it.
1921        let public = dsa::VerifyingKey::from_public_key_pem(include_str!(
1922            "../../tests/fixtures/keys/dsa/dsa-2048-public.pem"
1923        ))
1924        .expect("DSA fixture");
1925        let components = public.components();
1926        let values = [
1927            components.p().to_be_bytes_trimmed_vartime().to_vec(),
1928            components.q().to_be_bytes_trimmed_vartime().to_vec(),
1929            components.g().to_be_bytes_trimmed_vartime().to_vec(),
1930            public.y().to_be_bytes_trimmed_vartime().to_vec(),
1931        ];
1932        let expected = dsa_key_value_to_spki_der(&values[0], &values[1], &values[2], &values[3])
1933            .expect("valid DSA");
1934        for index in 0..4 {
1935            let mut oversized = values.clone();
1936            oversized[index] = vec![1; crate::hard_limits::DSA_KEY_COMPONENT_BYTE_CEILING + 1];
1937            assert!(
1938                dsa_key_value_to_spki_der(
1939                    &oversized[0],
1940                    &oversized[1],
1941                    &oversized[2],
1942                    &oversized[3]
1943                )
1944                .is_err()
1945            );
1946        }
1947        let padded = values.map(|value| {
1948            let mut padded = vec![0; 1024];
1949            padded.extend(value);
1950            padded
1951        });
1952        assert_eq!(
1953            dsa_key_value_to_spki_der(&padded[0], &padded[1], &padded[2], &padded[3])
1954                .expect("zero padding preserves unsigned DSA value"),
1955            expected
1956        );
1957    }
1958
1959    #[test]
1960    fn xml_rsa_components_are_bounded_before_bigint_decode() {
1961        // KeyValue import must reject oversized decoded modulus before conversion.
1962        let oversized = vec![1_u8; crate::hard_limits::RSA_MODULUS_BIT_CEILING.div_ceil(8) + 1];
1963        assert!(matches!(
1964            rsa_key_value_to_spki_der(&oversized, &[1, 0, 1]),
1965            Err(KeyResolutionError::InvalidPublicKey)
1966        ));
1967    }
1968
1969    #[test]
1970    fn oversized_dsa_spki_parameter_is_rejected_before_bigint_decode() {
1971        // A bounded SPKI may still contain a parameter much larger than the
1972        // non-configurable DSA component ceiling.
1973        let oversized = vec![1_u8; crate::hard_limits::DSA_KEY_COMPONENT_BYTE_CEILING + 1];
1974        let one = [1_u8];
1975        let params = der::Encode::to_der(&super::super::signature::BorrowedDsaPublicParameters {
1976            p: der::asn1::UintRef::new(&oversized).expect("positive P"),
1977            q: der::asn1::UintRef::new(&one).expect("positive Q"),
1978            g: der::asn1::UintRef::new(&one).expect("positive G"),
1979        })
1980        .expect("parameters encode");
1981        let y = der::Encode::to_der(&der::asn1::UintRef::new(&one).expect("positive Y"))
1982            .expect("public value encodes");
1983        let spki = pkcs8::SubjectPublicKeyInfoRef {
1984            algorithm: pkcs8::AlgorithmIdentifierRef {
1985                oid: dsa::OID,
1986                parameters: Some(der::asn1::AnyRef::from_der(&params).expect("parameters")),
1987            },
1988            subject_public_key: der::asn1::BitStringRef::new(0, &y).expect("bit string"),
1989        };
1990        let encoded = der::Encode::to_der(&spki).expect("SPKI encodes");
1991        assert!(matches!(
1992            decode_dsa_verifying_key(&encoded),
1993            Err(super::super::signature::SignatureVerificationError::InvalidKeyDer)
1994        ));
1995        // Ordinary verification must use the same borrowed preflight, not
1996        // reject only after an allocating crypto decoder reports mismatch.
1997        assert!(matches!(
1998            validate_spki_algorithm(&encoded, SignatureAlgorithm::DsaSha256),
1999            Err(KeyResolutionError::InvalidPublicKey)
2000        ));
2001    }
2002
2003    struct RejectSecondSha512Provider {
2004        sha512_calls: AtomicUsize,
2005        verification_calls: AtomicUsize,
2006        reject_verification_call: Option<usize>,
2007        rejected_verification_data: Option<Vec<u8>>,
2008    }
2009
2010    impl crate::provider::CryptoProvider for RejectSecondSha512Provider {
2011        fn name(&self) -> &'static str {
2012            "reject-second-sha512"
2013        }
2014
2015        fn supports(&self, capability: crate::provider::ProviderCapability<'_>) -> bool {
2016            crate::provider::default_provider().supports(capability)
2017        }
2018
2019        fn fill_random(&self, output: &mut [u8]) -> Result<(), crate::provider::ProviderError> {
2020            crate::provider::default_provider().fill_random(output)
2021        }
2022
2023        fn derive_key(
2024            &self,
2025            parameters: &crate::provider::KdfParameters<'_>,
2026            secret: &[u8],
2027        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
2028            crate::provider::default_provider().derive_key(parameters, secret)
2029        }
2030
2031        fn digest(
2032            &self,
2033            algorithm: super::super::DigestAlgorithm,
2034            data: &[u8],
2035        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
2036            if algorithm == super::super::DigestAlgorithm::Sha512
2037                && self.sha512_calls.fetch_add(1, Ordering::Relaxed) > 0
2038            {
2039                return Err(crate::provider::ProviderError::Unsupported {
2040                    operation: crate::provider::ProviderOperation::Digest,
2041                    algorithm: Some(algorithm.uri().to_owned()),
2042                });
2043            }
2044            crate::provider::default_provider().digest(algorithm, data)
2045        }
2046
2047        fn sign(
2048            &self,
2049            key: &dyn super::super::SigningKey,
2050            algorithm: SignatureAlgorithm,
2051            data: &[u8],
2052        ) -> Result<Vec<u8>, super::super::SigningKeyError> {
2053            crate::provider::default_provider().sign(key, algorithm, data)
2054        }
2055
2056        fn verify(
2057            &self,
2058            key: &dyn VerifyingKey,
2059            algorithm: SignatureAlgorithm,
2060            data: &[u8],
2061            signature: &[u8],
2062        ) -> Result<bool, DsigError> {
2063            let call = self.verification_calls.fetch_add(1, Ordering::Relaxed);
2064            if self.reject_verification_call == Some(call)
2065                || self
2066                    .rejected_verification_data
2067                    .as_deref()
2068                    .is_some_and(|rejected| rejected == data)
2069            {
2070                return Err(crate::provider::ProviderError::Unsupported {
2071                    operation: crate::provider::ProviderOperation::Verify,
2072                    algorithm: Some(algorithm.uri().to_owned()),
2073                }
2074                .into());
2075            }
2076            crate::provider::default_provider().verify(key, algorithm, data, signature)
2077        }
2078
2079        fn verify_x509_signature(
2080            &self,
2081            algorithm: crate::provider::X509SignatureAlgorithm,
2082            data: &[u8],
2083            signature: &[u8],
2084            issuer_spki_der: &[u8],
2085        ) -> Result<bool, crate::provider::ProviderError> {
2086            let call = self.verification_calls.fetch_add(1, Ordering::Relaxed);
2087            if self.reject_verification_call == Some(call)
2088                || self
2089                    .rejected_verification_data
2090                    .as_deref()
2091                    .is_some_and(|rejected| rejected == data)
2092            {
2093                return Err(crate::provider::ProviderError::Unsupported {
2094                    operation: crate::provider::ProviderOperation::VerifyCertificate,
2095                    algorithm: algorithm.oid().map(str::to_owned),
2096                });
2097            }
2098            crate::provider::default_provider().verify_x509_signature(
2099                algorithm,
2100                data,
2101                signature,
2102                issuer_spki_der,
2103            )
2104        }
2105
2106        #[cfg(feature = "xmlenc")]
2107        fn encrypt_data(
2108            &self,
2109            algorithm: crate::xmlenc::DataEncryptionAlgorithm,
2110            key: &[u8],
2111            plaintext: &[u8],
2112        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
2113            crate::provider::default_provider().encrypt_data(algorithm, key, plaintext)
2114        }
2115
2116        #[cfg(feature = "xmlenc")]
2117        fn decrypt_data(
2118            &self,
2119            algorithm: crate::xmlenc::DataEncryptionAlgorithm,
2120            key: &[u8],
2121            ciphertext: &[u8],
2122        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
2123            crate::provider::default_provider().decrypt_data(algorithm, key, ciphertext)
2124        }
2125
2126        #[cfg(feature = "xmlenc")]
2127        fn wrap_key(
2128            &self,
2129            algorithm: crate::xmlenc::KeyWrapAlgorithm,
2130            kek: &[u8],
2131            key: &[u8],
2132        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
2133            crate::provider::default_provider().wrap_key(algorithm, kek, key)
2134        }
2135
2136        #[cfg(feature = "xmlenc")]
2137        fn unwrap_key(
2138            &self,
2139            algorithm: crate::xmlenc::KeyWrapAlgorithm,
2140            kek: &[u8],
2141            wrapped: &[u8],
2142        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
2143            crate::provider::default_provider().unwrap_key(algorithm, kek, wrapped)
2144        }
2145
2146        #[cfg(feature = "xmlenc")]
2147        fn transport_key(
2148            &self,
2149            key: &dyn crate::provider::KeyTransportKey,
2150            parameters: &crate::xmlenc::RsaOaepParameters,
2151            plaintext: &[u8],
2152        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
2153            crate::provider::default_provider().transport_key(key, parameters, plaintext)
2154        }
2155
2156        #[cfg(feature = "xmlenc")]
2157        fn recover_key(
2158            &self,
2159            key: &dyn crate::provider::KeyRecoveryKey,
2160            parameters: &crate::xmlenc::RsaOaepParameters,
2161            ciphertext: &[u8],
2162        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
2163            crate::provider::default_provider().recover_key(key, parameters, ciphertext)
2164        }
2165    }
2166
2167    fn chain_policy() -> crate::policy::KeyTrustPolicy {
2168        crate::policy::KeyTrustPolicy {
2169            verify_x509_chains: true,
2170            ..crate::policy::KeyTrustPolicy::default()
2171        }
2172    }
2173
2174    fn cryptographic_policy() -> crate::policy::VerificationPolicy {
2175        // Key representation/selector tests do not assert signer authorization.
2176        verification_policy_with_trust(crate::policy::KeyTrustPolicy {
2177            mode: crate::policy::VerificationTrustMode::CryptographicOnly,
2178            ..crate::policy::KeyTrustPolicy::default()
2179        })
2180    }
2181
2182    fn chain_policy_at(verification_time: SystemTime) -> crate::policy::KeyTrustPolicy {
2183        crate::policy::KeyTrustPolicy {
2184            verification_time: Some(verification_time),
2185            ..chain_policy()
2186        }
2187    }
2188
2189    fn verification_policy_with_trust(
2190        key_trust: crate::policy::KeyTrustPolicy,
2191    ) -> crate::policy::VerificationPolicy {
2192        crate::policy::VerificationPolicy {
2193            key_trust,
2194            ..crate::policy::VerificationPolicy::default()
2195        }
2196    }
2197
2198    const SIGNED_SAML: &str =
2199        include_str!("../../tests/fixtures/saml/response_signed_by_idp_ecdsa.xml");
2200    const SAML_PUBLIC_KEY: &str =
2201        include_str!("../../tests/fixtures/keys/ec/saml-idp-ecdsa-pubkey.pem");
2202    const RSA_PUBLIC_KEY: &str = include_str!("../../tests/fixtures/keys/rsa/rsa-2048-pubkey.pem");
2203    const RSA_4096_CERTIFICATE: &str =
2204        include_str!("../../tests/fixtures/keys/rsa/rsa-4096-cert.pem");
2205    const X509_DIGEST_SIGNATURE: &str = include_str!(
2206        "../../tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloped-x509-digest-sha512.xml"
2207    );
2208    const X509_DIGEST_SHA256_SIGNATURE: &str = include_str!(
2209        "../../tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloped-x509-digest-sha256.xml"
2210    );
2211    const RSA_KEY_VALUE_SIGNATURE: &str = include_str!(
2212        "../../tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha256-rsa-sha256.xml"
2213    );
2214    const LEGACY_RSA_KEY_VALUE_SIGNATURE: &str = include_str!(
2215        "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-enveloping-rsa.xml"
2216    );
2217    const EC_P256_KEY_VALUE_SIGNATURE: &str = include_str!(
2218        "../../tests/fixtures/xmldsig/xmldsig11-interop-2012/signature-enveloping-p256_sha256.xml"
2219    );
2220    const EC_P384_KEY_VALUE_SIGNATURE: &str = include_str!(
2221        "../../tests/fixtures/xmldsig/xmldsig11-interop-2012/signature-enveloping-p384_sha384.xml"
2222    );
2223
2224    fn replace_key_info(xml: &str, replacement: &str) -> String {
2225        let start = xml.find("<ds:KeyInfo>").expect("fixture has KeyInfo");
2226        let end = xml
2227            .find("</ds:KeyInfo>")
2228            .expect("fixture has closing KeyInfo")
2229            + "</ds:KeyInfo>".len();
2230        format!("{}{}{}", &xml[..start], replacement, &xml[end..])
2231    }
2232
2233    fn replace_unprefixed_key_info(xml: &str, replacement: &str) -> String {
2234        let start = xml.find("<KeyInfo>").expect("fixture has KeyInfo");
2235        let end = xml.find("</KeyInfo>").expect("fixture has closing KeyInfo") + "</KeyInfo>".len();
2236        format!("{}{}{}", &xml[..start], replacement, &xml[end..])
2237    }
2238
2239    fn rsa_key_value_parts(public_key: &rsa::RsaPublicKey) -> (String, String) {
2240        (
2241            STANDARD.encode(public_key.n().to_be_bytes_trimmed_vartime()),
2242            STANDARD.encode(public_key.e().to_be_bytes_trimmed_vartime()),
2243        )
2244    }
2245
2246    fn x509_signature_with_leaf_subject() -> String {
2247        replace_unprefixed_key_info(
2248            X509_DIGEST_SIGNATURE,
2249            "<KeyInfo><X509Data><X509SubjectName>CN=Test Key rsa-4096,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509SubjectName></X509Data></KeyInfo>",
2250        )
2251    }
2252
2253    fn fixture_certificate_time() -> SystemTime {
2254        // 2027-01-15 UTC, inside the donor certificates' 2026-2126 validity window.
2255        SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(1_800_000_000)
2256    }
2257
2258    fn public_key_der(pem_text: &str) -> Vec<u8> {
2259        let (rest, pem) = x509_parser::pem::parse_x509_pem(pem_text.as_bytes())
2260            .expect("fixture public key is PEM");
2261        assert!(rest.iter().all(|byte| byte.is_ascii_whitespace()));
2262        assert_eq!(pem.label, "PUBLIC KEY");
2263        pem.contents
2264    }
2265
2266    fn certificate_der(pem_text: &str) -> Vec<u8> {
2267        let (rest, pem) = x509_parser::pem::parse_x509_pem(pem_text.as_bytes())
2268            .expect("fixture certificate is PEM");
2269        assert!(rest.iter().all(|byte| byte.is_ascii_whitespace()));
2270        assert_eq!(pem.label, "CERTIFICATE");
2271        pem.contents
2272    }
2273
2274    fn crl_der(pem_text: &str) -> Vec<u8> {
2275        let (rest, pem) =
2276            x509_parser::pem::parse_x509_pem(pem_text.as_bytes()).expect("fixture CRL is PEM");
2277        assert!(rest.iter().all(|byte| byte.is_ascii_whitespace()));
2278        assert_eq!(pem.label, "X509 CRL");
2279        pem.contents
2280    }
2281
2282    fn generated_certificate_params(common_name: &str, is_ca: bool) -> rcgen::CertificateParams {
2283        let mut params = rcgen::CertificateParams::new(Vec::new())
2284            .expect("empty SAN list should produce valid certificate parameters");
2285        params
2286            .distinguished_name
2287            .push(rcgen::DnType::CommonName, common_name);
2288        if is_ca {
2289            params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
2290            params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
2291        }
2292        params
2293    }
2294
2295    fn authorized_revoked_chain() -> (X509DataInfo, Vec<u8>) {
2296        let mut root_params = generated_certificate_params("revocation root", true);
2297        root_params.key_usages = vec![KeyUsagePurpose::KeyCertSign, KeyUsagePurpose::CrlSign];
2298        let root = rcgen::CertifiedIssuer::self_signed(
2299            root_params,
2300            KeyPair::generate().expect("root key generation should succeed"),
2301        )
2302        .expect("root should be self-signable");
2303
2304        let issuer_key = KeyPair::generate().expect("issuer key generation should succeed");
2305        let mut issuer_params = generated_certificate_params("revocation issuer", true);
2306        issuer_params.key_usages = vec![KeyUsagePurpose::KeyCertSign, KeyUsagePurpose::CrlSign];
2307        let issuer_certificate = issuer_params
2308            .signed_by(&issuer_key, &root)
2309            .expect("root should sign issuer certificate");
2310        let issuer = Issuer::new(issuer_params, issuer_key);
2311
2312        let mut leaf_params = generated_certificate_params("revoked signer", false);
2313        leaf_params.serial_number = Some(SerialNumber::from(42_u64));
2314        leaf_params.key_usages = vec![KeyUsagePurpose::DigitalSignature];
2315        let leaf = leaf_params
2316            .signed_by(
2317                &KeyPair::generate().expect("leaf key generation should succeed"),
2318                &issuer,
2319            )
2320            .expect("issuer should sign leaf certificate");
2321        let crl = CertificateRevocationListParams {
2322            this_update: date_time_ymd(2026, 3, 15),
2323            next_update: date_time_ymd(2026, 4, 15),
2324            crl_number: SerialNumber::from(1_u64),
2325            issuing_distribution_point: None,
2326            revoked_certs: vec![RevokedCertParams {
2327                serial_number: SerialNumber::from(42_u64),
2328                revocation_time: date_time_ymd(2026, 3, 16),
2329                reason_code: None,
2330                invalidity_date: None,
2331            }],
2332            key_identifier_method: KeyIdMethod::Sha256,
2333        }
2334        .signed_by(&issuer)
2335        .expect("issuer should sign CRL");
2336        let mut info = x509_info(
2337            vec![
2338                leaf.der().to_vec(),
2339                issuer_certificate.der().to_vec(),
2340                root.der().to_vec(),
2341            ],
2342            0,
2343        );
2344        info.crls.push(crl.der().to_vec());
2345        (info, root.der().to_vec())
2346    }
2347
2348    fn x509_info(certificates: Vec<Vec<u8>>, signing_index: usize) -> X509DataInfo {
2349        let parsed_certificates = certificates
2350            .iter()
2351            .map(|certificate| {
2352                parse_x509_certificate(certificate)
2353                    .expect("generated certificate should have supported metadata")
2354            })
2355            .collect();
2356        X509DataInfo {
2357            certificates,
2358            parsed_certificates,
2359            certificate_chain: vec![signing_index],
2360            ..X509DataInfo::default()
2361        }
2362    }
2363
2364    #[test]
2365    fn defaults_match_key_resolution_policy() {
2366        // Defaults must remain compatible with xmlsec1's depth and opt-in trust policy.
2367        let config = KeyResolverConfig::default();
2368
2369        assert!(config.trusted_certs.is_empty());
2370        assert!(config.lookup_certs.is_empty());
2371        assert!(config.named_keys.is_empty());
2372        let trust = crate::policy::VerificationPolicy::default().key_trust;
2373        assert!(!trust.verify_x509_chains);
2374        assert!(!trust.check_crls);
2375        assert_eq!(trust.verification_time, None);
2376        assert_eq!(trust.max_x509_chain_depth, 9);
2377    }
2378
2379    #[test]
2380    fn verification_policy_controls_leaf_extended_key_usage() {
2381        // The immutable operation snapshot must reach certificate-path
2382        // validation; resolver-local trust defaults cannot bypass EKU policy.
2383        let root = rcgen::CertifiedIssuer::self_signed(
2384            generated_certificate_params("EKU policy root", true),
2385            rcgen::KeyPair::generate().expect("root key generation should succeed"),
2386        )
2387        .expect("root should be self-signable");
2388        let mut leaf_params = generated_certificate_params("TLS-only XML signer", false);
2389        leaf_params.key_usages = vec![rcgen::KeyUsagePurpose::DigitalSignature];
2390        leaf_params.extended_key_usages = vec![rcgen::ExtendedKeyUsagePurpose::ServerAuth];
2391        let leaf = leaf_params
2392            .signed_by(
2393                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2394                &root,
2395            )
2396            .expect("root should sign leaf certificate");
2397        let key_info = KeyInfo {
2398            sources: vec![KeyInfoSource::X509Data(x509_info(
2399                vec![leaf.der().to_vec(), root.der().to_vec()],
2400                0,
2401            ))],
2402        };
2403        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
2404            trusted_certs: vec![root.der().to_vec()],
2405            ..KeyResolverConfig::default()
2406        });
2407        let mut policy = crate::policy::VerificationPolicy::default();
2408        policy.key_trust.verify_x509_chains = true;
2409
2410        let error = match resolver.resolve_with_policy(
2411            Some(&key_info),
2412            SignatureAlgorithm::EcdsaSha256,
2413            &policy,
2414        ) {
2415            Ok(_) => panic!("unapproved restricted EKU must be rejected"),
2416            Err(error) => error,
2417        };
2418        assert!(matches!(
2419            error,
2420            DsigError::KeyResolution(KeyResolutionError::Chain(
2421                super::super::X509ChainError::InvalidKeyUsage {
2422                    position: 0,
2423                    required: "an approved extended key usage",
2424                }
2425            ))
2426        ));
2427
2428        policy.key_trust.allowed_extended_key_usages =
2429            std::collections::HashSet::from([crate::policy::ExtendedKeyPurpose::ServerAuth]);
2430        assert!(
2431            resolver
2432                .resolve_with_policy(Some(&key_info), SignatureAlgorithm::EcdsaSha256, &policy,)
2433                .expect("approved restricted EKU must pass path validation")
2434                .is_some()
2435        );
2436    }
2437
2438    #[test]
2439    fn operation_policy_rejects_zero_x509_resource_limits() {
2440        // X.509 work limits belong to the immutable operation snapshot and are
2441        // rejected before resolver-owned certificate material is inspected.
2442        for trust in [
2443            crate::policy::KeyTrustPolicy {
2444                verify_x509_chains: true,
2445                max_x509_chain_depth: 0,
2446                ..crate::policy::KeyTrustPolicy::default()
2447            },
2448            crate::policy::KeyTrustPolicy {
2449                verify_x509_chains: true,
2450                max_x509_candidate_paths: 0,
2451                ..crate::policy::KeyTrustPolicy::default()
2452            },
2453        ] {
2454            let certificate = certificate_der(RSA_4096_CERTIFICATE);
2455            let resolver = DefaultKeyResolver::new(KeyResolverConfig {
2456                trusted_certs: vec![certificate],
2457                ..KeyResolverConfig::default()
2458            });
2459            let policy = crate::policy::VerificationPolicy {
2460                key_trust: trust,
2461                ..crate::policy::VerificationPolicy::default()
2462            };
2463            let error = super::super::VerifyContext::new()
2464                .policy(policy)
2465                .key_resolver(&resolver)
2466                .verify(&x509_signature_with_leaf_subject())
2467                .expect_err("zero composed X.509 limits must fail as policy errors");
2468
2469            assert!(matches!(
2470                error,
2471                DsigError::Policy(crate::policy::PolicyViolation::InvalidResourceLimit {
2472                    requirement: "limit must be nonzero",
2473                    actual: 0,
2474                    ..
2475                })
2476            ));
2477        }
2478    }
2479
2480    #[test]
2481    fn operation_policy_rejects_crl_checking_without_chain_validation() {
2482        // CRL authentication is part of path validation. A resolver must not
2483        // accept a configuration that would silently skip the requested check.
2484        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
2485            lookup_certs: vec![certificate_der(RSA_4096_CERTIFICATE)],
2486            ..KeyResolverConfig::default()
2487        });
2488        let policy = crate::policy::VerificationPolicy {
2489            key_trust: crate::policy::KeyTrustPolicy {
2490                check_crls: true,
2491                ..crate::policy::KeyTrustPolicy::default()
2492            },
2493            ..crate::policy::VerificationPolicy::default()
2494        };
2495        let error = super::super::VerifyContext::new()
2496            .policy(policy)
2497            .key_resolver(&resolver)
2498            .verify(&x509_signature_with_leaf_subject())
2499            .expect_err("CRL-only trust policy must fail before certificate use");
2500
2501        assert!(matches!(
2502            error,
2503            DsigError::Policy(crate::policy::PolicyViolation::KeyTrust {
2504                reason: "CRL checking requires X.509 chain validation"
2505            })
2506        ));
2507    }
2508
2509    #[test]
2510    fn hmac_key_rejects_empty_secret_and_wrong_algorithm() {
2511        // HMAC secrets are caller-owned and cannot be reused as asymmetric keys.
2512        assert!(matches!(
2513            HmacSha1VerificationKey::new(Vec::new()),
2514            Err(KeyResolutionError::InvalidPublicKey)
2515        ));
2516        let key = HmacSha1VerificationKey::new(b"secret".to_vec())
2517            .expect("non-empty HMAC secret must be accepted");
2518        assert!(matches!(
2519            key.verify(SignatureAlgorithm::RsaSha256, b"data", b"signature"),
2520            Err(DsigError::KeyResolution(
2521                KeyResolutionError::AlgorithmMismatch
2522            ))
2523        ));
2524    }
2525
2526    #[test]
2527    fn hmac_key_uses_the_operation_policy_for_truncation() {
2528        // Output length belongs to SignatureMethod and operation policy, not
2529        // reusable secret key material. Legacy truncation therefore requires
2530        // an explicit compatibility policy even on the policy-aware key hook.
2531        let key = HmacVerificationKey::new(b"secret".to_vec())
2532            .expect("the fixture HMAC secret is non-empty");
2533        let mut mac = hmac::Hmac::<sha1::Sha1>::new_from_slice(b"secret")
2534            .expect("HMAC accepts an arbitrary non-empty secret");
2535        mac.update(b"data");
2536        let expected = mac.finalize().into_bytes();
2537
2538        let policy = crate::policy::VerificationPolicy {
2539            hmac: crate::policy::HmacPolicy {
2540                minimum_key_bits: 40,
2541                minimum_output_bits: 80,
2542            },
2543            ..crate::policy::VerificationPolicy::default()
2544        };
2545        assert!(
2546            key.verify_with_policy(
2547                &policy,
2548                SignatureAlgorithm::HmacSha1,
2549                b"data",
2550                &expected[..10],
2551            )
2552            .expect("the compatibility policy and algorithm match")
2553        );
2554        assert!(
2555            !key.verify_with_policy(&policy, SignatureAlgorithm::HmacSha1, b"data", &[0_u8; 10],)
2556                .expect("a mismatched truncated MAC must be rejected")
2557        );
2558    }
2559
2560    #[test]
2561    fn hmac_key_direct_api_rejects_attacker_selected_short_output() {
2562        // The policy-free trait method applies secure defaults; signature bytes
2563        // cannot act as their own one-byte truncation declaration.
2564        let key = HmacVerificationKey::new([0x42; 16]).expect("fixed HMAC key must parse");
2565        let mut mac = hmac::Hmac::<sha2::Sha256>::new_from_slice(&[0x42; 16])
2566            .expect("HMAC accepts the fixed secret");
2567        mac.update(b"data");
2568        let expected = mac.finalize().into_bytes();
2569
2570        assert!(matches!(
2571            key.verify(SignatureAlgorithm::HmacSha256, b"data", &expected[..1]),
2572            Err(DsigError::Policy(
2573                crate::policy::PolicyViolation::HmacOutputLength {
2574                    minimum: 128,
2575                    maximum: 256,
2576                    actual: 8,
2577                }
2578            ))
2579        ));
2580    }
2581
2582    #[test]
2583    fn hmac_key_debug_redacts_secret_material() {
2584        // Debug output may expose public verification parameters, never caller secrets.
2585        let secret = b"unique-debug-secret-marker";
2586        let key = HmacVerificationKey::new(secret.to_vec())
2587            .expect("the fixture HMAC secret is non-empty");
2588
2589        let debug = format!("{key:?}");
2590        assert!(
2591            !debug
2592                .contains(std::str::from_utf8(secret).expect("the debug marker is literal ASCII"))
2593        );
2594        assert!(!debug.contains(&format!("{secret:?}")));
2595        assert!(!debug.contains("output_length_bits"));
2596    }
2597
2598    #[test]
2599    fn stores_named_verification_key_metadata() {
2600        // Named resolution must retain every field needed by the later resolver wiring.
2601        let key = VerificationKey {
2602            algorithm: SignatureAlgorithm::RsaSha256,
2603            public_key_bytes: vec![1, 2, 3],
2604            certificate_der: Some(vec![4, 5, 6]),
2605            name: Some("idp-signing".into()),
2606        };
2607        let mut config = KeyResolverConfig::default();
2608        config.named_keys.insert("idp-signing".into(), key.clone());
2609
2610        assert_eq!(config.named_keys.get("idp-signing"), Some(&key));
2611    }
2612
2613    #[test]
2614    fn resolves_embedded_certificate_end_to_end() {
2615        // The default resolver must make parsed X509Data usable by VerifyContext.
2616        let resolver = DefaultKeyResolver::default();
2617        let result = super::super::VerifyContext::new()
2618            .policy(cryptographic_policy())
2619            .key_resolver(&resolver)
2620            .verify(SIGNED_SAML)
2621            .expect("embedded certificate should resolve");
2622
2623        assert_eq!(result.status, super::super::DsigStatus::Valid);
2624        assert_eq!(
2625            result.key_trust,
2626            super::super::KeyTrustEvidence::NotEstablished
2627        );
2628    }
2629
2630    #[test]
2631    fn embedded_certificate_requires_trust_but_explicit_pin_authorizes_it() {
2632        // Cryptographic correctness alone must not authorize an embedded signer.
2633        let untrusted = DefaultKeyResolver::default();
2634        let error = super::super::VerifyContext::new()
2635            .key_resolver(&untrusted)
2636            .verify(SIGNED_SAML)
2637            .expect_err("an embedded certificate is not a trust declaration");
2638        assert!(matches!(
2639            error,
2640            DsigError::Policy(crate::policy::PolicyViolation::KeyTrust { .. })
2641        ));
2642
2643        let xml = roxmltree::Document::parse(SIGNED_SAML).expect("signed SAML fixture must parse");
2644        let certificate = xml
2645            .descendants()
2646            .find(|node| {
2647                node.has_tag_name(("http://www.w3.org/2000/09/xmldsig#", "X509Certificate"))
2648            })
2649            .expect("signed SAML must embed its certificate");
2650        let der = STANDARD
2651            .decode(
2652                certificate
2653                    .text()
2654                    .expect("fixture certificate must contain Base64 text")
2655                    .split_whitespace()
2656                    .collect::<String>(),
2657            )
2658            .expect("fixture certificate must be valid Base64");
2659        let trusted = DefaultKeyResolver::new(KeyResolverConfig {
2660            trusted_certs: vec![der],
2661            ..KeyResolverConfig::default()
2662        });
2663        let result = super::super::VerifyContext::new()
2664            .key_resolver(&trusted)
2665            .verify(SIGNED_SAML)
2666            .expect("an exact caller certificate pin authorizes the key");
2667        assert_eq!(result.status, super::super::DsigStatus::Valid);
2668        assert_eq!(
2669            result.key_trust,
2670            super::super::KeyTrustEvidence::CallerTrusted
2671        );
2672
2673        // A different operation must not retain the previous resolver's trust.
2674        assert!(
2675            super::super::VerifyContext::new()
2676                .key_resolver(&untrusted)
2677                .verify(SIGNED_SAML)
2678                .is_err()
2679        );
2680    }
2681
2682    #[test]
2683    fn certificate_pin_inspections_share_the_candidate_budget() {
2684        // Authorization is actual key-store work, not a free post-resolution scan.
2685        let xml = roxmltree::Document::parse(SIGNED_SAML).expect("fixture must parse");
2686        let text = xml
2687            .descendants()
2688            .find(|node| {
2689                node.has_tag_name(("http://www.w3.org/2000/09/xmldsig#", "X509Certificate"))
2690            })
2691            .and_then(|node| node.text())
2692            .expect("fixture embeds a certificate");
2693        let certificate = STANDARD
2694            .decode(text.split_whitespace().collect::<String>())
2695            .expect("fixture certificate must decode");
2696        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
2697            trusted_certs: vec![certificate],
2698            ..KeyResolverConfig::default()
2699        });
2700        let mut policy = crate::policy::VerificationPolicy::default();
2701        policy.resources.max_key_candidates = 1;
2702        let error = super::super::VerifyContext::new()
2703            .policy(policy)
2704            .key_resolver(&resolver)
2705            .verify(SIGNED_SAML)
2706            .expect_err("the certificate and pin are separate inspections");
2707        assert!(matches!(
2708            error,
2709            DsigError::Policy(crate::policy::PolicyViolation::ResourceLimit {
2710                resource: crate::policy::resource_name::KEY_CANDIDATES,
2711                maximum: 1,
2712                actual: 2,
2713            })
2714        ));
2715    }
2716
2717    #[test]
2718    fn resolves_x509_digest_from_configured_certificates() {
2719        // Selector-only X509Data must locate the signing certificate without
2720        // embedding key material or supplying a preset verification key.
2721        let leaf_certificate_der = certificate_der(RSA_4096_CERTIFICATE);
2722        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
2723            lookup_certs: vec![leaf_certificate_der],
2724            trusted_certs: vec![
2725                certificate_der(include_str!("../../tests/fixtures/keys/ca2cert.pem")),
2726                certificate_der(include_str!("../../tests/fixtures/keys/cacert.pem")),
2727            ],
2728            ..KeyResolverConfig::default()
2729        });
2730        for signature in [X509_DIGEST_SHA256_SIGNATURE, X509_DIGEST_SIGNATURE] {
2731            let result = super::super::VerifyContext::new()
2732                .policy(cryptographic_policy())
2733                .key_resolver(&resolver)
2734                .verify(signature)
2735                .expect("X509Digest should resolve a configured certificate");
2736
2737            assert_eq!(result.status, super::super::DsigStatus::Valid);
2738        }
2739    }
2740
2741    #[test]
2742    fn selector_resolved_certificate_obeys_chain_policy() {
2743        // Enabling chain verification must apply validity policy even when
2744        // X509Data contains only selectors and the matching cert is configured.
2745        let leaf_certificate_der = certificate_der(RSA_4096_CERTIFICATE);
2746        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
2747            lookup_certs: vec![leaf_certificate_der],
2748            trusted_certs: vec![
2749                certificate_der(include_str!("../../tests/fixtures/keys/ca2cert.pem")),
2750                certificate_der(include_str!("../../tests/fixtures/keys/cacert.pem")),
2751            ],
2752            ..KeyResolverConfig::default()
2753        });
2754        let error = super::super::VerifyContext::new()
2755            .policy(verification_policy_with_trust(chain_policy_at(
2756                SystemTime::UNIX_EPOCH,
2757            )))
2758            .key_resolver(&resolver)
2759            .verify(&x509_signature_with_leaf_subject())
2760            .expect_err("selector-resolved certificate must satisfy chain policy");
2761
2762        assert!(
2763            matches!(
2764                &error,
2765                DsigError::KeyResolution(KeyResolutionError::Chain(
2766                    super::super::X509ChainError::CertificateNotValid(_)
2767                ))
2768            ),
2769            "unexpected selector policy error: {error:?}"
2770        );
2771    }
2772
2773    #[test]
2774    fn selector_resolved_configured_root_remains_a_trust_anchor() {
2775        // A certificate explicitly configured in trusted_certs remains an
2776        // anchor when X509Data selects it by subject instead of embedding it.
2777        let mut params = rcgen::CertificateParams::new(Vec::new())
2778            .expect("empty SAN list should produce valid certificate parameters");
2779        params
2780            .distinguished_name
2781            .push(rcgen::DnType::CommonName, "configured root");
2782        params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
2783        let key_pair = rcgen::KeyPair::generate().expect("test key generation should succeed");
2784        let certificate = params
2785            .self_signed(&key_pair)
2786            .expect("test root should be self-signable");
2787        let certificate_der = certificate.der().to_vec();
2788        let key_info_xml = concat!(
2789            "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">",
2790            "<X509Data><X509SubjectName>CN=configured root</X509SubjectName></X509Data>",
2791            "</KeyInfo>"
2792        );
2793        let document = roxmltree::Document::parse(key_info_xml)
2794            .expect("static selector KeyInfo should parse as XML");
2795        let key_info = super::super::parse_key_info(document.root_element())
2796            .expect("static selector KeyInfo should satisfy XMLDSig structure");
2797        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
2798            trusted_certs: vec![certificate_der],
2799            ..KeyResolverConfig::default()
2800        });
2801
2802        let resolved = resolver
2803            .resolve_with_policy(
2804                Some(&key_info),
2805                SignatureAlgorithm::EcdsaSha256,
2806                &verification_policy_with_trust(chain_policy()),
2807            )
2808            .expect("configured self-signed certificate should validate as its own anchor");
2809
2810        assert!(resolved.is_some());
2811    }
2812
2813    #[test]
2814    fn selector_resolved_non_self_signed_trust_anchor_terminates_the_path() {
2815        // Trust is assigned to the exact configured certificate, not inferred
2816        // from self-signing. A lookup-only issuer must not extend that anchor
2817        // into a new path that requires another trust decision.
2818        let mut issuer_params = rcgen::CertificateParams::new(Vec::new())
2819            .expect("empty issuer SAN list should be valid");
2820        issuer_params
2821            .distinguished_name
2822            .push(rcgen::DnType::CommonName, "lookup-only issuer");
2823        issuer_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
2824        issuer_params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
2825        let issuer = rcgen::CertifiedIssuer::self_signed(
2826            issuer_params,
2827            rcgen::KeyPair::generate().expect("issuer key generation should succeed"),
2828        )
2829        .expect("issuer certificate should be self-signable");
2830
2831        let mut anchor_params = rcgen::CertificateParams::new(Vec::new())
2832            .expect("empty anchor SAN list should be valid");
2833        anchor_params
2834            .distinguished_name
2835            .push(rcgen::DnType::CommonName, "direct trust anchor");
2836        let anchor = anchor_params
2837            .signed_by(
2838                &rcgen::KeyPair::generate().expect("anchor key generation should succeed"),
2839                &issuer,
2840            )
2841            .expect("issuer should sign the directly trusted certificate");
2842        let key_info_xml = concat!(
2843            "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">",
2844            "<X509Data><X509SubjectName>CN=direct trust anchor</X509SubjectName></X509Data>",
2845            "</KeyInfo>"
2846        );
2847        let document = roxmltree::Document::parse(key_info_xml)
2848            .expect("static selector KeyInfo should parse as XML");
2849        let key_info = super::super::parse_key_info(document.root_element())
2850            .expect("static selector KeyInfo should satisfy XMLDSig structure");
2851        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
2852            trusted_certs: vec![anchor.der().to_vec()],
2853            lookup_certs: vec![issuer.der().to_vec()],
2854            ..KeyResolverConfig::default()
2855        });
2856
2857        let resolved = resolver
2858            .resolve_with_policy(
2859                Some(&key_info),
2860                SignatureAlgorithm::EcdsaSha256,
2861                &verification_policy_with_trust(chain_policy()),
2862            )
2863            .expect("an explicitly trusted selected certificate must terminate its path");
2864
2865        assert!(resolved.is_some());
2866    }
2867
2868    #[test]
2869    fn selector_resolved_leaf_stops_at_non_self_signed_trust_anchor() {
2870        // A configured anchor terminates trust even when a lookup certificate
2871        // could continue the issuer-name chain beyond it.
2872        let external_issuer = rcgen::CertifiedIssuer::self_signed(
2873            generated_certificate_params("external issuer", true),
2874            rcgen::KeyPair::generate().expect("external issuer key generation should succeed"),
2875        )
2876        .expect("external issuer should be self-signable");
2877        let anchor = rcgen::CertifiedIssuer::signed_by(
2878            generated_certificate_params("non-self-signed anchor", true),
2879            rcgen::KeyPair::generate().expect("anchor key generation should succeed"),
2880            &external_issuer,
2881        )
2882        .expect("external issuer should sign the anchor");
2883        let leaf = generated_certificate_params("anchor leaf", false)
2884            .signed_by(
2885                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2886                &anchor,
2887            )
2888            .expect("anchor should sign the leaf");
2889        let leaf_metadata = parse_x509_certificate(leaf.der())
2890            .expect("generated leaf should have supported metadata");
2891        let key_info = KeyInfo {
2892            sources: vec![KeyInfoSource::X509Data(X509DataInfo {
2893                subject_names: vec![leaf_metadata.subject_dn],
2894                ..X509DataInfo::default()
2895            })],
2896        };
2897        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
2898            trusted_certs: vec![anchor.der().to_vec()],
2899            lookup_certs: vec![leaf.der().to_vec(), external_issuer.der().to_vec()],
2900            ..KeyResolverConfig::default()
2901        });
2902
2903        let resolved = resolver
2904            .resolve_with_policy(
2905                Some(&key_info),
2906                SignatureAlgorithm::EcdsaSha256,
2907                &verification_policy_with_trust(chain_policy()),
2908            )
2909            .expect("path construction must stop at the configured anchor");
2910
2911        assert!(resolved.is_some());
2912    }
2913
2914    #[test]
2915    fn selector_resolved_leaf_does_not_anchor_itself() {
2916        // A certificate available for selector lookup is not automatically a
2917        // trust anchor; chain verification still requires a separate issuer.
2918        let certificate_der = certificate_der(RSA_4096_CERTIFICATE);
2919        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
2920            lookup_certs: vec![certificate_der],
2921            ..KeyResolverConfig::default()
2922        });
2923        let error = super::super::VerifyContext::new()
2924            .policy(verification_policy_with_trust(chain_policy_at(
2925                fixture_certificate_time(),
2926            )))
2927            .key_resolver(&resolver)
2928            .verify(&x509_signature_with_leaf_subject())
2929            .expect_err("selector-resolved leaf must not trust itself");
2930
2931        assert!(matches!(
2932            error,
2933            DsigError::KeyResolution(KeyResolutionError::Chain(
2934                super::super::X509ChainError::UntrustedRoot
2935            ))
2936        ));
2937    }
2938
2939    #[test]
2940    fn selector_resolved_leaf_uses_separate_anchor() {
2941        // Selector lookup may use the leaf from the configured set, but chain
2942        // verification must terminate at a different configured certificate.
2943        let leaf = certificate_der(RSA_4096_CERTIFICATE);
2944        let issuer = certificate_der(include_str!("../../tests/fixtures/keys/ca2cert.pem"));
2945        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
2946            lookup_certs: vec![leaf],
2947            trusted_certs: vec![issuer],
2948            ..KeyResolverConfig::default()
2949        });
2950        let result = super::super::VerifyContext::new()
2951            .policy(verification_policy_with_trust(chain_policy_at(
2952                fixture_certificate_time(),
2953            )))
2954            .key_resolver(&resolver)
2955            .verify(&x509_signature_with_leaf_subject())
2956            .expect("selector-resolved leaf should chain to its configured issuer");
2957
2958        assert_eq!(result.status, super::super::DsigStatus::Valid);
2959    }
2960
2961    #[test]
2962    fn selector_resolved_leaf_uses_lookup_intermediate() {
2963        // Lookup certificates may complete an untrusted path, but only the
2964        // separately configured root is allowed to establish trust.
2965        let mut root_params =
2966            rcgen::CertificateParams::new(Vec::new()).expect("empty root SAN list should be valid");
2967        root_params
2968            .distinguished_name
2969            .push(rcgen::DnType::CommonName, "lookup root");
2970        root_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
2971        root_params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
2972        let root = rcgen::CertifiedIssuer::self_signed(
2973            root_params,
2974            rcgen::KeyPair::generate().expect("root key generation should succeed"),
2975        )
2976        .expect("root certificate should be self-signable");
2977
2978        let mut intermediate_params = rcgen::CertificateParams::new(Vec::new())
2979            .expect("empty intermediate SAN list should be valid");
2980        intermediate_params
2981            .distinguished_name
2982            .push(rcgen::DnType::CommonName, "lookup intermediate");
2983        intermediate_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
2984        intermediate_params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
2985        let intermediate = rcgen::CertifiedIssuer::signed_by(
2986            intermediate_params,
2987            rcgen::KeyPair::generate().expect("intermediate key generation should succeed"),
2988            &root,
2989        )
2990        .expect("root should sign the intermediate certificate");
2991
2992        let mut leaf_params =
2993            rcgen::CertificateParams::new(Vec::new()).expect("empty leaf SAN list should be valid");
2994        leaf_params
2995            .distinguished_name
2996            .push(rcgen::DnType::CommonName, "lookup leaf");
2997        let leaf = leaf_params
2998            .signed_by(
2999                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3000                &intermediate,
3001            )
3002            .expect("intermediate should sign the leaf certificate");
3003        let key_info_xml = concat!(
3004            "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">",
3005            "<X509Data><X509SubjectName>CN=lookup leaf</X509SubjectName></X509Data>",
3006            "</KeyInfo>"
3007        );
3008        let document = roxmltree::Document::parse(key_info_xml)
3009            .expect("static selector KeyInfo should parse as XML");
3010        let key_info = super::super::parse_key_info(document.root_element())
3011            .expect("static selector KeyInfo should satisfy XMLDSig structure");
3012        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
3013            lookup_certs: vec![leaf.der().to_vec(), intermediate.der().to_vec()],
3014            trusted_certs: vec![root.der().to_vec()],
3015            ..KeyResolverConfig::default()
3016        });
3017
3018        let resolved = resolver
3019            .resolve_with_policy(
3020                Some(&key_info),
3021                SignatureAlgorithm::EcdsaSha256,
3022                &verification_policy_with_trust(chain_policy()),
3023            )
3024            .expect("selector-resolved leaf should chain through the lookup intermediate");
3025
3026        assert!(resolved.is_some());
3027    }
3028
3029    #[test]
3030    fn x509_path_signatures_use_the_operation_provider() {
3031        // Embedded and selector-resolved certificates converge on the same
3032        // path validator. Neither source may fall back to a crate-global
3033        // verifier when the operation provider rejects certificate signatures.
3034        let root = rcgen::CertifiedIssuer::self_signed(
3035            generated_certificate_params("provider root", true),
3036            rcgen::KeyPair::generate().expect("root key generation should succeed"),
3037        )
3038        .expect("root should be self-signable");
3039        let leaf = generated_certificate_params("provider leaf", false)
3040            .signed_by(
3041                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3042                &root,
3043            )
3044            .expect("root should sign the leaf");
3045        let leaf_der = leaf.der().to_vec();
3046        let leaf_metadata =
3047            parse_x509_certificate(&leaf_der).expect("generated leaf metadata should parse");
3048        let policy = crate::policy::VerificationPolicy {
3049            key_trust: chain_policy(),
3050            ..crate::policy::VerificationPolicy::default()
3051        };
3052
3053        let cases = [
3054            (
3055                KeyInfo {
3056                    sources: vec![KeyInfoSource::X509Data(x509_info(
3057                        vec![leaf_der.clone()],
3058                        0,
3059                    ))],
3060                },
3061                Vec::new(),
3062            ),
3063            (
3064                KeyInfo {
3065                    sources: vec![KeyInfoSource::X509Data(X509DataInfo {
3066                        subject_names: vec![leaf_metadata.subject_dn],
3067                        ..X509DataInfo::default()
3068                    })],
3069                },
3070                vec![leaf_der],
3071            ),
3072        ];
3073
3074        for (key_info, lookup_certs) in cases {
3075            let provider = RejectSecondSha512Provider {
3076                sha512_calls: AtomicUsize::new(0),
3077                verification_calls: AtomicUsize::new(0),
3078                reject_verification_call: Some(0),
3079                rejected_verification_data: None,
3080            };
3081            let resolver = DefaultKeyResolver::new(KeyResolverConfig {
3082                trusted_certs: vec![root.der().to_vec()],
3083                lookup_certs,
3084                ..KeyResolverConfig::default()
3085            });
3086            let error = match resolver.resolve_with_policy_and_provider(
3087                Some(&key_info),
3088                SignatureAlgorithm::EcdsaSha256,
3089                &policy,
3090                &provider,
3091            ) {
3092                Ok(_) => panic!("the operation provider must gate every X.509 path signature"),
3093                Err(error) => error,
3094            };
3095
3096            assert!(matches!(
3097                error,
3098                DsigError::KeyResolution(KeyResolutionError::Chain(
3099                    super::super::X509ChainError::UnsupportedSignatureAlgorithm { ref oid }
3100                )) if oid == "1.2.840.10045.4.3.2"
3101            ));
3102            assert_eq!(provider.verification_calls.load(Ordering::Relaxed), 1);
3103        }
3104
3105        // A provider rejection after path construction proves complete-path
3106        // validation does not switch back to the crate-global provider.
3107        let provider = RejectSecondSha512Provider {
3108            sha512_calls: AtomicUsize::new(0),
3109            verification_calls: AtomicUsize::new(0),
3110            reject_verification_call: Some(1),
3111            rejected_verification_data: None,
3112        };
3113        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
3114            trusted_certs: vec![root.der().to_vec()],
3115            ..KeyResolverConfig::default()
3116        });
3117        let key_info = KeyInfo {
3118            sources: vec![KeyInfoSource::X509Data(x509_info(
3119                vec![leaf.der().to_vec()],
3120                0,
3121            ))],
3122        };
3123        let error = match resolver.resolve_with_policy_and_provider(
3124            Some(&key_info),
3125            SignatureAlgorithm::EcdsaSha256,
3126            &policy,
3127            &provider,
3128        ) {
3129            Ok(_) => panic!("complete-path validation must retain the operation provider"),
3130            Err(error) => error,
3131        };
3132        assert!(matches!(
3133            error,
3134            DsigError::KeyResolution(KeyResolutionError::Chain(
3135                super::super::X509ChainError::Provider(_)
3136            ))
3137        ));
3138        assert_eq!(provider.verification_calls.load(Ordering::Relaxed), 2);
3139    }
3140
3141    #[test]
3142    fn embedded_leaf_uses_lookup_intermediate_with_duplicate_anchor() {
3143        // Deduplicating repeated trust anchors must not shift an untrusted
3144        // lookup intermediate into the trusted prefix used by path building.
3145        let trusted_root = rcgen::CertifiedIssuer::self_signed(
3146            generated_certificate_params("unrelated trusted root", true),
3147            rcgen::KeyPair::generate().expect("root key generation should succeed"),
3148        )
3149        .expect("root should be self-signable");
3150        let issuer_root = rcgen::CertifiedIssuer::self_signed(
3151            generated_certificate_params("untrusted issuer root", true),
3152            rcgen::KeyPair::generate().expect("issuer root key generation should succeed"),
3153        )
3154        .expect("issuer root should be self-signable");
3155        let intermediate = rcgen::CertifiedIssuer::signed_by(
3156            generated_certificate_params("embedded intermediate", true),
3157            rcgen::KeyPair::generate().expect("intermediate key generation should succeed"),
3158            &issuer_root,
3159        )
3160        .expect("issuer root should sign the intermediate");
3161        let leaf = generated_certificate_params("embedded leaf", false)
3162            .signed_by(
3163                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3164                &intermediate,
3165            )
3166            .expect("intermediate should sign the leaf");
3167        let key_info = KeyInfo {
3168            sources: vec![KeyInfoSource::X509Data(x509_info(
3169                vec![leaf.der().to_vec()],
3170                0,
3171            ))],
3172        };
3173        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
3174            lookup_certs: vec![intermediate.der().to_vec()],
3175            trusted_certs: vec![trusted_root.der().to_vec(), trusted_root.der().to_vec()],
3176            ..KeyResolverConfig::default()
3177        });
3178
3179        let policy = verification_policy_with_trust(chain_policy());
3180        let error = match resolver.resolve_with_policy(
3181            Some(&key_info),
3182            SignatureAlgorithm::EcdsaSha256,
3183            &policy,
3184        ) {
3185            Ok(_) => panic!("an untrusted lookup intermediate must not become a trust anchor"),
3186            Err(error) => error,
3187        };
3188
3189        assert!(matches!(
3190            error,
3191            DsigError::KeyResolution(KeyResolutionError::Chain(
3192                super::super::X509ChainError::UntrustedRoot
3193            ))
3194        ));
3195    }
3196
3197    #[test]
3198    fn selector_resolved_leaf_chooses_unique_valid_same_key_path() {
3199        // Cross-signing can produce issuer certificates with the same subject
3200        // and public key. Trust policy, not the immediate signature edge, must
3201        // select the sole path that reaches a configured anchor.
3202        let trusted_root = rcgen::CertifiedIssuer::self_signed(
3203            generated_certificate_params("trusted cross-sign root", true),
3204            rcgen::KeyPair::generate().expect("trusted root key generation should succeed"),
3205        )
3206        .expect("trusted root should be self-signable");
3207        let untrusted_root = rcgen::CertifiedIssuer::self_signed(
3208            generated_certificate_params("untrusted cross-sign root", true),
3209            rcgen::KeyPair::generate().expect("untrusted root key generation should succeed"),
3210        )
3211        .expect("untrusted root should be self-signable");
3212        let shared_params = generated_certificate_params("shared cross-sign issuer", true);
3213        let shared_key =
3214            rcgen::KeyPair::generate().expect("shared issuer key generation should succeed");
3215        let trusted_intermediate = shared_params
3216            .signed_by(&shared_key, &trusted_root)
3217            .expect("trusted root should cross-sign the shared issuer key");
3218        let untrusted_intermediate = shared_params
3219            .signed_by(&shared_key, &untrusted_root)
3220            .expect("untrusted root should cross-sign the shared issuer key");
3221        let shared_issuer = rcgen::Issuer::from_params(&shared_params, &shared_key);
3222        let leaf = generated_certificate_params("cross-signed leaf", false)
3223            .signed_by(
3224                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3225                &shared_issuer,
3226            )
3227            .expect("shared issuer key should sign the leaf");
3228        let leaf_metadata = parse_x509_certificate(leaf.der())
3229            .expect("generated leaf should have supported metadata");
3230        let key_info = KeyInfo {
3231            sources: vec![KeyInfoSource::X509Data(X509DataInfo {
3232                subject_names: vec![leaf_metadata.subject_dn],
3233                ..X509DataInfo::default()
3234            })],
3235        };
3236        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
3237            trusted_certs: vec![trusted_root.der().to_vec()],
3238            lookup_certs: vec![
3239                leaf.der().to_vec(),
3240                untrusted_intermediate.der().to_vec(),
3241                trusted_intermediate.der().to_vec(),
3242                untrusted_root.der().to_vec(),
3243            ],
3244            ..KeyResolverConfig::default()
3245        });
3246
3247        let resolved = resolver
3248            .resolve_with_policy(
3249                Some(&key_info),
3250                SignatureAlgorithm::EcdsaSha256,
3251                &verification_policy_with_trust(chain_policy()),
3252            )
3253            .expect("the sole path to a configured anchor should be selected");
3254
3255        assert!(resolved.is_some());
3256    }
3257
3258    #[test]
3259    fn self_issued_rollover_continues_to_same_name_trusted_signer() {
3260        // Subject/issuer name equality does not prove self-signing: rollover
3261        // certificates may be issued by a distinct same-name trust anchor.
3262        let root = rcgen::CertifiedIssuer::self_signed(
3263            generated_certificate_params("rollover authority", true),
3264            rcgen::KeyPair::generate().expect("root key generation should succeed"),
3265        )
3266        .expect("root should be self-signable");
3267        let rollover_params = generated_certificate_params("rollover authority", true);
3268        let rollover_key =
3269            rcgen::KeyPair::generate().expect("rollover key generation should succeed");
3270        let rollover_certificate = rollover_params
3271            .signed_by(&rollover_key, &root)
3272            .expect("root should sign the same-name rollover certificate");
3273        let rollover_issuer = rcgen::Issuer::from_params(&rollover_params, &rollover_key);
3274        let leaf = generated_certificate_params("rollover leaf", false)
3275            .signed_by(
3276                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3277                &rollover_issuer,
3278            )
3279            .expect("rollover key should sign the leaf");
3280        let leaf_metadata =
3281            parse_x509_certificate(leaf.der()).expect("generated leaf metadata should parse");
3282        let key_info = KeyInfo {
3283            sources: vec![KeyInfoSource::X509Data(X509DataInfo {
3284                subject_names: vec![leaf_metadata.subject_dn],
3285                ..X509DataInfo::default()
3286            })],
3287        };
3288        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
3289            trusted_certs: vec![root.der().to_vec()],
3290            lookup_certs: vec![leaf.der().to_vec(), rollover_certificate.der().to_vec()],
3291            ..KeyResolverConfig::default()
3292        });
3293
3294        let resolved = resolver
3295            .resolve_with_policy(
3296                Some(&key_info),
3297                SignatureAlgorithm::EcdsaSha256,
3298                &verification_policy_with_trust(chain_policy()),
3299            )
3300            .expect("same-name rollover path must reach its configured signer");
3301
3302        assert!(resolved.is_some());
3303    }
3304
3305    #[test]
3306    fn x509_candidate_limit_counts_generated_partial_paths() {
3307        // A narrow DFS frontier can still generate unbounded partial paths over
3308        // time, so the resource limit must account for every generated state.
3309        let root = rcgen::CertifiedIssuer::self_signed(
3310            generated_certificate_params("candidate root", true),
3311            rcgen::KeyPair::generate().expect("root key generation should succeed"),
3312        )
3313        .expect("root should be self-signable");
3314        let intermediate = rcgen::CertifiedIssuer::signed_by(
3315            generated_certificate_params("candidate intermediate", true),
3316            rcgen::KeyPair::generate().expect("intermediate key generation should succeed"),
3317            &root,
3318        )
3319        .expect("root should sign the intermediate");
3320        let leaf = generated_certificate_params("candidate leaf", false)
3321            .signed_by(
3322                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3323                &intermediate,
3324            )
3325            .expect("intermediate should sign the leaf");
3326        let info = x509_info(
3327            vec![
3328                root.der().to_vec(),
3329                intermediate.der().to_vec(),
3330                leaf.der().to_vec(),
3331            ],
3332            2,
3333        );
3334
3335        assert!(matches!(
3336            build_x509_certificate_paths_to_trusted_prefix(
3337                &info,
3338                2,
3339                1,
3340                9,
3341                2,
3342                crate::provider::default_provider(),
3343                None,
3344            ),
3345            Err(X509ChainBuildError::AmbiguousIssuer)
3346        ));
3347    }
3348
3349    #[test]
3350    fn selector_resolved_leaf_disambiguates_same_subject_issuers_by_signature() {
3351        // Certificate renewal may leave multiple configured intermediates with
3352        // the same subject DN. The leaf signature, not pool order, identifies
3353        // the one issuer that belongs to the verification path.
3354        let mut root_params =
3355            rcgen::CertificateParams::new(Vec::new()).expect("empty root SAN list should be valid");
3356        root_params
3357            .distinguished_name
3358            .push(rcgen::DnType::CommonName, "shared-issuer root");
3359        root_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
3360        root_params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
3361        let root = rcgen::CertifiedIssuer::self_signed(
3362            root_params,
3363            rcgen::KeyPair::generate().expect("root key generation should succeed"),
3364        )
3365        .expect("root certificate should be self-signable");
3366
3367        let intermediate = |key: rcgen::KeyPair| {
3368            let mut params = rcgen::CertificateParams::new(Vec::new())
3369                .expect("empty intermediate SAN list should be valid");
3370            params
3371                .distinguished_name
3372                .push(rcgen::DnType::CommonName, "renewed intermediate");
3373            params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
3374            params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
3375            rcgen::CertifiedIssuer::signed_by(params, key, &root)
3376                .expect("root should sign the intermediate certificate")
3377        };
3378        let unrelated_intermediate = intermediate(
3379            rcgen::KeyPair::generate().expect("unrelated intermediate key generation should work"),
3380        );
3381        let signing_intermediate = intermediate(
3382            rcgen::KeyPair::generate().expect("signing intermediate key generation should work"),
3383        );
3384
3385        let mut leaf_params =
3386            rcgen::CertificateParams::new(Vec::new()).expect("empty leaf SAN list should be valid");
3387        leaf_params
3388            .distinguished_name
3389            .push(rcgen::DnType::CommonName, "same-subject leaf");
3390        let leaf = leaf_params
3391            .signed_by(
3392                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3393                &signing_intermediate,
3394            )
3395            .expect("the selected intermediate should sign the leaf certificate");
3396        let key_info_xml = concat!(
3397            "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">",
3398            "<X509Data><X509SubjectName>CN=same-subject leaf</X509SubjectName></X509Data>",
3399            "</KeyInfo>"
3400        );
3401        let document = roxmltree::Document::parse(key_info_xml)
3402            .expect("static selector KeyInfo should parse as XML");
3403        let key_info = super::super::parse_key_info(document.root_element())
3404            .expect("static selector KeyInfo should satisfy XMLDSig structure");
3405        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
3406            lookup_certs: vec![
3407                leaf.der().to_vec(),
3408                unrelated_intermediate.der().to_vec(),
3409                signing_intermediate.der().to_vec(),
3410            ],
3411            trusted_certs: vec![root.der().to_vec()],
3412            ..KeyResolverConfig::default()
3413        });
3414
3415        let resolved = resolver
3416            .resolve_with_policy(
3417                Some(&key_info),
3418                SignatureAlgorithm::EcdsaSha256,
3419                &verification_policy_with_trust(chain_policy()),
3420            )
3421            .expect("the leaf signature should select its unique same-subject issuer");
3422
3423        assert!(resolved.is_some());
3424    }
3425
3426    #[test]
3427    fn x509_path_builder_skips_policy_rejected_alternatives() {
3428        use crate::provider::X509SignatureAlgorithm;
3429        use std::collections::HashSet;
3430        // One cross-signed issuer has a forbidden parent-edge algorithm;
3431        // neither its position nor traversal order may hide a permitted path.
3432        let root = rcgen::CertifiedIssuer::self_signed(
3433            generated_certificate_params("policy root", true),
3434            rcgen::KeyPair::generate().expect("root key"),
3435        )
3436        .expect("root certificate");
3437        let alternate_root = rcgen::CertifiedIssuer::self_signed(
3438            generated_certificate_params("policy alternate root", true),
3439            rcgen::KeyPair::generate_for(&rcgen::PKCS_ECDSA_P384_SHA384)
3440                .expect("alternate root key"),
3441        )
3442        .expect("alternate root certificate");
3443        let shared_params = generated_certificate_params("policy shared issuer", true);
3444        let shared_key = rcgen::KeyPair::generate().expect("issuer key");
3445        let rejected = shared_params
3446            .signed_by(&shared_key, &alternate_root)
3447            .expect("cross-signed issuer");
3448        let shared = rcgen::CertifiedIssuer::signed_by(shared_params, shared_key, &root)
3449            .expect("issuer certificate");
3450        let leaf = generated_certificate_params("policy leaf", false)
3451            .signed_by(&rcgen::KeyPair::generate().expect("leaf key"), &shared)
3452            .expect("leaf certificate");
3453        let rejected = rejected.der().to_vec();
3454        let allowed = crate::policy::CertificateSignatureAlgorithms::Allowlist(HashSet::from([
3455            X509SignatureAlgorithm::Ecdsa(super::super::DigestAlgorithm::Sha256),
3456        ]));
3457        for rejected_first in [true, false] {
3458            let mut alternatives = vec![shared.der().to_vec(), rejected.clone()];
3459            if rejected_first {
3460                alternatives.reverse();
3461            }
3462            let permitted_index = if rejected_first { 3 } else { 2 };
3463            let info = x509_info(
3464                vec![
3465                    root.der().to_vec(),
3466                    leaf.der().to_vec(),
3467                    alternatives.remove(0),
3468                    alternatives.remove(0),
3469                    alternate_root.der().to_vec(),
3470                ],
3471                1,
3472            );
3473            assert_eq!(
3474                build_x509_certificate_paths_to_trusted_prefix(
3475                    &info,
3476                    1,
3477                    1,
3478                    4,
3479                    8,
3480                    crate::provider::default_provider(),
3481                    Some(&allowed),
3482                )
3483                .expect("forbidden alternative must not abort search"),
3484                vec![vec![1, permitted_index, 0]]
3485            );
3486        }
3487        let info = x509_info(
3488            vec![
3489                root.der().to_vec(),
3490                leaf.der().to_vec(),
3491                rejected,
3492                alternate_root.der().to_vec(),
3493            ],
3494            1,
3495        );
3496        assert!(matches!(
3497            build_x509_certificate_paths_to_trusted_prefix(
3498                &info,
3499                1,
3500                1,
3501                4,
3502                8,
3503                crate::provider::default_provider(),
3504                Some(&allowed),
3505            ),
3506            Err(X509ChainBuildError::Policy(_))
3507        ));
3508    }
3509
3510    #[test]
3511    fn x509_path_builder_skips_branch_local_unsupported_algorithms() {
3512        // An untrusted intermediate can share both the subject and public key
3513        // of the valid path while using an unsupported signature algorithm on
3514        // its own parent edge. That branch must not suppress the valid path.
3515        let root = rcgen::CertifiedIssuer::self_signed(
3516            generated_certificate_params("unsupported-edge root", true),
3517            rcgen::KeyPair::generate().expect("root key generation should succeed"),
3518        )
3519        .expect("root certificate should be self-signable");
3520        let signing_intermediate = rcgen::CertifiedIssuer::signed_by(
3521            generated_certificate_params("shared unsupported-edge issuer", true),
3522            rcgen::KeyPair::generate().expect("signing issuer key generation should succeed"),
3523            &root,
3524        )
3525        .expect("root should sign the intermediate certificate");
3526        let key_unsupported_intermediate = rcgen::CertifiedIssuer::signed_by(
3527            generated_certificate_params("shared unsupported-edge issuer", true),
3528            rcgen::KeyPair::generate().expect("unsupported issuer key generation should succeed"),
3529            &root,
3530        )
3531        .expect("root should sign the alternate intermediate certificate");
3532        let leaf = generated_certificate_params("unsupported-edge leaf", false)
3533            .signed_by(
3534                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3535                &signing_intermediate,
3536            )
3537            .expect("signing intermediate should sign the leaf");
3538
3539        let ordered = x509_info(
3540            vec![
3541                leaf.der().to_vec(),
3542                key_unsupported_intermediate.der().to_vec(),
3543                signing_intermediate.der().to_vec(),
3544                root.der().to_vec(),
3545            ],
3546            0,
3547        );
3548        let key_selective_provider = RejectSecondSha512Provider {
3549            sha512_calls: AtomicUsize::new(0),
3550            verification_calls: AtomicUsize::new(0),
3551            reject_verification_call: Some(0),
3552            rejected_verification_data: None,
3553        };
3554        assert_eq!(
3555            super::super::parse::build_x509_certificate_chain_from(
3556                &ordered,
3557                0,
3558                &key_selective_provider,
3559                None,
3560            )
3561            .expect("one unsupported issuer key must not suppress a usable candidate"),
3562            vec![0, 2, 3]
3563        );
3564
3565        let anchored_same_edge = x509_info(
3566            vec![
3567                root.der().to_vec(),
3568                leaf.der().to_vec(),
3569                key_unsupported_intermediate.der().to_vec(),
3570                signing_intermediate.der().to_vec(),
3571            ],
3572            1,
3573        );
3574        let first_candidate_unsupported = RejectSecondSha512Provider {
3575            sha512_calls: AtomicUsize::new(0),
3576            verification_calls: AtomicUsize::new(0),
3577            reject_verification_call: Some(0),
3578            rejected_verification_data: None,
3579        };
3580        assert_eq!(
3581            build_x509_certificate_paths_to_trusted_prefix(
3582                &anchored_same_edge,
3583                1,
3584                1,
3585                4,
3586                8,
3587                &first_candidate_unsupported,
3588                None,
3589            )
3590            .expect("a later same-DN issuer must survive an earlier provider capability miss"),
3591            vec![vec![1, 3, 0]]
3592        );
3593
3594        let mut unsupported_intermediate = signing_intermediate.der().to_vec();
3595        let ecdsa_sha256_oid = [0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02];
3596        let offsets = unsupported_intermediate
3597            .windows(ecdsa_sha256_oid.len())
3598            .enumerate()
3599            .filter_map(|(offset, window)| (window == ecdsa_sha256_oid).then_some(offset))
3600            .collect::<Vec<_>>();
3601        assert_eq!(
3602            offsets.len(),
3603            2,
3604            "certificate must repeat its signature OID"
3605        );
3606        for offset in offsets {
3607            unsupported_intermediate[offset + ecdsa_sha256_oid.len() - 1] = 0x05;
3608        }
3609
3610        let anchored = x509_info(
3611            vec![
3612                root.der().to_vec(),
3613                leaf.der().to_vec(),
3614                signing_intermediate.der().to_vec(),
3615                unsupported_intermediate,
3616            ],
3617            1,
3618        );
3619        assert_eq!(
3620            build_x509_certificate_paths_to_trusted_prefix(
3621                &anchored,
3622                1,
3623                1,
3624                4,
3625                8,
3626                crate::provider::default_provider(),
3627                None,
3628            )
3629            .expect("a branch-local provider gap must not abort path enumeration"),
3630            vec![vec![1, 2, 0]]
3631        );
3632
3633        let unsupported_only = x509_info(
3634            vec![
3635                root.der().to_vec(),
3636                leaf.der().to_vec(),
3637                anchored.certificates[3].clone(),
3638            ],
3639            1,
3640        );
3641        assert!(matches!(
3642            build_x509_certificate_paths_to_trusted_prefix(
3643                &unsupported_only,
3644                1,
3645                1,
3646                4,
3647                8,
3648                crate::provider::default_provider(),
3649                None,
3650            ),
3651            Err(X509ChainBuildError::UnsupportedSignatureAlgorithm { ref oid })
3652                if oid == "1.2.840.10045.4.3.5"
3653        ));
3654    }
3655
3656    #[test]
3657    fn selector_resolved_certificate_preserves_supplied_crls() {
3658        let selector = "<KeyInfo><X509Data><X509SubjectName>CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509SubjectName><X509CRL>CRL_PLACEHOLDER</X509CRL></X509Data></KeyInfo>";
3659        let crl = crl_der(include_str!(
3660            "../../tests/fixtures/keys/rsa/rsa-2048-cert-revoked-crl.pem"
3661        ));
3662        let (_, parsed_crl) =
3663            x509_parser::revocation_list::CertificateRevocationList::from_der(&crl)
3664                .expect("tracked CRL must parse");
3665        let crl_signed_data = parsed_crl.tbs_cert_list.as_ref().to_vec();
3666        let xml = replace_unprefixed_key_info(
3667            RSA_KEY_VALUE_SIGNATURE,
3668            &selector.replace("CRL_PLACEHOLDER", &STANDARD.encode(&crl)),
3669        );
3670        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
3671            lookup_certs: vec![certificate_der(include_str!(
3672                "../../tests/fixtures/keys/rsa/rsa-2048-cert.pem"
3673            ))],
3674            trusted_certs: vec![
3675                certificate_der(include_str!("../../tests/fixtures/keys/ca2cert.pem")),
3676                certificate_der(include_str!("../../tests/fixtures/keys/cacert.pem")),
3677            ],
3678            ..KeyResolverConfig::default()
3679        });
3680        let policy = verification_policy_with_trust(crate::policy::KeyTrustPolicy {
3681            check_crls: true,
3682            max_x509_chain_depth: 3,
3683            ..chain_policy_at(
3684                SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(1_773_964_800),
3685            )
3686        });
3687
3688        let error = super::super::VerifyContext::new()
3689            .policy(policy.clone())
3690            .key_resolver(&resolver)
3691            .verify(&xml)
3692            .expect_err("selector lookup must retain and enforce the supplied CRL");
3693        // RFC 10007 section 4: the tracked v3 CA lacks KeyUsage, so CRL
3694        // authorization now fails before its revoked-serial list is consumed.
3695        // https://www.rfc-editor.org/rfc/rfc10007.html#section-4
3696        assert!(matches!(
3697            error,
3698            DsigError::KeyResolution(KeyResolutionError::Chain(
3699                super::super::X509ChainError::InvalidKeyUsage {
3700                    position: 1,
3701                    required: "cRLSign"
3702                }
3703            ))
3704        ));
3705
3706        // Match the exact TBSCertList bytes so earlier certificate-edge
3707        // verification succeeds and the provider rejection occurs at CRL
3708        // authentication itself.
3709        let provider = RejectSecondSha512Provider {
3710            sha512_calls: AtomicUsize::new(0),
3711            verification_calls: AtomicUsize::new(0),
3712            reject_verification_call: None,
3713            rejected_verification_data: Some(crl_signed_data),
3714        };
3715        let error = super::super::VerifyContext::new()
3716            .policy(policy)
3717            .key_resolver(&resolver)
3718            .provider(&provider)
3719            .verify(&xml)
3720            .expect_err("CRL authentication must retain the operation provider");
3721        assert!(matches!(
3722            error,
3723            DsigError::KeyResolution(KeyResolutionError::Chain(
3724                super::super::X509ChainError::Provider(_)
3725            ))
3726        ));
3727    }
3728
3729    #[test]
3730    fn resolver_rejects_revoked_leaf_with_authorized_crl_issuer() {
3731        // Keep revoked-serial enforcement covered independently from the cRLSign authorization
3732        // failure exercised by the legacy fixture above.
3733        let (mut info, root) = authorized_revoked_chain();
3734        let issuer = info.certificates[1].clone();
3735        info.certificates.truncate(1);
3736        info.parsed_certificates.truncate(1);
3737        let key_info = KeyInfo {
3738            sources: vec![KeyInfoSource::X509Data(info)],
3739        };
3740        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
3741            lookup_certs: vec![issuer],
3742            trusted_certs: vec![root],
3743            ..KeyResolverConfig::default()
3744        });
3745        let policy = verification_policy_with_trust(crate::policy::KeyTrustPolicy {
3746            check_crls: true,
3747            max_x509_chain_depth: 3,
3748            ..chain_policy_at(
3749                SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(1_773_964_800),
3750            )
3751        });
3752
3753        let error = match resolver.resolve_with_policy(
3754            Some(&key_info),
3755            SignatureAlgorithm::EcdsaSha256,
3756            &policy,
3757        ) {
3758            Ok(_) => panic!("an authenticated CRL must reject its revoked leaf"),
3759            Err(error) => error,
3760        };
3761        assert!(matches!(
3762            error,
3763            DsigError::KeyResolution(KeyResolutionError::Chain(
3764                super::super::X509ChainError::Revoked(0)
3765            ))
3766        ));
3767    }
3768
3769    #[test]
3770    fn resolves_each_x509_selector_from_configured_certificates() {
3771        // Every selector form documented by KeyInfo must independently locate
3772        // the same configured RSA certificate without embedded key material.
3773        let selectors = [
3774            "<X509SubjectName>CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509SubjectName>",
3775            "<X509SubjectName>CN=  test   key rsa-2048  ,O=xml security library (HTTP://WWW.ALEKSEY.COM/XMLSEC),ST=california,C=us</X509SubjectName>",
3776            "<X509IssuerSerial><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><X509SerialNumber>680572598617295163017172295025714171905498632019</X509SerialNumber></X509IssuerSerial>",
3777            "<X509SKI>bcOXN/nsVl8GatRbcKrPbzIbw0Y=</X509SKI>",
3778        ];
3779        let configured_certificate = certificate_der(include_str!(
3780            "../../tests/fixtures/keys/rsa/rsa-2048-cert.pem"
3781        ));
3782
3783        for selector in selectors {
3784            let key_info = format!("<KeyInfo><X509Data>{selector}</X509Data></KeyInfo>");
3785            let xml = replace_unprefixed_key_info(RSA_KEY_VALUE_SIGNATURE, &key_info);
3786            let resolver = DefaultKeyResolver::new(KeyResolverConfig {
3787                lookup_certs: vec![configured_certificate.clone()],
3788                ..KeyResolverConfig::default()
3789            });
3790            let result = super::super::VerifyContext::new()
3791                .policy(cryptographic_policy())
3792                .key_resolver(&resolver)
3793                .verify(&xml)
3794                .expect("X509 selector should resolve configured certificate");
3795
3796            assert_eq!(result.status, super::super::DsigStatus::Valid);
3797        }
3798    }
3799
3800    #[test]
3801    fn resolves_configured_chain_selectors_across_certificates() {
3802        // Selector categories may identify different members of one configured
3803        // chain; the unique leaf remains the signing certificate.
3804        let key_info = r#"<KeyInfo><X509Data><X509SubjectName>CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509SubjectName><X509SKI>0X0XrEVCio75sBcl1TxymJ2IOiU=</X509SKI></X509Data></KeyInfo>"#;
3805        let xml = replace_unprefixed_key_info(RSA_KEY_VALUE_SIGNATURE, key_info);
3806        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
3807            lookup_certs: vec![
3808                certificate_der(include_str!(
3809                    "../../tests/fixtures/keys/rsa/rsa-2048-cert.pem"
3810                )),
3811                certificate_der(include_str!("../../tests/fixtures/keys/ca2cert.pem")),
3812            ],
3813            ..KeyResolverConfig::default()
3814        });
3815        let result = super::super::VerifyContext::new()
3816            .policy(cryptographic_policy())
3817            .key_resolver(&resolver)
3818            .verify(&xml)
3819            .expect("selectors across one configured chain should resolve its leaf");
3820
3821        assert_eq!(result.status, super::super::DsigStatus::Valid);
3822    }
3823
3824    #[test]
3825    fn selectors_must_all_match_the_selected_certificate_path() {
3826        // Selector categories may identify different certificates only when
3827        // those certificates belong to the one path chosen for the signer.
3828        let signing_certificate = certificate_der(include_str!(
3829            "../../tests/fixtures/keys/rsa/rsa-2048-cert.pem"
3830        ));
3831        let issuer_certificate =
3832            certificate_der(include_str!("../../tests/fixtures/keys/ca2cert.pem"));
3833        let unrelated = generated_certificate_params("unrelated selector certificate", false)
3834            .self_signed(
3835                &rcgen::KeyPair::generate().expect("unrelated key generation should succeed"),
3836            )
3837            .expect("unrelated certificate should be self-signable")
3838            .der()
3839            .to_vec();
3840        let digest = crate::provider::default_provider()
3841            .digest(super::super::DigestAlgorithm::Sha256, &unrelated)
3842            .expect("SHA-256 selector digest must be available");
3843        let key_info_xml = format!(
3844            "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\" xmlns:dsig11=\"http://www.w3.org/2009/xmldsig11#\"><X509Data><X509SubjectName>CN=Test Key rsa-2048,O=XML Security Library (http://www.aleksey.com/xmlsec),ST=California,C=US</X509SubjectName><dsig11:X509Digest Algorithm=\"http://www.w3.org/2001/04/xmlenc#sha256\">{}</dsig11:X509Digest></X509Data></KeyInfo>",
3845            STANDARD.encode(digest)
3846        );
3847        let document = roxmltree::Document::parse(&key_info_xml)
3848            .expect("generated selector KeyInfo must be XML");
3849        let key_info = super::super::parse_key_info(document.root_element())
3850            .expect("generated selector KeyInfo must be structurally valid");
3851        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
3852            lookup_certs: vec![signing_certificate, issuer_certificate, unrelated],
3853            ..KeyResolverConfig::default()
3854        });
3855
3856        assert!(
3857            resolver
3858                .resolve(Some(&key_info), SignatureAlgorithm::RsaSha256)
3859                .expect("disjoint selector matches are a key miss")
3860                .is_none()
3861        );
3862    }
3863
3864    #[test]
3865    fn unmatched_x509_selector_does_not_resolve() {
3866        // A selector mismatch must not fall back to arbitrary configured key material.
3867        let key_info = "<KeyInfo><X509Data><X509SubjectName>CN=not-the-signer</X509SubjectName></X509Data></KeyInfo>";
3868        let xml = replace_unprefixed_key_info(RSA_KEY_VALUE_SIGNATURE, key_info);
3869        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
3870            lookup_certs: vec![certificate_der(include_str!(
3871                "../../tests/fixtures/keys/rsa/rsa-2048-cert.pem"
3872            ))],
3873            ..KeyResolverConfig::default()
3874        });
3875        let result = super::super::VerifyContext::new()
3876            .key_resolver(&resolver)
3877            .verify(&xml)
3878            .expect("an unmatched selector is a key miss, not a parser failure");
3879
3880        assert!(matches!(
3881            result.status,
3882            super::super::DsigStatus::Invalid(super::super::FailureReason::KeyNotFound)
3883        ));
3884    }
3885
3886    #[test]
3887    fn overlapping_trusted_and_lookup_certificate_preserves_trust() {
3888        // One physical certificate appearing in both pools is one candidate;
3889        // deduplication must retain the stronger trusted classification.
3890        let certificate = certificate_der(RSA_4096_CERTIFICATE);
3891        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
3892            trusted_certs: vec![certificate.clone()],
3893            lookup_certs: vec![certificate],
3894            ..KeyResolverConfig::default()
3895        });
3896        let result = super::super::VerifyContext::new()
3897            .policy(verification_policy_with_trust(chain_policy_at(
3898                fixture_certificate_time(),
3899            )))
3900            .key_resolver(&resolver)
3901            .verify(&x509_signature_with_leaf_subject())
3902            .expect("trusted/lookup overlap must resolve as one trusted candidate");
3903
3904        assert_eq!(result.status, super::super::DsigStatus::Valid);
3905        let super::super::KeyTrustEvidence::ValidatedX509(evidence) = result.key_trust else {
3906            panic!("successful path validation must retain typed evidence");
3907        };
3908        assert_eq!(evidence.verification_time(), fixture_certificate_time());
3909        assert!(!evidence.revocation_checked());
3910        assert_eq!(evidence.certificate_fingerprints().len(), 1);
3911        assert_eq!(
3912            evidence.anchor_fingerprint(),
3913            &evidence.certificate_fingerprints()[0]
3914        );
3915
3916        // The batch entry point must retain the actual validated path rather
3917        // than reducing trusted/lookup overlap to a boolean or XML claim.
3918        let document = crate::XmlDocument::parse(x509_signature_with_leaf_subject())
3919            .expect("signed certificate selector fixture must parse");
3920        let report = super::super::VerifyContext::new()
3921            .policy(verification_policy_with_trust(chain_policy_at(
3922                fixture_certificate_time(),
3923            )))
3924            .key_resolver(&resolver)
3925            .verify_all(&document)
3926            .expect("batch verification must preserve the validated path");
3927        assert!(
3928            report
3929                .accepted(&document)
3930                .expect("current document acceptance")
3931        );
3932        assert_eq!(
3933            report.signatures()[0]
3934                .result()
3935                .as_ref()
3936                .expect("the individually processed Signature must verify")
3937                .key_trust,
3938            super::super::KeyTrustEvidence::ValidatedX509(evidence)
3939        );
3940    }
3941
3942    #[test]
3943    fn distinct_x509_selector_matches_remain_ambiguous() {
3944        // Deduplication is identity-based, not selector-based: two distinct
3945        // certificates with the same subject remain separate candidates.
3946        let certificate = || {
3947            generated_certificate_params("ambiguous selector", false)
3948                .self_signed(
3949                    &rcgen::KeyPair::generate().expect("test key generation should succeed"),
3950                )
3951                .expect("test certificate should be self-signable")
3952                .der()
3953                .to_vec()
3954        };
3955        let xml = replace_unprefixed_key_info(
3956            X509_DIGEST_SIGNATURE,
3957            "<KeyInfo><X509Data><X509SubjectName>CN=ambiguous selector</X509SubjectName></X509Data></KeyInfo>",
3958        );
3959        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
3960            lookup_certs: vec![certificate(), certificate()],
3961            ..KeyResolverConfig::default()
3962        });
3963        let error = super::super::VerifyContext::new()
3964            .key_resolver(&resolver)
3965            .verify(&xml)
3966            .expect_err("distinct selector matches must fail closed");
3967
3968        assert!(matches!(
3969            error,
3970            DsigError::KeyResolution(KeyResolutionError::AmbiguousCertificate)
3971        ));
3972    }
3973
3974    #[test]
3975    fn unsupported_x509_digest_selector_fails_closed() {
3976        // Unknown digest URIs must not be treated as a normal key miss because
3977        // that would silently weaken the caller's explicit selector policy.
3978        let key_info = "<KeyInfo xmlns:dsig11=\"http://www.w3.org/2009/xmldsig11#\"><X509Data><dsig11:X509Digest Algorithm=\"urn:unsupported\">AQ==</dsig11:X509Digest></X509Data></KeyInfo>";
3979        let xml = replace_unprefixed_key_info(RSA_KEY_VALUE_SIGNATURE, key_info);
3980        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
3981            lookup_certs: vec![certificate_der(include_str!(
3982                "../../tests/fixtures/keys/rsa/rsa-2048-cert.pem"
3983            ))],
3984            ..KeyResolverConfig::default()
3985        });
3986        let error = super::super::VerifyContext::new()
3987            .key_resolver(&resolver)
3988            .verify(&xml)
3989            .expect_err("unsupported X509Digest algorithm must fail closed");
3990
3991        assert!(matches!(
3992            error,
3993            DsigError::KeyResolution(KeyResolutionError::UnsupportedDigestAlgorithm(uri))
3994                if uri == "urn:unsupported"
3995        ));
3996    }
3997
3998    #[test]
3999    fn x509_digest_selector_uses_operation_provider() {
4000        // The SHA-512 selector is distinct from the SHA-256 reference digest,
4001        // so only provider-aware key selection can surface this rejection.
4002        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
4003            lookup_certs: vec![certificate_der(RSA_4096_CERTIFICATE)],
4004            trusted_certs: vec![
4005                certificate_der(include_str!("../../tests/fixtures/keys/ca2cert.pem")),
4006                certificate_der(include_str!("../../tests/fixtures/keys/cacert.pem")),
4007            ],
4008            ..KeyResolverConfig::default()
4009        });
4010        let provider = RejectSecondSha512Provider {
4011            sha512_calls: AtomicUsize::new(0),
4012            verification_calls: AtomicUsize::new(0),
4013            reject_verification_call: None,
4014            rejected_verification_data: None,
4015        };
4016        let error = super::super::VerifyContext::new()
4017            .key_resolver(&resolver)
4018            .provider(&provider)
4019            .verify(X509_DIGEST_SIGNATURE)
4020            .expect_err("X509Digest selection must use the operation provider");
4021
4022        assert!(
4023            matches!(
4024                error,
4025                DsigError::Provider(crate::provider::ProviderError::Unsupported {
4026                    operation: crate::provider::ProviderOperation::Digest,
4027                    algorithm: Some(ref uri),
4028                }) if uri == super::super::DigestAlgorithm::Sha512.uri()
4029            ),
4030            "unexpected error: {error:?}"
4031        );
4032    }
4033
4034    #[test]
4035    fn resolves_named_key_end_to_end() {
4036        // KeyName lookup must preserve the same cryptographic result as embedded X509Data.
4037        let xml = replace_key_info(
4038            SIGNED_SAML,
4039            "<ds:KeyInfo><ds:KeyName>idp-signing</ds:KeyName></ds:KeyInfo>",
4040        );
4041        let mut config = KeyResolverConfig::default();
4042        config.named_keys.insert(
4043            "idp-signing".into(),
4044            VerificationKey {
4045                algorithm: SignatureAlgorithm::EcdsaSha256,
4046                public_key_bytes: public_key_der(SAML_PUBLIC_KEY),
4047                certificate_der: None,
4048                name: Some("idp-signing".into()),
4049            },
4050        );
4051        let resolver = DefaultKeyResolver::new(config);
4052        let result = super::super::VerifyContext::new()
4053            .key_resolver(&resolver)
4054            .verify(&xml)
4055            .expect("named key should resolve");
4056
4057        assert_eq!(result.status, super::super::DsigStatus::Valid);
4058    }
4059
4060    #[test]
4061    fn resolves_der_encoded_key_end_to_end() {
4062        // DSig 1.1 DEREncodedKeyValue must feed the same SPKI verifier path.
4063        let encoded = STANDARD.encode(public_key_der(SAML_PUBLIC_KEY));
4064        let xml = replace_key_info(
4065            SIGNED_SAML,
4066            &format!(
4067                "<ds:KeyInfo><dsig11:DEREncodedKeyValue xmlns:dsig11=\"http://www.w3.org/2009/xmldsig11#\">{encoded}</dsig11:DEREncodedKeyValue></ds:KeyInfo>"
4068            ),
4069        );
4070        let resolver = DefaultKeyResolver::default();
4071        let result = super::super::VerifyContext::new()
4072            .policy(cryptographic_policy())
4073            .key_resolver(&resolver)
4074            .verify(&xml)
4075            .expect("DER key should resolve");
4076
4077        assert_eq!(result.status, super::super::DsigStatus::Valid);
4078    }
4079
4080    #[test]
4081    fn resolves_rsa_key_value_end_to_end() {
4082        // Embedded CryptoBinary parameters must verify the original RSA-2048 donor signature.
4083        let public_key = rsa::RsaPublicKey::from_public_key_pem(RSA_PUBLIC_KEY)
4084            .expect("fixture must contain an RSA public key");
4085        let (modulus, exponent) = rsa_key_value_parts(&public_key);
4086        let key_info = format!(
4087            "<KeyInfo><KeyValue><RSAKeyValue><Modulus>{}</Modulus><Exponent>{}</Exponent></RSAKeyValue></KeyValue></KeyInfo>",
4088            modulus, exponent,
4089        );
4090        let xml = replace_unprefixed_key_info(RSA_KEY_VALUE_SIGNATURE, &key_info);
4091        let resolver = DefaultKeyResolver::default();
4092        let result = super::super::VerifyContext::new()
4093            .policy(cryptographic_policy())
4094            .key_resolver(&resolver)
4095            .verify(&xml)
4096            .expect("RSAKeyValue should resolve");
4097
4098        assert_eq!(result.status, super::super::DsigStatus::Valid);
4099    }
4100
4101    #[test]
4102    fn rsa_key_value_rejects_legacy_weak_modulus() {
4103        // The secure policy rejects legacy RSA-SHA1 independently of whether
4104        // the capable key came from RSAKeyValue, DER, X.509, or KeyName.
4105        let resolver = DefaultKeyResolver::default();
4106        let error = super::super::VerifyContext::new()
4107            .key_resolver(&resolver)
4108            .verify(LEGACY_RSA_KEY_VALUE_SIGNATURE)
4109            .expect_err("context policy must override permissive resolver defaults");
4110
4111        assert!(matches!(
4112            error,
4113            DsigError::Policy(crate::policy::PolicyViolation::Algorithm {
4114                operation: "verification",
4115                ..
4116            })
4117        ));
4118    }
4119
4120    #[test]
4121    fn operation_policy_rejects_disabled_embedded_key_source() {
4122        // Resolver-owned key material cannot override the operation snapshot's
4123        // decision about which attacker-controlled KeyInfo forms are trusted.
4124        let key_info = KeyInfo {
4125            sources: vec![KeyInfoSource::KeyValue(KeyValueInfo::Rsa {
4126                modulus: vec![0x80; 256],
4127                exponent: vec![1, 0, 1],
4128            })],
4129        };
4130        let mut policy = crate::policy::VerificationPolicy::default();
4131        policy.key_sources.key_value = false;
4132
4133        let error = match DefaultKeyResolver::default().resolve_with_policy(
4134            Some(&key_info),
4135            SignatureAlgorithm::RsaSha256,
4136            &policy,
4137        ) {
4138            Ok(_) => panic!("disabled KeyValue must fail before key construction"),
4139            Err(error) => error,
4140        };
4141
4142        assert!(matches!(
4143            error,
4144            DsigError::Policy(crate::policy::PolicyViolation::KeyTrust {
4145                reason: "KeyValue key sources are disabled"
4146            })
4147        ));
4148    }
4149
4150    #[test]
4151    fn operation_policy_preflights_every_key_info_source_before_resolution() {
4152        // A permitted source resolving first must not hide a later source that
4153        // the immutable operation policy rejects.
4154        let mut config = KeyResolverConfig::default();
4155        config.named_keys.insert(
4156            "idp-signing".into(),
4157            VerificationKey {
4158                algorithm: SignatureAlgorithm::EcdsaSha256,
4159                public_key_bytes: public_key_der(SAML_PUBLIC_KEY),
4160                certificate_der: None,
4161                name: Some("idp-signing".into()),
4162            },
4163        );
4164        let resolver = DefaultKeyResolver::new(config);
4165        let mut policy = crate::policy::VerificationPolicy::default();
4166        policy.key_sources.x509_data = false;
4167
4168        for sources in [
4169            vec![
4170                KeyInfoSource::KeyName("idp-signing".into()),
4171                KeyInfoSource::X509Data(X509DataInfo::default()),
4172            ],
4173            vec![
4174                KeyInfoSource::X509Data(X509DataInfo::default()),
4175                KeyInfoSource::KeyName("idp-signing".into()),
4176            ],
4177        ] {
4178            let error = match resolver.resolve_with_policy(
4179                Some(&KeyInfo { sources }),
4180                SignatureAlgorithm::EcdsaSha256,
4181                &policy,
4182            ) {
4183                Ok(_) => panic!("source order must not hide disabled X509Data"),
4184                Err(error) => error,
4185            };
4186
4187            assert!(matches!(
4188                error,
4189                DsigError::Policy(crate::policy::PolicyViolation::KeyTrust {
4190                    reason: "X509Data key sources are disabled"
4191                })
4192            ));
4193        }
4194    }
4195
4196    #[test]
4197    fn operation_policy_bounds_ordered_key_info_candidates() {
4198        // The candidate ceiling belongs to the complete verification snapshot:
4199        // neither a first source nor fallback to a later source may bypass it.
4200        let mut config = KeyResolverConfig::default();
4201        config.named_keys.insert(
4202            "idp-signing".into(),
4203            VerificationKey {
4204                algorithm: SignatureAlgorithm::EcdsaSha256,
4205                public_key_bytes: public_key_der(SAML_PUBLIC_KEY),
4206                certificate_der: None,
4207                name: Some("idp-signing".into()),
4208            },
4209        );
4210        let resolver = DefaultKeyResolver::new(config);
4211        let key_info = KeyInfo {
4212            sources: vec![
4213                KeyInfoSource::KeyValue(KeyValueInfo::Ec {
4214                    curve_oid: "1.3.132.0.35".into(),
4215                    public_key: vec![4],
4216                }),
4217                KeyInfoSource::KeyName("idp-signing".into()),
4218            ],
4219        };
4220
4221        for maximum in [0, 1] {
4222            let mut policy = crate::policy::VerificationPolicy::default();
4223            policy.resources.max_key_candidates = maximum;
4224            let error = match resolver.resolve_with_policy(
4225                Some(&key_info),
4226                SignatureAlgorithm::EcdsaSha256,
4227                &policy,
4228            ) {
4229                Ok(_) => panic!("candidate ceiling {maximum} must stop resolution"),
4230                Err(error) => error,
4231            };
4232            assert!(matches!(
4233                error,
4234                DsigError::Policy(crate::policy::PolicyViolation::ResourceLimit {
4235                    resource: crate::policy::resource_name::KEY_CANDIDATES,
4236                    maximum: observed,
4237                    actual,
4238                }) if observed == maximum && actual == maximum + 1
4239            ));
4240        }
4241
4242        let mut policy = crate::policy::VerificationPolicy::default();
4243        policy.resources.max_key_candidates = 2;
4244        assert!(
4245            resolver
4246                .resolve_with_policy(Some(&key_info), SignatureAlgorithm::EcdsaSha256, &policy,)
4247                .expect("two allowed attempts must reach the named key")
4248                .is_some()
4249        );
4250    }
4251
4252    #[test]
4253    fn operation_policy_bounds_configured_x509_selector_candidates() {
4254        // One X509Data selector can fan out across the resolver-owned store.
4255        // Every distinct certificate inspected is candidate work, rather than
4256        // the complete store counting as one KeyInfo source.
4257        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
4258            lookup_certs: vec![
4259                certificate_der(include_str!("../../tests/fixtures/keys/ca2cert.pem")),
4260                certificate_der(RSA_4096_CERTIFICATE),
4261            ],
4262            ..KeyResolverConfig::default()
4263        });
4264        let mut policy = crate::policy::VerificationPolicy::default();
4265        policy.resources.max_key_candidates = 1;
4266
4267        let error = super::super::VerifyContext::new()
4268            .policy(policy)
4269            .key_resolver(&resolver)
4270            .verify(&x509_signature_with_leaf_subject())
4271            .expect_err("the second configured certificate must exceed the candidate budget");
4272
4273        assert!(matches!(
4274            error,
4275            DsigError::Policy(crate::policy::PolicyViolation::ResourceLimit {
4276                resource: crate::policy::resource_name::KEY_CANDIDATES,
4277                maximum: 1,
4278                actual: 2,
4279            })
4280        ));
4281    }
4282
4283    #[test]
4284    fn configured_crls_are_bounded_before_der_parsing() {
4285        // Invalid DER must not be parsed when its size or count already violates policy.
4286        let key_info = KeyInfo {
4287            sources: vec![KeyInfoSource::X509Data(X509DataInfo {
4288                subject_names: vec!["CN=selected".into()],
4289                ..X509DataInfo::default()
4290            })],
4291        };
4292        let mut policy = crate::policy::VerificationPolicy::default();
4293        policy.key_trust.check_crls = true;
4294        policy.key_trust.verify_x509_chains = true;
4295        policy.resources.max_external_resource_bytes = 4;
4296        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
4297            crls: vec![vec![0; 5]],
4298            ..KeyResolverConfig::default()
4299        });
4300        let error = resolver
4301            .resolve_with_policy(Some(&key_info), SignatureAlgorithm::RsaSha256, &policy)
4302            .err()
4303            .expect("oversized CRL must fail before DER parsing");
4304        assert!(
4305            matches!(
4306                error,
4307                DsigError::Policy(crate::policy::PolicyViolation::ResourceLimit {
4308                    resource: crate::policy::resource_name::EXTERNAL_RESOURCE_BYTES,
4309                    ..
4310                })
4311            ),
4312            "{error:?}"
4313        );
4314
4315        policy.resources.max_external_resource_bytes = 4;
4316        policy.resources.max_external_resource_total_bytes = 7;
4317        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
4318            crls: vec![vec![0; 4], vec![0; 4]],
4319            ..KeyResolverConfig::default()
4320        });
4321        let error = resolver
4322            .resolve_with_policy(Some(&key_info), SignatureAlgorithm::RsaSha256, &policy)
4323            .err()
4324            .expect("aggregate CRL bytes must fail before DER parsing");
4325        assert!(matches!(
4326            error,
4327            DsigError::Policy(crate::policy::PolicyViolation::ResourceLimit {
4328                resource: crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
4329                ..
4330            })
4331        ));
4332
4333        policy.resources.max_external_resource_total_bytes = 8;
4334        policy.resources.max_key_candidates = 1;
4335        let error = resolver
4336            .resolve_with_policy(Some(&key_info), SignatureAlgorithm::RsaSha256, &policy)
4337            .err()
4338            .expect("CRL candidate count must fail before DER parsing");
4339        assert!(matches!(
4340            error,
4341            DsigError::Policy(crate::policy::PolicyViolation::ResourceLimit {
4342                resource: crate::policy::resource_name::KEY_CANDIDATES,
4343                ..
4344            })
4345        ));
4346    }
4347
4348    #[test]
4349    fn configured_certificates_and_crls_share_external_byte_budget() {
4350        // A stricter operation policy must account for all resolver-owned
4351        // material on a selector path, even when the resolver was built under
4352        // a broader policy.
4353        let key_info = KeyInfo {
4354            sources: vec![KeyInfoSource::X509Data(X509DataInfo {
4355                subject_names: vec!["CN=selected".into()],
4356                ..X509DataInfo::default()
4357            })],
4358        };
4359        let mut policy = crate::policy::VerificationPolicy::default();
4360        policy.key_trust.check_crls = true;
4361        policy.key_trust.verify_x509_chains = true;
4362        policy.resources.max_external_resource_bytes = 8;
4363        policy.resources.max_external_resource_total_bytes = 12;
4364        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
4365            trusted_certs: vec![vec![0; 8]],
4366            crls: vec![vec![0; 8]],
4367            ..KeyResolverConfig::default()
4368        });
4369        let error = resolver
4370            .resolve_with_policy(Some(&key_info), SignatureAlgorithm::RsaSha256, &policy)
4371            .err()
4372            .expect("combined external material exceeds the operation limit");
4373        assert!(matches!(
4374            error,
4375            DsigError::Policy(crate::policy::PolicyViolation::ResourceLimit {
4376                resource: crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
4377                ..
4378            })
4379        ));
4380    }
4381
4382    #[test]
4383    fn embedded_and_configured_x509_share_one_byte_budget() {
4384        // Both halves fit separately; their combined live material must fail
4385        // before attempting to parse the deliberately invalid certificate DER.
4386        let mut info = KeyInfo {
4387            sources: vec![KeyInfoSource::X509Data(X509DataInfo {
4388                certificates: vec![vec![0; 8]],
4389                certificate_chain: vec![0],
4390                crls: vec![vec![0; 2]],
4391                ..X509DataInfo::default()
4392            })],
4393        };
4394        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
4395            crls: vec![vec![0; 8]],
4396            ..KeyResolverConfig::default()
4397        });
4398        let mut policy = crate::policy::VerificationPolicy::default();
4399        policy.key_trust.check_crls = true;
4400        policy.key_trust.verify_x509_chains = true;
4401        policy.resources.max_external_resource_total_bytes = 17;
4402        let error = resolver
4403            .resolve_with_policy(Some(&info), SignatureAlgorithm::RsaSha256, &policy)
4404            .err()
4405            .expect("combined material must be rejected");
4406        assert!(
4407            matches!(
4408                error,
4409                DsigError::Policy(crate::policy::PolicyViolation::ResourceLimit {
4410                    resource: crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
4411                    actual: 18,
4412                    ..
4413                })
4414            ),
4415            "{error:?}"
4416        );
4417        policy.resources.max_external_resource_total_bytes = 18;
4418        let error = resolver
4419            .resolve_with_policy(Some(&info), SignatureAlgorithm::RsaSha256, &policy)
4420            .err()
4421            .expect("exact byte allowance reaches DER parsing");
4422        assert!(
4423            matches!(
4424                error,
4425                DsigError::KeyResolution(KeyResolutionError::InvalidCertificate)
4426            ),
4427            "{error:?}"
4428        );
4429        // A prior lookup source may charge configured CRLs once, but cannot
4430        // suppress the combined-byte preflight of a later embedded source.
4431        info.sources.insert(
4432            0,
4433            KeyInfoSource::X509Data(X509DataInfo {
4434                subject_names: vec!["CN=absent".into()],
4435                ..X509DataInfo::default()
4436            }),
4437        );
4438        policy.resources.max_external_resource_total_bytes = 17;
4439        policy.resources.max_key_candidates = 2;
4440        assert!(matches!(
4441            resolver.resolve_with_policy(Some(&info), SignatureAlgorithm::RsaSha256, &policy),
4442            Err(DsigError::Policy(
4443                crate::policy::PolicyViolation::ResourceLimit {
4444                    resource: crate::policy::resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
4445                    actual: 18,
4446                    ..
4447                }
4448            ))
4449        ));
4450    }
4451
4452    #[test]
4453    fn direct_certificate_does_not_charge_unused_configured_store() {
4454        // A direct embedded certificate bypasses configured lookup material
4455        // when chain verification is disabled.
4456        let certificate = certificate_der(RSA_4096_CERTIFICATE);
4457        let key_info = KeyInfo {
4458            sources: vec![KeyInfoSource::X509Data(X509DataInfo {
4459                certificates: vec![certificate],
4460                certificate_chain: vec![0],
4461                subject_names: vec!["CN=unused-selector".into()],
4462                ..X509DataInfo::default()
4463            })],
4464        };
4465        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
4466            lookup_certs: vec![vec![0; 4096]],
4467            trusted_certs: vec![vec![0; 4096]],
4468            ..KeyResolverConfig::default()
4469        });
4470        let mut policy = crate::policy::VerificationPolicy::default();
4471        policy.resources.max_external_resource_bytes = 1024;
4472        policy.resources.max_external_resource_total_bytes = 1024;
4473        assert!(
4474            resolver
4475                .resolve_with_policy(Some(&key_info), SignatureAlgorithm::RsaSha256, &policy)
4476                .expect("unused configured certificates are not charged")
4477                .is_some()
4478        );
4479    }
4480
4481    #[test]
4482    fn operation_policy_bounds_embedded_x509_certificate_candidates() {
4483        // Embedded X509Data is also composite key material. Its certificate
4484        // entries must not collapse into one candidate merely because they
4485        // share a single KeyInfo source node.
4486        let key_info = KeyInfo {
4487            sources: vec![KeyInfoSource::X509Data(X509DataInfo {
4488                certificates: vec![
4489                    certificate_der(RSA_4096_CERTIFICATE),
4490                    certificate_der(include_str!("../../tests/fixtures/keys/ca2cert.pem")),
4491                ],
4492                certificate_chain: vec![0],
4493                ..X509DataInfo::default()
4494            })],
4495        };
4496        let mut policy = crate::policy::VerificationPolicy::default();
4497        policy.resources.max_key_candidates = 1;
4498
4499        let error = match DefaultKeyResolver::default().resolve_with_policy(
4500            Some(&key_info),
4501            SignatureAlgorithm::RsaSha256,
4502            &policy,
4503        ) {
4504            Ok(_) => panic!("the second embedded certificate must exceed the candidate budget"),
4505            Err(error) => error,
4506        };
4507
4508        assert!(matches!(
4509            error,
4510            DsigError::Policy(crate::policy::PolicyViolation::ResourceLimit {
4511                resource: crate::policy::resource_name::KEY_CANDIDATES,
4512                maximum: 1,
4513                actual: 2,
4514            })
4515        ));
4516    }
4517
4518    #[test]
4519    fn operation_policy_charges_duplicate_configured_x509_candidates() {
4520        // Deduplication may avoid repeated parsing, but inspecting a duplicate
4521        // resolver entry still consumes work and must not bypass the budget.
4522        let certificate = certificate_der(RSA_4096_CERTIFICATE);
4523        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
4524            lookup_certs: vec![certificate.clone(), certificate],
4525            ..KeyResolverConfig::default()
4526        });
4527        let mut policy = crate::policy::VerificationPolicy::default();
4528        policy.resources.max_key_candidates = 1;
4529
4530        let error = super::super::VerifyContext::new()
4531            .policy(policy)
4532            .key_resolver(&resolver)
4533            .verify(&x509_signature_with_leaf_subject())
4534            .expect_err("the duplicate configured entry must consume candidate work");
4535
4536        assert!(matches!(
4537            error,
4538            DsigError::Policy(crate::policy::PolicyViolation::ResourceLimit {
4539                resource: crate::policy::resource_name::KEY_CANDIDATES,
4540                maximum: 1,
4541                actual: 2,
4542            })
4543        ));
4544    }
4545
4546    #[test]
4547    fn operation_policy_charges_duplicate_embedded_x509_candidates() {
4548        // Public callers can construct KeyInfo without passing through parser
4549        // entry limits, so duplicate embedded entries must consume the budget.
4550        let certificate = certificate_der(RSA_4096_CERTIFICATE);
4551        let key_info = KeyInfo {
4552            sources: vec![KeyInfoSource::X509Data(X509DataInfo {
4553                certificates: vec![certificate.clone(), certificate],
4554                certificate_chain: vec![0],
4555                ..X509DataInfo::default()
4556            })],
4557        };
4558        let mut policy = crate::policy::VerificationPolicy::default();
4559        policy.resources.max_key_candidates = 1;
4560
4561        let error = match DefaultKeyResolver::default().resolve_with_policy(
4562            Some(&key_info),
4563            SignatureAlgorithm::RsaSha256,
4564            &policy,
4565        ) {
4566            Ok(_) => panic!("the duplicate embedded entry must consume candidate work"),
4567            Err(error) => error,
4568        };
4569
4570        assert!(matches!(
4571            error,
4572            DsigError::Policy(crate::policy::PolicyViolation::ResourceLimit {
4573                resource: crate::policy::resource_name::KEY_CANDIDATES,
4574                maximum: 1,
4575                actual: 2,
4576            })
4577        ));
4578    }
4579
4580    #[test]
4581    fn policy_aware_resolver_rejects_resources_above_hard_ceiling() {
4582        // The resolver is a public policy enforcement boundary in its own
4583        // right; callers must not need VerifyContext to validate the snapshot.
4584        let mut policy = crate::policy::VerificationPolicy::default();
4585        policy.resources.max_key_candidates = usize::MAX;
4586
4587        let error = match DefaultKeyResolver::default().resolve_with_policy(
4588            None,
4589            SignatureAlgorithm::RsaSha256,
4590            &policy,
4591        ) {
4592            Ok(_) => panic!("invalid resource policy must fail before key resolution"),
4593            Err(error) => error,
4594        };
4595
4596        assert!(matches!(
4597            error,
4598            DsigError::Policy(crate::policy::PolicyViolation::ResourceLimit {
4599                resource: crate::policy::resource_name::KEY_CANDIDATES,
4600                actual: usize::MAX,
4601                ..
4602            })
4603        ));
4604    }
4605
4606    #[test]
4607    fn embedded_x509_digest_selection_uses_operation_provider() {
4608        // Embedded certificate selection happens while KeyInfo is parsed, so
4609        // that parser path must retain the verification operation's provider.
4610        let certificate = certificate_der(RSA_4096_CERTIFICATE);
4611        let digest =
4612            super::super::compute_digest(super::super::DigestAlgorithm::Sha512, &certificate);
4613        let xml = format!(
4614            "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><X509Data><X509Certificate>{}</X509Certificate><X509Digest xmlns=\"http://www.w3.org/2009/xmldsig11#\" Algorithm=\"{}\">{}</X509Digest></X509Data></KeyInfo>",
4615            STANDARD.encode(&certificate),
4616            super::super::DigestAlgorithm::Sha512.uri(),
4617            STANDARD.encode(digest),
4618        );
4619        let document = roxmltree::Document::parse(&xml).expect("generated KeyInfo must be XML");
4620        let provider = RejectSecondSha512Provider {
4621            sha512_calls: AtomicUsize::new(1),
4622            verification_calls: AtomicUsize::new(0),
4623            reject_verification_call: None,
4624            rejected_verification_data: None,
4625        };
4626
4627        let error =
4628            super::super::parse::parse_key_info_with_provider(document.root_element(), &provider)
4629                .expect_err("embedded X509Digest selection must use the operation provider");
4630
4631        assert!(
4632            matches!(
4633                error,
4634                ParseError::Provider(crate::provider::ProviderError::Unsupported {
4635                    operation: crate::provider::ProviderOperation::Digest,
4636                    algorithm: Some(ref uri),
4637                }) if uri == super::super::DigestAlgorithm::Sha512.uri()
4638            ),
4639            "unexpected error: {error:?}"
4640        );
4641    }
4642
4643    #[cfg(feature = "legacy-algorithms")]
4644    #[test]
4645    fn x509_digest_selectors_obey_operation_policy() {
4646        // Both lookup-only and embedded selectors must reject a denied digest
4647        // before certificate selection; explicit permission must still work.
4648        for algorithm in [
4649            super::super::DigestAlgorithm::Md5,
4650            super::super::DigestAlgorithm::Ripemd160,
4651        ] {
4652            let certificate = certificate_der(RSA_4096_CERTIFICATE);
4653            let digest = super::super::compute_digest(algorithm, &certificate);
4654            let resolver = DefaultKeyResolver::new(KeyResolverConfig {
4655                lookup_certs: vec![certificate.clone()],
4656                trusted_certs: vec![
4657                    certificate.clone(),
4658                    certificate_der(include_str!("../../tests/fixtures/keys/ca2cert.pem")),
4659                    certificate_der(include_str!("../../tests/fixtures/keys/cacert.pem")),
4660                ],
4661                ..KeyResolverConfig::default()
4662            });
4663            for embedded in [false, true] {
4664                let cert_xml = if embedded {
4665                    format!(
4666                        "<X509Certificate>{}</X509Certificate>",
4667                        STANDARD.encode(&certificate)
4668                    )
4669                } else {
4670                    String::new()
4671                };
4672                let xml = replace_unprefixed_key_info(
4673                    X509_DIGEST_SIGNATURE,
4674                    &format!(
4675                        "<KeyInfo><X509Data>{cert_xml}<X509Digest xmlns=\"http://www.w3.org/2009/xmldsig11#\" Algorithm=\"{}\">{}</X509Digest></X509Data></KeyInfo>",
4676                        algorithm.uri(),
4677                        STANDARD.encode(&digest)
4678                    ),
4679                );
4680                let provider = RejectSecondSha512Provider {
4681                    sha512_calls: AtomicUsize::new(0),
4682                    verification_calls: AtomicUsize::new(0),
4683                    reject_verification_call: None,
4684                    rejected_verification_data: None,
4685                };
4686                assert!(
4687                    matches!(super::super::VerifyContext::new().key_resolver(&resolver).provider(&provider).verify(&xml),
4688                    Err(DsigError::Policy(crate::policy::PolicyViolation::Algorithm { algorithm: uri, .. })) if uri == algorithm.uri())
4689                );
4690                assert_eq!(provider.sha512_calls.load(Ordering::SeqCst), 0);
4691                assert_eq!(provider.verification_calls.load(Ordering::SeqCst), 0);
4692                // Direct resolver callers must not bypass the same snapshot.
4693                let info = KeyInfo {
4694                    sources: vec![KeyInfoSource::X509Data(X509DataInfo {
4695                        digests: vec![(algorithm.uri().into(), digest.clone())],
4696                        ..X509DataInfo::default()
4697                    })],
4698                };
4699                assert!(matches!(
4700                    resolver.resolve_with_policy(
4701                        Some(&info),
4702                        SignatureAlgorithm::RsaSha512,
4703                        &crate::policy::VerificationPolicy::default()
4704                    ),
4705                    Err(DsigError::Policy(_))
4706                ));
4707                let policy = crate::policy::VerificationPolicy {
4708                    digest_algorithms: Some(
4709                        [algorithm, super::super::DigestAlgorithm::Sha512].into(),
4710                    ),
4711                    ..crate::policy::VerificationPolicy::default()
4712                };
4713                assert_eq!(
4714                    super::super::VerifyContext::new()
4715                        .key_resolver(&resolver)
4716                        .policy(policy.clone())
4717                        .verify(&xml)
4718                        .expect("explicitly permitted selector must verify")
4719                        .status,
4720                    super::super::DsigStatus::Valid
4721                );
4722                // Referenced KeyInfo and retrieved X509Data must not create a
4723                // separate algorithm permission path during materialization.
4724                let start = xml.find("<KeyInfo>").expect("fixture has KeyInfo");
4725                let end =
4726                    xml.find("</KeyInfo>").expect("fixture closes KeyInfo") + "</KeyInfo>".len();
4727                let target = &xml[start..end];
4728                let referenced = replace_unprefixed_key_info(
4729                    &xml,
4730                    &format!(
4731                        "<KeyInfo><KeyInfoReference xmlns=\"http://www.w3.org/2009/xmldsig11#\" URI=\"#selector\"/></KeyInfo><Object>{}</Object>",
4732                        target.replace("<KeyInfo>", "<KeyInfo Id=\"selector\">")
4733                    ),
4734                );
4735                let retrieved = replace_unprefixed_key_info(
4736                    &xml,
4737                    &format!(
4738                        "<KeyInfo><RetrievalMethod URI=\"#selector\" Type=\"http://www.w3.org/2000/09/xmldsig#X509Data\"/></KeyInfo><Object>{}</Object>",
4739                        target
4740                            .strip_prefix("<KeyInfo>")
4741                            .expect("selected range starts at KeyInfo")
4742                            .strip_suffix("</KeyInfo>")
4743                            .expect("selected range ends at KeyInfo")
4744                            .replace("<X509Data>", "<X509Data Id=\"selector\">")
4745                    ),
4746                );
4747                for indirect in [referenced, retrieved] {
4748                    assert!(
4749                        matches!(super::super::VerifyContext::new().key_resolver(&resolver).verify(&indirect),
4750                        Err(DsigError::Policy(crate::policy::PolicyViolation::Algorithm { algorithm: uri, .. })) if uri == algorithm.uri())
4751                    );
4752                    assert_eq!(
4753                        super::super::VerifyContext::new()
4754                            .key_resolver(&resolver)
4755                            .policy(policy.clone())
4756                            .verify(&indirect)
4757                            .expect("permitted indirect selector must verify")
4758                            .status,
4759                        super::super::DsigStatus::Valid
4760                    );
4761                }
4762            }
4763        }
4764    }
4765
4766    #[test]
4767    fn generic_key_resolution_keeps_legacy_capability_source_independent() {
4768        let certificate =
4769            include_bytes!("../../tests/fixtures/xmldsig/phaos-xmldsig-three/certs/rsa-cert.der")
4770                .to_vec();
4771        let (_, parsed_certificate) = X509Certificate::from_der(&certificate)
4772            .expect("the Phaos fixture is a DER certificate");
4773        let public_key = parsed_certificate.public_key().raw.to_vec();
4774        let rsa_public_key = rsa::RsaPublicKey::from_public_key_der(&public_key)
4775            .expect("the Phaos certificate contains an RSA public key");
4776        let certificate_metadata = parse_x509_certificate(&certificate)
4777            .expect("the Phaos fixture has supported X.509 metadata");
4778        let named_key = VerificationKey {
4779            algorithm: SignatureAlgorithm::RsaSha1,
4780            public_key_bytes: public_key.clone(),
4781            certificate_der: None,
4782            name: Some("legacy".into()),
4783        };
4784        let key_infos = [
4785            KeyInfo {
4786                sources: vec![KeyInfoSource::KeyName("legacy".into())],
4787            },
4788            KeyInfo {
4789                sources: vec![KeyInfoSource::DerEncodedKeyValue(public_key.clone())],
4790            },
4791            KeyInfo {
4792                sources: vec![KeyInfoSource::KeyValue(KeyValueInfo::Rsa {
4793                    modulus: rsa_public_key.n().to_be_bytes_trimmed_vartime().to_vec(),
4794                    exponent: rsa_public_key.e().to_be_bytes_trimmed_vartime().to_vec(),
4795                })],
4796            },
4797            KeyInfo {
4798                sources: vec![KeyInfoSource::X509Data(X509DataInfo {
4799                    certificates: vec![certificate],
4800                    parsed_certificates: vec![certificate_metadata],
4801                    certificate_chain: vec![0],
4802                    ..X509DataInfo::default()
4803                })],
4804            },
4805        ];
4806        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
4807            named_keys: HashMap::from([("legacy".into(), named_key.clone())]),
4808            ..KeyResolverConfig::default()
4809        });
4810        let mut policy = crate::policy::VerificationPolicy::default();
4811        policy.key_trust.rsa_keys.minimum_modulus_bits = 1024;
4812        policy
4813            .key_trust
4814            .allowed_legacy_signature_algorithms
4815            .insert(SignatureAlgorithm::RsaSha1);
4816
4817        for key_info in &key_infos {
4818            let key = resolver
4819                .resolve_with_policy(Some(key_info), SignatureAlgorithm::RsaSha1, &policy)
4820                .expect("the key source is valid")
4821                .expect("key resolution remains independent from operation policy");
4822            assert!(
4823                !key.verify(SignatureAlgorithm::RsaSha1, b"data", &[0; 128])
4824                    .expect("the legacy RSA key is structurally valid")
4825            );
4826        }
4827    }
4828
4829    #[test]
4830    fn rsa_key_value_rejects_ecdsa_signature_method() {
4831        // Embedded RSA parameters must not be relabeled for an ECDSA SignatureMethod.
4832        let public_key = rsa::RsaPublicKey::from_public_key_pem(RSA_PUBLIC_KEY)
4833            .expect("fixture must contain an RSA public key");
4834        let (modulus, exponent) = rsa_key_value_parts(&public_key);
4835        let key_info = format!(
4836            "<ds:KeyInfo><ds:KeyValue><ds:RSAKeyValue><ds:Modulus>{}</ds:Modulus><ds:Exponent>{}</ds:Exponent></ds:RSAKeyValue></ds:KeyValue></ds:KeyInfo>",
4837            modulus, exponent,
4838        );
4839        let xml = replace_key_info(SIGNED_SAML, &key_info);
4840        let resolver = DefaultKeyResolver::default();
4841        let error = super::super::VerifyContext::new()
4842            .key_resolver(&resolver)
4843            .verify(&xml)
4844            .expect_err("RSAKeyValue must not resolve for ECDSA");
4845
4846        assert!(matches!(
4847            error,
4848            DsigError::KeyResolution(KeyResolutionError::AlgorithmMismatch)
4849        ));
4850    }
4851
4852    #[test]
4853    fn resolves_ec_p256_key_value_end_to_end() {
4854        // XMLDSig 1.1 ECKeyValue must verify without a preset key or certificate.
4855        let resolver = DefaultKeyResolver::default();
4856        let result = super::super::VerifyContext::new()
4857            .policy(cryptographic_policy())
4858            .key_resolver(&resolver)
4859            .verify(EC_P256_KEY_VALUE_SIGNATURE)
4860            .expect("P-256 ECKeyValue should resolve");
4861
4862        assert_eq!(result.status, super::super::DsigStatus::Valid);
4863    }
4864
4865    #[test]
4866    fn resolves_ec_p384_key_value_end_to_end() {
4867        // The donor P-384 vector uses NamedCurve + uncompressed PublicKey.
4868        let resolver = DefaultKeyResolver::default();
4869        let result = super::super::VerifyContext::new()
4870            .policy(cryptographic_policy())
4871            .key_resolver(&resolver)
4872            .verify(EC_P384_KEY_VALUE_SIGNATURE)
4873            .expect("P-384 ECKeyValue should resolve");
4874
4875        assert_eq!(result.status, super::super::DsigStatus::Valid);
4876    }
4877
4878    #[test]
4879    fn ec_key_value_ignored_for_rsa_signature_method() {
4880        // Embedded EC key material must not be relabeled for an RSA SignatureMethod.
4881        let key_info = r#"<KeyInfo xmlns:dsig11="http://www.w3.org/2009/xmldsig11#"><KeyValue><dsig11:ECKeyValue><dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/><dsig11:PublicKey>BJ/yaXNlq4FRObyJCBhb5jAz8GVzinK3bBGLjSDfjbJwNfydtgjnlS4EsDmxSRhWyJWq6GIqy5wvnaiARK04uB4=</dsig11:PublicKey></dsig11:ECKeyValue></KeyValue></KeyInfo>"#;
4882        let xml = replace_unprefixed_key_info(RSA_KEY_VALUE_SIGNATURE, key_info);
4883        let resolver = DefaultKeyResolver::default();
4884        let result = super::super::VerifyContext::new()
4885            .key_resolver(&resolver)
4886            .verify(&xml)
4887            .expect("single incompatible ECKeyValue should be ignored");
4888
4889        assert_eq!(
4890            result.status,
4891            super::super::DsigStatus::Invalid(super::super::FailureReason::KeyNotFound)
4892        );
4893    }
4894
4895    #[test]
4896    fn incompatible_ec_key_value_falls_back_to_later_rsa_key_value() {
4897        // Mixed KeyInfo should keep scanning after an incompatible ECKeyValue source.
4898        let public_key = rsa::RsaPublicKey::from_public_key_pem(RSA_PUBLIC_KEY)
4899            .expect("fixture must contain an RSA public key");
4900        let (modulus, exponent) = rsa_key_value_parts(&public_key);
4901        let key_info = format!(
4902            r#"<KeyInfo xmlns:dsig11="http://www.w3.org/2009/xmldsig11#"><KeyValue><dsig11:ECKeyValue><dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/><dsig11:PublicKey>BJ/yaXNlq4FRObyJCBhb5jAz8GVzinK3bBGLjSDfjbJwNfydtgjnlS4EsDmxSRhWyJWq6GIqy5wvnaiARK04uB4=</dsig11:PublicKey></dsig11:ECKeyValue></KeyValue><KeyValue><RSAKeyValue><Modulus>{}</Modulus><Exponent>{}</Exponent></RSAKeyValue></KeyValue></KeyInfo>"#,
4903            modulus, exponent,
4904        );
4905        let xml = replace_unprefixed_key_info(RSA_KEY_VALUE_SIGNATURE, &key_info);
4906        let resolver = DefaultKeyResolver::default();
4907        let result = super::super::VerifyContext::new()
4908            .policy(cryptographic_policy())
4909            .key_resolver(&resolver)
4910            .verify(&xml)
4911            .expect("later RSAKeyValue should resolve");
4912
4913        assert_eq!(result.status, super::super::DsigStatus::Valid);
4914    }
4915
4916    #[test]
4917    fn unsupported_ec_key_value_falls_back_to_later_key_name() {
4918        // Unsupported curves are non-fatal so a later compatible source can verify.
4919        let key_info = r#"<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:dsig11="http://www.w3.org/2009/xmldsig11#"><ds:KeyValue><dsig11:ECKeyValue><dsig11:NamedCurve URI="urn:oid:1.3.132.0.35"/><dsig11:PublicKey>BA==</dsig11:PublicKey></dsig11:ECKeyValue></ds:KeyValue><ds:KeyName>idp-signing</ds:KeyName></ds:KeyInfo>"#;
4920        let xml = replace_key_info(SIGNED_SAML, key_info);
4921        let mut config = KeyResolverConfig::default();
4922        config.named_keys.insert(
4923            "idp-signing".into(),
4924            VerificationKey {
4925                algorithm: SignatureAlgorithm::EcdsaSha256,
4926                public_key_bytes: public_key_der(SAML_PUBLIC_KEY),
4927                certificate_der: None,
4928                name: Some("idp-signing".into()),
4929            },
4930        );
4931        let resolver = DefaultKeyResolver::new(config);
4932        let result = super::super::VerifyContext::new()
4933            .key_resolver(&resolver)
4934            .verify(&xml)
4935            .expect("later KeyName should resolve");
4936
4937        assert_eq!(result.status, super::super::DsigStatus::Valid);
4938    }
4939
4940    #[test]
4941    fn invalid_ec_key_value_falls_back_to_later_key_name() {
4942        // Off-curve EC points are typed errors only if no later source can verify.
4943        let key_info = r#"<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:dsig11="http://www.w3.org/2009/xmldsig11#"><ds:KeyValue><dsig11:ECKeyValue><dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/><dsig11:PublicKey>BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</dsig11:PublicKey></dsig11:ECKeyValue></ds:KeyValue><ds:KeyName>idp-signing</ds:KeyName></ds:KeyInfo>"#;
4944        let xml = replace_key_info(SIGNED_SAML, key_info);
4945        let mut config = KeyResolverConfig::default();
4946        config.named_keys.insert(
4947            "idp-signing".into(),
4948            VerificationKey {
4949                algorithm: SignatureAlgorithm::EcdsaSha256,
4950                public_key_bytes: public_key_der(SAML_PUBLIC_KEY),
4951                certificate_der: None,
4952                name: Some("idp-signing".into()),
4953            },
4954        );
4955        let resolver = DefaultKeyResolver::new(config);
4956        let result = super::super::VerifyContext::new()
4957            .key_resolver(&resolver)
4958            .verify(&xml)
4959            .expect("later KeyName should resolve after invalid ECKeyValue");
4960
4961        assert_eq!(result.status, super::super::DsigStatus::Valid);
4962    }
4963
4964    #[test]
4965    fn malformed_ec_key_value_falls_back_to_later_key_name() {
4966        // Parse-level EC point errors remain non-fatal while later sources exist.
4967        let key_info = r#"<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:dsig11="http://www.w3.org/2009/xmldsig11#"><ds:KeyValue><dsig11:ECKeyValue><dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/><dsig11:PublicKey>AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</dsig11:PublicKey></dsig11:ECKeyValue></ds:KeyValue><ds:KeyName>idp-signing</ds:KeyName></ds:KeyInfo>"#;
4968        let xml = replace_key_info(SIGNED_SAML, key_info);
4969        let mut config = KeyResolverConfig::default();
4970        config.named_keys.insert(
4971            "idp-signing".into(),
4972            VerificationKey {
4973                algorithm: SignatureAlgorithm::EcdsaSha256,
4974                public_key_bytes: public_key_der(SAML_PUBLIC_KEY),
4975                certificate_der: None,
4976                name: Some("idp-signing".into()),
4977            },
4978        );
4979        let resolver = DefaultKeyResolver::new(config);
4980        let result = super::super::VerifyContext::new()
4981            .key_resolver(&resolver)
4982            .verify(&xml)
4983            .expect("later KeyName should resolve after malformed ECKeyValue");
4984
4985        assert_eq!(result.status, super::super::DsigStatus::Valid);
4986    }
4987
4988    #[test]
4989    fn invalid_base64_ec_key_value_falls_back_to_later_key_name() {
4990        // A bad ECKeyValue payload is an unusable source, not a reason to skip
4991        // later ordered KeyInfo sources that can verify the signature.
4992        let key_info = r#"<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:dsig11="http://www.w3.org/2009/xmldsig11#"><ds:KeyValue><dsig11:ECKeyValue><dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/><dsig11:PublicKey>not base64!</dsig11:PublicKey></dsig11:ECKeyValue></ds:KeyValue><ds:KeyName>idp-signing</ds:KeyName></ds:KeyInfo>"#;
4993        let xml = replace_key_info(SIGNED_SAML, key_info);
4994        let mut config = KeyResolverConfig::default();
4995        config.named_keys.insert(
4996            "idp-signing".into(),
4997            VerificationKey {
4998                algorithm: SignatureAlgorithm::EcdsaSha256,
4999                public_key_bytes: public_key_der(SAML_PUBLIC_KEY),
5000                certificate_der: None,
5001                name: Some("idp-signing".into()),
5002            },
5003        );
5004        let resolver = DefaultKeyResolver::new(config);
5005        let result = super::super::VerifyContext::new()
5006            .key_resolver(&resolver)
5007            .verify(&xml)
5008            .expect("later KeyName should resolve after bad ECKeyValue base64");
5009
5010        assert_eq!(result.status, super::super::DsigStatus::Valid);
5011    }
5012
5013    #[test]
5014    fn missing_curve_uri_ec_key_value_falls_back_to_later_key_name() {
5015        // Missing EC curve parameters make only this KeyValue source unusable.
5016        let key_info = r#"<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:dsig11="http://www.w3.org/2009/xmldsig11#"><ds:KeyValue><dsig11:ECKeyValue><dsig11:NamedCurve/><dsig11:PublicKey>BA==</dsig11:PublicKey></dsig11:ECKeyValue></ds:KeyValue><ds:KeyName>idp-signing</ds:KeyName></ds:KeyInfo>"#;
5017        let xml = replace_key_info(SIGNED_SAML, key_info);
5018        let mut config = KeyResolverConfig::default();
5019        config.named_keys.insert(
5020            "idp-signing".into(),
5021            VerificationKey {
5022                algorithm: SignatureAlgorithm::EcdsaSha256,
5023                public_key_bytes: public_key_der(SAML_PUBLIC_KEY),
5024                certificate_der: None,
5025                name: Some("idp-signing".into()),
5026            },
5027        );
5028        let resolver = DefaultKeyResolver::new(config);
5029        let result = super::super::VerifyContext::new()
5030            .key_resolver(&resolver)
5031            .verify(&xml)
5032            .expect("later KeyName should resolve after missing EC curve URI");
5033
5034        assert_eq!(result.status, super::super::DsigStatus::Valid);
5035    }
5036
5037    #[test]
5038    fn malformed_ec_key_value_children_fall_back_to_later_key_name() {
5039        // An unusable EC source must not prevent later ordered KeyInfo sources
5040        // from resolving, regardless of which required child-shape check fails.
5041        let malformed_ec_key_values = [
5042            r#"<dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/>"#,
5043            r#"<dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/><dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/>"#,
5044            r#"<dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/><dsig11:PublicKey>BA==</dsig11:PublicKey><dsig11:PublicKey>BA==</dsig11:PublicKey>"#,
5045        ];
5046
5047        for malformed_children in malformed_ec_key_values {
5048            let key_info = format!(
5049                r#"<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:dsig11="http://www.w3.org/2009/xmldsig11#"><ds:KeyValue><dsig11:ECKeyValue>{malformed_children}</dsig11:ECKeyValue></ds:KeyValue><ds:KeyName>idp-signing</ds:KeyName></ds:KeyInfo>"#
5050            );
5051            let xml = replace_key_info(SIGNED_SAML, &key_info);
5052            let mut config = KeyResolverConfig::default();
5053            config.named_keys.insert(
5054                "idp-signing".into(),
5055                VerificationKey {
5056                    algorithm: SignatureAlgorithm::EcdsaSha256,
5057                    public_key_bytes: public_key_der(SAML_PUBLIC_KEY),
5058                    certificate_der: None,
5059                    name: Some("idp-signing".into()),
5060                },
5061            );
5062            let resolver = DefaultKeyResolver::new(config);
5063            let result = super::super::VerifyContext::new()
5064                .key_resolver(&resolver)
5065                .verify(&xml)
5066                .expect("later KeyName should resolve after malformed EC child shape");
5067
5068            assert_eq!(result.status, super::super::DsigStatus::Valid);
5069        }
5070    }
5071
5072    #[test]
5073    fn supported_ec_curve_does_not_fall_back_to_later_key_name() {
5074        // ECDSA-SHA256 accepts P-384, so this first source is a usable key and
5075        // must not be skipped merely because a later P-256 KeyName happens to
5076        // verify the signature. Verification fails against the selected key.
5077        let key_info = r#"<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:dsig11="http://www.w3.org/2009/xmldsig11#"><ds:KeyValue><dsig11:ECKeyValue><dsig11:NamedCurve URI="urn:oid:1.3.132.0.34"/><dsig11:PublicKey>BO/yd/OZzDfjX4qivDY/vsUIuh6KWAxoxW5P4ukvwd+T6pVljWsX2UBJNNy5MdhTwB8e2YwB8kUbJwdsAS/XGi/fz8unFrs+lVlAgIs6s/xBYFbfUoRiAacD2SpVDe6XBA==</dsig11:PublicKey></dsig11:ECKeyValue></ds:KeyValue><ds:KeyName>idp-signing</ds:KeyName></ds:KeyInfo>"#;
5078        let xml = replace_key_info(SIGNED_SAML, key_info);
5079        let mut config = KeyResolverConfig::default();
5080        config.named_keys.insert(
5081            "idp-signing".into(),
5082            VerificationKey {
5083                algorithm: SignatureAlgorithm::EcdsaSha256,
5084                public_key_bytes: public_key_der(SAML_PUBLIC_KEY),
5085                certificate_der: None,
5086                name: Some("idp-signing".into()),
5087            },
5088        );
5089        let resolver = DefaultKeyResolver::new(config);
5090        let error = super::super::VerifyContext::new()
5091            .policy(cryptographic_policy())
5092            .key_resolver(&resolver)
5093            .verify(&xml)
5094            .expect_err("a usable first key source must not fall through after verification");
5095
5096        assert!(matches!(
5097            error,
5098            DsigError::Crypto(super::super::SignatureVerificationError::InvalidSignatureFormat)
5099        ));
5100    }
5101
5102    #[test]
5103    fn lone_malformed_ec_key_value_reports_invalid_public_key() {
5104        let key_info = r#"<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:dsig11="http://www.w3.org/2009/xmldsig11#"><ds:KeyValue><dsig11:ECKeyValue><dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/><dsig11:PublicKey>AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</dsig11:PublicKey></dsig11:ECKeyValue></ds:KeyValue></ds:KeyInfo>"#;
5105        let xml = replace_key_info(SIGNED_SAML, key_info);
5106        let error = super::super::VerifyContext::new()
5107            .key_resolver(&DefaultKeyResolver::default())
5108            .verify(&xml)
5109            .expect_err("lone malformed ECKeyValue should surface typed key error");
5110
5111        assert!(matches!(
5112            error,
5113            DsigError::KeyResolution(KeyResolutionError::InvalidPublicKey)
5114        ));
5115    }
5116
5117    #[test]
5118    fn lone_supported_ec_curve_reaches_signature_verification() {
5119        let key_info = r#"<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:dsig11="http://www.w3.org/2009/xmldsig11#"><ds:KeyValue><dsig11:ECKeyValue><dsig11:NamedCurve URI="urn:oid:1.3.132.0.34"/><dsig11:PublicKey>BO/yd/OZzDfjX4qivDY/vsUIuh6KWAxoxW5P4ukvwd+T6pVljWsX2UBJNNy5MdhTwB8e2YwB8kUbJwdsAS/XGi/fz8unFrs+lVlAgIs6s/xBYFbfUoRiAacD2SpVDe6XBA==</dsig11:PublicKey></dsig11:ECKeyValue></ds:KeyValue></ds:KeyInfo>"#;
5120        let xml = replace_key_info(SIGNED_SAML, key_info);
5121        let error = super::super::VerifyContext::new()
5122            .policy(cryptographic_policy())
5123            .key_resolver(&DefaultKeyResolver::default())
5124            .verify(&xml)
5125            .expect_err("a supported EC curve must reach signature verification");
5126
5127        assert!(matches!(
5128            error,
5129            DsigError::Crypto(super::super::SignatureVerificationError::InvalidSignatureFormat)
5130        ));
5131    }
5132
5133    #[test]
5134    fn chain_verification_rejects_untrusted_embedded_certificate() {
5135        // Enabling chain policy must fail closed when no trust anchor is configured.
5136        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
5137            ..KeyResolverConfig::default()
5138        });
5139        let error = super::super::VerifyContext::new()
5140            .policy(verification_policy_with_trust(chain_policy()))
5141            .key_resolver(&resolver)
5142            .verify(SIGNED_SAML)
5143            .expect_err("untrusted certificate must fail chain validation");
5144
5145        assert!(matches!(
5146            error,
5147            DsigError::KeyResolution(KeyResolutionError::Chain(
5148                super::super::X509ChainError::UntrustedRoot
5149            ))
5150        ));
5151    }
5152
5153    #[test]
5154    fn named_key_algorithm_mismatch_fails_closed() {
5155        // A key registered for RSA must never be attempted for an ECDSA signature.
5156        let xml = replace_key_info(
5157            SIGNED_SAML,
5158            "<ds:KeyInfo><ds:KeyName>wrong-algorithm</ds:KeyName></ds:KeyInfo>",
5159        );
5160        let mut config = KeyResolverConfig::default();
5161        config.named_keys.insert(
5162            "wrong-algorithm".into(),
5163            VerificationKey {
5164                algorithm: SignatureAlgorithm::RsaSha256,
5165                public_key_bytes: public_key_der(SAML_PUBLIC_KEY),
5166                certificate_der: None,
5167                name: Some("wrong-algorithm".into()),
5168            },
5169        );
5170        let resolver = DefaultKeyResolver::new(config);
5171        let error = super::super::VerifyContext::new()
5172            .key_resolver(&resolver)
5173            .verify(&xml)
5174            .expect_err("algorithm mismatch must fail closed");
5175
5176        assert!(matches!(
5177            error,
5178            DsigError::KeyResolution(KeyResolutionError::AlgorithmMismatch)
5179        ));
5180    }
5181
5182    #[test]
5183    fn named_key_spki_type_mismatch_fails_during_resolution() {
5184        // The configured algorithm label cannot override the actual SPKI key type.
5185        let xml = replace_key_info(
5186            SIGNED_SAML,
5187            "<ds:KeyInfo><ds:KeyName>mislabeled</ds:KeyName></ds:KeyInfo>",
5188        );
5189        let mut config = KeyResolverConfig::default();
5190        config.named_keys.insert(
5191            "mislabeled".into(),
5192            VerificationKey {
5193                algorithm: SignatureAlgorithm::EcdsaSha256,
5194                public_key_bytes: public_key_der(RSA_PUBLIC_KEY),
5195                certificate_der: None,
5196                name: Some("mislabeled".into()),
5197            },
5198        );
5199        let resolver = DefaultKeyResolver::new(config);
5200        let error = super::super::VerifyContext::new()
5201            .key_resolver(&resolver)
5202            .verify(&xml)
5203            .expect_err("mislabeled named key must fail during resolution");
5204
5205        assert!(matches!(
5206            error,
5207            DsigError::KeyResolution(KeyResolutionError::AlgorithmMismatch)
5208        ));
5209    }
5210
5211    #[test]
5212    fn malformed_named_key_reports_public_key_error() {
5213        // Non-certificate SPKI failures must not be mislabeled as certificate errors.
5214        let xml = replace_key_info(
5215            SIGNED_SAML,
5216            "<ds:KeyInfo><ds:KeyName>malformed</ds:KeyName></ds:KeyInfo>",
5217        );
5218        let mut config = KeyResolverConfig::default();
5219        config.named_keys.insert(
5220            "malformed".into(),
5221            VerificationKey {
5222                algorithm: SignatureAlgorithm::EcdsaSha256,
5223                public_key_bytes: vec![1, 2, 3],
5224                certificate_der: None,
5225                name: Some("malformed".into()),
5226            },
5227        );
5228        let resolver = DefaultKeyResolver::new(config);
5229        let error = super::super::VerifyContext::new()
5230            .key_resolver(&resolver)
5231            .verify(&xml)
5232            .expect_err("malformed named key must fail during resolution");
5233
5234        assert!(matches!(
5235            error,
5236            DsigError::KeyResolution(KeyResolutionError::InvalidPublicKey)
5237        ));
5238    }
5239}