From 54d934e3dbf18393359ca5984a96f2d83f6ac516 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Wed, 1 Oct 2025 01:44:04 +0000 Subject: [PATCH] feat(jose-jws): make generic over claims 1. Allow custom types to be used for `Protected` and `Unprotected` 2. Prepare to support JWT claims 3. Implement `Deref` and `DerefMut` for `Protected` --- jose-jws/README.md | 2 +- jose-jws/src/compact.rs | 17 +++++++++++++---- jose-jws/src/crypto/mod.rs | 35 ++++++++++++++++++----------------- jose-jws/src/head.rs | 23 +++++++++++++++++++---- jose-jws/src/lib.rs | 36 ++++++++++++++++++++---------------- 5 files changed, 71 insertions(+), 42 deletions(-) diff --git a/jose-jws/README.md b/jose-jws/README.md index 9c62805..42537d6 100644 --- a/jose-jws/README.md +++ b/jose-jws/README.md @@ -54,7 +54,7 @@ let jws_json = serde_json::json!({ ] }); -let Jws::General(jws) = serde_json::from_value(jws_json).unwrap() else { +let Jws::General(jws) = serde_json::from_value::(jws_json).unwrap() else { panic!("couldn't deserialize JWS"); }; diff --git a/jose-jws/src/compact.rs b/jose-jws/src/compact.rs index 19e4b47..6188f84 100644 --- a/jose-jws/src/compact.rs +++ b/jose-jws/src/compact.rs @@ -9,7 +9,10 @@ use jose_b64::stream::Error; use crate::{Flattened, General, Jws, Signature}; -impl FromStr for Jws { +impl FromStr for Jws +where + P: serde::de::DeserializeOwned, +{ type Err = Error; fn from_str(s: &str) -> Result { @@ -17,7 +20,10 @@ impl FromStr for Jws { } } -impl FromStr for General { +impl FromStr for General +where + P: serde::de::DeserializeOwned, +{ type Err = Error; fn from_str(s: &str) -> Result { @@ -25,7 +31,10 @@ impl FromStr for General { } } -impl FromStr for Flattened { +impl FromStr for Flattened +where + P: serde::de::DeserializeOwned, +{ type Err = Error; fn from_str(s: &str) -> Result { @@ -54,7 +63,7 @@ impl FromStr for Flattened { } } -impl Display for Flattened { +impl Display for Flattened { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let mut prot = alloc::string::String::new(); if let Some(x) = self.signature.protected.as_ref() { diff --git a/jose-jws/src/crypto/mod.rs b/jose-jws/src/crypto/mod.rs index 1b84e77..db8b986 100644 --- a/jose-jws/src/crypto/mod.rs +++ b/jose-jws/src/crypto/mod.rs @@ -11,28 +11,24 @@ use rand_core::RngCore; use crate::{Flattened, General, Jws, Protected, Signature, Unprotected}; /// Signature creation state -pub trait Signer: Update { +pub trait Signer>: Update { #[allow(missing_docs)] type FinishError: From; /// Finish processing payload and create the signature. - fn finish(self, rng: impl 'static + RngCore) -> Result; + fn finish(self, rng: impl 'static + RngCore) -> Result, Self::FinishError>; } /// A signature creation key -pub trait SigningKey<'a> { +pub trait SigningKey<'a, U = Unprotected, P = Protected> { #[allow(missing_docs)] type StartError: From<::Error>; /// The state object used during signing. - type Signer: Signer; + type Signer: Signer; /// Begin the signature creation process. - fn sign( - &'a self, - prot: Option, - head: Option, - ) -> Result; + fn sign(&'a self, prot: Option

, head: Option) -> Result; } /// Signature verification state @@ -98,26 +94,31 @@ where } } -impl<'a, T: VerifyingKey<'a, &'a Signature>> VerifyingKey<'a, &'a Flattened> for T +impl<'a, T, U, P> VerifyingKey<'a, &'a Flattened> for T where + T: VerifyingKey<'a, &'a Signature>, >::FinishError: Default, { type StartError = T::StartError; type Verifier = Vec; - fn verify(&'a self, flattened: &'a Flattened) -> Result { + fn verify( + &'a self, + flattened: &'a Flattened, + ) -> Result { Ok(vec![self.verify(&flattened.signature)?]) } } -impl<'a, T: VerifyingKey<'a, &'a Signature>> VerifyingKey<'a, &'a General> for T +impl<'a, T, U, P> VerifyingKey<'a, &'a General> for T where + T: VerifyingKey<'a, &'a Signature>, >::FinishError: Default, { type StartError = T::StartError; type Verifier = Vec; - fn verify(&'a self, general: &'a General) -> Result { + fn verify(&'a self, general: &'a General) -> Result { general .signatures .iter() @@ -126,17 +127,17 @@ where } } -impl<'a, T, V, E> VerifyingKey<'a, &'a Jws> for T +impl<'a, T, V, E, U, P> VerifyingKey<'a, &'a Jws> for T where - T: VerifyingKey<'a, &'a Flattened, Verifier = V, StartError = E>, - T: VerifyingKey<'a, &'a General, Verifier = V, StartError = E>, + T: VerifyingKey<'a, &'a Flattened, Verifier = V, StartError = E>, + T: VerifyingKey<'a, &'a General, Verifier = V, StartError = E>, E: From, V: Verifier<'a>, { type StartError = E; type Verifier = V; - fn verify(&'a self, jws: &'a Jws) -> Result { + fn verify(&'a self, jws: &'a Jws) -> Result { match jws { Jws::General(general) => self.verify(general), Jws::Flattened(flattened) => self.verify(flattened), diff --git a/jose-jws/src/head.rs b/jose-jws/src/head.rs index fe480a3..7fcad8f 100644 --- a/jose-jws/src/head.rs +++ b/jose-jws/src/head.rs @@ -3,6 +3,7 @@ use alloc::vec::Vec; use alloc::{boxed::Box, string::String}; +use core::ops::{Deref, DerefMut}; use jose_b64::base64ct::Base64; use jose_b64::serde::Bytes; @@ -22,7 +23,7 @@ fn b64_serialize(value: &bool) -> bool { /// The JWS Protected Header #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Protected { +pub struct Protected { /// RFC 7517 Section 4.1.11 #[serde(skip_serializing_if = "Option::is_none", default)] pub crit: Option>, @@ -37,20 +38,34 @@ pub struct Protected { /// Other values that may appear in the protected header. #[serde(flatten)] - pub oth: Unprotected, + pub oth: U, } -impl Default for Protected { +impl Default for Protected { fn default() -> Self { Self { crit: None, nonce: None, b64: true, - oth: Unprotected::default(), + oth: U::default(), } } } +impl Deref for Protected { + type Target = U; + + fn deref(&self) -> &Self::Target { + &self.oth + } +} + +impl DerefMut for Protected { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.oth + } +} + /// The JWS Unprotected Header #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct Unprotected { diff --git a/jose-jws/src/lib.rs b/jose-jws/src/lib.rs index c33c36d..aebeaf1 100644 --- a/jose-jws/src/lib.rs +++ b/jose-jws/src/lib.rs @@ -37,23 +37,24 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Serialize, Deserialize)] #[non_exhaustive] #[allow(clippy::large_enum_variant)] +#[serde(bound(deserialize = "U: Deserialize<'de>, P: serde::de::DeserializeOwned"))] #[serde(untagged)] -pub enum Jws { +pub enum Jws> { /// General Serialization. This is - General(General), + General(General), /// Flattened Serialization - Flattened(Flattened), + Flattened(Flattened), } -impl From for Jws { - fn from(value: General) -> Self { +impl From> for Jws { + fn from(value: General) -> Self { Jws::General(value) } } -impl From for Jws { - fn from(value: Flattened) -> Self { +impl From> for Jws { + fn from(value: Flattened) -> Self { Jws::Flattened(value) } } @@ -77,16 +78,17 @@ impl From for Jws { /// } /// ``` #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct General { +#[serde(bound(deserialize = "U: Deserialize<'de>, P: serde::de::DeserializeOwned"))] +pub struct General> { /// The payload of the signature. pub payload: Option, /// The signatures over the payload. - pub signatures: Vec, + pub signatures: Vec>, } -impl From for General { - fn from(value: Flattened) -> Self { +impl From> for General { + fn from(value: Flattened) -> Self { Self { payload: value.payload, signatures: vec![value.signature], @@ -108,23 +110,25 @@ impl From for General { /// } /// ``` #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Flattened { +#[serde(bound(deserialize = "U: Deserialize<'de>, P: serde::de::DeserializeOwned"))] +pub struct Flattened> { /// The payload of the signature. pub payload: Option, /// The signature over the payload. #[serde(flatten)] - pub signature: Signature, + pub signature: Signature, } /// A Signature #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Signature { +#[serde(bound(deserialize = "U: Deserialize<'de>, P: serde::de::DeserializeOwned"))] +pub struct Signature> { /// The JWS Unprotected Header - pub header: Option, + pub header: Option, /// The JWS Protected Header - pub protected: Option>, + pub protected: Option>, /// The Signature Bytes pub signature: Bytes,