1use base64ct::{Base64UrlUnpadded, Encoding};
9use rand::rng;
10use serde::{Serialize, de::DeserializeOwned};
11use signature::{RandomizedSigner, SignatureEncoding, Verifier, rand_core::CryptoRng};
12use thiserror::Error;
13
14use super::{header::JsonWebSignatureHeader, raw::RawJwt};
15use crate::{constraints::ConstraintSet, jwk::PublicJsonWebKeySet};
16
17#[derive(Clone, PartialEq, Eq)]
18pub struct Jwt<'a, T> {
19 raw: RawJwt<'a>,
20 header: JsonWebSignatureHeader,
21 payload: T,
22 signature: Vec<u8>,
23}
24
25impl<T> std::fmt::Display for Jwt<'_, T> {
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 write!(f, "{}", self.raw)
28 }
29}
30
31impl<T> std::fmt::Debug for Jwt<'_, T>
32where
33 T: std::fmt::Debug,
34{
35 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36 f.debug_struct("Jwt")
37 .field("raw", &"...")
38 .field("header", &self.header)
39 .field("payload", &self.payload)
40 .field("signature", &"...")
41 .finish()
42 }
43}
44
45#[derive(Debug, Error)]
46pub enum JwtDecodeError {
47 #[error(transparent)]
48 RawDecode {
49 #[from]
50 inner: super::raw::DecodeError,
51 },
52
53 #[error("failed to decode JWT header")]
54 DecodeHeader {
55 #[source]
56 inner: base64ct::Error,
57 },
58
59 #[error("failed to deserialize JWT header")]
60 DeserializeHeader {
61 #[source]
62 inner: serde_json::Error,
63 },
64
65 #[error("failed to decode JWT payload")]
66 DecodePayload {
67 #[source]
68 inner: base64ct::Error,
69 },
70
71 #[error("failed to deserialize JWT payload")]
72 DeserializePayload {
73 #[source]
74 inner: serde_json::Error,
75 },
76
77 #[error("failed to decode JWT signature")]
78 DecodeSignature {
79 #[source]
80 inner: base64ct::Error,
81 },
82}
83
84impl JwtDecodeError {
85 fn decode_header(inner: base64ct::Error) -> Self {
86 Self::DecodeHeader { inner }
87 }
88
89 fn deserialize_header(inner: serde_json::Error) -> Self {
90 Self::DeserializeHeader { inner }
91 }
92
93 fn decode_payload(inner: base64ct::Error) -> Self {
94 Self::DecodePayload { inner }
95 }
96
97 fn deserialize_payload(inner: serde_json::Error) -> Self {
98 Self::DeserializePayload { inner }
99 }
100
101 fn decode_signature(inner: base64ct::Error) -> Self {
102 Self::DecodeSignature { inner }
103 }
104}
105
106impl<'a, T> TryFrom<RawJwt<'a>> for Jwt<'a, T>
107where
108 T: DeserializeOwned,
109{
110 type Error = JwtDecodeError;
111 fn try_from(raw: RawJwt<'a>) -> Result<Self, Self::Error> {
112 let header_reader =
113 base64ct::Decoder::<'_, Base64UrlUnpadded>::new(raw.header().as_bytes())
114 .map_err(JwtDecodeError::decode_header)?;
115 let header =
116 serde_json::from_reader(header_reader).map_err(JwtDecodeError::deserialize_header)?;
117
118 let payload_reader =
119 base64ct::Decoder::<'_, Base64UrlUnpadded>::new(raw.payload().as_bytes())
120 .map_err(JwtDecodeError::decode_payload)?;
121 let payload =
122 serde_json::from_reader(payload_reader).map_err(JwtDecodeError::deserialize_payload)?;
123
124 let signature = Base64UrlUnpadded::decode_vec(raw.signature())
125 .map_err(JwtDecodeError::decode_signature)?;
126
127 Ok(Self {
128 raw,
129 header,
130 payload,
131 signature,
132 })
133 }
134}
135
136impl<'a, T> TryFrom<&'a str> for Jwt<'a, T>
137where
138 T: DeserializeOwned,
139{
140 type Error = JwtDecodeError;
141 fn try_from(value: &'a str) -> Result<Self, Self::Error> {
142 let raw = RawJwt::try_from(value)?;
143 Self::try_from(raw)
144 }
145}
146
147impl<T> TryFrom<String> for Jwt<'static, T>
148where
149 T: DeserializeOwned,
150{
151 type Error = JwtDecodeError;
152 fn try_from(value: String) -> Result<Self, Self::Error> {
153 let raw = RawJwt::try_from(value)?;
154 Self::try_from(raw)
155 }
156}
157
158#[derive(Debug, Error)]
159pub enum JwtVerificationError {
160 #[error("failed to parse signature")]
161 ParseSignature,
162
163 #[error("signature verification failed")]
164 Verify {
165 #[source]
166 inner: signature::Error,
167 },
168}
169
170impl JwtVerificationError {
171 fn parse_signature<E>(_inner: E) -> Self {
172 Self::ParseSignature
173 }
174
175 fn verify(inner: signature::Error) -> Self {
176 Self::Verify { inner }
177 }
178}
179
180#[derive(Debug, Error, Default)]
181#[error("none of the keys worked")]
182pub struct NoKeyWorked {
183 _inner: (),
184}
185
186impl<'a, T> Jwt<'a, T> {
187 pub fn header(&self) -> &JsonWebSignatureHeader {
189 &self.header
190 }
191
192 pub fn payload(&self) -> &T {
194 &self.payload
195 }
196
197 pub fn into_owned(self) -> Jwt<'static, T> {
198 Jwt {
199 raw: self.raw.into_owned(),
200 header: self.header,
201 payload: self.payload,
202 signature: self.signature,
203 }
204 }
205
206 pub fn verify<K, S>(&self, key: &K) -> Result<(), JwtVerificationError>
212 where
213 K: Verifier<S>,
214 S: SignatureEncoding,
215 {
216 let signature =
217 S::try_from(&self.signature).map_err(JwtVerificationError::parse_signature)?;
218
219 key.verify(self.raw.signed_part().as_bytes(), &signature)
220 .map_err(JwtVerificationError::verify)
221 }
222
223 pub fn verify_with_shared_secret(&self, secret: Vec<u8>) -> Result<(), NoKeyWorked> {
230 let verifier = crate::jwa::SymmetricKey::new_for_alg(secret, self.header().alg())
231 .map_err(|_| NoKeyWorked::default())?;
232
233 self.verify(&verifier).map_err(|_| NoKeyWorked::default())?;
234
235 Ok(())
236 }
237
238 pub fn verify_with_jwks(&self, jwks: &PublicJsonWebKeySet) -> Result<(), NoKeyWorked> {
245 let constraints = ConstraintSet::from(self.header());
246 let candidates = constraints.filter(&**jwks);
247
248 for candidate in candidates {
249 let Ok(key) = crate::jwa::AsymmetricVerifyingKey::from_jwk_and_alg(
250 candidate.params(),
251 self.header().alg(),
252 ) else {
253 continue;
254 };
255
256 if self.verify(&key).is_ok() {
257 return Ok(());
258 }
259 }
260
261 Err(NoKeyWorked::default())
262 }
263
264 pub fn as_str(&'a self) -> &'a str {
266 &self.raw
267 }
268
269 pub fn into_string(self) -> String {
271 self.raw.into()
272 }
273
274 pub fn into_parts(self) -> (JsonWebSignatureHeader, T) {
276 (self.header, self.payload)
277 }
278}
279
280#[derive(Debug, Error)]
281pub enum JwtSignatureError {
282 #[error("failed to serialize header")]
283 EncodeHeader {
284 #[source]
285 inner: serde_json::Error,
286 },
287
288 #[error("failed to serialize payload")]
289 EncodePayload {
290 #[source]
291 inner: serde_json::Error,
292 },
293
294 #[error("failed to sign")]
295 Signature {
296 #[from]
297 inner: signature::Error,
298 },
299}
300
301impl JwtSignatureError {
302 fn encode_header(inner: serde_json::Error) -> Self {
303 Self::EncodeHeader { inner }
304 }
305
306 fn encode_payload(inner: serde_json::Error) -> Self {
307 Self::EncodePayload { inner }
308 }
309}
310
311impl<T> Jwt<'static, T> {
312 pub fn sign<K, S>(
319 header: JsonWebSignatureHeader,
320 payload: T,
321 key: &K,
322 ) -> Result<Self, JwtSignatureError>
323 where
324 K: RandomizedSigner<S>,
325 S: SignatureEncoding,
326 T: Serialize,
327 {
328 #[expect(clippy::disallowed_methods)]
329 Self::sign_with_rng(&mut rng(), header, payload, key)
330 }
331
332 pub fn sign_with_rng<R, K, S>(
339 rng: &mut R,
340 header: JsonWebSignatureHeader,
341 payload: T,
342 key: &K,
343 ) -> Result<Self, JwtSignatureError>
344 where
345 R: CryptoRng,
346 K: RandomizedSigner<S>,
347 S: SignatureEncoding,
348 T: Serialize,
349 {
350 let header_ = serde_json::to_vec(&header).map_err(JwtSignatureError::encode_header)?;
351 let header_ = Base64UrlUnpadded::encode_string(&header_);
352
353 let payload_ = serde_json::to_vec(&payload).map_err(JwtSignatureError::encode_payload)?;
354 let payload_ = Base64UrlUnpadded::encode_string(&payload_);
355
356 let mut inner = format!("{header_}.{payload_}");
357
358 let first_dot = header_.len();
359 let second_dot = inner.len();
360
361 let signature = key.try_sign_with_rng(rng, inner.as_bytes())?.to_vec();
362 let signature_ = Base64UrlUnpadded::encode_string(&signature);
363 inner.reserve_exact(1 + signature_.len());
364 inner.push('.');
365 inner.push_str(&signature_);
366
367 let raw = RawJwt::new(inner, first_dot, second_dot);
368
369 Ok(Self {
370 raw,
371 header,
372 payload,
373 signature,
374 })
375 }
376}
377
378#[cfg(test)]
379mod tests {
380 #![allow(clippy::disallowed_methods)]
381 use mas_iana::jose::JsonWebSignatureAlg;
382 use rand::rng;
383
384 use super::*;
385
386 #[test]
387 fn test_jwt_decode() {
388 let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
389 let jwt: Jwt<'_, serde_json::Value> = Jwt::try_from(jwt).unwrap();
390 assert_eq!(jwt.raw.header(), "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9");
391 assert_eq!(
392 jwt.raw.payload(),
393 "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ"
394 );
395 assert_eq!(
396 jwt.raw.signature(),
397 "SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
398 );
399 assert_eq!(
400 jwt.raw.signed_part(),
401 "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ"
402 );
403 }
404
405 #[test]
406 fn test_jwt_sign_and_verify() {
407 let header = JsonWebSignatureHeader::new(JsonWebSignatureAlg::Es256);
408 let payload = serde_json::json!({"hello": "world"});
409
410 let key =
411 <ecdsa::SigningKey<p256::NistP256> as elliptic_curve::Generate>::generate_from_rng(
412 &mut rng(),
413 );
414 let signed = Jwt::sign::<_, ecdsa::Signature<_>>(header, payload, &key).unwrap();
415 signed
416 .verify::<_, ecdsa::Signature<_>>(key.verifying_key())
417 .unwrap();
418 }
419}