1use std::{
4 sync::Arc,
5 time::{Duration, SystemTime, UNIX_EPOCH},
6};
7
8use http::{HeaderMap, request::Parts, uri::Authority};
9
10use crate::{
11 base::CanonicalRequest,
12 profile::{Component, INTEGRITY_TOKEN_HEADER, SIGNATURE_HEADER, SIGNATURE_INPUT_HEADER},
13 reject::{RejectReason, Rejection},
14 replay::ReplayGuard,
15 signature::{SignatureInputError, SignatureParams, parse_signature, validate_nonce},
16 token::{AttestedDevice, TokenError, TokenVerifier},
17};
18
19pub const DEFAULT_MAX_AGE: Duration = Duration::from_mins(5);
21pub const DEFAULT_MAX_FUTURE_SKEW: Duration = Duration::from_secs(60);
23
24pub trait Clock: Send + Sync {
26 fn now(&self) -> SystemTime;
28}
29
30#[derive(Debug, Clone, Copy, Default)]
32pub struct SystemClock;
33
34impl Clock for SystemClock {
35 fn now(&self) -> SystemTime {
36 SystemTime::now()
37 }
38}
39
40#[derive(Debug, Clone, Copy)]
42pub struct FixedClock(pub SystemTime);
43
44impl Clock for FixedClock {
45 fn now(&self) -> SystemTime {
46 self.0
47 }
48}
49
50#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct VerifiedAttestedKeyContext {
55 pub device: AttestedDevice,
57 pub request_binding: String,
59}
60
61#[derive(Clone)]
63pub struct Verifier {
64 tokens: TokenVerifier,
65 scheme: String,
66 authority: String,
67 max_age: Duration,
68 max_future_skew: Duration,
69 replay_guard: Option<Arc<dyn ReplayGuard>>,
70 clock: Arc<dyn Clock>,
71}
72
73pub struct VerifierBuilder {
75 verifier: Verifier,
76}
77
78#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
80pub enum VerifierConfigError {
81 #[error("authority must be set")]
83 EmptyAuthority,
84 #[error("authority must be a valid URI authority")]
86 InvalidAuthority,
87 #[error("scheme must be set")]
89 EmptyScheme,
90 #[error("max age and max future skew must be positive")]
92 EmptyWindow,
93}
94
95impl VerifierBuilder {
96 #[must_use]
98 pub fn scheme(mut self, scheme: impl Into<String>) -> Self {
99 self.verifier.scheme = scheme.into();
100 self
101 }
102
103 #[must_use]
105 pub const fn max_age(mut self, max_age: Duration) -> Self {
106 self.verifier.max_age = max_age;
107 self
108 }
109
110 #[must_use]
112 pub const fn max_future_skew(mut self, max_future_skew: Duration) -> Self {
113 self.verifier.max_future_skew = max_future_skew;
114 self
115 }
116
117 #[must_use]
119 pub fn replay_guard(mut self, replay_guard: Arc<dyn ReplayGuard>) -> Self {
120 self.verifier.replay_guard = Some(replay_guard);
121 self
122 }
123
124 #[must_use]
126 pub fn clock(mut self, clock: Arc<dyn Clock>) -> Self {
127 self.verifier.clock = clock;
128 self
129 }
130
131 pub fn build(self) -> Result<Verifier, VerifierConfigError> {
137 let verifier = self.verifier;
138 if verifier.authority.trim().is_empty() {
139 return Err(VerifierConfigError::EmptyAuthority);
140 }
141 verifier
142 .authority
143 .parse::<Authority>()
144 .map_err(|_| VerifierConfigError::InvalidAuthority)?;
145 if verifier.scheme.trim().is_empty() {
146 return Err(VerifierConfigError::EmptyScheme);
147 }
148 if verifier.max_age.is_zero() || verifier.max_future_skew.is_zero() {
149 return Err(VerifierConfigError::EmptyWindow);
150 }
151 Ok(verifier)
152 }
153}
154
155impl Verifier {
156 #[must_use]
158 pub fn builder(tokens: TokenVerifier, authority: impl Into<String>) -> VerifierBuilder {
159 VerifierBuilder {
160 verifier: Self {
161 tokens,
162 scheme: "https".to_owned(),
163 authority: authority.into(),
164 max_age: DEFAULT_MAX_AGE,
165 max_future_skew: DEFAULT_MAX_FUTURE_SKEW,
166 replay_guard: None,
167 clock: Arc::new(SystemClock),
168 },
169 }
170 }
171
172 #[must_use]
174 pub fn authority(&self) -> &str {
175 &self.authority
176 }
177
178 pub async fn verify(
187 &self,
188 head: &Parts,
189 body: &[u8],
190 ) -> Result<VerifiedAttestedKeyContext, Rejection> {
191 let headers = SignedHeaders::extract(&head.headers)?;
192
193 let params = SignatureParams::parse(headers.signature_input).map_err(|error| {
194 let reason = match error {
195 SignatureInputError::UnsupportedComponent => RejectReason::SignatureBaseIncomplete,
196 _ => RejectReason::SignatureInputMalformed,
197 };
198 Rejection::new(reason).with_source(error)
199 })?;
200 let signature = parse_signature(headers.signature)
201 .map_err(|error| Rejection::new(RejectReason::SignatureMalformed).with_source(error))?;
202 if let Some(missing) = Component::ALL
203 .into_iter()
204 .find(|component| !params.components().contains(component))
205 {
206 return Err(Rejection::new(RejectReason::ComponentMissing)
207 .with_source(format!("{missing} is not covered")));
208 }
209 let now = self.clock.now();
210 self.check_created(params.created(), now)?;
211 validate_nonce(params.nonce())
212 .map_err(|error| Rejection::new(RejectReason::NonceInvalid).with_source(error))?;
213
214 let device = self
215 .tokens
216 .verify(headers.integrity_token, now)
217 .await
218 .map_err(token_rejection)?;
219 let platform = device.platform;
220 if params.alg() != platform.alg() {
221 return Err(Rejection::new(RejectReason::AlgMismatch).with_platform(platform));
222 }
223
224 let base = CanonicalRequest::from_http(
225 &head.method,
226 &head.uri,
227 &self.scheme,
228 &self.authority,
229 body,
230 )
231 .map_err(|error| {
232 Rejection::new(RejectReason::SignatureBaseIncomplete)
233 .with_platform(platform)
234 .with_source(error)
235 })?
236 .signature_base(¶ms, headers.integrity_token);
237 device
238 .key
239 .verify(platform, &base.client_data_hash(), &signature)
240 .map_err(|error| {
241 Rejection::new(RejectReason::SignatureInvalid)
242 .with_platform(platform)
243 .with_source(error)
244 })?;
245
246 let request_binding = base.request_binding();
247 if let Some(replay_guard) = &self.replay_guard {
248 let ttl = self.max_age + self.max_future_skew;
249 let claimed = replay_guard
250 .claim(&request_binding, ttl)
251 .await
252 .map_err(|error| {
253 Rejection::new(RejectReason::NonceStoreUnavailable)
254 .with_platform(platform)
255 .with_source(error)
256 })?;
257 if !claimed {
258 return Err(Rejection::new(RejectReason::Replayed).with_platform(platform));
259 }
260 }
261
262 Ok(VerifiedAttestedKeyContext {
263 device,
264 request_binding,
265 })
266 }
267
268 fn check_created(&self, created: i64, now: SystemTime) -> Result<(), Rejection> {
269 let created = UNIX_EPOCH + Duration::from_secs(created.unsigned_abs());
270 if let Ok(ahead) = created.duration_since(now) {
271 if ahead > self.max_future_skew {
272 return Err(Rejection::new(RejectReason::CreatedTooFarInFuture)
273 .with_source(format!("created is {}s ahead", ahead.as_secs())));
274 }
275 } else if let Ok(age) = now.duration_since(created)
276 && age > self.max_age
277 {
278 return Err(Rejection::new(RejectReason::CreatedTooOld)
279 .with_source(format!("created is {}s old", age.as_secs())));
280 }
281 Ok(())
282 }
283}
284
285fn token_rejection(error: TokenError) -> Rejection {
286 let reason = match error {
287 TokenError::KeysUnavailable(_) => RejectReason::JwksUnavailable,
288 TokenError::IntegrityFailed => RejectReason::DeviceIntegrityFailed,
289 _ => RejectReason::IntegrityTokenInvalid,
290 };
291 Rejection::new(reason).with_source(error)
292}
293
294struct SignedHeaders<'a> {
296 integrity_token: &'a str,
297 signature_input: &'a str,
298 signature: &'a str,
299}
300
301impl<'a> SignedHeaders<'a> {
302 fn extract(headers: &'a HeaderMap) -> Result<Self, Rejection> {
303 let names = [
304 INTEGRITY_TOKEN_HEADER,
305 SIGNATURE_INPUT_HEADER,
306 SIGNATURE_HEADER,
307 ];
308 if names.iter().any(|name| !headers.contains_key(*name)) {
309 return Err(RejectReason::HeadersMissing.into());
310 }
311 Ok(Self {
312 integrity_token: single(headers, INTEGRITY_TOKEN_HEADER)
313 .ok_or(RejectReason::IntegrityTokenInvalid)?,
314 signature_input: single(headers, SIGNATURE_INPUT_HEADER)
315 .ok_or(RejectReason::SignatureInputMalformed)?,
316 signature: single(headers, SIGNATURE_HEADER).ok_or(RejectReason::SignatureMalformed)?,
317 })
318 }
319}
320
321fn single<'a>(headers: &'a HeaderMap, name: &str) -> Option<&'a str> {
324 let mut values = headers.get_all(name).iter();
325 match (values.next(), values.next()) {
326 (Some(value), None) => value.to_str().ok(),
327 _ => None,
328 }
329}