mod serde_utils;
mod source;
use crate::jwk::serde_utils::{base64url, base64url_uint, trim_leading_zeros};
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;
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct PublicJwks {
pub keys: Vec<PublicJwk>,
}
#[derive(Debug, Serialize, Deserialize, Builder, PartialEq, Clone)]
#[builder(derive(Into), builder_type(
doc {
/// Builder for creating a [`PublicJwk`] value (call `build()` or `into()` to finish).
}
))]
pub struct PublicJwk {
#[builder(into)]
#[serde(flatten)]
pub key: PublicKey,
#[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 PublicJwk {
#[must_use]
pub fn thumbprint(&self) -> Option<String> {
let canonical_form = match &self.key {
PublicKey::Rsa(rsa_public_key) => Some(rsa_public_key.canonical_form()),
PublicKey::Ec(ec_public_key) => Some(ec_public_key.canonical_form()),
PublicKey::Okp(okp_public_key) => Some(okp_public_key.canonical_form()),
PublicKey::UnknownOrPrivate => None,
};
canonical_form.map(|canonical| {
let mut hasher = Sha256::new();
hasher.update(canonical.as_bytes());
let hash = hasher.finalize();
URL_SAFE_NO_PAD.encode(hash)
})
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
pub enum KeyUse {
#[serde(rename = "sig")]
Sign,
#[serde(rename = "enc")]
Encrypt,
#[serde(skip, other)]
Unknown,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
#[serde(rename_all = "camelCase")]
pub enum KeyOperation {
Sign,
Verify,
Encrypt,
Decrypt,
WrapKey,
UnwrapKey,
DeriveKey,
DeriveBits,
#[serde(skip, other)]
Unknown,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
#[serde(tag = "kty")] pub enum PublicKey {
#[serde(rename = "RSA")]
Rsa(RsaPublicKey),
#[serde(rename = "EC")]
Ec(EcPublicKey),
#[serde(rename = "OKP")]
Okp(OkpPublicKey),
#[serde(skip, other)]
UnknownOrPrivate,
}
#[derive(Debug, Serialize, Deserialize, Builder, PartialEq, Clone)]
#[builder(derive(Into), builder_type(
doc {
/// Builder for creating an [`RsaPublicKey`] value (call `build()` or `into()` to finish).
}
))]
pub struct RsaPublicKey {
#[builder(with = <_>::from_iter)]
#[serde(with = "base64url_uint")]
pub n: Vec<u8>,
#[builder(with = <_>::from_iter)]
#[serde(with = "base64url_uint")]
pub e: Vec<u8>,
}
impl RsaPublicKey {
pub(super) fn canonical_form(&self) -> String {
let e = URL_SAFE_NO_PAD.encode(trim_leading_zeros(&self.e));
let n = URL_SAFE_NO_PAD.encode(trim_leading_zeros(&self.n));
format!(r#"{{"e":"{e}","kty":"RSA","n":"{n}"}}"#)
}
}
impl From<RsaPublicKey> for PublicKey {
fn from(value: RsaPublicKey) -> Self {
Self::Rsa(value)
}
}
impl<S: rsa_public_key_builder::State> From<RsaPublicKeyBuilder<S>> for PublicKey
where
S: rsa_public_key_builder::IsComplete,
{
fn from(value: RsaPublicKeyBuilder<S>) -> Self {
Self::Rsa(value.build())
}
}
#[derive(Debug, Serialize, Deserialize, Builder, PartialEq, Clone)]
#[builder(derive(Into), builder_type(
doc {
/// Builder for creating a [`EcPublicKey`] value (call `build()` or `into()` to finish).
}
))]
pub struct EcPublicKey {
#[builder(into)]
pub crv: String,
#[builder(with = <_>::from_iter)]
#[serde(with = "base64url")]
pub x: Vec<u8>,
#[builder(with = <_>::from_iter)]
#[serde(with = "base64url")]
pub y: Vec<u8>,
}
impl EcPublicKey {
pub(super) fn canonical_form(&self) -> String {
let crv = serde_json::to_string(&self.crv).unwrap();
let x = URL_SAFE_NO_PAD.encode(&self.x);
let y = URL_SAFE_NO_PAD.encode(&self.y);
format!(r#"{{"crv":{crv},"kty":"EC","x":"{x}","y":"{y}"}}"#)
}
}
impl From<EcPublicKey> for PublicKey {
fn from(value: EcPublicKey) -> Self {
Self::Ec(value)
}
}
impl<S: ec_public_key_builder::State> From<EcPublicKeyBuilder<S>> for PublicKey
where
S: ec_public_key_builder::IsComplete,
{
fn from(value: EcPublicKeyBuilder<S>) -> Self {
Self::Ec(value.build())
}
}
#[derive(Debug, Serialize, Deserialize, Builder, PartialEq, Clone)]
#[builder(derive(Into), builder_type(
doc {
/// Builder for creating a [`OkpPublicKey`] value (call `build()` or `into()` to finish).
}
))]
pub struct OkpPublicKey {
#[builder(into)]
pub crv: String,
#[builder(with = <_>::from_iter)]
#[serde(with = "base64url")]
pub x: Vec<u8>,
}
impl OkpPublicKey {
pub(super) fn canonical_form(&self) -> String {
let crv = serde_json::to_string(&self.crv).unwrap();
let x = URL_SAFE_NO_PAD.encode(&self.x);
format!(r#"{{"crv":{crv},"kty":"OKP","x":"{x}"}}"#)
}
}
impl From<OkpPublicKey> for PublicKey {
fn from(value: OkpPublicKey) -> Self {
Self::Okp(value)
}
}
impl<S: okp_public_key_builder::State> From<OkpPublicKeyBuilder<S>> for PublicKey
where
S: okp_public_key_builder::IsComplete,
{
fn from(value: OkpPublicKeyBuilder<S>) -> Self {
Self::Okp(value.build())
}
}
#[cfg(test)]
mod tests {
use base64::{Engine, prelude::BASE64_URL_SAFE_NO_PAD};
use super::*;
#[test]
fn test_parse_jwks_appendix_a1() {
let jwks_json = r#"{"keys":[
{"kty":"EC","crv":"P-256","x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4","y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM","use":"enc","kid":"1"},
{"kty":"RSA","n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw","e":"AQAB","alg":"RS256","kid":"2011-04-29"}
]}"#;
let jwks: PublicJwks = serde_json::from_str(jwks_json).unwrap();
let key1 = PublicJwk::builder()
.key(
EcPublicKey::builder()
.crv("P-256")
.x(BASE64_URL_SAFE_NO_PAD
.decode("MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4")
.unwrap())
.y(BASE64_URL_SAFE_NO_PAD
.decode("4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM")
.unwrap()),
)
.key_use(KeyUse::Encrypt)
.kid("1")
.build();
let key2 = PublicJwk::builder().key(
RsaPublicKey::builder()
.n(BASE64_URL_SAFE_NO_PAD.decode(
"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw"
).unwrap())
.e(BASE64_URL_SAFE_NO_PAD.decode("AQAB").unwrap())
)
.algorithm("RS256")
.kid("2011-04-29")
.build();
assert_eq!(jwks.keys, vec![key1, key2]);
}
#[test]
fn test_unknown_curve_parses() {
let unknown_curve = r#"{"kty":"EC","crv":"brainpoolP256r1","x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4","y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"}"#;
let _: PublicJwk = serde_json::from_str(unknown_curve).unwrap();
}
}