use crate::{Result, SignerError};
use base64::{prelude::BASE64_URL_SAFE, Engine};
use chrono::TimeDelta;
use serde::{Deserialize, Serialize};
use crate::{signer_user::SignerUser, SignerSigned, SignerUserPublic};
#[derive(Debug, Serialize, Deserialize)]
pub struct SignerJWT {
pub header: SignerJWTHeader,
pub claims: SignerJWTClaims,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SignerJWTHeader {
pub typ: String,
pub alg: String,
pub jwk: SignerJWK,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SignerJWTClaims {
pub aud: String,
pub jti: String,
pub iat: i64,
pub exp: i64,
pub nbf: i64,
pub iss: String,
pub sub: String,
pub user_public: SignerSigned<SignerUserPublic>,
pub extra: Option<serde_json::Value>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SignerJWK {
pub kty: String,
pub alg: String,
pub crv: String,
pub x: String,
}
impl SignerJWT {
pub fn new(header: SignerJWTHeader, claims: SignerJWTClaims) -> Self {
Self { header, claims }
}
pub fn decode_unverify(str: &str) -> Result<Self> {
let split: Vec<&str> = str.split(".").collect();
let header_buffer = BASE64_URL_SAFE.decode(split[0].as_bytes())?;
let claims_buffer = BASE64_URL_SAFE.decode(split[1].as_bytes())?;
let header: SignerJWTHeader = serde_json::from_str(&String::from_utf8(header_buffer)?)?;
let claims: SignerJWTClaims = serde_json::from_str(&String::from_utf8(claims_buffer)?)?;
Ok(SignerJWT::new(header, claims))
}
pub fn decode(str: &str) -> Result<Self> {
let split: Vec<&str> = str.split(".").collect();
let header_buffer = BASE64_URL_SAFE.decode(split[0].as_bytes())?;
let claims_buffer = BASE64_URL_SAFE.decode(split[1].as_bytes())?;
let header: SignerJWTHeader = serde_json::from_str(&String::from_utf8(header_buffer)?)?;
let claims: SignerJWTClaims = serde_json::from_str(&String::from_utf8(claims_buffer)?)?;
let base = serde_json::to_string(&[split[0], split[1]].join("."))?;
let base = BASE64_URL_SAFE.encode(base.as_bytes());
let sig_str = split[2];
let sig: SignerSigned<String> =
SignerSigned::from_field(&header.jwk.x.clone(), &base, sig_str)?;
sig.verify_to_value()?;
let u = claims.user_public.verify_to_value()?;
if header.jwk.x != u.pub_key {
return Err(SignerError::Msg(
"invalid claims, header key is not equal to claims user public key".to_string(),
));
}
claims.verify()?;
Ok(SignerJWT::new(header, claims))
}
pub fn encode(&self, u: &SignerUser) -> Result<String> {
let header = BASE64_URL_SAFE.encode(serde_json::to_string(&self.header)?.as_bytes());
let claims = BASE64_URL_SAFE.encode(serde_json::to_string(&self.claims)?.as_bytes());
let base = [header, claims].join(".");
let sig = SignerSigned::from_value(u, &base)?;
Ok([base, sig.sig].join("."))
}
}
impl SignerJWTClaims {
pub fn default(signer: &SignerUser, aud: String, jti: String) -> Self {
let now = chrono::Local::now();
Self {
aud,
jti,
iat: now.timestamp_millis(),
exp: now
.checked_add_signed(TimeDelta::hours(72))
.unwrap()
.timestamp_millis(),
nbf: now
.checked_sub_signed(TimeDelta::minutes(5))
.unwrap()
.timestamp_millis(),
iss: format!(""),
sub: format!(""),
extra: None,
user_public: SignerSigned::from_value(signer, &signer.public.clone())
.expect("sign user public failed"),
}
}
pub fn with_issuer(mut self, issuer: &str) -> Self {
self.iss = issuer.to_string();
self
}
pub fn with_expired_duration(mut self, expired_time: chrono::Duration) -> Self {
self.exp = chrono::DateTime::from_timestamp_millis(self.iat)
.unwrap()
.checked_add_signed(expired_time)
.unwrap()
.timestamp_millis();
self
}
pub fn with_subject(mut self, subject: &str) -> Self {
self.sub = subject.to_string();
self
}
pub fn verify(&self) -> Result<()> {
let now = chrono::Local::now();
if now.timestamp_millis() < self.nbf {
return Err(SignerError::Msg(format!(
"blocked by token not before field, nbf {} but now {}",
self.nbf,
now.timestamp_millis()
)));
}
if now.timestamp_millis() > self.exp {
return Err(SignerError::Msg(format!(
"blocked by token expired field, exp {} but now {}",
self.exp,
now.timestamp_millis()
)));
}
Ok(())
}
}
impl SignerJWTHeader {
pub fn default(u: &SignerUser) -> Self {
Self {
typ: format!("JWT"),
alg: format!("Schnorr SHA3-256"),
jwk: SignerJWK::default(u),
}
}
}
impl SignerJWK {
fn default(u: &SignerUser) -> Self {
Self {
kty: format!("OKP"),
alg: format!("schnorr"),
crv: format!("sr25519"),
x: u.public.pub_key.clone(),
}
}
}