use crate::Error;
use ring::signature;
use serde::Serialize;
#[derive(Serialize)]
pub(crate) struct Claims {
#[serde(rename = "iss")]
pub(crate) issuer: String,
#[serde(rename = "aud")]
pub(crate) audience: String,
#[serde(rename = "exp")]
pub(crate) expiration: i64,
#[serde(rename = "iat")]
pub(crate) issued_at: i64,
#[serde(rename = "sub")]
pub(crate) subject: Option<String>,
pub(crate) scope: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, serde::Deserialize)]
pub struct Header {
#[serde(skip_serializing_if = "Option::is_none")]
pub typ: Option<String>,
pub alg: Algorithm,
#[serde(skip_serializing_if = "Option::is_none")]
pub cty: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub jku: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub kid: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub x5u: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub x5t: Option<String>,
}
impl Header {
pub fn new(algorithm: Algorithm) -> Header {
Header {
typ: Some("JWT".to_string()),
alg: algorithm,
cty: None,
jku: None,
kid: None,
x5u: None,
x5t: None,
}
}
}
impl Default for Header {
fn default() -> Self {
Header::new(Algorithm::default())
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, serde::Deserialize)]
#[allow(clippy::upper_case_acronyms)]
pub enum Algorithm {
HS256,
HS384,
HS512,
ES256,
ES384,
RS256,
RS384,
RS512,
PS256,
PS384,
PS512,
}
impl Default for Algorithm {
fn default() -> Self {
Algorithm::HS256
}
}
pub enum Key<'a> {
Pkcs8(&'a [u8]),
}
pub fn to_jwt_part<T: Serialize>(input: &T) -> Result<String, Error> {
let encoded = serde_json::to_string(input)?;
Ok(base64::encode_config(
encoded.as_bytes(),
base64::URL_SAFE_NO_PAD,
))
}
fn sign_rsa(
alg: &'static dyn signature::RsaEncoding,
key: Key<'_>,
signing_input: &str,
) -> Result<String, Error> {
let key_pair = match key {
Key::Pkcs8(bytes) => {
signature::RsaKeyPair::from_pkcs8(bytes).map_err(Error::InvalidRsaKeyRejected)?
}
};
let key_pair = std::sync::Arc::new(key_pair);
let mut signature = vec![0; key_pair.public_modulus_len()];
let rng = ring::rand::SystemRandom::new();
key_pair
.sign(alg, &rng, signing_input.as_bytes(), &mut signature)
.map_err(Error::InvalidRsaKey)?;
Ok(base64::encode_config(&signature, base64::URL_SAFE_NO_PAD))
}
pub fn sign(signing_input: &str, key: Key<'_>, algorithm: Algorithm) -> Result<String, Error> {
match algorithm {
Algorithm::RS256 => sign_rsa(&signature::RSA_PKCS1_SHA256, key, signing_input),
Algorithm::RS384 => sign_rsa(&signature::RSA_PKCS1_SHA384, key, signing_input),
Algorithm::RS512 => sign_rsa(&signature::RSA_PKCS1_SHA512, key, signing_input),
Algorithm::PS256 => sign_rsa(&signature::RSA_PSS_SHA256, key, signing_input),
Algorithm::PS384 => sign_rsa(&signature::RSA_PSS_SHA384, key, signing_input),
Algorithm::PS512 => sign_rsa(&signature::RSA_PSS_SHA512, key, signing_input),
_ => panic!("Unsupported algorithm {:?}", algorithm),
}
}
pub fn encode<T: Serialize>(header: &Header, claims: &T, key: Key<'_>) -> Result<String, Error> {
let encoded_header = to_jwt_part(&header)?;
let encoded_claims = to_jwt_part(&claims)?;
let signing_input = [encoded_header.as_ref(), encoded_claims.as_ref()].join(".");
let signature = sign(&signing_input, key, header.alg)?;
Ok([signing_input, signature].join("."))
}