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