1use std::{ops::Deref, time::SystemTime};
4
5use super::{DsigError, SignatureAlgorithm, VerifyingKey};
6use crate::policy::{KeyTrustPolicy, PolicyViolation, VerificationPolicy};
7
8#[derive(Debug, Clone, PartialEq, Eq)]
10#[expect(
12 clippy::large_enum_variant,
13 reason = "bounded inline path evidence avoids heap allocation"
14)]
15pub enum KeyTrustEvidence {
16 NotEvaluated,
18 NotEstablished,
20 CallerTrusted,
22 ValidatedX509(X509TrustEvidence),
24}
25
26#[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 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 pub fn certificate_fingerprints(&self) -> &[[u8; 32]] {
66 &self.fingerprints[..self.path_len]
67 }
68
69 pub fn anchor_fingerprint(&self) -> &[u8; 32] {
71 &self.fingerprints[self.path_len - 1]
72 }
73
74 pub fn verification_time(&self) -> SystemTime {
76 self.verification_time
77 }
78
79 pub fn revocation_checked(&self) -> bool {
81 self.revocation_checked
82 }
83}
84
85pub 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 trust.verification_time = Some(evidence.verification_time);
103 Self {
104 key,
105 trust,
106 evidence,
107 }
108 }
109
110 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#[expect(
197 clippy::large_enum_variant,
198 reason = "one bounded resolver result per operation needs no extra box"
199)]
200pub enum ResolvedVerificationKey<'a> {
201 Candidate(Box<dyn VerifyingKey + 'a>),
203 CallerTrusted(Box<dyn VerifyingKey + 'a>),
205 Validated(TrustedPublicKey),
207}
208
209impl<'a> ResolvedVerificationKey<'a> {
210 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 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 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}