use std::collections::BTreeSet;
use std::fmt::Formatter;
use serde::de::{IgnoredAny, MapAccess, Visitor};
use serde::{Deserialize, Serialize};
use crate::Jwk;
use super::error::JwtError;
#[derive(Debug, Clone, Serialize)]
pub struct JwtHeader {
pub alg: String,
#[serde(rename = "typ", skip_serializing_if = "Option::is_none")]
pub typ: Option<String>,
#[serde(rename = "kid", skip_serializing_if = "Option::is_none")]
pub kid: Option<String>,
#[serde(skip)]
pub embedded_key_header: bool,
}
impl<'de> Deserialize<'de> for JwtHeader {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_map(JwtHeaderVisitor)
}
}
struct JwtHeaderVisitor;
impl<'de> Visitor<'de> for JwtHeaderVisitor {
type Value = JwtHeader;
fn expecting(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
formatter.write_str("a JWT JOSE header object")
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let mut seen = BTreeSet::new();
let mut alg = None;
let mut typ = None;
let mut kid = None;
let mut embedded_key_header = false;
while let Some(key) = map.next_key::<String>()? {
if !seen.insert(key.clone()) {
return Err(serde::de::Error::custom(JwtError::InvalidHeader));
}
match key.as_str() {
"alg" => alg = Some(map.next_value()?),
"typ" => typ = Some(map.next_value()?),
"kid" => kid = Some(map.next_value()?),
"jwk" | "x5c" => {
embedded_key_header = true;
let _ = map.next_value::<IgnoredAny>()?;
}
"b64" | "crit" | "zip" | "jku" | "x5u" => {
let _ = map.next_value::<IgnoredAny>()?;
return Err(serde::de::Error::custom(JwtError::InvalidHeader));
}
_ => {
let _ = map.next_value::<IgnoredAny>()?;
}
}
}
Ok(JwtHeader {
alg: alg.ok_or_else(|| serde::de::Error::custom(JwtError::InvalidHeader))?,
typ,
kid,
embedded_key_header,
})
}
}
impl JwtHeader {
pub fn validate_with_options(
&self,
options: &JwtHeaderValidationOptions<'_>,
) -> Result<(), JwtError> {
match self.alg.as_str() {
"ES256" | "EdDSA" | "ES256K" => {}
"none" => return Err(JwtError::InvalidHeader), _ => return Err(JwtError::UnsupportedAlgorithm),
}
match self.typ.as_deref() {
Some(typ) => {
if options.accepted_typ_values.is_empty()
|| !options.accepted_typ_values.contains(&typ)
{
return Err(JwtError::InvalidHeader);
}
}
None => {
if !options.allow_missing_typ {
return Err(JwtError::InvalidHeader);
}
}
}
if self.embedded_key_header && !options.allow_embedded_key_header {
return Err(JwtError::InvalidHeader);
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct JwtHeaderEncodeOptions {
pub(super) typ: Option<String>,
}
impl JwtHeaderEncodeOptions {
#[must_use]
pub const fn new(typ: Option<String>) -> Self {
Self { typ }
}
#[must_use]
pub fn jwt() -> Self {
Self::new(Some("JWT".to_owned()))
}
#[must_use]
pub fn typ(&self) -> Option<&str> {
self.typ.as_deref()
}
}
#[derive(Debug, Clone, Copy)]
pub struct JwtHeaderValidationOptions<'a> {
allow_missing_typ: bool,
allow_embedded_key_header: bool,
accepted_typ_values: &'a [&'a str],
}
impl<'a> JwtHeaderValidationOptions<'a> {
#[must_use]
pub const fn new(
allow_missing_typ: bool,
allow_embedded_key_header: bool,
accepted_typ_values: &'a [&'a str],
) -> Self {
Self {
allow_missing_typ,
allow_embedded_key_header,
accepted_typ_values,
}
}
#[must_use]
pub const fn standard_jwt() -> Self {
Self::new(true, false, &["JWT"])
}
#[must_use]
pub const fn allow_missing_typ(&self) -> bool {
self.allow_missing_typ
}
#[must_use]
pub const fn allow_embedded_key_header(&self) -> bool {
self.allow_embedded_key_header
}
#[must_use]
pub const fn accepted_typ_values(&self) -> &'a [&'a str] {
self.accepted_typ_values
}
}
pub(super) fn select_jwk_algorithm(jwk: &Jwk) -> Result<String, JwtError> {
let use_ = match jwk {
Jwk::Ec(j) => j.use_.as_deref(),
Jwk::Okp(j) => j.use_.as_deref(),
Jwk::Akp(j) => j.use_.as_deref(),
};
if use_.is_some_and(|value| value != "sig") {
return Err(JwtError::AlgorithmMismatch);
}
let alg = match jwk {
Jwk::Ec(j) => j.alg.as_deref(),
Jwk::Okp(j) => j.alg.as_deref(),
Jwk::Akp(j) => Some(j.alg.as_str()),
}
.ok_or(JwtError::MissingAlgorithm)?;
match jwk {
Jwk::Ec(j) if j.crv == "P-256" && alg == "ES256" => Ok(alg.to_string()),
Jwk::Ec(j) if j.crv == "secp256k1" && alg == "ES256K" => Ok(alg.to_string()),
Jwk::Okp(j) if j.crv == "Ed25519" && alg == "EdDSA" => Ok(alg.to_string()),
Jwk::Akp(_) | Jwk::Ec(_) | Jwk::Okp(_) => Err(JwtError::UnsupportedAlgorithm),
}
}
pub(super) fn select_jwk_key_id(jwk: &Jwk) -> Option<String> {
match jwk {
Jwk::Ec(j) => j.kid.clone(),
Jwk::Okp(j) => j.kid.clone(),
Jwk::Akp(j) => j.kid.clone(),
}
}