Skip to main content

xml_sec/xmldsig/
trust.rs

1//! Key discovery and authorization are distinct from signature validation.
2
3use std::{ops::Deref, time::SystemTime};
4
5use super::{DsigError, SignatureAlgorithm, VerifyingKey};
6use crate::policy::{KeyTrustPolicy, PolicyViolation, VerificationPolicy};
7
8/// Evidence of key authorization, independent of the signature's validity.
9#[derive(Debug, Clone, PartialEq, Eq)]
10// The fixed path bound avoids a heap allocation for every verification result.
11#[expect(
12    clippy::large_enum_variant,
13    reason = "bounded inline path evidence avoids heap allocation"
14)]
15pub enum KeyTrustEvidence {
16    /// Verification stopped before key authorization.
17    NotEvaluated,
18    /// Only mathematical signature verification was requested.
19    NotEstablished,
20    /// The application explicitly supplied or authorized this key.
21    CallerTrusted,
22    /// A certificate path was validated to a caller-provided anchor.
23    ValidatedX509(X509TrustEvidence),
24}
25
26/// Immutable identity of the exact path that established key authorization.
27/// Fields cannot be constructed or changed by a certificate parser or caller.
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct X509TrustEvidence {
30    fingerprints: [[u8; 32]; crate::hard_limits::X509_CHAIN_DEPTH_CEILING],
31    path_len: usize,
32    verification_time: SystemTime,
33    revocation_checked: bool,
34}
35
36impl X509TrustEvidence {
37    pub(crate) fn new(
38        certificates: impl ExactSizeIterator<Item = impl AsRef<[u8]>>,
39        verification_time: SystemTime,
40        revocation_checked: bool,
41    ) -> Result<Self, DsigError> {
42        use sha2::{Digest, Sha256};
43        let path_len = certificates.len();
44        if path_len == 0 || path_len > crate::hard_limits::X509_CHAIN_DEPTH_CEILING {
45            return Err(PolicyViolation::KeyTrust {
46                reason: "invalid validated certificate path",
47            }
48            .into());
49        }
50        // Path identity is bounded by the same hard depth ceiling as validation.
51        // Fixed storage avoids an unmetered post-validation allocation.
52        let mut fingerprints = [[0; 32]; crate::hard_limits::X509_CHAIN_DEPTH_CEILING];
53        for (fingerprint, certificate) in fingerprints.iter_mut().zip(certificates) {
54            *fingerprint = Sha256::digest(certificate.as_ref()).into();
55        }
56        Ok(Self {
57            fingerprints,
58            path_len,
59            verification_time,
60            revocation_checked,
61        })
62    }
63
64    /// SHA-256 identities in leaf-to-anchor order, including the trust anchor.
65    pub fn certificate_fingerprints(&self) -> &[[u8; 32]] {
66        &self.fingerprints[..self.path_len]
67    }
68
69    /// SHA-256 identity of the caller-supplied anchor that terminated the path.
70    pub fn anchor_fingerprint(&self) -> &[u8; 32] {
71        &self.fingerprints[self.path_len - 1]
72    }
73
74    /// The single time captured for all candidate-path validation in the operation.
75    pub fn verification_time(&self) -> SystemTime {
76        self.verification_time
77    }
78
79    /// Whether the policy requested and validation enforced CRL processing.
80    pub fn revocation_checked(&self) -> bool {
81        self.revocation_checked
82    }
83}
84
85/// A public key authorized by certificate path validation, not just DER parsing.
86/// Only the validating resolver can create it; callers may inspect evidence.
87pub struct TrustedPublicKey {
88    key: Box<dyn VerifyingKey>,
89    trust: KeyTrustPolicy,
90    evidence: X509TrustEvidence,
91}
92
93impl TrustedPublicKey {
94    pub(crate) fn new(
95        key: Box<dyn VerifyingKey>,
96        mut trust: KeyTrustPolicy,
97        evidence: X509TrustEvidence,
98    ) -> Self {
99        // RFC 5280 §6.1.1: a proof is tied to an actual validation time, never
100        // to a reusable "choose now later" placeholder.
101        // https://www.rfc-editor.org/rfc/rfc5280#section-6.1.1
102        trust.verification_time = Some(evidence.verification_time);
103        Self {
104            key,
105            trust,
106            evidence,
107        }
108    }
109
110    /// Validated certificate identities and the operation's validation time.
111    pub fn evidence(&self) -> &X509TrustEvidence {
112        &self.evidence
113    }
114}
115
116impl VerifyingKey for TrustedPublicKey {
117    fn association_spki(&self, algorithm: SignatureAlgorithm) -> Option<&[u8]> {
118        self.key.association_spki(algorithm)
119    }
120    fn verification_spki(&self, algorithm: SignatureAlgorithm) -> Result<Option<&[u8]>, DsigError> {
121        self.key.verification_spki(algorithm)
122    }
123
124    fn ecdsa_encoding(&self) -> crate::policy::EcdsaSignatureValueEncoding {
125        self.key.ecdsa_encoding()
126    }
127
128    fn verify_candidate_keys(
129        &self,
130        verify: &mut dyn FnMut(&dyn VerifyingKey) -> Result<bool, DsigError>,
131    ) -> Result<Option<bool>, DsigError> {
132        self.key.verify_candidate_keys(verify)
133    }
134    fn verify_with_context(
135        &self,
136        algorithm: SignatureAlgorithm,
137        context: &super::SignatureContext,
138        data: &[u8],
139        signature: &[u8],
140    ) -> Result<bool, DsigError> {
141        self.key
142            .verify_with_context(algorithm, context, data, signature)
143    }
144    fn validate_policy(&self, policy: &VerificationPolicy) -> Result<(), DsigError> {
145        if self.trust != policy.key_trust {
146            return Err(PolicyViolation::KeyTrust {
147                reason: "validated key belongs to a different trust policy",
148            }
149            .into());
150        }
151        self.key.validate_policy(policy)
152    }
153
154    fn validate_signature_value(
155        &self,
156        algorithm: SignatureAlgorithm,
157        signature: &[u8],
158    ) -> Result<bool, DsigError> {
159        self.key.validate_signature_value(algorithm, signature)
160    }
161
162    fn validate_signature_value_with_policy(
163        &self,
164        policy: &VerificationPolicy,
165        algorithm: SignatureAlgorithm,
166        signature: &[u8],
167    ) -> Result<bool, DsigError> {
168        self.validate_policy(policy)?;
169        self.key
170            .validate_signature_value_with_policy(policy, algorithm, signature)
171    }
172
173    fn verify(
174        &self,
175        algorithm: SignatureAlgorithm,
176        data: &[u8],
177        signature: &[u8],
178    ) -> Result<bool, DsigError> {
179        self.key.verify(algorithm, data, signature)
180    }
181
182    fn verify_with_policy(
183        &self,
184        policy: &VerificationPolicy,
185        algorithm: SignatureAlgorithm,
186        data: &[u8],
187        signature: &[u8],
188    ) -> Result<bool, DsigError> {
189        self.validate_policy(policy)?;
190        self.key
191            .verify_with_policy(policy, algorithm, data, signature)
192    }
193}
194
195/// Resolver output: finding a key does not implicitly authorize its signer.
196#[expect(
197    clippy::large_enum_variant,
198    reason = "one bounded resolver result per operation needs no extra box"
199)]
200pub enum ResolvedVerificationKey<'a> {
201    /// Document-supplied or otherwise unauthenticated key material.
202    Candidate(Box<dyn VerifyingKey + 'a>),
203    /// An explicit trust declaration made by application-owned resolver code.
204    CallerTrusted(Box<dyn VerifyingKey + 'a>),
205    /// An unforgeable result of the built-in certificate validation boundary.
206    Validated(TrustedPublicKey),
207}
208
209impl<'a> ResolvedVerificationKey<'a> {
210    /// Extract the mathematical verifier without establishing signer trust.
211    pub fn into_key(self) -> Box<dyn VerifyingKey + 'a> {
212        match self {
213            Self::Candidate(key) | Self::CallerTrusted(key) => key,
214            Self::Validated(key) => Box::new(key),
215        }
216    }
217
218    pub(crate) fn authorize(
219        &self,
220        policy: &VerificationPolicy,
221    ) -> Result<KeyTrustEvidence, DsigError> {
222        use crate::policy::VerificationTrustMode;
223        // XMLDSig 1.1 §4.5 leaves key trust to the application:
224        // https://www.w3.org/TR/2013/REC-xmldsig-core1-20130411/#sec-KeyInfo
225        // This explicit mode is application policy, not a conformance requirement.
226        if policy.key_trust.mode == VerificationTrustMode::CryptographicOnly {
227            return Ok(KeyTrustEvidence::NotEstablished);
228        }
229        match self {
230            Self::Candidate(_) => Err(PolicyViolation::KeyTrust {
231                reason: "verification requires an authorized key",
232            }
233            .into()),
234            Self::CallerTrusted(_) => Ok(KeyTrustEvidence::CallerTrusted),
235            Self::Validated(key) => {
236                key.validate_policy(policy)?;
237                Ok(KeyTrustEvidence::ValidatedX509(key.evidence.clone()))
238            }
239        }
240    }
241}
242
243impl<'a> AsRef<dyn VerifyingKey + 'a> for ResolvedVerificationKey<'a> {
244    fn as_ref(&self) -> &(dyn VerifyingKey + 'a) {
245        match self {
246            Self::Candidate(key) | Self::CallerTrusted(key) => key.as_ref(),
247            Self::Validated(key) => key,
248        }
249    }
250}
251
252impl<'a> Deref for ResolvedVerificationKey<'a> {
253    type Target = dyn VerifyingKey + 'a;
254    fn deref(&self) -> &Self::Target {
255        self.as_ref()
256    }
257}
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262
263    struct NeverExecuted;
264    impl VerifyingKey for NeverExecuted {
265        fn verify(&self, _: SignatureAlgorithm, _: &[u8], _: &[u8]) -> Result<bool, DsigError> {
266            panic!("stale authorization must fail before mathematical work");
267        }
268    }
269
270    #[test]
271    fn validated_key_is_bound_to_time_purpose_and_policy() {
272        // A validated key must not be reusable under another time or purpose,
273        // or through a caller policy whose implicit clock has not been captured.
274        let time = SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(1_800_000_000);
275        let evidence =
276            X509TrustEvidence::new([b"bounded certificate identity"].into_iter(), time, false)
277                .expect("a one-certificate proof fits the hard path bound");
278        let mut policy = VerificationPolicy::default();
279        policy.key_trust.verify_x509_chains = true;
280        let key =
281            TrustedPublicKey::new(Box::new(NeverExecuted), policy.key_trust.clone(), evidence);
282        assert!(key.validate_policy(&policy).is_err());
283        policy.key_trust.verification_time = Some(time);
284        key.validate_policy(&policy)
285            .expect("the exact captured snapshot must match");
286        let mut changed = policy.clone();
287        changed.key_trust.verification_time = Some(time + std::time::Duration::from_secs(1));
288        assert!(
289            key.verify_with_policy(
290                &changed,
291                SignatureAlgorithm::RsaSha256,
292                b"data",
293                b"signature"
294            )
295            .is_err()
296        );
297        changed = policy.clone();
298        changed
299            .key_trust
300            .allowed_extended_key_usages
301            .insert(crate::policy::ExtendedKeyPurpose::CodeSigning);
302        assert!(key.validate_policy(&changed).is_err());
303        changed = policy;
304        changed.key_trust.check_crls = true;
305        assert!(key.validate_policy(&changed).is_err());
306    }
307}