1use std::{
7 collections::HashMap,
8 error::Error,
9 future::Future,
10 pin::Pin,
11 sync::Arc,
12 time::{Duration, SystemTime, UNIX_EPOCH},
13};
14
15use jsonwebtoken::{Algorithm, DecodingKey, Validation, decode, decode_header};
16use p256::ecdsa::VerifyingKey;
17use serde::Deserialize;
18
19use crate::{
20 device::{DeviceKey, DeviceKeyError, EcJwk},
21 profile::Platform,
22};
23
24const TOKEN_ALG: &str = "ES256";
25
26pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
28
29pub trait IssuerKeys: Send + Sync {
31 fn key<'a>(
34 &'a self,
35 kid: &'a str,
36 ) -> BoxFuture<'a, Result<Option<VerifyingKey>, KeysUnavailable>>;
37}
38
39#[derive(Debug, thiserror::Error)]
41#[error("issuer keys are unavailable")]
42pub struct KeysUnavailable(#[source] pub Box<dyn Error + Send + Sync>);
43
44#[derive(Debug, Clone, Default)]
46pub struct StaticKeys(HashMap<String, VerifyingKey>);
47
48impl StaticKeys {
49 pub fn from_jwks(document: &[u8]) -> Result<Self, JwksError> {
55 parse_jwks(document).map(Self)
56 }
57
58 #[must_use]
60 pub fn with_key(mut self, kid: impl Into<String>, key: VerifyingKey) -> Self {
61 self.0.insert(kid.into(), key);
62 self
63 }
64}
65
66impl IssuerKeys for StaticKeys {
67 fn key<'a>(
68 &'a self,
69 kid: &'a str,
70 ) -> BoxFuture<'a, Result<Option<VerifyingKey>, KeysUnavailable>> {
71 Box::pin(async move { Ok(self.0.get(kid).copied()) })
72 }
73}
74
75#[derive(Debug, thiserror::Error)]
77#[error("not a JWKS document")]
78pub struct JwksError(#[source] serde_json::Error);
79
80pub fn parse_jwks(document: &[u8]) -> Result<HashMap<String, VerifyingKey>, JwksError> {
89 #[derive(Deserialize)]
90 struct JwkSet {
91 keys: Vec<Jwk>,
92 }
93 #[derive(Deserialize)]
94 struct Jwk {
95 kid: Option<String>,
96 alg: Option<String>,
97 #[serde(rename = "use")]
98 key_use: Option<String>,
99 #[serde(flatten)]
100 ec: Option<EcJwk>,
101 }
102
103 let set: JwkSet = serde_json::from_slice(document).map_err(JwksError)?;
104 Ok(set
105 .keys
106 .into_iter()
107 .filter(|jwk| jwk.alg.as_deref().is_none_or(|alg| alg == TOKEN_ALG))
108 .filter(|jwk| {
109 jwk.key_use
110 .as_deref()
111 .is_none_or(|key_use| key_use == "sig")
112 })
113 .filter_map(|jwk| Some((jwk.kid?, jwk.ec?.to_verifying_key().ok()?)))
114 .collect())
115}
116
117#[derive(Debug, thiserror::Error)]
119pub enum TokenError {
120 #[error("integrity token is malformed")]
122 Malformed,
123 #[error("integrity token is not ES256")]
125 UnsupportedAlgorithm,
126 #[error("integrity token issuer is not trusted")]
128 UntrustedIssuer,
129 #[error("integrity token kid is unknown")]
131 UnknownKey,
132 #[error(transparent)]
134 KeysUnavailable(#[from] KeysUnavailable),
135 #[error("integrity token signature does not verify")]
137 BadSignature,
138 #[error("integrity token is expired")]
140 Expired,
141 #[error("integrity token is not valid yet")]
143 NotYetValid,
144 #[error("integrity token audience is not accepted")]
146 WrongAudience,
147 #[error("integrity token has no pass claim")]
149 MissingPass,
150 #[error("device failed the attestation check")]
152 IntegrityFailed,
153 #[error("integrity token platform is not supported")]
155 UnsupportedPlatform,
156 #[error(transparent)]
158 DeviceKey(#[from] DeviceKeyError),
159}
160
161#[derive(Debug, Clone, PartialEq, Eq)]
163pub struct AttestedDevice {
164 pub key: DeviceKey,
166 pub platform: Platform,
168 pub issuer: String,
170 pub audience: String,
172 pub expires_at: SystemTime,
174}
175
176#[derive(Clone)]
178pub struct TokenVerifier {
179 issuers: HashMap<String, Arc<dyn IssuerKeys>>,
181 audiences: Vec<String>,
183 validation: Validation,
185}
186
187#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
189pub enum TokenVerifierConfigError {
190 #[error("at least one trusted issuer is required")]
192 NoIssuer,
193 #[error("at least one audience is required")]
195 NoAudience,
196}
197
198#[derive(Deserialize)]
200#[serde(untagged)]
201enum Audience {
202 One(String),
203 Many(Vec<String>),
204}
205
206#[derive(Deserialize)]
208struct Claims {
209 #[serde(rename = "iss")]
211 issuer: String,
212 #[serde(rename = "aud")]
214 audience: Audience,
215 #[serde(rename = "exp")]
217 expires_at: u64,
218 #[serde(rename = "nbf")]
220 not_before: Option<u64>,
221 #[serde(rename = "pass")]
223 attestation_passed: Option<bool>,
224 platform: String,
226 #[serde(rename = "cnf")]
228 confirmation: Confirmation,
229}
230
231#[derive(Deserialize)]
233struct Confirmation {
234 jwk: EcJwk,
235}
236
237impl TokenVerifier {
238 pub fn new(
246 issuers: impl IntoIterator<Item = (String, Arc<dyn IssuerKeys>)>,
247 audiences: impl IntoIterator<Item = String>,
248 ) -> Result<Self, TokenVerifierConfigError> {
249 let issuers: HashMap<_, _> = issuers.into_iter().collect();
250 let audiences: Vec<_> = audiences
251 .into_iter()
252 .filter(|audience| !audience.trim().is_empty())
253 .collect();
254 if issuers.is_empty() {
255 return Err(TokenVerifierConfigError::NoIssuer);
256 }
257 if audiences.is_empty() {
258 return Err(TokenVerifierConfigError::NoAudience);
259 }
260
261 let mut validation = Validation::new(Algorithm::ES256);
262 validation.set_audience(&audiences);
263 validation.validate_exp = false;
265 validation.validate_nbf = false;
266
267 Ok(Self {
268 issuers,
269 audiences,
270 validation,
271 })
272 }
273
274 pub async fn verify(&self, token: &str, now: SystemTime) -> Result<AttestedDevice, TokenError> {
282 let header = decode_header(token).map_err(token_error)?;
283 if header.alg != Algorithm::ES256 {
284 return Err(TokenError::UnsupportedAlgorithm);
285 }
286 if header.crit.is_some() {
287 return Err(TokenError::Malformed);
288 }
289 let kid = header
290 .kid
291 .filter(|kid| !kid.is_empty())
292 .ok_or(TokenError::Malformed)?;
293
294 let unverified: Claims =
296 jsonwebtoken::dangerous::insecure_decode_claims(token).map_err(token_error)?;
297 let keys = self
298 .issuers
299 .get(&unverified.issuer)
300 .ok_or(TokenError::UntrustedIssuer)?;
301 let issuer_key = keys.key(&kid).await?.ok_or(TokenError::UnknownKey)?;
302 let key = DecodingKey::from_ec_der(issuer_key.to_encoded_point(false).as_bytes());
303 let claims = decode::<Claims>(token, &key, &self.validation)
304 .map_err(token_error)?
305 .claims;
306
307 let now_secs = now
308 .duration_since(UNIX_EPOCH)
309 .map_or(0, |elapsed| elapsed.as_secs());
310 if now_secs >= claims.expires_at {
311 return Err(TokenError::Expired);
312 }
313 if claims.not_before.is_some_and(|start| now_secs < start) {
314 return Err(TokenError::NotYetValid);
315 }
316 let audience = self
317 .accepted_audience(&claims.audience)
318 .ok_or(TokenError::WrongAudience)?;
319 match claims.attestation_passed {
320 None => return Err(TokenError::MissingPass),
321 Some(false) => return Err(TokenError::IntegrityFailed),
322 Some(true) => {}
323 }
324 let platform =
325 Platform::from_claim(&claims.platform).ok_or(TokenError::UnsupportedPlatform)?;
326 let key = DeviceKey::new(claims.confirmation.jwk.to_verifying_key()?);
327
328 Ok(AttestedDevice {
329 key,
330 platform,
331 issuer: claims.issuer,
332 audience: audience.to_owned(),
333 expires_at: UNIX_EPOCH
334 .checked_add(Duration::from_secs(claims.expires_at))
335 .ok_or(TokenError::Malformed)?,
336 })
337 }
338
339 fn accepted_audience(&self, audience: &Audience) -> Option<&str> {
341 let offered: &[String] = match audience {
342 Audience::One(audience) => std::slice::from_ref(audience),
343 Audience::Many(audiences) => audiences,
344 };
345 self.audiences
346 .iter()
347 .find(|accepted| offered.contains(accepted))
348 .map(String::as_str)
349 }
350}
351
352fn token_error(error: jsonwebtoken::errors::Error) -> TokenError {
354 use jsonwebtoken::errors::ErrorKind;
355 match error.into_kind() {
356 ErrorKind::InvalidSignature => TokenError::BadSignature,
357 ErrorKind::InvalidAlgorithm
358 | ErrorKind::InvalidAlgorithmName
359 | ErrorKind::UnsupportedAlgorithm => TokenError::UnsupportedAlgorithm,
360 ErrorKind::InvalidIssuer => TokenError::UntrustedIssuer,
361 ErrorKind::InvalidAudience => TokenError::WrongAudience,
362 _ => TokenError::Malformed,
363 }
364}