mod tests;
use base64;
use hmac::{digest, digest::core_api, digest::generic_array::typenum, Mac};
use std::fmt;
use std::str::FromStr;
const SEPARATOR: &'static str = ".";
#[derive(Debug, PartialEq)]
pub enum Error {
HeaderNotFound,
PayloadNotFound,
SignatureNotFound,
TooManySections,
InvalidBase64(base64::DecodeError),
InvalidUtf8(std::string::FromUtf8Error),
InvalidHeader,
HashSecret(digest::InvalidLength),
SerializePayload,
}
impl From<base64::DecodeError> for Error {
fn from(err: base64::DecodeError) -> Error {
Error::InvalidBase64(err)
}
}
impl From<std::string::FromUtf8Error> for Error {
fn from(err: std::string::FromUtf8Error) -> Error {
Error::InvalidUtf8(err)
}
}
impl From<digest::InvalidLength> for Error {
fn from(err: digest::InvalidLength) -> Error {
Error::HashSecret(err)
}
}
impl From<serde_json::Error> for Error {
fn from(_: serde_json::Error) -> Error {
Error::SerializePayload
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum Algorithm {
HS256,
HS512,
HS384,
}
impl Algorithm {
fn to_bytes(&self, payload: &str, secret: &str) -> Result<Vec<u8>, Error> {
match self {
Self::HS256 => Self::to_bytes_hmac::<sha2::Sha256>(payload, secret),
Self::HS384 => Self::to_bytes_hmac::<sha2::Sha384>(payload, secret),
Self::HS512 => Self::to_bytes_hmac::<sha2::Sha512>(payload, secret),
}
}
fn to_bytes_hmac<H>(payload: &str, secret: &str) -> Result<Vec<u8>, Error>
where
H: core_api::CoreProxy,
H::Core: digest::HashMarker
+ core_api::UpdateCore
+ core_api::FixedOutputCore
+ core_api::BufferKindUser<BufferKind = digest::block_buffer::Eager>
+ Default
+ Clone,
<H::Core as core_api::BlockSizeUser>::BlockSize: typenum::IsLess<typenum::U256>,
typenum::Le<<H::Core as core_api::BlockSizeUser>::BlockSize, typenum::U256>:
typenum::NonZero,
{
let mut mac = hmac::Hmac::<H>::new_from_slice(secret.as_bytes())?;
mac.update(payload.as_bytes());
let bytes = mac.finalize();
Ok(bytes.into_bytes()[..].to_vec())
}
}
#[derive(Debug)]
pub struct Token {
header: Header,
payload: Payload,
signature: Signature,
}
impl Token {
pub fn is_valid(&self, secret: &str) -> Result<bool, Error> {
let signature = Signature::try_new(&self.header, &self.payload, secret)?;
Ok(signature.0 == self.signature.0)
}
pub fn try_new<T: serde::Serialize>(
alg: Algorithm,
payload: T,
secret: &str,
) -> Result<Self, Error> {
let header = Header::new(alg);
let payload = Payload::try_new(payload)?;
let signature = Signature::try_new(&header, &payload, secret)?;
Ok(Self {
signature,
header,
payload,
})
}
pub fn get_if_valid<'a, T: serde::Deserialize<'a>>(
&'a self,
secret: &str,
) -> Result<Option<T>, Error> {
if self.is_valid(secret)? {
Ok(Some(serde_json::from_str::<T>(&self.payload.decoded)?))
} else {
Ok(None)
}
}
}
impl fmt::Display for Token {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}{}{}{}{}",
self.header.encoded, SEPARATOR, self.payload.encoded, SEPARATOR, self.signature
)
}
}
impl FromStr for Token {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut parts = s.split(SEPARATOR);
let token = Self {
header: parts.next().ok_or(Error::HeaderNotFound)?.parse()?,
payload: parts.next().ok_or(Error::PayloadNotFound)?.parse()?,
signature: parts.next().ok_or(Error::SignatureNotFound)?.parse()?,
};
if parts.next().is_some() {
Err(Error::TooManySections)
} else {
Ok(token)
}
}
}
#[derive(Debug)]
pub struct Header {
encoded: String,
decoded: HeaderDecoded,
}
impl Header {
pub fn new(alg: Algorithm) -> Self {
let decoded = HeaderDecoded::new(alg);
let serialized = serde_json::to_string(&decoded).expect("Should always serialized");
Self {
encoded: base64::encode_config(serialized, base64::URL_SAFE_NO_PAD),
decoded,
}
}
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[allow(dead_code)]
pub struct HeaderDecoded {
alg: Algorithm,
typ: String,
}
impl HeaderDecoded {
pub fn new(alg: Algorithm) -> Self {
Self {
alg,
typ: String::from("JWT"),
}
}
}
impl FromStr for Header {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let bytes = base64::decode(s)?;
let header = String::from_utf8(bytes)?;
match serde_json::from_str::<HeaderDecoded>(&header) {
Ok(decoded) => Ok(Self {
encoded: s.to_string(),
decoded,
}),
Err(_) => Err(Error::InvalidHeader),
}
}
}
#[derive(Debug)]
pub struct Payload {
decoded: String,
encoded: String,
}
impl Payload {
pub fn try_new<T: serde::Serialize>(payload: T) -> Result<Self, Error> {
let decoded = serde_json::to_string(&payload)?;
let encoded = base64::encode_config(&decoded, base64::URL_SAFE_NO_PAD);
Ok(Payload { decoded, encoded })
}
}
impl FromStr for Payload {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let bytes = base64::decode(s)?;
let decoded = String::from_utf8(bytes)?;
Ok(Payload {
decoded,
encoded: s.to_string(),
})
}
}
#[derive(Debug)]
pub struct Signature(Vec<u8>);
impl Signature {
pub fn try_new(header: &Header, payload: &Payload, secret: &str) -> Result<Self, Error> {
let full_payload = format!("{}{}{}", header.encoded, SEPARATOR, payload.encoded);
let bytes = header.decoded.alg.to_bytes(&full_payload, &secret)?;
Ok(Self(bytes))
}
}
impl fmt::Display for Signature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
base64::encode_config(&self.0, base64::URL_SAFE_NO_PAD)
)
}
}
impl FromStr for Signature {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let bytes = base64::decode_config(s, base64::URL_SAFE_NO_PAD)?;
Ok(Signature(bytes))
}
}