Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ devenv.local.nix

# pre-commit
.pre-commit-config.yaml

# Visual Studio Code
.vscode
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ extern crate alloc;
use alloc::string::ToString;

use jose::{
format::{Compact, DecodeFormat},
format::{CompactJws, DecodeFormat},
jwk::{
policy::{Checkable, StandardPolicy},
JwkSigner, JwkVerifier,
Expand Down Expand Up @@ -111,7 +111,7 @@ let mut verifier: JwkVerifier = public_key
.unwrap();

// deserialize the JWT
let encoded: Compact = serialized.parse().expect("valid format");
let encoded: CompactJws = serialized.parse().expect("valid format");

// decode the JWT
// Note: this JWT is not yet verified!
Expand Down
4 changes: 2 additions & 2 deletions examples/basic-jwt/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use jose::{
ec::P256PrivateKey,
hmac::{Hs256, Key as HmacKey},
},
format::{Compact, DecodeFormat},
format::{Compact, CompactJws, DecodeFormat},
jwk::{
policy::{Checkable, StandardPolicy},
IntoJsonWebKey, JwkSigner, JwkVerifier, KeyOperation,
Expand Down Expand Up @@ -78,7 +78,7 @@ fn main() -> eyre::Result<()> {
let key: JsonWebKey = serde_json::from_reader(key)?;
let key = key.check(StandardPolicy::default()).map_err(|(_, e)| e)?;
let mut verifier: JwkVerifier = key.try_into()?;
let encoded: Compact = jwt.parse()?;
let encoded: CompactJws = jwt.parse()?;
let unverified_jwt = Jwt::<UntypedAdditionalProperties>::decode(encoded)?;
let jwt = unverified_jwt.verify(&mut verifier)?;
let payload = jwt.payload();
Expand Down
2 changes: 1 addition & 1 deletion src/base64_url.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use serde::{de::Error, Deserialize, Deserializer, Serialize};
use thiserror::Error;
use zeroize::{Zeroize, Zeroizing};

/// Error type indicating that one part of the compact
/// Error type indicating that one part of the CompactJws
/// representation was an invalid Base64Url string.
#[derive(Debug, Clone, Copy, Error)]
#[error("the string is not a valid Base64Url representation")]
Expand Down
7 changes: 3 additions & 4 deletions src/crypto/rsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ impl<'de> Deserialize<'de> for PublicKey {
e: repr.e.0,
};
let key = rsa::PublicKey::from_components(components)
.map_err(|e| D::Error::custom(format!("failed to construct RSA public key: {}", e)))?;
.map_err(|e| D::Error::custom(format!("failed to construct RSA public key: {e}")))?;
Ok(Self { inner: key })
}
}
Expand Down Expand Up @@ -332,9 +332,8 @@ impl<'de> Deserialize<'de> for PrivateKey {
let prime_info = if any_prime_present {
let err = |field: &str| {
D::Error::custom(format!(
"expected `{}` to be present because all prime fields must be set if one of \
them is set",
field
"expected `{field}` to be present because all prime fields must be set if one \
of them is set",
))
};

Expand Down
59 changes: 47 additions & 12 deletions src/format.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
//! Contains abstractions for different kinds of
//! serialization formats.
//!
//! Currently, the only two formats are [`Compact`] and [`JsonFlattened`].
//! The formats are [`Compact`], [`JsonFlattened`] and [`JsonGeneral`].

mod compact;
mod json_flattened;
mod json_general;

use core::fmt;

pub use compact::Compact;
pub use json_flattened::JsonFlattened;
pub use json_general::JsonGeneral;
pub use compact::{Compact, CompactJwe, CompactJws};
pub use json_flattened::{JsonFlattened, JsonFlattenedJwe, JsonFlattenedJws};
pub(crate) use json_general::Signature as JsonGeneralSignature;
pub use json_general::{JsonGeneral, JsonGeneralJwe, JsonGeneralJws};

use crate::sealed::Sealed;

pub(crate) mod sealed {
use alloc::fmt;
use core::convert::Infallible;
use core::{convert::Infallible, fmt::Display};

use crate::{
header::{self, JoseHeaderBuilder, JoseHeaderBuilderError},
Expand All @@ -28,7 +26,7 @@ pub(crate) mod sealed {
// We put all methods, types, etc into a sealed trait, so
// the user is not able to access these thing as they should
// only be used internally by this crate
pub trait SealedFormat<F>: Sized {
pub trait SealedFormatJws<F>: Sized + Display {
type JwsHeader: fmt::Debug;
type SerializedJwsHeader: fmt::Debug;

Expand Down Expand Up @@ -57,11 +55,11 @@ pub(crate) mod sealed {
new_builder: JoseHeaderBuilder<F, header::Jws>,
);
}
}

/// This trait represents any possible format in which a JWS or JWE can be
/// represented.
pub trait Format: fmt::Display + sealed::SealedFormat<Self> + Sized {}
pub trait SealedFormatJwe: Sized {
type JweHeader: fmt::Debug;
}
}

/// Used to parse a [`Compact`] or another format representation
/// into a concrete type.
Expand Down Expand Up @@ -100,3 +98,40 @@ pub trait DecodeFormatWithContext<F, C>: Sealed + Sized {
/// this type.
fn decode_with_context(input: F, context: &C) -> Result<Self::Decoded<Self>, Self::Error>;
}

/// A trait to distinguish between
/// [`JsonWebSignature`](crate::JsonWebSignature)s and
/// [`JsonWebEncryption`](crate::JsonWebEncryption) in different serialization
/// formats.
///
/// This allows us to reuse types like [`Compact`] across JWS and JWE.
pub trait SealedFormatType: Sealed {
/// In [`Compact`] serialization, the different parts are base64urlsafe no
/// pad encoded and then separated by `.`.
///
/// For example, in [`JsonWebSignature`](crate::JsonWebSignature)s, it is
/// header.payload.signature (all base64urlsafe no pad encoded of course)
const COMAPCT_PARTS: usize;
}

/// A marker type to represent a [`JsonWebSignature`](crate::JsonWebSignature)
/// in some serialization format
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct Jws {}

impl Sealed for Jws {}
impl SealedFormatType for Jws {
const COMAPCT_PARTS: usize = 3;
}

/// A marker type to represent a [`JsonWebEncryption`](crate::JsonWebEncryption)
/// in some serialization format
#[derive(Debug)]
#[non_exhaustive]
pub struct Jwe {}

impl Sealed for Jwe {}
impl SealedFormatType for Jwe {
const COMAPCT_PARTS: usize = 5;
}
37 changes: 26 additions & 11 deletions src/format/compact.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use alloc::vec::Vec;
use core::{fmt, str::FromStr};
use core::{fmt, marker::PhantomData, str::FromStr};

use super::{sealed, Format};
use super::{sealed, Jwe, Jws, SealedFormatType};
use crate::{
base64_url::NoBase64UrlString,
header,
Expand All @@ -12,13 +12,18 @@ use crate::{
/// The compact representation is essentially a list of Base64Url
/// strings that are separated by `.`.
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
pub struct Compact {
pub struct Compact<T> {
parts: Vec<Base64UrlString>,
_crypto_typ: PhantomData<T>,
}

impl Format for Compact {}
impl sealed::SealedFormat<Compact> for Compact {
type JwsHeader = JoseHeader<Compact, header::Jws>;
/// A [`JsonWebSignature`](crate::JsonWebSignature) in [`Compact`] format
pub type CompactJws = Compact<Jws>;
/// A [`JsonWebEncryption`](crate::JsonWebEncryption) in [`Compact`] format
pub type CompactJwe = Compact<Jwe>;

impl sealed::SealedFormatJws<CompactJws> for CompactJws {
type JwsHeader = JoseHeader<Compact<Jws>, header::Jws>;
type SerializedJwsHeader = Base64UrlString;

fn update_header<S: AsRef<[u8]>>(header: &mut Self::JwsHeader, signer: &dyn Signer<S>) {
Expand Down Expand Up @@ -72,16 +77,20 @@ impl sealed::SealedFormat<Compact> for Compact {

fn finalize_jws_header_builder(
value_ref: &mut Result<Self::JwsHeader, header::JoseHeaderBuilderError>,
new_builder: header::JoseHeaderBuilder<Compact, header::Jws>,
new_builder: header::JoseHeaderBuilder<Compact<Jws>, header::Jws>,
) {
*value_ref = new_builder.build();
}
}

impl Compact {
impl<T> Compact<T>
where
T: SealedFormatType,
{
pub(crate) fn with_capacity(cap: usize) -> Self {
Compact {
parts: Vec::with_capacity(cap),
_crypto_typ: PhantomData,
}
}

Expand All @@ -102,7 +111,7 @@ impl Compact {
}
}

impl FromStr for Compact {
impl<T> FromStr for Compact<T> {
type Err = NoBase64UrlString;

/// Verifies if every part of the string is valid base64url format
Expand All @@ -111,11 +120,17 @@ impl FromStr for Compact {
.split('.')
.map(Base64UrlString::from_str)
.collect::<Result<Vec<_>, _>>()?;
Ok(Self { parts })
Ok(Self {
parts,
_crypto_typ: PhantomData,
})
}
}

impl fmt::Display for Compact {
impl<T> fmt::Display for Compact<T>
where
T: SealedFormatType,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let len = self.parts.len();

Expand Down
22 changes: 14 additions & 8 deletions src/format/json_flattened.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use alloc::string::String;
use core::fmt;
use core::{fmt, marker::PhantomData};

use serde::{Deserialize, Serialize};
use serde_json::Value;

use super::{sealed, Format};
use super::{sealed, Jwe, Jws};
use crate::{
header,
jws::{PayloadData, SignError},
Expand All @@ -13,18 +13,23 @@ use crate::{

/// The flattened json serialization format.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct JsonFlattened {
pub struct JsonFlattened<T> {
pub(crate) payload: Option<Base64UrlString>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) protected: Option<Base64UrlString>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) header: Option<serde_json::Map<String, Value>>,
pub(crate) signature: Base64UrlString,
pub(crate) _crypto_typ: PhantomData<T>,
}

impl Format for JsonFlattened {}
/// A [`JsonWebSignature`](crate::JsonWebSignature) in [`JsonFlattened`] format
pub type JsonFlattenedJws = JsonFlattened<Jws>;
/// A [`JsonWebEncryption`](crate::JsonWebEncryption) in [`JsonFlattened`]
/// format
pub type JsonFlattenedJwe = JsonFlattened<Jwe>;

impl fmt::Display for JsonFlattened {
impl<T> fmt::Display for JsonFlattened<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let repr = if f.alternate() {
serde_json::to_string_pretty(&self).map_err(|_| fmt::Error)?
Expand All @@ -36,8 +41,8 @@ impl fmt::Display for JsonFlattened {
}
}

impl sealed::SealedFormat<JsonFlattened> for JsonFlattened {
type JwsHeader = JoseHeader<JsonFlattened, header::Jws>;
impl sealed::SealedFormatJws<JsonFlattenedJws> for JsonFlattenedJws {
type JwsHeader = JoseHeader<JsonFlattenedJws, header::Jws>;
type SerializedJwsHeader = (
Option<Base64UrlString>,
Option<serde_json::Map<String, Value>>,
Expand Down Expand Up @@ -86,12 +91,13 @@ impl sealed::SealedFormat<JsonFlattened> for JsonFlattened {
protected,
header: unprotected,
signature,
_crypto_typ: PhantomData,
})
}

fn finalize_jws_header_builder(
value_ref: &mut Result<Self::JwsHeader, header::JoseHeaderBuilderError>,
new_builder: header::JoseHeaderBuilder<JsonFlattened, header::Jws>,
new_builder: header::JoseHeaderBuilder<JsonFlattenedJws, header::Jws>,
) {
*value_ref = new_builder.build();
}
Expand Down
30 changes: 18 additions & 12 deletions src/format/json_general.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use alloc::{string::String, vec, vec::Vec};
use core::{convert::Infallible, fmt};
use core::{convert::Infallible, fmt, marker::PhantomData};

use serde::{Deserialize, Serialize};
use serde_json::Value;

use super::{sealed, Format};
use super::{sealed, Jwe, Jws};
use crate::{
header::{self, JoseHeaderBuilder, JoseHeaderBuilderError},
jws::{PayloadData, SignError, Signer},
Expand All @@ -25,12 +25,18 @@ pub(crate) struct Signature {
///
/// [Section 7.2.1]: https://datatracker.ietf.org/doc/html/rfc7515#section-7.2.1
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct JsonGeneral {
pub struct JsonGeneral<T> {
pub(crate) payload: Option<Base64UrlString>,
pub(crate) signatures: Vec<Signature>,
pub(crate) _crypto_typ: PhantomData<T>,
}

impl fmt::Display for JsonGeneral {
/// A [`JsonWebSignature`](crate::JsonWebSignature) in [`JsonGeneral`] format
pub type JsonGeneralJws = JsonGeneral<Jws>;
/// A [`JsonWebEncryption`](crate::JsonWebEncryption) in [`JsonGeneral`] format
pub type JsonGeneralJwe = JsonGeneral<Jwe>;

impl<T> fmt::Display for JsonGeneral<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let repr = if f.alternate() {
serde_json::to_string_pretty(&self).map_err(|_| fmt::Error)?
Expand All @@ -42,12 +48,11 @@ impl fmt::Display for JsonGeneral {
}
}

impl Format for JsonGeneral {}

impl sealed::SealedFormat<JsonGeneral> for JsonGeneral {
type JwsHeader = Vec<JoseHeader<JsonGeneral, header::Jws>>;
// this only a single header, even though JsonGeneral supports multiple headers,
// because this trait implementation is only be used for a single signer.
impl sealed::SealedFormatJws<JsonGeneralJws> for JsonGeneralJws {
type JwsHeader = Vec<JoseHeader<JsonGeneralJws, header::Jws>>;
// this only a single header, even though JsonGeneralJws supports multiple
// headers, because this trait implementation is only be used for a single
// signer.
type SerializedJwsHeader = (
Option<Base64UrlString>,
Option<serde_json::Map<String, Value>>,
Expand Down Expand Up @@ -98,19 +103,20 @@ impl sealed::SealedFormat<JsonGeneral> for JsonGeneral {

let signature = Base64UrlString::encode(signature);

Ok(JsonGeneral {
Ok(JsonGeneralJws {
payload,
signatures: vec![Signature {
protected: header.0,
header: header.1,
signature,
}],
_crypto_typ: PhantomData,
})
}

fn finalize_jws_header_builder(
value_ref: &mut Result<Self::JwsHeader, JoseHeaderBuilderError>,
new_builder: JoseHeaderBuilder<JsonGeneral, header::Jws>,
new_builder: JoseHeaderBuilder<JsonGeneralJws, header::Jws>,
) {
let header = match new_builder.build() {
Ok(header) => header,
Expand Down
Loading
Loading