Skip to content

Commit

Permalink
Merge pull request #3 from alexrudy/token-format-flat
Browse files Browse the repository at this point in the history
Flat token format
  • Loading branch information
alexrudy authored Nov 23, 2023
2 parents 915b6f1 + e7183cf commit 48e3a40
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 23 deletions.
24 changes: 15 additions & 9 deletions src/jose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,11 +193,15 @@ impl<Builder, Key> DerivedKey<Builder, Key>
where
Builder: KeyDerivedBuilder<Key>,
<Builder as KeyDerivedBuilder<Key>>::Value: Serialize,
Key: Clone,
{
fn parameter(&self, key: &str) -> Option<serde_json::Value> {
fn parameter(&self) -> Option<serde_json::Value> {
match self {
DerivedKey::Omit => None,
DerivedKey::Derived(_) => Some(json!(format!("<{key}>"))),
DerivedKey::Derived(key) => Some(
serde_json::to_value(Builder::from(key.clone()).build())
.expect("failed to serialize derived key"),
),
DerivedKey::Explicit(value) => serde_json::to_value(value).ok(),
}
}
Expand Down Expand Up @@ -265,20 +269,22 @@ where
#[cfg(feature = "fmt")]
impl<Key> Signed<Key>
where
Key: SerializeJWK,
Key: SerializeJWK + Clone,
{
fn parameters(&self) -> serde_json::Value {
let mut data = json!({});

if let Some(value) = self.key.parameter("jwk") {
data["alg"] = serde_json::to_value(self.algorithm).unwrap();

if let Some(value) = self.key.parameter() {
data["jwk"] = value;
}

if let Some(value) = self.thumbprint.parameter("x5t") {
if let Some(value) = self.thumbprint.parameter() {
data["x5t"] = value;
}

if let Some(value) = self.thumbprint_sha256.parameter("x5t#S256") {
if let Some(value) = self.thumbprint_sha256.parameter() {
data["x5t#S256"] = value;
}

Expand All @@ -289,7 +295,7 @@ where
#[cfg(feature = "fmt")]
impl<Key> fmt::JWTFormat for Signed<Key>
where
Key: SerializeJWK,
Key: SerializeJWK + Clone,
{
fn fmt<W: std::fmt::Write>(&self, f: &mut fmt::IndentWriter<'_, W>) -> std::fmt::Result {
Base64JSON(&self.parameters()).fmt(f)
Expand Down Expand Up @@ -692,7 +698,7 @@ where
impl<H, Key> Header<H, Signed<Key>>
where
H: Serialize,
Key: SerializeJWK,
Key: SerializeJWK + Clone,
{
pub(crate) fn value(&self) -> serde_json::Value {
let value = self.state.parameters();
Expand All @@ -717,7 +723,7 @@ where
impl<H, Key> fmt::JWTFormat for Header<H, Signed<Key>>
where
H: Serialize,
Key: SerializeJWK,
Key: SerializeJWK + Clone,
{
fn fmt<W: std::fmt::Write>(&self, f: &mut fmt::IndentWriter<'_, W>) -> std::fmt::Result {
let value = self.value();
Expand Down
25 changes: 24 additions & 1 deletion src/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ where
/// JSON Web Key in serialized form.
///
/// This struct just contains the parameters of the JWK.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct JsonWebKey {
#[serde(rename = "kty")]
key_type: String,
Expand Down Expand Up @@ -140,6 +140,29 @@ where
}
}

impl Serialize for JsonWebKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
// JWKs must serialize keys in alphabetical order.

let mut entries = self
.parameters
.iter()
.map(|(key, value)| (key.as_str(), value))
.collect::<BTreeMap<_, _>>();
let kty = serde_json::Value::String(self.key_type.clone());
entries.insert("kty", &kty);

let mut map = serializer.serialize_map(Some(entries.len()))?;
for (key, value) in entries {
map.serialize_entry(key, value)?;
}
map.end()
}
}

/// A JSON Web Key Thumbprint (RFC 7638) calculator.
///
/// This type contains the raw parts to build a JWK and then digest
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ pub use claims::{Claims, RegisteredClaims};
pub use fmt::JWTFormat;
pub use jose::{Header, RegisteredHeader};
pub use token::Token;
pub use token::{Compact, Flat};
pub use token::{Compact, Flat, FlatUnprotected};
108 changes: 103 additions & 5 deletions src/token/formats.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::fmt::Write;

use serde::{Deserialize, Serialize};
use serde::{ser, Deserialize, Serialize};

use super::{HasSignature, MaybeSigned};
use super::{Payload, Token};
Expand All @@ -20,13 +20,14 @@ impl Compact {
}
}

/// A token format that serializes the token as a single JSON object.
/// A token format that serializes the token as a single JSON object,
/// with the unprotected header as a top-level field.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Flat<U> {
pub struct FlatUnprotected<U> {
unprotected: U,
}

impl<U> Flat<U> {
impl<U> FlatUnprotected<U> {
/// Creates a new `Flat` token format with the given unprotected header.
pub fn new(unprotected: U) -> Self {
Self { unprotected }
Expand All @@ -43,6 +44,10 @@ impl<U> Flat<U> {
}
}

/// A token format that serializes the token as a single JSON object.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Flat;

/// Error returned when a token cannot be formatted as a string.
///
/// This error can occur when serializing the header or payload, or
Expand Down Expand Up @@ -103,7 +108,7 @@ struct FlatToken<'t, P, U> {
signature: String,
}

impl<U> TokenFormat for Flat<U>
impl<U> TokenFormat for FlatUnprotected<U>
where
U: Serialize,
{
Expand Down Expand Up @@ -134,3 +139,96 @@ where
Ok(())
}
}

impl<P, S, U> Serialize for Token<P, S, FlatUnprotected<U>>
where
S: HasSignature,
<S as MaybeSigned>::Header: Serialize,
<S as MaybeSigned>::HeaderState: Serialize,
U: Serialize,
P: Serialize,
{
fn serialize<Ser>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error>
where
Ser: ser::Serializer,
{
let header = Base64JSON(self.state.header())
.serialized_value()
.map_err(ser::Error::custom)?;
let signature = Base64Data(self.state.signature())
.serialized_value()
.map_err(ser::Error::custom)?;

let flat = FlatToken {
payload: &self.payload,
protected: header,
unprotected: &self.fmt.unprotected,
signature,
};

flat.serialize(serializer)
}
}

#[derive(Debug, Serialize)]
struct FlatSimpleToken<'t, P> {
payload: &'t Payload<P>,
protected: String,
signature: String,
}

impl TokenFormat for Flat {
fn render<S>(
&self,
writer: &mut impl Write,
token: &Token<impl Serialize, S, Self>,
) -> Result<(), TokenFormattingError>
where
Self: Sized,
S: HasSignature,
<S as MaybeSigned>::Header: Serialize,
<S as MaybeSigned>::HeaderState: Serialize,
{
let header = Base64JSON(&token.state.header()).serialized_value()?;
let signature = Base64Data(token.state.signature()).serialized_value()?;

let flat = FlatSimpleToken {
payload: &token.payload,
protected: header,
signature,
};

let data = serde_json::to_string(&flat)?;
write!(writer, "{}", data)?;

Ok(())
}
}

impl<P, S> Serialize for Token<P, S, Flat>
where
S: HasSignature,
<S as MaybeSigned>::Header: Serialize,
<S as MaybeSigned>::HeaderState: Serialize,
P: Serialize,
{
fn serialize<Ser>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error>
where
Ser: ser::Serializer,
{
let header = Base64JSON(self.state.header())
.serialized_value()
.map_err(ser::Error::custom)?;
let signature = Base64Data(self.state.signature())
.serialized_value()
.map_err(ser::Error::custom)?;

let flat = FlatSimpleToken {
payload: &self.payload,
protected: header,
signature,
};

flat.serialize(serializer)
}
}
11 changes: 4 additions & 7 deletions src/token/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use crate::{
mod formats;
mod state;

pub use self::formats::{Compact, Flat, TokenFormat, TokenFormattingError};
pub use self::formats::{Compact, Flat, FlatUnprotected, TokenFormat, TokenFormattingError};
pub use self::state::{HasSignature, MaybeSigned, Signed, Unsigned, Unverified, Verified};

/// A JWT Playload. Most payloads are JSON objects, which are serialized, and then converted
Expand Down Expand Up @@ -223,19 +223,16 @@ impl<P, H> Token<P, Unsigned<H>, Compact> {
}
}

impl<P, U, H> Token<P, Unsigned<H>, Flat<U>>
where
U: Serialize,
{
impl<P, H> Token<P, Unsigned<H>, Flat> {
/// Create a new token with the given header and payload, in the flat format.
///
/// See also [`Token::new`] and [`Token::compact`] to create a token in a specific format.
///
/// The flat format is the format with a JSON object containing the header, payload, and
/// signature, all in the same object. It can also include additional JSON data as "unprotected"\
/// headers, which are not signed and cannot be verified.
pub fn flat(header: H, unprotected: U, payload: P) -> Token<P, Unsigned<H>, Flat<U>> {
Token::new(header, payload, Flat::new(unprotected))
pub fn flat(header: H, payload: P) -> Token<P, Unsigned<H>, Flat> {
Token::new(header, payload, Flat)
}
}

Expand Down

0 comments on commit 48e3a40

Please sign in to comment.