Skip to main content

mas_jose/jwt/
signed.rs

1// Copyright 2025, 2026 Element Creations Ltd.
2// Copyright 2024, 2025 New Vector Ltd.
3// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
4//
5// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
6// Please see LICENSE files in the repository root for full details.
7
8use 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    /// Get the JWT header
188    pub fn header(&self) -> &JsonWebSignatureHeader {
189        &self.header
190    }
191
192    /// Get the JWT payload
193    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    /// Verify the signature of this JWT using the given key.
207    ///
208    /// # Errors
209    ///
210    /// Returns an error if the signature is invalid.
211    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    /// Verify the signature of this JWT using the given symmetric key.
224    ///
225    /// # Errors
226    ///
227    /// Returns an error if the signature is invalid or if the algorithm is not
228    /// supported.
229    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    /// Verify the signature of this JWT using the given JWKS.
239    ///
240    /// # Errors
241    ///
242    /// Returns an error if the signature is invalid, if no key matches the
243    /// constraints, or if the algorithm is not supported.
244    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    /// Get the raw JWT string as a borrowed [`str`]
265    pub fn as_str(&'a self) -> &'a str {
266        &self.raw
267    }
268
269    /// Get the raw JWT string as an owned [`String`]
270    pub fn into_string(self) -> String {
271        self.raw.into()
272    }
273
274    /// Split the JWT into its parts (header and payload).
275    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    /// Sign the given payload with the given key.
313    ///
314    /// # Errors
315    ///
316    /// Returns an error if the payload could not be serialized or if the key
317    /// could not sign the payload.
318    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    /// Sign the given payload with the given key using the given RNG.
333    ///
334    /// # Errors
335    ///
336    /// Returns an error if the payload could not be serialized or if the key
337    /// could not sign the payload.
338    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}