pub trait UntrustedToken {
fn kid(&self) -> Option<&String>;
}
pub mod untrusted_jwt_raw {
use super::UntrustedToken;
use crate::error::jwt_error::JWTError;
use base64::{decode_config, URL_SAFE_NO_PAD};
use serde_json::Value;
use std::collections::HashMap;
pub struct UntrustedJWT {
kid: Option<String>,
header: HashMap<String, Value>,
}
impl UntrustedJWT {
pub fn new<S: AsRef<str> + ?Sized>(token_string: &S) -> Result<Self, JWTError> {
let structure_error = || {
JWTError::TokenParseFailed(String::from(
"A JWT should have xxxxx.yyyyy.zzzzz or xxxxx.yyyyy. format",
))
};
let parts: Vec<_> = token_string.as_ref().splitn(4, '.').collect();
let [header_b64, _claims_b64, signature_b64] = parts[..] else {
return Err(structure_error());
};
let header_bytes =
decode_config(header_b64, URL_SAFE_NO_PAD).map_err(|_| structure_error())?;
let header: HashMap<String, Value> =
serde_json::from_slice(&header_bytes).map_err(|_| structure_error())?;
if !header.get("alg").map(Value::is_string).unwrap_or(false) {
return Err(structure_error());
}
if !signature_b64.is_empty() {
decode_config(signature_b64, URL_SAFE_NO_PAD).map_err(|_| structure_error())?;
}
let kid = header.get("kid").and_then(Value::as_str).map(String::from);
Ok(UntrustedJWT { kid, header })
}
pub fn header(&self) -> &HashMap<String, Value> {
&self.header
}
}
impl UntrustedToken for UntrustedJWT {
fn kid(&self) -> Option<&String> {
self.kid.as_ref()
}
}
#[cfg(test)]
mod tests {
use super::{UntrustedJWT, UntrustedToken};
const VALID_WITH_KID: &str =
"eyJhbGciOiJSUzI1NiIsImtpZCI6ImtleS0xIn0.eyJzdWIiOiJzIn0.c2lnbmF0dXJl";
const VALID_NO_KID: &str = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzIn0.c2lnbmF0dXJl";
const EMPTY_SIG: &str = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzIn0.";
#[test]
fn parses_valid_token_and_extracts_kid() {
let token = UntrustedJWT::new(VALID_WITH_KID).unwrap();
assert_eq!(token.kid(), Some(&"key-1".to_string()));
assert_eq!(
token.header().get("alg").and_then(|v| v.as_str()),
Some("RS256")
);
}
#[test]
fn parses_valid_token_without_kid() {
let token = UntrustedJWT::new(VALID_NO_KID).unwrap();
assert_eq!(token.kid(), None);
}
#[test]
fn accepts_empty_signature_segment() {
let token = UntrustedJWT::new(EMPTY_SIG).unwrap();
assert_eq!(token.kid(), None);
}
#[test]
fn rejects_token_with_too_few_segments() {
assert!(UntrustedJWT::new("eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzIn0").is_err());
}
#[test]
fn rejects_non_base64url_header() {
assert!(UntrustedJWT::new("not+valid/base64.eyJzdWIiOiJzIn0.c2ln").is_err());
}
#[test]
fn rejects_header_that_is_not_a_json_object() {
assert!(UntrustedJWT::new("ImFzdHJpbmci.eyJzdWIiOiJzIn0.c2lnbmF0dXJl").is_err());
}
#[test]
fn rejects_missing_alg() {
assert!(UntrustedJWT::new("eyJ0eXAiOiJKV1QifQ.eyJzdWIiOiJzIn0.c2lnbmF0dXJl").is_err());
}
#[test]
fn rejects_non_string_alg() {
assert!(UntrustedJWT::new("eyJhbGciOjEyM30.eyJzdWIiOiJzIn0.c2lnbmF0dXJl").is_err());
}
#[test]
fn rejects_non_base64url_signature() {
let token = format!("eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzIn0.{}", "not+base64/");
assert!(UntrustedJWT::new(&token).is_err());
}
}
}
#[cfg(not(any(feature = "fips", fips)))]
pub mod untrusted_jwt_compact {
use super::UntrustedToken;
use crate::error::jwt_error::JWTError;
pub struct UntrustedJWT<'a> {
untrusted_token: jwt_compact::UntrustedToken<'a>,
}
impl<'a> UntrustedJWT<'a> {
pub fn new<S: AsRef<str> + ?Sized>(token_string: &'a S) -> Result<Self, JWTError> {
let untrusted = jwt_compact::UntrustedToken::new(token_string)?;
Ok(UntrustedJWT {
untrusted_token: untrusted,
})
}
}
impl UntrustedToken for UntrustedJWT<'_> {
fn kid(&self) -> Option<&String> {
self.untrusted_token.header().key_id.as_ref()
}
}
impl<'a> From<UntrustedJWT<'a>> for jwt_compact::UntrustedToken<'a> {
fn from(token: UntrustedJWT<'a>) -> Self {
token.untrusted_token
}
}
}
#[cfg(not(any(feature = "fips", fips)))]
pub mod untrusted_jwt {
use super::UntrustedToken;
use crate::error::jwt_error::JWTError;
use jwt_simple::prelude::Token;
use std::option::Option;
pub struct UntrustedJWT {
kid: Option<String>,
value: Option<String>,
}
impl UntrustedJWT {
pub fn new(token_string: &str) -> Result<Self, JWTError> {
let untrusted = Token::decode_metadata(token_string)?;
Ok(UntrustedJWT {
kid: untrusted.key_id().map(String::from),
value: Some(String::from(token_string)),
})
}
pub fn value(&self) -> &String {
self.value.as_ref().unwrap()
}
}
impl UntrustedToken for UntrustedJWT {
fn kid(&self) -> Option<&String> {
self.kid.as_ref()
}
}
}