use std::str::FromStr;
use serde::ser::Serialize;
use crate::crypto::algorithm::{Algorithm, AlgorithmID};
use crate::error::{Error, ErrorDetails};
use crate::TokenData;
const URL_SAFE_ENGINE: base64::engine::fast_portable::FastPortable =
base64::engine::fast_portable::FastPortable::from(
&base64::alphabet::URL_SAFE,
base64::engine::fast_portable::NO_PAD,
);
pub(crate) fn b64_encode(input: &[u8]) -> String {
base64::encode_engine(input, &URL_SAFE_ENGINE)
}
pub(crate) fn b64_decode(input: &str) -> Result<Vec<u8>, Error> {
base64::decode_engine(input, &URL_SAFE_ENGINE)
.map_err(|e| Error::InvalidInput(ErrorDetails::map("base64 decode failure", Box::new(e))))
}
pub(crate) fn b64_encode_part<T: Serialize>(input: &T) -> Result<String, Error> {
let json = serde_json::to_string(input).map_err(|e| {
Error::InvalidInput(ErrorDetails::map("json serialize failure", Box::new(e)))
})?;
Ok(b64_encode(json.as_bytes()))
}
macro_rules! expect_two {
($iter:expr) => {{
let mut i = $iter;
match (i.next(), i.next(), i.next()) {
(Some(first), Some(second), None) => (first, second),
_ => {
return Err(Error::MalformedToken(ErrorDetails::new(
"Failed to split JWT into header.claims.signature parts",
)))
}
}
}};
}
pub struct TokenSlices<'a> {
pub message: &'a str,
pub signature: &'a str,
pub header: &'a str,
pub claims: &'a str,
}
pub fn split_token(token: &str) -> Result<TokenSlices, Error> {
let (signature, message) = expect_two!(token.rsplitn(2, '.'));
let (header, claims) = expect_two!(message.splitn(2, '.'));
Ok(TokenSlices {
message,
signature,
header,
claims,
})
}
pub fn decode_json_token_slice(
encoded_slice: impl AsRef<str>,
) -> Result<serde_json::value::Value, Error> {
let s = String::from_utf8(b64_decode(encoded_slice.as_ref())?)
.map_err(|e| Error::InvalidInput(ErrorDetails::map("utf8 decode failure", Box::new(e))))?;
let value = serde_json::from_str(&s)
.map_err(|e| Error::MalformedToken(ErrorDetails::map("json parse failure", Box::new(e))))?;
Ok(value)
}
pub fn decode_header_only(token: impl AsRef<str>) -> Result<serde_json::value::Value, Error> {
let TokenSlices { header, .. } = split_token(token.as_ref())?;
decode_json_token_slice(header)
}
pub fn decode_only(token: impl AsRef<str>) -> Result<TokenData, Error> {
let TokenSlices { header, claims, .. } = split_token(token.as_ref())?;
let header = decode_json_token_slice(header)?;
let claims = decode_json_token_slice(claims)?;
Ok(TokenData {
header,
claims,
_extensible: (),
})
}
pub fn verify_signature_only(
header: &serde_json::value::Value,
message: impl AsRef<str>,
signature: impl AsRef<str>,
algorithm: &Algorithm,
) -> Result<(), Error> {
match header.get("alg") {
Some(serde_json::value::Value::String(alg)) => {
let alg = AlgorithmID::from_str(alg)?;
if alg != algorithm.id() {
return Err(Error::AlgorithmMismatch());
}
let kid = match header.get("kid") {
Some(serde_json::value::Value::String(k)) => Some(k.as_ref()),
Some(_) => {
return Err(Error::MalformedToken(ErrorDetails::new(
"Non-string 'kid' found",
)))
}
None => None,
};
algorithm.verify(kid, message, signature)?;
}
_ => return Err(Error::AlgorithmMismatch()),
}
Ok(())
}