ts_token/
token_issuer.rs

1//! An issuer for a token.
2
3use core::time::Duration;
4
5use base64ct::{Base64UrlUnpadded, Encoding};
6use rand::RngCore;
7use ts_crypto::{
8    Sha256, Sha384, Sha512, SigningKey,
9    rsa::{Pkcs1v15, Pss},
10};
11
12use crate::{
13    JsonWebKey,
14    jwt::{Claims, Header},
15};
16
17/// An issuer for a token.
18pub struct TokenIssuer {
19    /// The URL a recipiant of the token can use to fetch the JSON web key.
20    pub jku: String,
21    /// The URL of this issuer.
22    pub iss: String,
23    /// The key for certain token parameters.
24    pub jwk: JsonWebKey,
25    /// The signing key.
26    pub key: SigningKey,
27}
28
29impl TokenIssuer {
30    /// Create a new issuer from its parts
31    pub fn new(key: SigningKey, jwk: JsonWebKey, iss: String, jku: String) -> Self {
32        Self { jku, iss, jwk, key }
33    }
34
35    /// Issue a signed JSON web token.
36    ///
37    /// ## Panics
38    /// * If system time is before `UNIX_EPOCH`.
39    /// * Serializing as JSON fails.
40    /// * The signing key does not match the JSON web key algorithm.
41    pub fn issue(&self, aud: String, sub: String, scopes: String, valid_for: Duration) -> String {
42        let Ok(now) = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) else {
43            panic!("system time is before UNIX_EPOCH")
44        };
45        let exp = (now + valid_for).as_secs();
46        let iat = now.as_secs();
47
48        let mut jti = [0; 32];
49        rand::rng().fill_bytes(&mut jti[..]);
50        let jti = Base64UrlUnpadded::encode_string(&jti);
51
52        let header = Header {
53            alg: self.jwk.alg.clone(),
54            typ: "JWT".to_string(),
55            kid: self.jwk.kid.clone(),
56            jku: self.jku.clone(),
57        };
58
59        let claims = Claims {
60            jti,
61            iss: self.iss.clone(),
62            exp,
63            iat,
64            sub,
65            aud,
66            scopes,
67        };
68
69        let header = Base64UrlUnpadded::encode_string(
70            &serde_json::to_vec(&header).expect("serializing token parts should not fail"),
71        );
72        let claims = Base64UrlUnpadded::encode_string(
73            &serde_json::to_vec(&claims).expect("serializing token parts should not fail"),
74        );
75        let message = [header, claims].join(".");
76
77        let signature = match self.jwk.alg.as_str() {
78            "RS256" => {
79                let SigningKey::Rsa(key) = &self.key else {
80                    panic!("signing key type does not match the JWK algorithm");
81                };
82                key.sign::<Pkcs1v15, Sha256>(message.as_bytes())
83            }
84            "RS384" => {
85                let SigningKey::Rsa(key) = &self.key else {
86                    panic!("signing key type does not match the JWK algorithm");
87                };
88                key.sign::<Pkcs1v15, Sha384>(message.as_bytes())
89            }
90            "RS512" => {
91                let SigningKey::Rsa(key) = &self.key else {
92                    panic!("signing key type does not match the JWK algorithm");
93                };
94                key.sign::<Pkcs1v15, Sha512>(message.as_bytes())
95            }
96
97            "PS256" => {
98                let SigningKey::Rsa(key) = &self.key else {
99                    panic!("signing key type does not match the JWK algorithm");
100                };
101                key.sign::<Pss, Sha256>(message.as_bytes())
102            }
103            "PS384" => {
104                let SigningKey::Rsa(key) = &self.key else {
105                    panic!("signing key type does not match the JWK algorithm");
106                };
107                key.sign::<Pss, Sha384>(message.as_bytes())
108            }
109            "PS512" => {
110                let SigningKey::Rsa(key) = &self.key else {
111                    panic!("signing key type does not match the JWK algorithm");
112                };
113                key.sign::<Pss, Sha512>(message.as_bytes())
114            }
115
116            "ES256" => {
117                let SigningKey::Elliptic(key) = &self.key else {
118                    panic!("signing key type does not match the JWK algorithm");
119                };
120                key.sign::<Sha256>(message.as_bytes())
121            }
122            "ES384" => {
123                let SigningKey::Elliptic(key) = &self.key else {
124                    panic!("signing key type does not match the JWK algorithm");
125                };
126                key.sign::<Sha384>(message.as_bytes())
127            }
128            "ES512" => {
129                let SigningKey::Elliptic(key) = &self.key else {
130                    panic!("signing key type does not match the JWK algorithm");
131                };
132                key.sign::<Sha512>(message.as_bytes())
133            }
134
135            "Ed25519" | "Ed448" | "EdDSA" => {
136                let SigningKey::Edwards(key) = &self.key else {
137                    panic!("signing key type does not match the JWK algorithm");
138                };
139                key.sign(message.as_bytes())
140            }
141
142            other => unimplemented!("the algorithm `{other}` is not supported"),
143        };
144        let signature = Base64UrlUnpadded::encode_string(&signature);
145
146        [message, signature].join(".")
147    }
148}