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