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