diff --git a/Cargo.lock b/Cargo.lock index 6a600a9..e3e5a98 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,6 +38,12 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "block-buffer" version = "0.10.4" @@ -224,6 +230,15 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c007b1ae3abe1cb6f85a16305acd418b7ca6343b953633fee2b76d8f108b830f" +[[package]] +name = "fluent-uri" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d" +dependencies = [ + "bitflags", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -294,6 +309,7 @@ dependencies = [ "ed25519", "ed25519-dalek", "elliptic-curve", + "fluent-uri", "generic-array", "hashbrown", "hmac", diff --git a/Cargo.toml b/Cargo.toml index a4c9461..6cf8972 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,13 @@ rust-version = "1.65" [features] default = [] -std = ["signature/std", "thiserror-no-std/std", "rsa/std", "rand_core/std"] +std = [ + "signature/std", + "thiserror-no-std/std", + "rsa/std", + "rand_core/std", + "fluent-uri/std" +] [dependencies] thiserror-no-std = "2.0.2" @@ -75,6 +81,7 @@ serde-value = { version = "0.7.0", default-features = false } # a replacement for the `mime` crate from hyper which seems to be no longer maintained mediatype = { version = "0.19.3", features = ["serde"] } +fluent-uri = { version = "0.1.4", default-features = false } [dev-dependencies] rand = "0.8.5" diff --git a/src/header.rs b/src/header.rs index 1498888..6aaa940 100644 --- a/src/header.rs +++ b/src/header.rs @@ -29,6 +29,7 @@ pub use self::{ use crate::{ format::Format, jwa::{JsonWebContentEncryptionAlgorithm, JsonWebEncryptionAlgorithm, JsonWebSigningAlgorithm}, + uri::BorrowedUri, JsonWebKey, UntypedAdditionalProperties, }; @@ -185,11 +186,11 @@ where /// [section 4.1.2 of RFC 7515]: /// [section 5 of RFC 7517]: // FIXME: use url type instead - pub fn jwk_set_url(&self) -> Option> { + pub fn jwk_set_url(&self) -> Option>> { self.parameters .jwk_set_url .as_ref() - .map(HeaderValue::as_deref) + .map(|x| x.as_ref().map(|x| x.borrow())) } /// Depending where this [`JoseHeader`] is being used, in JWE it contains @@ -232,8 +233,11 @@ where /// [RFC 3986]: /// [RFC 5280]: /// [section 4.1.5 of RFC 7515]: - pub fn x509_url(&self) -> Option> { - self.parameters.x509_url.as_ref().map(HeaderValue::as_deref) + pub fn x509_url(&self) -> Option>> { + self.parameters + .x509_url + .as_ref() + .map(|x| x.as_ref().map(|x| x.borrow())) } /// An [`Iterator`] over a X.509 certificate chain that certify the public diff --git a/src/header/builder.rs b/src/header/builder.rs index e887d55..9cb8c44 100644 --- a/src/header/builder.rs +++ b/src/header/builder.rs @@ -14,7 +14,7 @@ use crate::{ header::parameters::Parameters, jwa::{JsonWebContentEncryptionAlgorithm, JsonWebEncryptionAlgorithm, JsonWebSigningAlgorithm}, jwk::serde_impl::Base64DerCertificate, - JoseHeader, JsonWebKey, UntypedAdditionalProperties, + JoseHeader, JsonWebKey, UntypedAdditionalProperties, Uri, }; /// A builder for a [`JoseHeader`]. @@ -23,10 +23,10 @@ use crate::{ pub struct JoseHeaderBuilder { // data critical_headers: Option>, - jwk_set_url: Option>, + jwk_set_url: Option>, json_web_key: Option>>, key_identifier: Option>, - x509_url: Option>, + x509_url: Option>, x509_certificate_chain: Option>>>, x509_certificate_sha1_thumbprint: Option>, x509_certificate_sha256_thumbprint: Option>, @@ -367,10 +367,10 @@ macro_rules! setter { setter! { x509_certificate_chain: Vec>, - jwk_set_url: String, + jwk_set_url: Uri, json_web_key: JsonWebKey, key_identifier: String, - x509_url: String, + x509_url: Uri, x509_certificate_sha1_thumbprint: [u8; 20], x509_certificate_sha256_thumbprint: [u8; 32], typ: MediaTypeBuf, diff --git a/src/header/parameters.rs b/src/header/parameters.rs index f85d3c1..8741bf5 100644 --- a/src/header/parameters.rs +++ b/src/header/parameters.rs @@ -8,7 +8,7 @@ use mediatype::MediaTypeBuf; use serde_json::Value; use super::HeaderValue; -use crate::{jwk::serde_impl::Base64DerCertificate, JsonWebKey, UntypedAdditionalProperties}; +use crate::{jwk::serde_impl::Base64DerCertificate, JsonWebKey, UntypedAdditionalProperties, Uri}; #[derive(Debug)] #[non_exhaustive] @@ -16,14 +16,14 @@ pub(crate) struct Parameters { /// `crit` header MUST always be protected pub(crate) critical_headers: Option>, /// `jku` parameter defined in section 4.1.2 of JWS and section 4.1.4 of JWE - pub(crate) jwk_set_url: Option>, + pub(crate) jwk_set_url: Option>, /// `jwk` parameter defined in section 4.1.3 of JWS and section 4.1.5 of JWE pub(crate) json_web_key: Option>>, // `kid` parameter defined in section 4.1.4 of JWS and section 4.1.6 of JWE pub(crate) key_id: Option>, /// `x5u` parameter defined in section 4.1.5 of JWS and section 4.1.7 of JWE // FIXME: use url type instead - pub(crate) x509_url: Option>, + pub(crate) x509_url: Option>, /// `x5c` parameter defined in section 4.1.6 of JWS and section 4.1.8 of JWE pub(crate) x509_certificate_chain: Option>>, /// `x5t` parameter defined in section 4.1.7 of JWS and section 4.1.9 of JWE diff --git a/src/jwk.rs b/src/jwk.rs index 994205b..c951b4d 100644 --- a/src/jwk.rs +++ b/src/jwk.rs @@ -24,7 +24,8 @@ use crate::{ }, policy::{Checkable, Checked, CryptographicOperation, Policy}, sealed::Sealed, - UntypedAdditionalProperties, + uri::BorrowedUri, + UntypedAdditionalProperties, Uri, }; pub mod ec; @@ -219,11 +220,11 @@ pub struct JsonWebKey { #[serde(skip_serializing_if = "Option::is_none")] kid: Option, /// `x5u` parameter section 4.6 - // FIXME: consider using an dedicated URL type for this and ensure the protocol + // FIXME: considerung to ensure the protocol // uses TLS or some other form of integrity protection. // There are other things to consider, see the relevant section in the RFC. #[serde(rename = "x5u", skip_serializing_if = "Option::is_none")] - x509_url: Option, + x509_url: Option, /// `x5c` parameter section 4.7 // If the `x5c` parameter is not present, this will be an empty Vec // FIXME: find a good way and crate to parse the DER-encoded X.509 certificate(s) @@ -340,8 +341,8 @@ impl JsonWebKey { /// [Section 4.6 of RFC 7517] defines the `x5u` (X.509 URL) Parameter. /// /// [Section 4.6 of RFC 7517]: - pub fn x509_url(&self) -> Option<&str> { - self.x509_url.as_deref() + pub fn x509_url(&self) -> Option> { + self.x509_url.as_ref().map(|x| x.borrow()) } /// [Section 4.7 of RFC 7517] defines the `x5c` (X.509 Certificate Chain) diff --git a/src/jwk/builder.rs b/src/jwk/builder.rs index 585b50a..7a41ece 100644 --- a/src/jwk/builder.rs +++ b/src/jwk/builder.rs @@ -7,6 +7,7 @@ use super::{serde_impl::Base64DerCertificate, JsonWebKey, JsonWebKeyType, KeyOpe use crate::{ jwa::JsonWebAlgorithm, policy::{Checkable, Checked, Policy}, + Uri, }; /// Reasons the construction of a `JsonWebKey` via the @@ -34,7 +35,7 @@ pub struct JsonWebKeyBuilder { pub(super) key_operations: Option>, pub(super) algorithm: Option, pub(super) kid: Option, - pub(super) x509_url: Option, + pub(super) x509_url: Option, pub(super) x509_certificate_chain: Vec, pub(super) x509_certificate_sha1_thumbprint: Option<[u8; 20]>, pub(super) x509_certificate_sha256_thumbprint: Option<[u8; 32]>, @@ -148,7 +149,7 @@ impl JsonWebKeyBuilder { key_operations: HashSet, algorithm: JsonWebAlgorithm, kid: String, - x509_url: String, + x509_url: Uri, x509_certificate_sha1_thumbprint: [u8; 20], x509_certificate_sha256_thumbprint: [u8; 32], } diff --git a/src/lib.rs b/src/lib.rs index a01c336..d9b0fdc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,10 +38,12 @@ pub mod jwk; pub mod jws; mod jwt; pub mod policy; +mod uri; use alloc::string::String; pub use base64_url::Base64UrlString; +pub use uri::Uri; #[doc(inline)] pub use self::{header::JoseHeader, jwk::JsonWebKey, jws::JsonWebSignature, jwt::JsonWebToken}; diff --git a/src/uri.rs b/src/uri.rs new file mode 100644 index 0000000..77ee193 --- /dev/null +++ b/src/uri.rs @@ -0,0 +1,116 @@ +use alloc::string::String; +use core::{fmt, ops::Deref}; + +use serde::{Deserialize, Serialize}; + +/// A serializable URI type implemented using [`serde`] and [`fluent_uri`]. +/// +/// This is a thing wrapper around a [`fluent_uri::Uri`] that implements +/// [`Serialize`] and [`Deserialize`]. +#[derive(Debug, Clone, Default)] +pub struct Uri(fluent_uri::Uri); + +impl Uri { + /// Borrows this URI. + pub fn borrow(&self) -> BorrowedUri<'_> { + BorrowedUri(self.0.borrow()) + } + + /// Turns this URI into the underlying [`fluent_uri::Uri`]. + pub fn into_inner(self) -> fluent_uri::Uri { + self.0 + } +} + +impl PartialEq for Uri { + fn eq(&self, other: &Self) -> bool { + self.0.as_str().eq(other.0.as_str()) + } +} +impl Eq for Uri {} + +impl Deref for Uri { + type Target = fluent_uri::Uri; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From> for Uri { + fn from(uri: fluent_uri::Uri) -> Self { + Self(uri) + } +} + +impl fmt::Display for Uri { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl Serialize for Uri { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0.as_str().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for Uri { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let uri = String::deserialize(deserializer)?; + + Ok(Uri( + fluent_uri::Uri::parse_from(uri).map_err(|(_, e)| serde::de::Error::custom(e))? + )) + } +} + +/// A borrowed version of the [`Uri`]. +/// +/// This is a thing wrapper around a [`fluent_uri::Uri<&str>`] that implements +/// [`Serialize`]. +#[derive(Debug)] +pub struct BorrowedUri<'s>(&'s fluent_uri::Uri<&'s str>); + +impl<'s> BorrowedUri<'s> { + /// Turns this borrowed URI into an owned [`Uri`]. + pub fn to_owned(&self) -> Uri { + Uri(self.0.to_owned()) + } +} + +impl<'s> Deref for BorrowedUri<'s> { + type Target = fluent_uri::Uri<&'s str>; + + fn deref(&self) -> &Self::Target { + self.0 + } +} + +impl fmt::Display for BorrowedUri<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (*self.0).fmt(f) + } +} + +impl Serialize for BorrowedUri<'_> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0.as_str().serialize(serializer) + } +} + +impl PartialEq for BorrowedUri<'_> { + fn eq(&self, other: &Self) -> bool { + self.0.as_str().eq(other.0.as_str()) + } +} +impl Eq for BorrowedUri<'_> {}