ts_token/
token_verifier.rs

1//! A verifier for a token.
2
3use core::time::Duration;
4
5use base64ct::{Base64UrlUnpadded, Encoding};
6use ts_crypto::{
7    EdwardsVerifyingKey, EllipticVerifyingKey, RsaVerifyingKey, Sha256, Sha384, Sha512,
8    VerifyingKey,
9    rsa::{Pkcs1v15, Pss},
10};
11
12use crate::{
13    JsonWebKey, JsonWebToken,
14    jwt::{Claims, Header},
15};
16
17/// A verifier for a token.
18pub struct TokenVerifier {
19    /// The JSON web key for this verifier.
20    pub jwk: JsonWebKey,
21    /// The verifying key.
22    pub key: VerifyingKey,
23}
24
25impl TokenVerifier {
26    /// Create a new token verifier from a JSON web key.
27    pub fn new(jwk: JsonWebKey) -> Option<Self> {
28        let key = match &jwk.kty {
29            crate::KeyType::Ec { x, y } => {
30                let x = Base64UrlUnpadded::decode_vec(x).ok()?;
31                let y = Base64UrlUnpadded::decode_vec(y).ok()?;
32                let key = EllipticVerifyingKey::from_coordinates(&x, &y)?;
33                VerifyingKey::Elliptic(key)
34            }
35            crate::KeyType::Rsa { n, e } => {
36                let n = Base64UrlUnpadded::decode_vec(n).ok()?;
37                let e = Base64UrlUnpadded::decode_vec(e).ok()?;
38                let key = RsaVerifyingKey::from_parameters(&n, &e)?;
39                VerifyingKey::Rsa(key)
40            }
41            crate::KeyType::Okp { x } => {
42                let raw_key = Base64UrlUnpadded::decode_vec(x).ok()?;
43                let key = EdwardsVerifyingKey::from_raw_key(&raw_key)?;
44                VerifyingKey::Edwards(key)
45            }
46        };
47        // TODO verify the algorithm matches the key.
48
49        Some(Self { jwk, key })
50    }
51
52    /// Verify a signed JSON web token, returning the verified token.
53    pub fn verify(&self, jws: &str) -> Option<JsonWebToken> {
54        let mut parts = jws.split('.');
55        let header_str = parts.next()?;
56        let claims_str = parts.next()?;
57        let signature = parts.next()?;
58
59        let header = Base64UrlUnpadded::decode_vec(header_str).ok()?;
60        let header: Header = serde_json::from_slice(&header).ok()?;
61
62        let claims = Base64UrlUnpadded::decode_vec(claims_str).ok()?;
63        let claims: Claims = serde_json::from_slice(&claims).ok()?;
64
65        let signature = Base64UrlUnpadded::decode_vec(signature).ok()?;
66
67        if header.kid != self.jwk.kid {
68            return None;
69        }
70
71        let message = [header_str, claims_str].join(".");
72
73        // Verify the signature.
74        match self.jwk.alg.as_str() {
75            "RS256" => {
76                let VerifyingKey::Rsa(key) = &self.key else {
77                    return None;
78                };
79                if !key.verifies::<Pkcs1v15, Sha256>(&signature, message.as_bytes()) {
80                    return None;
81                }
82            }
83            "RS384" => {
84                let VerifyingKey::Rsa(key) = &self.key else {
85                    return None;
86                };
87                if !key.verifies::<Pkcs1v15, Sha384>(&signature, message.as_bytes()) {
88                    return None;
89                }
90            }
91            "RS512" => {
92                let VerifyingKey::Rsa(key) = &self.key else {
93                    return None;
94                };
95                if !key.verifies::<Pkcs1v15, Sha512>(&signature, message.as_bytes()) {
96                    return None;
97                }
98            }
99
100            "PS256" => {
101                let VerifyingKey::Rsa(key) = &self.key else {
102                    return None;
103                };
104                if !key.verifies::<Pss, Sha256>(&signature, message.as_bytes()) {
105                    return None;
106                }
107            }
108            "PS384" => {
109                let VerifyingKey::Rsa(key) = &self.key else {
110                    return None;
111                };
112                if !key.verifies::<Pss, Sha384>(&signature, message.as_bytes()) {
113                    return None;
114                }
115            }
116            "PS512" => {
117                let VerifyingKey::Rsa(key) = &self.key else {
118                    return None;
119                };
120                if !key.verifies::<Pss, Sha512>(&signature, message.as_bytes()) {
121                    return None;
122                }
123            }
124
125            "ES256" => {
126                let VerifyingKey::Elliptic(key) = &self.key else {
127                    return None;
128                };
129                if !key.verifies::<Sha256>(&signature, message.as_bytes()) {
130                    return None;
131                }
132            }
133            "ES384" => {
134                let VerifyingKey::Elliptic(key) = &self.key else {
135                    return None;
136                };
137                if !key.verifies::<Sha384>(&signature, message.as_bytes()) {
138                    return None;
139                }
140            }
141            "ES512" => {
142                let VerifyingKey::Elliptic(key) = &self.key else {
143                    return None;
144                };
145                if !key.verifies::<Sha512>(&signature, message.as_bytes()) {
146                    return None;
147                }
148            }
149
150            "Ed25519" | "Ed448" | "EdDSA" => {
151                let VerifyingKey::Edwards(key) = &self.key else {
152                    return None;
153                };
154                if !key.verifies(&signature, message.as_bytes()) {
155                    return None;
156                }
157            }
158
159            _ => return None,
160        }
161
162        // Check the token is still valid.
163        let exp = Duration::from_secs(claims.exp);
164        let iat = Duration::from_secs(claims.iat);
165        let Ok(now) = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) else {
166            return None;
167        };
168        if exp < now || iat > now {
169            return None;
170        }
171
172        Some(JsonWebToken { header, claims })
173    }
174}