use core::time::Duration;
use base64ct::{Base64UrlUnpadded, Encoding};
use rand::RngCore;
use ts_crypto::{
Sha256, Sha384, Sha512, SigningKey,
rsa::{Pkcs1v15, Pss},
};
use crate::{
JsonWebKey,
jwt::{Claims, Header},
};
pub struct TokenIssuer {
pub jku: String,
pub iss: String,
pub jwk: JsonWebKey,
pub key: SigningKey,
}
impl TokenIssuer {
pub fn new(key: SigningKey, jwk: JsonWebKey, iss: String, jku: String) -> Self {
Self { jku, iss, jwk, key }
}
pub fn issue(&self, aud: String, sub: String, scopes: String, valid_for: Duration) -> String {
let Ok(now) = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) else {
panic!("system time is before UNIX_EPOCH")
};
let exp = (now + valid_for).as_secs();
let iat = now.as_secs();
let mut jti = [0; 32];
rand::rng().fill_bytes(&mut jti[..]);
let jti = Base64UrlUnpadded::encode_string(&jti);
let header = Header {
alg: self.jwk.alg.clone(),
typ: "JWT".to_string(),
kid: self.jwk.kid.clone(),
jku: self.jku.clone(),
};
let claims = Claims {
jti,
iss: self.iss.clone(),
exp,
iat,
sub,
aud,
scopes,
};
let header = Base64UrlUnpadded::encode_string(
&serde_json::to_vec(&header).expect("serializing token parts should not fail"),
);
let claims = Base64UrlUnpadded::encode_string(
&serde_json::to_vec(&claims).expect("serializing token parts should not fail"),
);
let message = [header, claims].join(".");
let signature = match self.jwk.alg.as_str() {
"RS256" => {
let SigningKey::Rsa(key) = &self.key else {
panic!("signing key type does not match the JWK algorithm");
};
key.sign::<Pkcs1v15, Sha256>(message.as_bytes())
}
"RS384" => {
let SigningKey::Rsa(key) = &self.key else {
panic!("signing key type does not match the JWK algorithm");
};
key.sign::<Pkcs1v15, Sha384>(message.as_bytes())
}
"RS512" => {
let SigningKey::Rsa(key) = &self.key else {
panic!("signing key type does not match the JWK algorithm");
};
key.sign::<Pkcs1v15, Sha512>(message.as_bytes())
}
"PS256" => {
let SigningKey::Rsa(key) = &self.key else {
panic!("signing key type does not match the JWK algorithm");
};
key.sign::<Pss, Sha256>(message.as_bytes())
}
"PS384" => {
let SigningKey::Rsa(key) = &self.key else {
panic!("signing key type does not match the JWK algorithm");
};
key.sign::<Pss, Sha384>(message.as_bytes())
}
"PS512" => {
let SigningKey::Rsa(key) = &self.key else {
panic!("signing key type does not match the JWK algorithm");
};
key.sign::<Pss, Sha512>(message.as_bytes())
}
"ES256" => {
let SigningKey::Elliptic(key) = &self.key else {
panic!("signing key type does not match the JWK algorithm");
};
key.sign::<Sha256>(message.as_bytes())
}
"ES384" => {
let SigningKey::Elliptic(key) = &self.key else {
panic!("signing key type does not match the JWK algorithm");
};
key.sign::<Sha384>(message.as_bytes())
}
"ES512" => {
let SigningKey::Elliptic(key) = &self.key else {
panic!("signing key type does not match the JWK algorithm");
};
key.sign::<Sha512>(message.as_bytes())
}
"Ed25519" | "Ed448" | "EdDSA" => {
let SigningKey::Edwards(key) = &self.key else {
panic!("signing key type does not match the JWK algorithm");
};
key.sign(message.as_bytes())
}
other => unimplemented!("the algorithm `{other}` is not supported"),
};
let signature = Base64UrlUnpadded::encode_string(&signature);
[message, signature].join(".")
}
}