Skip to main content

xml_sec/xmldsig/
keys.rs

1//! Configuration and key material for XMLDSig key resolution.
2
3use std::{collections::HashMap, time::SystemTime};
4
5use crypto_bigint::BoxedUint;
6use p256::pkcs8::EncodePublicKey as P256EncodePublicKey;
7use x509_parser::{
8    prelude::{FromDer, X509Certificate},
9    public_key::PublicKey,
10    x509::SubjectPublicKeyInfo,
11};
12
13use super::{
14    DsigError, KeyInfo, KeyInfoSource, KeyResolver, KeyValueInfo, SignatureAlgorithm, VerifyingKey,
15    X509ChainOptions, X509DataInfo,
16    parse::{
17        EC_P256_OID, EC_P384_OID, ParseError, parse_x509_certificate,
18        x509_certificate_matches_any_selector, x509_data_has_lookup_identifiers,
19        x509_selector_categories_match_chain,
20    },
21    verify_ecdsa_signature_spki, verify_rsa_signature_spki, verify_x509_certificate_chain,
22};
23
24/// A public verification key available to key resolvers.
25#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct VerificationKey {
27    /// Signature algorithm this key is configured to verify.
28    pub algorithm: SignatureAlgorithm,
29    /// DER-encoded SubjectPublicKeyInfo bytes.
30    pub public_key_bytes: Vec<u8>,
31    /// DER certificate from which the key was extracted, when applicable.
32    pub certificate_der: Option<Vec<u8>>,
33    /// Name used to register this key for `<KeyName>` resolution.
34    pub name: Option<String>,
35}
36
37impl VerifyingKey for VerificationKey {
38    fn verify(
39        &self,
40        algorithm: SignatureAlgorithm,
41        signed_data: &[u8],
42        signature_value: &[u8],
43    ) -> Result<bool, DsigError> {
44        if algorithm != self.algorithm {
45            return Err(KeyResolutionError::AlgorithmMismatch.into());
46        }
47        let result = match algorithm {
48            SignatureAlgorithm::RsaSha1
49            | SignatureAlgorithm::RsaSha256
50            | SignatureAlgorithm::RsaSha384
51            | SignatureAlgorithm::RsaSha512 => verify_rsa_signature_spki(
52                algorithm,
53                &self.public_key_bytes,
54                signed_data,
55                signature_value,
56            ),
57            SignatureAlgorithm::EcdsaP256Sha256 | SignatureAlgorithm::EcdsaP384Sha384 => {
58                verify_ecdsa_signature_spki(
59                    algorithm,
60                    &self.public_key_bytes,
61                    signed_data,
62                    signature_value,
63                )
64            }
65        };
66        result.map_err(DsigError::Crypto)
67    }
68}
69
70/// Failures while applying [`KeyResolverConfig`] to parsed key material.
71#[derive(Debug, thiserror::Error)]
72#[non_exhaustive]
73pub enum KeyResolutionError {
74    /// A configured or embedded key does not match the signature method.
75    #[error("verification key does not match the signature algorithm")]
76    AlgorithmMismatch,
77    /// An embedded certificate could not be parsed completely.
78    #[error("invalid embedded certificate DER")]
79    InvalidCertificate,
80    /// Configured or embedded public key DER could not be parsed completely.
81    #[error("invalid public key DER")]
82    InvalidPublicKey,
83    /// More than one configured certificate satisfies all X.509 selectors.
84    #[error("X.509 lookup selectors match multiple configured certificates")]
85    AmbiguousCertificate,
86    /// An X.509 selector uses a digest algorithm unsupported by this crate.
87    #[error("unsupported X.509 digest algorithm: {0}")]
88    UnsupportedDigestAlgorithm(String),
89    /// Embedded certificate path validation failed.
90    #[error("certificate chain validation failed: {0}")]
91    Chain(#[from] super::X509ChainError),
92    /// System time was unavailable for certificate validation.
93    #[error("system time is unavailable")]
94    SystemTime,
95}
96
97/// Configuration for the default XMLDSig key resolver.
98///
99/// The configuration owns all key material and has no global registry. Chain
100/// verification is opt-in so callers that pin an embedded certificate can use
101/// the documented TOFU model without constructing a certificate path.
102#[derive(Debug, Clone, PartialEq, Eq)]
103pub struct KeyResolverConfig {
104    /// DER-encoded certificates accepted as trust anchors.
105    pub trusted_certs: Vec<Vec<u8>>,
106    /// Verification keys addressable by `<KeyName>` content.
107    pub named_keys: HashMap<String, VerificationKey>,
108    /// Whether embedded X.509 certificate chains must terminate at a trust anchor.
109    pub verify_chains: bool,
110    /// Certificate verification time override; `None` selects the system clock.
111    pub verification_time: Option<SystemTime>,
112    /// Maximum certificates in a validated path, including the trust anchor.
113    pub max_chain_depth: usize,
114}
115
116impl Default for KeyResolverConfig {
117    fn default() -> Self {
118        Self {
119            trusted_certs: Vec::new(),
120            named_keys: HashMap::new(),
121            verify_chains: false,
122            verification_time: None,
123            max_chain_depth: 9,
124        }
125    }
126}
127
128/// Configuration-driven resolver for embedded certificates, DER keys, and key names.
129#[derive(Debug, Clone, Default)]
130pub struct DefaultKeyResolver {
131    config: KeyResolverConfig,
132}
133
134impl DefaultKeyResolver {
135    /// Construct a resolver from explicit caller-owned key policy.
136    #[must_use]
137    pub fn new(config: KeyResolverConfig) -> Self {
138        Self { config }
139    }
140
141    /// Borrow the active resolver configuration.
142    #[must_use]
143    pub fn config(&self) -> &KeyResolverConfig {
144        &self.config
145    }
146
147    fn resolve_x509(
148        &self,
149        info: &X509DataInfo,
150        algorithm: SignatureAlgorithm,
151    ) -> Result<Option<VerificationKey>, KeyResolutionError> {
152        let certificate_der = if let Some(&signing_index) = info.certificate_chain.first() {
153            let certificate_der = info
154                .certificates
155                .get(signing_index)
156                .ok_or(KeyResolutionError::InvalidCertificate)?;
157            if self.config.verify_chains {
158                self.verify_x509_policy(info, None)?;
159            }
160            certificate_der
161        } else {
162            let Some(certificate) = self.resolve_configured_x509(info)? else {
163                return Ok(None);
164            };
165            if self.config.verify_chains {
166                let parsed = parse_x509_certificate(certificate)
167                    .map_err(|_| KeyResolutionError::InvalidCertificate)?;
168                let selected = X509DataInfo {
169                    certificates: vec![certificate.clone()],
170                    parsed_certificates: vec![parsed],
171                    certificate_chain: vec![0],
172                    ..X509DataInfo::default()
173                };
174                // Validate the selected certificate's own policy before
175                // requiring a distinct configured certificate as its anchor.
176                self.verify_x509_policy(&selected, None)?;
177                self.verify_x509_policy(&selected, Some(certificate))?;
178            }
179            certificate
180        };
181
182        let (rest, certificate) = X509Certificate::from_der(certificate_der)
183            .map_err(|_| KeyResolutionError::InvalidCertificate)?;
184        if !rest.is_empty() {
185            return Err(KeyResolutionError::InvalidCertificate);
186        }
187        let public_key_bytes = certificate.public_key().raw.to_vec();
188        validate_spki_algorithm(&public_key_bytes, algorithm)?;
189        Ok(Some(VerificationKey {
190            algorithm,
191            public_key_bytes,
192            certificate_der: Some(certificate_der.clone()),
193            name: None,
194        }))
195    }
196
197    fn verify_x509_policy(
198        &self,
199        info: &X509DataInfo,
200        selected_lookup_certificate: Option<&[u8]>,
201    ) -> Result<(), KeyResolutionError> {
202        let trusted_certs = self
203            .config
204            .trusted_certs
205            .iter()
206            .filter(|certificate| {
207                selected_lookup_certificate
208                    .is_none_or(|selected| certificate.as_slice() != selected)
209            })
210            .cloned()
211            .collect::<Vec<_>>();
212        let options = X509ChainOptions {
213            trusted_certs: &trusted_certs,
214            verification_time: self
215                .config
216                .verification_time
217                .unwrap_or_else(SystemTime::now),
218            max_chain_depth: self.config.max_chain_depth,
219            check_crls: false,
220        };
221        verify_x509_certificate_chain(info, &options)?;
222        Ok(())
223    }
224
225    fn resolve_configured_x509<'a>(
226        &'a self,
227        info: &X509DataInfo,
228    ) -> Result<Option<&'a Vec<u8>>, KeyResolutionError> {
229        if !x509_data_has_lookup_identifiers(info) {
230            return Ok(None);
231        }
232
233        let mut matches = Vec::new();
234        for certificate_der in &self.config.trusted_certs {
235            let parsed = parse_x509_certificate(certificate_der)
236                .map_err(|_| KeyResolutionError::InvalidCertificate)?;
237            let is_match = x509_certificate_matches_any_selector(info, &parsed, certificate_der)
238                .map_err(|error| match error {
239                    ParseError::UnsupportedAlgorithm { uri } => {
240                        KeyResolutionError::UnsupportedDigestAlgorithm(uri)
241                    }
242                    _ => KeyResolutionError::InvalidCertificate,
243                })?;
244            if is_match {
245                matches.push((certificate_der, parsed));
246            }
247        }
248
249        let matched_chain = X509DataInfo {
250            certificates: matches
251                .iter()
252                .map(|(certificate, _)| (*certificate).clone())
253                .collect(),
254            parsed_certificates: matches.iter().map(|(_, parsed)| parsed.clone()).collect(),
255            ..X509DataInfo::default()
256        };
257        if !x509_selector_categories_match_chain(&X509DataInfo {
258            subject_names: info.subject_names.clone(),
259            issuer_serials: info.issuer_serials.clone(),
260            skis: info.skis.clone(),
261            digests: info.digests.clone(),
262            ..matched_chain
263        })
264        .map_err(|error| match error {
265            ParseError::UnsupportedAlgorithm { uri } => {
266                KeyResolutionError::UnsupportedDigestAlgorithm(uri)
267            }
268            _ => KeyResolutionError::InvalidCertificate,
269        })? {
270            return Ok(None);
271        }
272
273        match matches.as_slice() {
274            [] => Ok(None),
275            [(certificate, _)] => Ok(Some(certificate)),
276            _ => {
277                let leaves = matches
278                    .iter()
279                    .filter(|(_, candidate)| {
280                        candidate.subject_dn != candidate.issuer_dn
281                            && !matches
282                                .iter()
283                                .any(|(_, other)| other.issuer_dn == candidate.subject_dn)
284                    })
285                    .collect::<Vec<_>>();
286                match leaves.as_slice() {
287                    [(certificate, _)] => Ok(Some(certificate)),
288                    _ => Err(KeyResolutionError::AmbiguousCertificate),
289                }
290            }
291        }
292    }
293
294    fn resolve_key_value(
295        key_value: &KeyValueInfo,
296        algorithm: SignatureAlgorithm,
297    ) -> Result<Option<VerificationKey>, KeyResolutionError> {
298        let public_key_bytes = match key_value {
299            KeyValueInfo::Rsa { modulus, exponent } => {
300                if !matches!(
301                    algorithm,
302                    SignatureAlgorithm::RsaSha1
303                        | SignatureAlgorithm::RsaSha256
304                        | SignatureAlgorithm::RsaSha384
305                        | SignatureAlgorithm::RsaSha512
306                ) {
307                    return Err(KeyResolutionError::AlgorithmMismatch);
308                }
309                rsa_key_value_to_spki_der(modulus, exponent)?
310            }
311            KeyValueInfo::Ec {
312                curve_oid,
313                public_key,
314            } => {
315                if !matches!(
316                    algorithm,
317                    SignatureAlgorithm::EcdsaP256Sha256 | SignatureAlgorithm::EcdsaP384Sha384
318                ) {
319                    return Ok(None);
320                }
321                ec_key_value_to_spki_der(curve_oid, public_key)?
322            }
323            KeyValueInfo::InvalidEcKeyValue => return Err(KeyResolutionError::InvalidPublicKey),
324            KeyValueInfo::Unsupported { .. } => return Ok(None),
325        };
326        validate_spki_algorithm(&public_key_bytes, algorithm)?;
327
328        Ok(Some(VerificationKey {
329            algorithm,
330            public_key_bytes,
331            certificate_der: None,
332            name: None,
333        }))
334    }
335}
336
337impl KeyResolver for DefaultKeyResolver {
338    fn resolve<'a>(
339        &'a self,
340        key_info: Option<&KeyInfo>,
341        algorithm: SignatureAlgorithm,
342    ) -> Result<Option<Box<dyn VerifyingKey + 'a>>, DsigError> {
343        let Some(key_info) = key_info else {
344            return Ok(None);
345        };
346        let mut deferred_key_value_error = None;
347        for source in &key_info.sources {
348            let resolved = match source {
349                KeyInfoSource::X509Data(info) => self.resolve_x509(info, algorithm)?,
350                KeyInfoSource::DerEncodedKeyValue(public_key_bytes) => {
351                    validate_spki_algorithm(public_key_bytes, algorithm)?;
352                    Some(VerificationKey {
353                        algorithm,
354                        public_key_bytes: public_key_bytes.clone(),
355                        certificate_der: None,
356                        name: None,
357                    })
358                }
359                KeyInfoSource::KeyName(name) => self
360                    .config
361                    .named_keys
362                    .get(name)
363                    .map(|key| {
364                        if key.algorithm != algorithm {
365                            return Err(KeyResolutionError::AlgorithmMismatch);
366                        }
367                        validate_spki_algorithm(&key.public_key_bytes, algorithm)?;
368                        Ok(key.clone())
369                    })
370                    .transpose()?,
371                KeyInfoSource::KeyValue(key_value) => {
372                    match Self::resolve_key_value(key_value, algorithm) {
373                        Ok(resolved) => resolved,
374                        Err(error) if ec_key_value_error_allows_fallback(key_value, &error) => {
375                            deferred_key_value_error.get_or_insert(error);
376                            None
377                        }
378                        Err(error) => return Err(error.into()),
379                    }
380                }
381            };
382            if let Some(key) = resolved {
383                return Ok(Some(Box::new(key)));
384            }
385        }
386        if let Some(error) = deferred_key_value_error {
387            return Err(error.into());
388        }
389        Ok(None)
390    }
391
392    fn consumes_document_key_info(&self) -> bool {
393        true
394    }
395}
396
397fn rsa_key_value_to_spki_der(
398    modulus: &[u8],
399    exponent: &[u8],
400) -> Result<Vec<u8>, KeyResolutionError> {
401    let key = rsa::RsaPublicKey::new(
402        BoxedUint::from_be_slice_vartime(modulus),
403        BoxedUint::from_be_slice_vartime(exponent),
404    )
405    .map_err(|_| KeyResolutionError::InvalidPublicKey)?;
406    key.to_public_key_der()
407        .map_err(|_| KeyResolutionError::InvalidPublicKey)
408        .map(|der| der.as_bytes().to_vec())
409}
410
411fn ec_key_value_to_spki_der(
412    curve_oid: &str,
413    public_key: &[u8],
414) -> Result<Vec<u8>, KeyResolutionError> {
415    match curve_oid {
416        EC_P256_OID => p256::PublicKey::from_sec1_bytes(public_key)
417            .map_err(|_| KeyResolutionError::InvalidPublicKey)?
418            .to_public_key_der()
419            .map_err(|_| KeyResolutionError::InvalidPublicKey)
420            .map(|der| der.as_bytes().to_vec()),
421        EC_P384_OID => p384::PublicKey::from_sec1_bytes(public_key)
422            .map_err(|_| KeyResolutionError::InvalidPublicKey)?
423            .to_public_key_der()
424            .map_err(|_| KeyResolutionError::InvalidPublicKey)
425            .map(|der| der.as_bytes().to_vec()),
426        _ => Err(KeyResolutionError::InvalidPublicKey),
427    }
428}
429
430fn ec_key_value_error_allows_fallback(
431    key_value: &KeyValueInfo,
432    error: &KeyResolutionError,
433) -> bool {
434    matches!(
435        key_value,
436        KeyValueInfo::Ec { .. } | KeyValueInfo::InvalidEcKeyValue
437    ) && matches!(
438        error,
439        KeyResolutionError::InvalidPublicKey | KeyResolutionError::AlgorithmMismatch
440    )
441}
442
443fn validate_spki_algorithm(
444    public_key_bytes: &[u8],
445    algorithm: SignatureAlgorithm,
446) -> Result<(), KeyResolutionError> {
447    let (rest, spki) = SubjectPublicKeyInfo::from_der(public_key_bytes)
448        .map_err(|_| KeyResolutionError::InvalidPublicKey)?;
449    if !rest.is_empty() {
450        return Err(KeyResolutionError::InvalidPublicKey);
451    }
452    let parsed = spki
453        .parsed()
454        .map_err(|_| KeyResolutionError::InvalidPublicKey)?;
455    let curve_oid = spki
456        .algorithm
457        .parameters
458        .as_ref()
459        .and_then(|value| value.as_oid().ok())
460        .map(|oid| oid.to_id_string());
461    match (algorithm, parsed) {
462        (
463            SignatureAlgorithm::RsaSha1
464            | SignatureAlgorithm::RsaSha256
465            | SignatureAlgorithm::RsaSha384
466            | SignatureAlgorithm::RsaSha512,
467            PublicKey::RSA(_),
468        ) => Ok(()),
469        (SignatureAlgorithm::EcdsaP256Sha256, PublicKey::EC(_))
470            if curve_oid.as_deref() == Some("1.2.840.10045.3.1.7") =>
471        {
472            Ok(())
473        }
474        // xmlsec's OpenSSL backend maps ecdsa-sha384 to EVP_sha384() plus the
475        // generic EC key class, without restricting the curve to P-384. Keep
476        // P-521/SHA-384 compatible with that donor contract.
477        (SignatureAlgorithm::EcdsaP384Sha384, PublicKey::EC(_))
478            if matches!(curve_oid.as_deref(), Some("1.3.132.0.34" | "1.3.132.0.35")) =>
479        {
480            Ok(())
481        }
482        _ => Err(KeyResolutionError::AlgorithmMismatch),
483    }
484}
485
486#[cfg(test)]
487mod tests {
488    use base64::{Engine, engine::general_purpose::STANDARD};
489    use rsa::{pkcs8::DecodePublicKey, traits::PublicKeyParts};
490
491    use super::*;
492
493    const SIGNED_SAML: &str =
494        include_str!("../../tests/fixtures/saml/response_signed_by_idp_ecdsa.xml");
495    const SAML_PUBLIC_KEY: &str =
496        include_str!("../../tests/fixtures/keys/ec/saml-idp-ecdsa-pubkey.pem");
497    const RSA_PUBLIC_KEY: &str = include_str!("../../tests/fixtures/keys/rsa/rsa-2048-pubkey.pem");
498    const RSA_4096_CERTIFICATE: &str =
499        include_str!("../../tests/fixtures/keys/rsa/rsa-4096-cert.pem");
500    const X509_DIGEST_SIGNATURE: &str = include_str!(
501        "../../tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloped-x509-digest-sha512.xml"
502    );
503    const RSA_KEY_VALUE_SIGNATURE: &str = include_str!(
504        "../../tests/fixtures/xmldsig/aleksey-xmldsig-01/enveloping-sha256-rsa-sha256.xml"
505    );
506    const LEGACY_RSA_KEY_VALUE_SIGNATURE: &str = include_str!(
507        "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-enveloping-rsa.xml"
508    );
509    const EC_P256_KEY_VALUE_SIGNATURE: &str = include_str!(
510        "../../tests/fixtures/xmldsig/xmldsig11-interop-2012/signature-enveloping-p256_sha256.xml"
511    );
512    const EC_P384_KEY_VALUE_SIGNATURE: &str = include_str!(
513        "../../tests/fixtures/xmldsig/xmldsig11-interop-2012/signature-enveloping-p384_sha384.xml"
514    );
515
516    fn replace_key_info(xml: &str, replacement: &str) -> String {
517        let start = xml.find("<ds:KeyInfo>").expect("fixture has KeyInfo");
518        let end = xml
519            .find("</ds:KeyInfo>")
520            .expect("fixture has closing KeyInfo")
521            + "</ds:KeyInfo>".len();
522        format!("{}{}{}", &xml[..start], replacement, &xml[end..])
523    }
524
525    fn replace_unprefixed_key_info(xml: &str, replacement: &str) -> String {
526        let start = xml.find("<KeyInfo>").expect("fixture has KeyInfo");
527        let end = xml.find("</KeyInfo>").expect("fixture has closing KeyInfo") + "</KeyInfo>".len();
528        format!("{}{}{}", &xml[..start], replacement, &xml[end..])
529    }
530
531    fn rsa_key_value_parts(public_key: &rsa::RsaPublicKey) -> (String, String) {
532        (
533            STANDARD.encode(public_key.n().to_be_bytes_trimmed_vartime()),
534            STANDARD.encode(public_key.e().to_be_bytes_trimmed_vartime()),
535        )
536    }
537
538    fn x509_signature_with_leaf_subject() -> String {
539        replace_unprefixed_key_info(
540            X509_DIGEST_SIGNATURE,
541            "<KeyInfo><X509Data><X509SubjectName>C=US, ST=California, O=XML Security Library (http://www.aleksey.com/xmlsec), CN=Test Key rsa-4096</X509SubjectName></X509Data></KeyInfo>",
542        )
543    }
544
545    fn fixture_certificate_time() -> SystemTime {
546        // 2027-01-15 UTC, inside the donor certificates' 2026-2126 validity window.
547        SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(1_800_000_000)
548    }
549
550    fn public_key_der(pem_text: &str) -> Vec<u8> {
551        let (rest, pem) = x509_parser::pem::parse_x509_pem(pem_text.as_bytes())
552            .expect("fixture public key is PEM");
553        assert!(rest.iter().all(|byte| byte.is_ascii_whitespace()));
554        assert_eq!(pem.label, "PUBLIC KEY");
555        pem.contents
556    }
557
558    fn certificate_der(pem_text: &str) -> Vec<u8> {
559        let (rest, pem) = x509_parser::pem::parse_x509_pem(pem_text.as_bytes())
560            .expect("fixture certificate is PEM");
561        assert!(rest.iter().all(|byte| byte.is_ascii_whitespace()));
562        assert_eq!(pem.label, "CERTIFICATE");
563        pem.contents
564    }
565
566    #[test]
567    fn defaults_match_key_resolution_policy() {
568        // Defaults must remain compatible with xmlsec1's depth and opt-in trust policy.
569        let config = KeyResolverConfig::default();
570
571        assert!(config.trusted_certs.is_empty());
572        assert!(config.named_keys.is_empty());
573        assert!(!config.verify_chains);
574        assert_eq!(config.verification_time, None);
575        assert_eq!(config.max_chain_depth, 9);
576    }
577
578    #[test]
579    fn stores_named_verification_key_metadata() {
580        // Named resolution must retain every field needed by the later resolver wiring.
581        let key = VerificationKey {
582            algorithm: SignatureAlgorithm::RsaSha256,
583            public_key_bytes: vec![1, 2, 3],
584            certificate_der: Some(vec![4, 5, 6]),
585            name: Some("idp-signing".into()),
586        };
587        let mut config = KeyResolverConfig::default();
588        config.named_keys.insert("idp-signing".into(), key.clone());
589
590        assert_eq!(config.named_keys.get("idp-signing"), Some(&key));
591    }
592
593    #[test]
594    fn resolves_embedded_certificate_end_to_end() {
595        // The default resolver must make parsed X509Data usable by VerifyContext.
596        let resolver = DefaultKeyResolver::default();
597        let result = super::super::VerifyContext::new()
598            .key_resolver(&resolver)
599            .verify(SIGNED_SAML)
600            .expect("embedded certificate should resolve");
601
602        assert_eq!(result.status, super::super::DsigStatus::Valid);
603    }
604
605    #[test]
606    fn resolves_x509_digest_from_configured_certificates() {
607        // Selector-only X509Data must locate the signing certificate without
608        // embedding key material or supplying a preset verification key.
609        let leaf_certificate_der = certificate_der(RSA_4096_CERTIFICATE);
610        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
611            trusted_certs: vec![
612                leaf_certificate_der,
613                certificate_der(include_str!("../../tests/fixtures/keys/ca2cert.pem")),
614                certificate_der(include_str!("../../tests/fixtures/keys/cacert.pem")),
615            ],
616            ..KeyResolverConfig::default()
617        });
618        let result = super::super::VerifyContext::new()
619            .key_resolver(&resolver)
620            .verify(X509_DIGEST_SIGNATURE)
621            .expect("X509Digest should resolve a configured certificate");
622
623        assert_eq!(result.status, super::super::DsigStatus::Valid);
624    }
625
626    #[test]
627    fn selector_resolved_certificate_obeys_chain_policy() {
628        // Enabling chain verification must apply validity policy even when
629        // X509Data contains only selectors and the matching cert is configured.
630        let certificate_der = certificate_der(RSA_4096_CERTIFICATE);
631        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
632            trusted_certs: vec![certificate_der],
633            verify_chains: true,
634            verification_time: Some(SystemTime::UNIX_EPOCH),
635            ..KeyResolverConfig::default()
636        });
637        let error = super::super::VerifyContext::new()
638            .key_resolver(&resolver)
639            .verify(&x509_signature_with_leaf_subject())
640            .expect_err("selector-resolved certificate must satisfy chain policy");
641
642        assert!(matches!(
643            error,
644            DsigError::KeyResolution(KeyResolutionError::Chain(
645                super::super::X509ChainError::CertificateNotValid(_)
646            ))
647        ));
648    }
649
650    #[test]
651    fn selector_resolved_leaf_does_not_anchor_itself() {
652        // A certificate available for selector lookup is not automatically a
653        // trust anchor; chain verification still requires a separate issuer.
654        let certificate_der = certificate_der(RSA_4096_CERTIFICATE);
655        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
656            trusted_certs: vec![certificate_der],
657            verify_chains: true,
658            verification_time: Some(fixture_certificate_time()),
659            ..KeyResolverConfig::default()
660        });
661        let error = super::super::VerifyContext::new()
662            .key_resolver(&resolver)
663            .verify(&x509_signature_with_leaf_subject())
664            .expect_err("selector-resolved leaf must not trust itself");
665
666        assert!(matches!(
667            error,
668            DsigError::KeyResolution(KeyResolutionError::Chain(
669                super::super::X509ChainError::UntrustedRoot
670            ))
671        ));
672    }
673
674    #[test]
675    fn selector_resolved_leaf_uses_separate_anchor() {
676        // Selector lookup may use the leaf from the configured set, but chain
677        // verification must terminate at a different configured certificate.
678        let leaf = certificate_der(RSA_4096_CERTIFICATE);
679        let issuer = certificate_der(include_str!("../../tests/fixtures/keys/ca2cert.pem"));
680        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
681            trusted_certs: vec![leaf, issuer],
682            verify_chains: true,
683            verification_time: Some(fixture_certificate_time()),
684            ..KeyResolverConfig::default()
685        });
686        let result = super::super::VerifyContext::new()
687            .key_resolver(&resolver)
688            .verify(&x509_signature_with_leaf_subject())
689            .expect("selector-resolved leaf should chain to its configured issuer");
690
691        assert_eq!(result.status, super::super::DsigStatus::Valid);
692    }
693
694    #[test]
695    fn resolves_each_x509_selector_from_configured_certificates() {
696        // Every selector form documented by KeyInfo must independently locate
697        // the same configured RSA certificate without embedded key material.
698        let selectors = [
699            "<X509SubjectName>C=US, ST=California, O=XML Security Library (http://www.aleksey.com/xmlsec), CN=Test Key rsa-2048</X509SubjectName>",
700            "<X509IssuerSerial><X509IssuerName>C=US, ST=California, O=XML Security Library (http://www.aleksey.com/xmlsec), OU=Second level CA, CN=Aleksey Sanin, Email=xmlsec@aleksey.com</X509IssuerName><X509SerialNumber>680572598617295163017172295025714171905498632019</X509SerialNumber></X509IssuerSerial>",
701            "<X509SKI>bcOXN/nsVl8GatRbcKrPbzIbw0Y=</X509SKI>",
702        ];
703        let configured_certificate = certificate_der(include_str!(
704            "../../tests/fixtures/keys/rsa/rsa-2048-cert.pem"
705        ));
706
707        for selector in selectors {
708            let key_info = format!("<KeyInfo><X509Data>{selector}</X509Data></KeyInfo>");
709            let xml = replace_unprefixed_key_info(RSA_KEY_VALUE_SIGNATURE, &key_info);
710            let resolver = DefaultKeyResolver::new(KeyResolverConfig {
711                trusted_certs: vec![configured_certificate.clone()],
712                ..KeyResolverConfig::default()
713            });
714            let result = super::super::VerifyContext::new()
715                .key_resolver(&resolver)
716                .verify(&xml)
717                .expect("X509 selector should resolve configured certificate");
718
719            assert_eq!(result.status, super::super::DsigStatus::Valid);
720        }
721    }
722
723    #[test]
724    fn resolves_configured_chain_selectors_across_certificates() {
725        // Selector categories may identify different members of one configured
726        // chain; the unique leaf remains the signing certificate.
727        let key_info = r#"<KeyInfo><X509Data><X509SubjectName>C=US, ST=California, O=XML Security Library (http://www.aleksey.com/xmlsec), CN=Test Key rsa-2048</X509SubjectName><X509SKI>0X0XrEVCio75sBcl1TxymJ2IOiU=</X509SKI></X509Data></KeyInfo>"#;
728        let xml = replace_unprefixed_key_info(RSA_KEY_VALUE_SIGNATURE, key_info);
729        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
730            trusted_certs: vec![
731                certificate_der(include_str!(
732                    "../../tests/fixtures/keys/rsa/rsa-2048-cert.pem"
733                )),
734                certificate_der(include_str!("../../tests/fixtures/keys/ca2cert.pem")),
735            ],
736            ..KeyResolverConfig::default()
737        });
738        let result = super::super::VerifyContext::new()
739            .key_resolver(&resolver)
740            .verify(&xml)
741            .expect("selectors across one configured chain should resolve its leaf");
742
743        assert_eq!(result.status, super::super::DsigStatus::Valid);
744    }
745
746    #[test]
747    fn unmatched_x509_selector_does_not_resolve() {
748        // A selector mismatch must not fall back to arbitrary configured key material.
749        let key_info = "<KeyInfo><X509Data><X509SubjectName>CN=not-the-signer</X509SubjectName></X509Data></KeyInfo>";
750        let xml = replace_unprefixed_key_info(RSA_KEY_VALUE_SIGNATURE, key_info);
751        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
752            trusted_certs: vec![certificate_der(include_str!(
753                "../../tests/fixtures/keys/rsa/rsa-2048-cert.pem"
754            ))],
755            ..KeyResolverConfig::default()
756        });
757        let result = super::super::VerifyContext::new()
758            .key_resolver(&resolver)
759            .verify(&xml)
760            .expect("an unmatched selector is a key miss, not a parser failure");
761
762        assert!(matches!(
763            result.status,
764            super::super::DsigStatus::Invalid(super::super::FailureReason::KeyNotFound)
765        ));
766    }
767
768    #[test]
769    fn ambiguous_x509_selector_fails_closed() {
770        // Duplicate configured certificates must not make key selection order-dependent.
771        let certificate = certificate_der(RSA_4096_CERTIFICATE);
772        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
773            trusted_certs: vec![certificate.clone(), certificate],
774            ..KeyResolverConfig::default()
775        });
776        let error = super::super::VerifyContext::new()
777            .key_resolver(&resolver)
778            .verify(&x509_signature_with_leaf_subject())
779            .expect_err("ambiguous X509 selector lookup must fail closed");
780
781        assert!(matches!(
782            error,
783            DsigError::KeyResolution(KeyResolutionError::AmbiguousCertificate)
784        ));
785    }
786
787    #[test]
788    fn unsupported_x509_digest_selector_fails_closed() {
789        // Unknown digest URIs must not be treated as a normal key miss because
790        // that would silently weaken the caller's explicit selector policy.
791        let key_info = "<KeyInfo xmlns:dsig11=\"http://www.w3.org/2009/xmldsig11#\"><X509Data><dsig11:X509Digest Algorithm=\"urn:unsupported\">AQ==</dsig11:X509Digest></X509Data></KeyInfo>";
792        let xml = replace_unprefixed_key_info(RSA_KEY_VALUE_SIGNATURE, key_info);
793        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
794            trusted_certs: vec![certificate_der(include_str!(
795                "../../tests/fixtures/keys/rsa/rsa-2048-cert.pem"
796            ))],
797            ..KeyResolverConfig::default()
798        });
799        let error = super::super::VerifyContext::new()
800            .key_resolver(&resolver)
801            .verify(&xml)
802            .expect_err("unsupported X509Digest algorithm must fail closed");
803
804        assert!(matches!(
805            error,
806            DsigError::KeyResolution(KeyResolutionError::UnsupportedDigestAlgorithm(uri))
807                if uri == "urn:unsupported"
808        ));
809    }
810
811    #[test]
812    fn resolves_named_key_end_to_end() {
813        // KeyName lookup must preserve the same cryptographic result as embedded X509Data.
814        let xml = replace_key_info(
815            SIGNED_SAML,
816            "<ds:KeyInfo><ds:KeyName>idp-signing</ds:KeyName></ds:KeyInfo>",
817        );
818        let mut config = KeyResolverConfig::default();
819        config.named_keys.insert(
820            "idp-signing".into(),
821            VerificationKey {
822                algorithm: SignatureAlgorithm::EcdsaP256Sha256,
823                public_key_bytes: public_key_der(SAML_PUBLIC_KEY),
824                certificate_der: None,
825                name: Some("idp-signing".into()),
826            },
827        );
828        let resolver = DefaultKeyResolver::new(config);
829        let result = super::super::VerifyContext::new()
830            .key_resolver(&resolver)
831            .verify(&xml)
832            .expect("named key should resolve");
833
834        assert_eq!(result.status, super::super::DsigStatus::Valid);
835    }
836
837    #[test]
838    fn resolves_der_encoded_key_end_to_end() {
839        // DSig 1.1 DEREncodedKeyValue must feed the same SPKI verifier path.
840        let encoded = STANDARD.encode(public_key_der(SAML_PUBLIC_KEY));
841        let xml = replace_key_info(
842            SIGNED_SAML,
843            &format!(
844                "<ds:KeyInfo><dsig11:DEREncodedKeyValue xmlns:dsig11=\"http://www.w3.org/2009/xmldsig11#\">{encoded}</dsig11:DEREncodedKeyValue></ds:KeyInfo>"
845            ),
846        );
847        let resolver = DefaultKeyResolver::default();
848        let result = super::super::VerifyContext::new()
849            .key_resolver(&resolver)
850            .verify(&xml)
851            .expect("DER key should resolve");
852
853        assert_eq!(result.status, super::super::DsigStatus::Valid);
854    }
855
856    #[test]
857    fn resolves_rsa_key_value_end_to_end() {
858        // Embedded CryptoBinary parameters must verify the original RSA-2048 donor signature.
859        let public_key = rsa::RsaPublicKey::from_public_key_pem(RSA_PUBLIC_KEY)
860            .expect("fixture must contain an RSA public key");
861        let (modulus, exponent) = rsa_key_value_parts(&public_key);
862        let key_info = format!(
863            "<KeyInfo><KeyValue><RSAKeyValue><Modulus>{}</Modulus><Exponent>{}</Exponent></RSAKeyValue></KeyValue></KeyInfo>",
864            modulus, exponent,
865        );
866        let xml = replace_unprefixed_key_info(RSA_KEY_VALUE_SIGNATURE, &key_info);
867        let resolver = DefaultKeyResolver::default();
868        let result = super::super::VerifyContext::new()
869            .key_resolver(&resolver)
870            .verify(&xml)
871            .expect("RSAKeyValue should resolve");
872
873        assert_eq!(result.status, super::super::DsigStatus::Valid);
874    }
875
876    #[test]
877    fn rsa_key_value_rejects_legacy_weak_modulus() {
878        // Embedded keys must obey the same 2048-bit minimum as certificate and DER keys.
879        let resolver = DefaultKeyResolver::default();
880        let error = super::super::VerifyContext::new()
881            .key_resolver(&resolver)
882            .verify(LEGACY_RSA_KEY_VALUE_SIGNATURE)
883            .expect_err("1024-bit RSAKeyValue must fail closed");
884
885        assert!(matches!(
886            error,
887            DsigError::Crypto(super::super::SignatureVerificationError::InvalidKeyDer)
888        ));
889    }
890
891    #[test]
892    fn rsa_key_value_rejects_ecdsa_signature_method() {
893        // Embedded RSA parameters must not be relabeled for an ECDSA SignatureMethod.
894        let public_key = rsa::RsaPublicKey::from_public_key_pem(RSA_PUBLIC_KEY)
895            .expect("fixture must contain an RSA public key");
896        let (modulus, exponent) = rsa_key_value_parts(&public_key);
897        let key_info = format!(
898            "<ds:KeyInfo><ds:KeyValue><ds:RSAKeyValue><ds:Modulus>{}</ds:Modulus><ds:Exponent>{}</ds:Exponent></ds:RSAKeyValue></ds:KeyValue></ds:KeyInfo>",
899            modulus, exponent,
900        );
901        let xml = replace_key_info(SIGNED_SAML, &key_info);
902        let resolver = DefaultKeyResolver::default();
903        let error = super::super::VerifyContext::new()
904            .key_resolver(&resolver)
905            .verify(&xml)
906            .expect_err("RSAKeyValue must not resolve for ECDSA");
907
908        assert!(matches!(
909            error,
910            DsigError::KeyResolution(KeyResolutionError::AlgorithmMismatch)
911        ));
912    }
913
914    #[test]
915    fn resolves_ec_p256_key_value_end_to_end() {
916        // XMLDSig 1.1 ECKeyValue must verify without a preset key or certificate.
917        let resolver = DefaultKeyResolver::default();
918        let result = super::super::VerifyContext::new()
919            .key_resolver(&resolver)
920            .verify(EC_P256_KEY_VALUE_SIGNATURE)
921            .expect("P-256 ECKeyValue should resolve");
922
923        assert_eq!(result.status, super::super::DsigStatus::Valid);
924    }
925
926    #[test]
927    fn resolves_ec_p384_key_value_end_to_end() {
928        // The donor P-384 vector uses NamedCurve + uncompressed PublicKey.
929        let resolver = DefaultKeyResolver::default();
930        let result = super::super::VerifyContext::new()
931            .key_resolver(&resolver)
932            .verify(EC_P384_KEY_VALUE_SIGNATURE)
933            .expect("P-384 ECKeyValue should resolve");
934
935        assert_eq!(result.status, super::super::DsigStatus::Valid);
936    }
937
938    #[test]
939    fn ec_key_value_ignored_for_rsa_signature_method() {
940        // Embedded EC key material must not be relabeled for an RSA SignatureMethod.
941        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>"#;
942        let xml = replace_unprefixed_key_info(RSA_KEY_VALUE_SIGNATURE, key_info);
943        let resolver = DefaultKeyResolver::default();
944        let result = super::super::VerifyContext::new()
945            .key_resolver(&resolver)
946            .verify(&xml)
947            .expect("single incompatible ECKeyValue should be ignored");
948
949        assert_eq!(
950            result.status,
951            super::super::DsigStatus::Invalid(super::super::FailureReason::KeyNotFound)
952        );
953    }
954
955    #[test]
956    fn incompatible_ec_key_value_falls_back_to_later_rsa_key_value() {
957        // Mixed KeyInfo should keep scanning after an incompatible ECKeyValue source.
958        let public_key = rsa::RsaPublicKey::from_public_key_pem(RSA_PUBLIC_KEY)
959            .expect("fixture must contain an RSA public key");
960        let (modulus, exponent) = rsa_key_value_parts(&public_key);
961        let key_info = format!(
962            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>"#,
963            modulus, exponent,
964        );
965        let xml = replace_unprefixed_key_info(RSA_KEY_VALUE_SIGNATURE, &key_info);
966        let resolver = DefaultKeyResolver::default();
967        let result = super::super::VerifyContext::new()
968            .key_resolver(&resolver)
969            .verify(&xml)
970            .expect("later RSAKeyValue should resolve");
971
972        assert_eq!(result.status, super::super::DsigStatus::Valid);
973    }
974
975    #[test]
976    fn unsupported_ec_key_value_falls_back_to_later_key_name() {
977        // Unsupported curves are non-fatal so a later compatible source can verify.
978        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>"#;
979        let xml = replace_key_info(SIGNED_SAML, key_info);
980        let mut config = KeyResolverConfig::default();
981        config.named_keys.insert(
982            "idp-signing".into(),
983            VerificationKey {
984                algorithm: SignatureAlgorithm::EcdsaP256Sha256,
985                public_key_bytes: public_key_der(SAML_PUBLIC_KEY),
986                certificate_der: None,
987                name: Some("idp-signing".into()),
988            },
989        );
990        let resolver = DefaultKeyResolver::new(config);
991        let result = super::super::VerifyContext::new()
992            .key_resolver(&resolver)
993            .verify(&xml)
994            .expect("later KeyName should resolve");
995
996        assert_eq!(result.status, super::super::DsigStatus::Valid);
997    }
998
999    #[test]
1000    fn invalid_ec_key_value_falls_back_to_later_key_name() {
1001        // Off-curve EC points are typed errors only if no later source can verify.
1002        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>"#;
1003        let xml = replace_key_info(SIGNED_SAML, key_info);
1004        let mut config = KeyResolverConfig::default();
1005        config.named_keys.insert(
1006            "idp-signing".into(),
1007            VerificationKey {
1008                algorithm: SignatureAlgorithm::EcdsaP256Sha256,
1009                public_key_bytes: public_key_der(SAML_PUBLIC_KEY),
1010                certificate_der: None,
1011                name: Some("idp-signing".into()),
1012            },
1013        );
1014        let resolver = DefaultKeyResolver::new(config);
1015        let result = super::super::VerifyContext::new()
1016            .key_resolver(&resolver)
1017            .verify(&xml)
1018            .expect("later KeyName should resolve after invalid ECKeyValue");
1019
1020        assert_eq!(result.status, super::super::DsigStatus::Valid);
1021    }
1022
1023    #[test]
1024    fn malformed_ec_key_value_falls_back_to_later_key_name() {
1025        // Parse-level EC point errors remain non-fatal while later sources exist.
1026        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>"#;
1027        let xml = replace_key_info(SIGNED_SAML, key_info);
1028        let mut config = KeyResolverConfig::default();
1029        config.named_keys.insert(
1030            "idp-signing".into(),
1031            VerificationKey {
1032                algorithm: SignatureAlgorithm::EcdsaP256Sha256,
1033                public_key_bytes: public_key_der(SAML_PUBLIC_KEY),
1034                certificate_der: None,
1035                name: Some("idp-signing".into()),
1036            },
1037        );
1038        let resolver = DefaultKeyResolver::new(config);
1039        let result = super::super::VerifyContext::new()
1040            .key_resolver(&resolver)
1041            .verify(&xml)
1042            .expect("later KeyName should resolve after malformed ECKeyValue");
1043
1044        assert_eq!(result.status, super::super::DsigStatus::Valid);
1045    }
1046
1047    #[test]
1048    fn invalid_base64_ec_key_value_falls_back_to_later_key_name() {
1049        // A bad ECKeyValue payload is an unusable source, not a reason to skip
1050        // later ordered KeyInfo sources that can verify the signature.
1051        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>"#;
1052        let xml = replace_key_info(SIGNED_SAML, key_info);
1053        let mut config = KeyResolverConfig::default();
1054        config.named_keys.insert(
1055            "idp-signing".into(),
1056            VerificationKey {
1057                algorithm: SignatureAlgorithm::EcdsaP256Sha256,
1058                public_key_bytes: public_key_der(SAML_PUBLIC_KEY),
1059                certificate_der: None,
1060                name: Some("idp-signing".into()),
1061            },
1062        );
1063        let resolver = DefaultKeyResolver::new(config);
1064        let result = super::super::VerifyContext::new()
1065            .key_resolver(&resolver)
1066            .verify(&xml)
1067            .expect("later KeyName should resolve after bad ECKeyValue base64");
1068
1069        assert_eq!(result.status, super::super::DsigStatus::Valid);
1070    }
1071
1072    #[test]
1073    fn missing_curve_uri_ec_key_value_falls_back_to_later_key_name() {
1074        // Missing EC curve parameters make only this KeyValue source unusable.
1075        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>"#;
1076        let xml = replace_key_info(SIGNED_SAML, key_info);
1077        let mut config = KeyResolverConfig::default();
1078        config.named_keys.insert(
1079            "idp-signing".into(),
1080            VerificationKey {
1081                algorithm: SignatureAlgorithm::EcdsaP256Sha256,
1082                public_key_bytes: public_key_der(SAML_PUBLIC_KEY),
1083                certificate_der: None,
1084                name: Some("idp-signing".into()),
1085            },
1086        );
1087        let resolver = DefaultKeyResolver::new(config);
1088        let result = super::super::VerifyContext::new()
1089            .key_resolver(&resolver)
1090            .verify(&xml)
1091            .expect("later KeyName should resolve after missing EC curve URI");
1092
1093        assert_eq!(result.status, super::super::DsigStatus::Valid);
1094    }
1095
1096    #[test]
1097    fn malformed_ec_key_value_children_fall_back_to_later_key_name() {
1098        // An unusable EC source must not prevent later ordered KeyInfo sources
1099        // from resolving, regardless of which required child-shape check fails.
1100        let malformed_ec_key_values = [
1101            r#"<dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/>"#,
1102            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"/>"#,
1103            r#"<dsig11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/><dsig11:PublicKey>BA==</dsig11:PublicKey><dsig11:PublicKey>BA==</dsig11:PublicKey>"#,
1104        ];
1105
1106        for malformed_children in malformed_ec_key_values {
1107            let key_info = format!(
1108                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>"#
1109            );
1110            let xml = replace_key_info(SIGNED_SAML, &key_info);
1111            let mut config = KeyResolverConfig::default();
1112            config.named_keys.insert(
1113                "idp-signing".into(),
1114                VerificationKey {
1115                    algorithm: SignatureAlgorithm::EcdsaP256Sha256,
1116                    public_key_bytes: public_key_der(SAML_PUBLIC_KEY),
1117                    certificate_der: None,
1118                    name: Some("idp-signing".into()),
1119                },
1120            );
1121            let resolver = DefaultKeyResolver::new(config);
1122            let result = super::super::VerifyContext::new()
1123                .key_resolver(&resolver)
1124                .verify(&xml)
1125                .expect("later KeyName should resolve after malformed EC child shape");
1126
1127            assert_eq!(result.status, super::super::DsigStatus::Valid);
1128        }
1129    }
1130
1131    #[test]
1132    fn mismatched_ec_curve_falls_back_to_later_key_name() {
1133        // A valid P-384 key is unusable for an ECDSA-SHA256 signature but must not
1134        // prevent a later P-256 KeyName from resolving the same document.
1135        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>"#;
1136        let xml = replace_key_info(SIGNED_SAML, key_info);
1137        let mut config = KeyResolverConfig::default();
1138        config.named_keys.insert(
1139            "idp-signing".into(),
1140            VerificationKey {
1141                algorithm: SignatureAlgorithm::EcdsaP256Sha256,
1142                public_key_bytes: public_key_der(SAML_PUBLIC_KEY),
1143                certificate_der: None,
1144                name: Some("idp-signing".into()),
1145            },
1146        );
1147        let resolver = DefaultKeyResolver::new(config);
1148        let result = super::super::VerifyContext::new()
1149            .key_resolver(&resolver)
1150            .verify(&xml)
1151            .expect("later KeyName should resolve after mismatched ECKeyValue");
1152
1153        assert_eq!(result.status, super::super::DsigStatus::Valid);
1154    }
1155
1156    #[test]
1157    fn lone_malformed_ec_key_value_reports_invalid_public_key() {
1158        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>"#;
1159        let xml = replace_key_info(SIGNED_SAML, key_info);
1160        let error = super::super::VerifyContext::new()
1161            .key_resolver(&DefaultKeyResolver::default())
1162            .verify(&xml)
1163            .expect_err("lone malformed ECKeyValue should surface typed key error");
1164
1165        assert!(matches!(
1166            error,
1167            DsigError::KeyResolution(KeyResolutionError::InvalidPublicKey)
1168        ));
1169    }
1170
1171    #[test]
1172    fn lone_mismatched_ec_curve_reports_algorithm_mismatch() {
1173        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>"#;
1174        let xml = replace_key_info(SIGNED_SAML, key_info);
1175        let error = super::super::VerifyContext::new()
1176            .key_resolver(&DefaultKeyResolver::default())
1177            .verify(&xml)
1178            .expect_err("lone mismatched ECKeyValue should surface typed key error");
1179
1180        assert!(matches!(
1181            error,
1182            DsigError::KeyResolution(KeyResolutionError::AlgorithmMismatch)
1183        ));
1184    }
1185
1186    #[test]
1187    fn chain_verification_rejects_untrusted_embedded_certificate() {
1188        // Enabling chain policy must fail closed when no trust anchor is configured.
1189        let resolver = DefaultKeyResolver::new(KeyResolverConfig {
1190            verify_chains: true,
1191            ..KeyResolverConfig::default()
1192        });
1193        let error = super::super::VerifyContext::new()
1194            .key_resolver(&resolver)
1195            .verify(SIGNED_SAML)
1196            .expect_err("untrusted certificate must fail chain validation");
1197
1198        assert!(matches!(
1199            error,
1200            DsigError::KeyResolution(KeyResolutionError::Chain(
1201                super::super::X509ChainError::UntrustedRoot
1202            ))
1203        ));
1204    }
1205
1206    #[test]
1207    fn named_key_algorithm_mismatch_fails_closed() {
1208        // A key registered for RSA must never be attempted for an ECDSA signature.
1209        let xml = replace_key_info(
1210            SIGNED_SAML,
1211            "<ds:KeyInfo><ds:KeyName>wrong-algorithm</ds:KeyName></ds:KeyInfo>",
1212        );
1213        let mut config = KeyResolverConfig::default();
1214        config.named_keys.insert(
1215            "wrong-algorithm".into(),
1216            VerificationKey {
1217                algorithm: SignatureAlgorithm::RsaSha256,
1218                public_key_bytes: public_key_der(SAML_PUBLIC_KEY),
1219                certificate_der: None,
1220                name: Some("wrong-algorithm".into()),
1221            },
1222        );
1223        let resolver = DefaultKeyResolver::new(config);
1224        let error = super::super::VerifyContext::new()
1225            .key_resolver(&resolver)
1226            .verify(&xml)
1227            .expect_err("algorithm mismatch must fail closed");
1228
1229        assert!(matches!(
1230            error,
1231            DsigError::KeyResolution(KeyResolutionError::AlgorithmMismatch)
1232        ));
1233    }
1234
1235    #[test]
1236    fn named_key_spki_type_mismatch_fails_during_resolution() {
1237        // The configured algorithm label cannot override the actual SPKI key type.
1238        let xml = replace_key_info(
1239            SIGNED_SAML,
1240            "<ds:KeyInfo><ds:KeyName>mislabeled</ds:KeyName></ds:KeyInfo>",
1241        );
1242        let mut config = KeyResolverConfig::default();
1243        config.named_keys.insert(
1244            "mislabeled".into(),
1245            VerificationKey {
1246                algorithm: SignatureAlgorithm::EcdsaP256Sha256,
1247                public_key_bytes: public_key_der(RSA_PUBLIC_KEY),
1248                certificate_der: None,
1249                name: Some("mislabeled".into()),
1250            },
1251        );
1252        let resolver = DefaultKeyResolver::new(config);
1253        let error = super::super::VerifyContext::new()
1254            .key_resolver(&resolver)
1255            .verify(&xml)
1256            .expect_err("mislabeled named key must fail during resolution");
1257
1258        assert!(matches!(
1259            error,
1260            DsigError::KeyResolution(KeyResolutionError::AlgorithmMismatch)
1261        ));
1262    }
1263
1264    #[test]
1265    fn malformed_named_key_reports_public_key_error() {
1266        // Non-certificate SPKI failures must not be mislabeled as certificate errors.
1267        let xml = replace_key_info(
1268            SIGNED_SAML,
1269            "<ds:KeyInfo><ds:KeyName>malformed</ds:KeyName></ds:KeyInfo>",
1270        );
1271        let mut config = KeyResolverConfig::default();
1272        config.named_keys.insert(
1273            "malformed".into(),
1274            VerificationKey {
1275                algorithm: SignatureAlgorithm::EcdsaP256Sha256,
1276                public_key_bytes: vec![1, 2, 3],
1277                certificate_der: None,
1278                name: Some("malformed".into()),
1279            },
1280        );
1281        let resolver = DefaultKeyResolver::new(config);
1282        let error = super::super::VerifyContext::new()
1283            .key_resolver(&resolver)
1284            .verify(&xml)
1285            .expect_err("malformed named key must fail during resolution");
1286
1287        assert!(matches!(
1288            error,
1289            DsigError::KeyResolution(KeyResolutionError::InvalidPublicKey)
1290        ));
1291    }
1292}