mod serde_utils;
mod source;
use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
use bon::Builder;
use serde::{Deserialize, Serialize};
use sha2::{Digest as _, Sha256};
pub use source::JwksSource;
use zeroize::Zeroize;
use crate::{
error::{Error, ErrorKind},
jwk::serde_utils::{
base64url, base64url_uint, option_base64url, option_base64url_uint, trim_leading_zeros,
},
};
mod decode;
mod key;
mod private;
mod public;
pub use decode::*;
pub use key::*;
pub use private::*;
pub use public::*;
#[non_exhaustive]
#[derive(Debug, Serialize, Deserialize, Builder, PartialEq, Clone)]
#[builder(derive(Into))]
pub struct Jwk {
#[builder(into)]
#[serde(flatten)]
pub key: Key,
#[serde(rename = "use", skip_serializing_if = "Option::is_none")]
pub key_use: Option<KeyUse>,
#[serde(skip_serializing_if = "Option::is_none")]
#[builder(with = <_>::from_iter)]
#[serde(rename = "key_ops")]
pub key_operations: Option<Vec<KeyOperation>>,
#[builder(into)]
#[serde(rename = "alg", skip_serializing_if = "Option::is_none")]
pub algorithm: Option<String>,
#[builder(into)]
#[serde(skip_serializing_if = "Option::is_none")]
pub kid: Option<String>,
#[builder(skip)]
#[serde(rename = "x5u", default, skip_serializing)]
pub x5u: Option<String>,
}
impl Jwk {
#[must_use]
pub fn public_jwk(&self) -> Option<PublicJwk> {
self.key.public_key().map(|key| PublicJwk {
key,
key_use: self.key_use,
key_operations: self.key_operations.clone(),
algorithm: self.algorithm.clone(),
kid: self.kid.clone(),
x5u: self.x5u.clone(),
has_private_parameters: false,
})
}
#[must_use]
pub fn private_key(&self) -> Option<PrivateKey> {
self.key.private_key()
}
#[must_use]
pub fn private_jwk(&self) -> Option<PrivateJwk> {
if let Key::Oct(oct) = &self.key {
return Some(PrivateJwk::Symmetric(SymmetricJwk {
key: oct.clone(),
key_use: self.key_use,
key_operations: self.key_operations.clone(),
algorithm: self.algorithm.clone(),
kid: self.kid.clone(),
}));
}
self.key.private_key().map(|key| {
PrivateJwk::Asymmetric(Box::new(AsymmetricPrivateJwk {
key,
key_use: self.key_use,
key_operations: self.key_operations.clone(),
algorithm: self.algorithm.clone(),
kid: self.kid.clone(),
x5u: self.x5u.clone(),
}))
})
}
}
#[non_exhaustive]
#[derive(Debug, Builder, PartialEq, Clone)]
#[builder(derive(Into))]
pub struct AsymmetricPrivateJwk {
#[builder(into)]
pub key: PrivateKey,
pub key_use: Option<KeyUse>,
#[builder(with = <_>::from_iter)]
pub key_operations: Option<Vec<KeyOperation>>,
#[builder(into)]
pub algorithm: Option<String>,
#[builder(into)]
pub kid: Option<String>,
#[builder(skip)]
pub x5u: Option<String>,
}
impl AsymmetricPrivateJwk {
#[must_use]
pub fn public_jwk(&self) -> PublicJwk {
PublicJwk {
key: self.key.public_key(),
key_use: self.key_use,
key_operations: self.key_operations.clone(),
algorithm: self.algorithm.clone(),
kid: self.kid.clone(),
x5u: self.x5u.clone(),
has_private_parameters: false,
}
}
#[must_use]
pub fn thumbprint(&self) -> String {
self.public_jwk().thumbprint()
}
}
impl From<AsymmetricPrivateJwk> for Jwk {
fn from(pjwk: AsymmetricPrivateJwk) -> Self {
Self {
key: pjwk.key.into(),
key_use: pjwk.key_use,
key_operations: pjwk.key_operations,
algorithm: pjwk.algorithm,
kid: pjwk.kid,
x5u: pjwk.x5u,
}
}
}
impl From<AsymmetricPrivateJwk> for PublicJwk {
fn from(pjwk: AsymmetricPrivateJwk) -> Self {
pjwk.public_jwk()
}
}
#[non_exhaustive]
#[derive(Debug, Builder, PartialEq, Clone)]
#[builder(derive(Into))]
pub struct SymmetricJwk {
#[builder(into)]
pub key: OctKey,
pub key_use: Option<KeyUse>,
#[builder(with = <_>::from_iter)]
pub key_operations: Option<Vec<KeyOperation>>,
#[builder(into)]
pub algorithm: Option<String>,
#[builder(into)]
pub kid: Option<String>,
}
impl From<SymmetricJwk> for Jwk {
fn from(sjwk: SymmetricJwk) -> Self {
Self {
key: sjwk.key.into(),
key_use: sjwk.key_use,
key_operations: sjwk.key_operations,
algorithm: sjwk.algorithm,
kid: sjwk.kid,
x5u: None,
}
}
}
#[non_exhaustive]
#[derive(Debug, PartialEq, Clone)]
pub enum PrivateJwk {
Asymmetric(Box<AsymmetricPrivateJwk>),
Symmetric(SymmetricJwk),
}
impl PrivateJwk {
#[must_use]
pub fn kid(&self) -> Option<&str> {
match self {
PrivateJwk::Asymmetric(jwk) => jwk.kid.as_deref(),
PrivateJwk::Symmetric(jwk) => jwk.kid.as_deref(),
}
}
#[must_use]
pub fn with_kid_fallback(mut self, fallback: Option<String>) -> Self {
let kid = match &mut self {
PrivateJwk::Asymmetric(jwk) => &mut jwk.kid,
PrivateJwk::Symmetric(jwk) => &mut jwk.kid,
};
if kid.is_none() {
*kid = fallback;
}
self
}
}
impl From<AsymmetricPrivateJwk> for PrivateJwk {
fn from(jwk: AsymmetricPrivateJwk) -> Self {
Self::Asymmetric(Box::new(jwk))
}
}
impl From<SymmetricJwk> for PrivateJwk {
fn from(jwk: SymmetricJwk) -> Self {
Self::Symmetric(jwk)
}
}
impl From<PrivateJwk> for Jwk {
fn from(pjwk: PrivateJwk) -> Self {
match pjwk {
PrivateJwk::Asymmetric(jwk) => (*jwk).into(),
PrivateJwk::Symmetric(jwk) => jwk.into(),
}
}
}
impl TryFrom<PrivateJwk> for AsymmetricPrivateJwk {
type Error = Error;
fn try_from(value: PrivateJwk) -> Result<Self, Self::Error> {
match value {
PrivateJwk::Asymmetric(jwk) => Ok(*jwk),
PrivateJwk::Symmetric(_) => Err(Error::from(ErrorKind::Config)
.with_context("expected an asymmetric private JWK, got a symmetric (oct) key")),
}
}
}
impl TryFrom<PrivateJwk> for SymmetricJwk {
type Error = Error;
fn try_from(value: PrivateJwk) -> Result<Self, Self::Error> {
match value {
PrivateJwk::Symmetric(jwk) => Ok(jwk),
PrivateJwk::Asymmetric(_) => Err(Error::from(ErrorKind::Config)
.with_context("expected a symmetric (oct) JWK, got an asymmetric private key")),
}
}
}
#[non_exhaustive]
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct Jwks {
#[serde(deserialize_with = "deserialize_keys_lenient")]
pub keys: Vec<Jwk>,
}
fn deserialize_keys_lenient<'de, D>(deserializer: D) -> Result<Vec<Jwk>, D::Error>
where
D: serde::Deserializer<'de>,
{
let entries = Vec::<serde_json::Value>::deserialize(deserializer)?;
Ok(entries
.into_iter()
.filter_map(|entry| serde_json::from_value(entry).ok())
.collect())
}
impl Jwks {
#[must_use]
pub fn new(keys: Vec<Jwk>) -> Self {
Self { keys }
}
}
impl From<Jwks> for PublicJwks {
fn from(jwks: Jwks) -> Self {
PublicJwks::new(jwks.keys.iter().filter_map(Jwk::public_jwk).collect())
}
}
#[cfg(test)]
mod tests;