1use 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
17pub struct TokenIssuer {
19 pub jku: String,
21 pub iss: String,
23 pub jwk: JsonWebKey,
25 pub key: SigningKey,
27}
28
29impl TokenIssuer {
30 pub fn new(key: SigningKey, jwk: JsonWebKey, iss: String, jku: String) -> Self {
32 Self { jku, iss, jwk, key }
33 }
34
35 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}