use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use base64::Engine;
use ring::constant_time::verify_slices_are_equal;
use ring::hmac::{self, HMAC_SHA256};
use serde::de::DeserializeOwned;
use serde::de::{self, Visitor};
use serde::Deserializer;
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use std::fmt;
use std::marker::PhantomData;
use std::time::{SystemTime, UNIX_EPOCH};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum EncodeError {
#[error("{0}")]
Json(#[from] serde_json::Error),
}
#[derive(Debug, Error)]
pub enum DecodeError {
#[error("Invalid token")]
InvalidToken,
#[error("Invalid signature")]
InvalidSignature,
#[error("Missing required claim: {0}")]
MissingRequiredClaim(String),
#[error("Expired signature")]
ExpiredSignature,
#[error("Invalid issuer")]
InvalidIssuer,
#[error("Invalid audience")]
InvalidAudience,
#[error("Invalid subject")]
InvalidSubject,
#[error("Immature signature")]
ImmatureSignature,
#[error("Invalid algorithm")]
InvalidAlgorithm,
#[error("{0}")]
Base64(#[from] base64::DecodeError),
#[error("{0}")]
Json(#[from] serde_json::Error),
}
const ALGORITHM: &str = "HS256";
fn b64_encode<T: AsRef<[u8]>>(input: T) -> String {
URL_SAFE_NO_PAD.encode(input)
}
fn b64_decode<T: AsRef<[u8]>>(input: T) -> Result<Vec<u8>, DecodeError> {
URL_SAFE_NO_PAD.decode(input).map_err(|e| e.into())
}
fn b64_encode_part<T: Serialize>(input: &T) -> serde_json::Result<String> {
let json = serde_json::to_vec(input)?;
Ok(b64_encode(json))
}
pub struct Jwt {
header: String,
key: hmac::Key,
}
impl Jwt {
pub fn new(secret: &[u8]) -> Self {
let header: String = b64_encode(b"{\"alg\":\"HS256\",\"typ\":\"JWT\"}");
let key = hmac::Key::new(HMAC_SHA256, secret);
Self { key, header }
}
fn sign(&self, message: &[u8]) -> String {
let digest = hmac::sign(&self.key, message);
b64_encode(digest)
}
fn verify(&self, signature: &str, message: &[u8]) -> bool {
let signed = self.sign(message);
verify_slices_are_equal(signature.as_bytes(), signed.as_bytes()).is_ok()
}
pub fn encode<T: Serialize>(&self, claims: &T) -> Result<String, EncodeError> {
let encoded_claims = b64_encode_part(claims)?;
let header = &self.header;
let mut message = String::with_capacity(header.len() + 1 + encoded_claims.len());
message.push_str(header);
message.push('.');
message.push_str(&encoded_claims);
let signature = self.sign(message.as_bytes());
message.reserve_exact(signature.len() + 1);
message.push('.');
message.push_str(&signature);
Ok(message)
}
pub fn decode<T: DeserializeOwned>(&self, token: &str) -> Result<T, DecodeError> {
match self.verify_signature(token) {
Err(e) => Err(e),
Ok((_, claims)) => {
let decoded_claims = DecodedJwtPartClaims::from_jwt_part_claims(claims)?;
let claims = decoded_claims.deserialize()?;
self.validate(decoded_claims.deserialize()?)?;
Ok(claims)
}
}
}
fn validate(&self, claims: ClaimsForValidation) -> Result<(), DecodeError> {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_secs();
if !matches!(claims.exp, TryParse::Parsed(_)) {
return Err(DecodeError::MissingRequiredClaim("exp".to_string()));
}
if matches!(claims.exp, TryParse::Parsed(exp) if exp < now - 60) {
return Err(DecodeError::ExpiredSignature);
}
Ok(())
}
fn verify_signature<'a>(&self, token: &'a str) -> Result<(Header, &'a str), DecodeError> {
let (message, signature) = match token.rsplit_once('.') {
Some(value) => value,
None => return Err(DecodeError::InvalidToken),
};
let (header, payload) = match message.rsplit_once('.') {
Some(value) => value,
None => return Err(DecodeError::InvalidToken),
};
let header = Header::from_encoded(header)?;
if header.alg != ALGORITHM {
return Err(DecodeError::InvalidAlgorithm);
}
if !self.verify(signature, message.as_bytes()) {
return Err(DecodeError::InvalidSignature);
}
Ok((header, payload))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
struct Header {
pub typ: String,
pub alg: String,
}
impl Header {
fn from_encoded<T: AsRef<[u8]>>(encoded_part: T) -> Result<Self, DecodeError> {
let decoded = b64_decode(encoded_part)?;
Ok(serde_json::from_slice(&decoded)?)
}
}
impl Default for Header {
fn default() -> Self {
Self {
typ: "JWT".to_string(),
alg: ALGORITHM.to_string(),
}
}
}
struct DecodedJwtPartClaims {
b64_decoded: Vec<u8>,
}
impl DecodedJwtPartClaims {
fn from_jwt_part_claims(
encoded_jwt_part_claims: impl AsRef<[u8]>,
) -> Result<Self, DecodeError> {
Ok(Self {
b64_decoded: b64_decode(encoded_jwt_part_claims)?,
})
}
fn deserialize<'a, T: Deserialize<'a>>(&'a self) -> Result<T, DecodeError> {
Ok(serde_json::from_slice(&self.b64_decoded)?)
}
}
#[derive(Deserialize)]
struct ClaimsForValidation {
#[serde(deserialize_with = "numeric_type", default)]
exp: TryParse<u64>,
}
#[derive(Debug)]
enum TryParse<T> {
Parsed(T),
FailedToParse,
NotPresent,
}
impl<'de, T: Deserialize<'de>> Deserialize<'de> for TryParse<T> {
fn deserialize<D: serde::Deserializer<'de>>(
deserializer: D,
) -> std::result::Result<Self, D::Error> {
Ok(match Option::<T>::deserialize(deserializer) {
Ok(Some(value)) => TryParse::Parsed(value),
Ok(None) => TryParse::NotPresent,
Err(_) => TryParse::FailedToParse,
})
}
}
impl<T> Default for TryParse<T> {
fn default() -> Self {
Self::NotPresent
}
}
#[derive(Deserialize, PartialEq, Eq, Hash)]
struct BorrowedCowIfPossible<'a>(#[serde(borrow)] Cow<'a, str>);
impl std::borrow::Borrow<str> for BorrowedCowIfPossible<'_> {
fn borrow(&self) -> &str {
&self.0
}
}
fn numeric_type<'de, D>(deserializer: D) -> std::result::Result<TryParse<u64>, D::Error>
where
D: Deserializer<'de>,
{
struct NumericType(PhantomData<fn() -> TryParse<u64>>);
impl<'de> Visitor<'de> for NumericType {
type Value = TryParse<u64>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("A NumericType that can be reasonably coerced into a u64")
}
fn visit_f64<E>(self, value: f64) -> std::result::Result<Self::Value, E>
where
E: de::Error,
{
if value.is_finite() && value >= 0.0 && value < (u64::MAX as f64) {
Ok(TryParse::Parsed(value.round() as u64))
} else {
Err(serde::de::Error::custom(
"NumericType must be representable as a u64",
))
}
}
fn visit_u64<E>(self, value: u64) -> std::result::Result<Self::Value, E>
where
E: de::Error,
{
Ok(TryParse::Parsed(value))
}
}
match deserializer.deserialize_any(NumericType(PhantomData)) {
Ok(ok) => Ok(ok),
Err(_) => Ok(TryParse::FailedToParse),
}
}