From 0418b1ce7b1fb2b7b137f9fa8c20ef50ee8c7a5d Mon Sep 17 00:00:00 2001 From: Alex Rudy Date: Thu, 23 Nov 2023 19:07:12 +0000 Subject: [PATCH] Refactor header access patterns Stop exposing registered fields directly as strucutres - since there are registered fields stored on the HeaderState type as well (to track registered fields which can be key dependent). Access is replaced with accessor structs which allow access to all header fields in both shared and mutable reference forms. The access structs depend on the header state, and so can always return the correct types. Also refactors header documentation to be in markdown files alongside the source, so that header documentation can be easily re-used without copy and paste. --- Cargo.toml | 2 +- README.md | 4 +- docs/jose/algorithm.md | 18 + docs/jose/certificate_chain.md | 16 + docs/jose/certificate_url.md | 22 ++ docs/jose/content_type.md | 24 ++ docs/jose/critical.md | 17 + docs/jose/json_web_key.md | 6 + docs/jose/jwk_set_url.md | 14 + docs/jose/key_id.md | 11 + docs/jose/thumbprint.md | 7 + docs/jose/thumbprint_sha256.md | 7 + docs/jose/type.md | 30 ++ examples/acme-new-account.rs | 2 +- examples/rfc7515a2.rs | 4 +- src/algorithms/ecdsa.rs | 2 +- src/jose.rs | 632 ++++++++++++++++++--------------- src/lib.rs | 2 +- src/token/mod.rs | 41 ++- src/token/state.rs | 16 +- 20 files changed, 572 insertions(+), 305 deletions(-) create mode 100644 docs/jose/algorithm.md create mode 100644 docs/jose/certificate_chain.md create mode 100644 docs/jose/certificate_url.md create mode 100644 docs/jose/content_type.md create mode 100644 docs/jose/critical.md create mode 100644 docs/jose/json_web_key.md create mode 100644 docs/jose/jwk_set_url.md create mode 100644 docs/jose/key_id.md create mode 100644 docs/jose/thumbprint.md create mode 100644 docs/jose/thumbprint_sha256.md create mode 100644 docs/jose/type.md diff --git a/Cargo.toml b/Cargo.toml index 6ce0c2d..197901e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "jaws" -version = "0.3.0" +version = "0.4.0" edition = "2021" authors = ["Alex Rudy "] license = "MIT" diff --git a/README.md b/README.md index 444fadb..3d92fb5 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ fn main() -> Result<(), Box> { let mut token = Token::compact((), claims); // We can modify the headers freely before signing the JWT. In this case, // we provide the `typ` header, which is optional in the JWT spec. - token.header_mut().registered.r#type = Some("JWT".to_string()); + *token.header_mut().r#type() = Some("JWT".to_string()); // Sign the token with the algorithm, and print the result. let signed = token.sign(&alg).unwrap(); @@ -127,7 +127,7 @@ fn main() -> Result<(), Box> { // but we can access fields and read from them: println!( "Type: {:?}, Algorithm: {:?}", - signed.header().registered.r#type, + signed.header().r#type(), signed.header().algorithm(), ); diff --git a/docs/jose/algorithm.md b/docs/jose/algorithm.md new file mode 100644 index 0000000..034609e --- /dev/null +++ b/docs/jose/algorithm.md @@ -0,0 +1,18 @@ +The "alg" (algorithm) Header Parameter identifies the cryptographic +algorithm used to secure the JWS. The JWS Signature value is not +valid if the "alg" value does not represent a supported algorithm or +if there is not a key for use with that algorithm associated with the +party that digitally signed or MACed the content. "alg" values +should either be registered in the IANA "JSON Web Signature and +Encryption Algorithms" registry established by [JWA][] or be a value +that contains a Collision-Resistant Name. The "alg" value is a case- +sensitive ASCII string containing a StringOrURI value. This Header +Parameter MUST be present and MUST be understood and processed by +implementations. + +A list of defined "alg" values for this use can be found in the IANA +"JSON Web Signature and Encryption Algorithms" registry established +by [JWA][]; the initial contents of this registry are the values +defined in Section 3.1 of [JWA][]. + +[JWA]: https://tools.ietf.org/html/rfc7518 diff --git a/docs/jose/certificate_chain.md b/docs/jose/certificate_chain.md new file mode 100644 index 0000000..7385a99 --- /dev/null +++ b/docs/jose/certificate_chain.md @@ -0,0 +1,16 @@ +The "x5c" (X.509 certificate chain) Header Parameter contains the X.509 public +key certificate or certificate chain ([RFC 5280][RFC5280]) corresponding to the key used +to digitally sign the JWS. The certificate or certificate chain is represented +as a JSON array of certificate value strings. Each string in the array is a +base64-encoded (Section 4 of [RFC 4648][RFC4648] -- not base64url-encoded) DER +([ITU.X690.2008][]) PKIX certificate value. The certificate containing the public +key corresponding to the key used to digitally sign the JWS MUST be the first +certificate. This MAY be followed by additional certificates, with each +subsequent certificate being the one used to certify the previous one. The +recipient MUST validate the certificate chain according to [RFC 5280][RFC5280] +and consider the certificate or certificate chain to be invalid if any +validation failure occurs. Use of this Header Parameter is OPTIONAL. + +[RFC4648]: https://tools.ietf.org/html/rfc4648 +[RFC5280]: https://tools.ietf.org/html/rfc5280 +[ITU.X690.2008]: hhttps://www.itu.int/rec/T-REC-X.680-X.693-200811-S/en diff --git a/docs/jose/certificate_url.md b/docs/jose/certificate_url.md new file mode 100644 index 0000000..5dd8c75 --- /dev/null +++ b/docs/jose/certificate_url.md @@ -0,0 +1,22 @@ +The "x5u" (X.509 URL) Header Parameter is a URI ([RFC 3986][RFC3986]) that refers to a +resource for the X.509 public key certificate or certificate chain +([RFC 5280][RFC5280]) corresponding to the key used to digitally sign the JWS. The +identified resource MUST provide a representation of the certificate or +certificate chain that conforms to [RFC 5280][RFC5280] in PEM-encoded form, +with each certificate delimited as specified in Section 6.1 of [RFC 4945][RFC4945]. +The certificate containing the public key corresponding to the +key used to digitally sign the [JWS][] MUST be the first certificate. This MAY +be followed by additional certificates, with each subsequent certificate +being the one used to certify the previous one. The protocol used to +acquire the resource MUST provide integrity protection; an HTTP GET request +to retrieve the certificate MUST use TLS ([RFC 2818][RFC2818]) ([RFC 5246][RFC5246]); and the +identity of the server MUST be validated, as per Section 6 of [RFC 6125][RFC6125]. +Also, see Section 8 on TLS requirements. Use of this Header Parameter is OPTIONAL. + +[JWS]: https://datatracker.ietf.org/doc/html/rfc7515 +[RFC2818]: https://tools.ietf.org/html/rfc2818 +[RFC3986]: https://tools.ietf.org/html/rfc3986 +[RFC4945]: https://tools.ietf.org/html/rfc4945 +[RFC5246]: https://tools.ietf.org/html/rfc5246 +[RFC5280]: https://tools.ietf.org/html/rfc5280 +[RFC6125]: https://tools.ietf.org/html/rfc6125 diff --git a/docs/jose/content_type.md b/docs/jose/content_type.md new file mode 100644 index 0000000..e9c24dd --- /dev/null +++ b/docs/jose/content_type.md @@ -0,0 +1,24 @@ +The "cty" (content type) Header Parameter is used by JWS applications to +declare the media type ([IANA.MediaTypes][]) of the secured content (the +payload). This is intended for use by the application when more than one kind +of object could be present in the JWS Payload; the application can use this +value to disambiguate among the different kinds of objects that might be +present. It will typically not be used by applications when the kind of object +is already known. This parameter is ignored by JWS implementations; any +processing of this parameter is performed by the JWS application. Use of this +Header Parameter is OPTIONAL. + +Per [RFC 2045][], all media type values, subtype values, and parameter names +are case insensitive. However, parameter values are case sensitive unless +otherwise specified for the specific parameter. + +To keep messages compact in common situations, it is RECOMMENDED that producers +omit an "application/" prefix of a media type value in a "cty" Header Parameter +when no other '/' appears in the media type value. A recipient using the media +type value MUST treat it as if "application/" were prepended to any "cty" value +not containing a '/'. For instance, a "cty" value of "example" SHOULD be used +to represent the "application/example" media type, whereas the media type +"application/example;part="1/2"" cannot be shortened to "example;part="1/2"". + +[IANA.MediaTypes]: https://www.iana.org/assignments/media-types/media-types.xhtml +[RFC 2045]: https://tools.ietf.org/html/rfc2045 diff --git a/docs/jose/critical.md b/docs/jose/critical.md new file mode 100644 index 0000000..86cf1e4 --- /dev/null +++ b/docs/jose/critical.md @@ -0,0 +1,17 @@ +The "crit" (critical) Header Parameter indicates that extensions to this +specification and/or [JWA][] are being used that MUST be understood and +processed. Its value is an array listing the Header Parameter names present in +the JOSE Header that use those extensions. If any of the listed extension +Header Parameters are not understood and supported by the recipient, then the +JWS is invalid. Producers MUST NOT include Header Parameter names defined by +this specification or [JWA][] for use with JWS, duplicate names, or names that +do not occur as Header Parameter names within the JOSE Header in the "crit" +list. Producers MUST NOT use the empty list "[]" as the "crit" value. +Recipients MAY consider the JWS to be invalid if the critical list contains any +Header Parameter names defined by this specification or [JWA][] for use with +JWS or if any other constraints on its use are violated. When used, this Header +Parameter MUST be integrity protected; therefore, it MUST occur only within the +JWS Protected Header. Use of this Header Parameter is OPTIONAL. This Header +Parameter MUST be understood and processed by implementations. + +[JWA]: https://datatracker.ietf.org/doc/html/rfc7518 diff --git a/docs/jose/json_web_key.md b/docs/jose/json_web_key.md new file mode 100644 index 0000000..1d4974a --- /dev/null +++ b/docs/jose/json_web_key.md @@ -0,0 +1,6 @@ +The "jwk" (JSON Web Key) Header Parameter is the public key that +corresponds to the key used to digitally sign the JWS. This key is +represented as a JSON Web Key [JWK][]. Use of this Header Parameter is +OPTIONAL. + +[JWK]: https://tools.ietf.org/html/rfc7517 diff --git a/docs/jose/jwk_set_url.md b/docs/jose/jwk_set_url.md new file mode 100644 index 0000000..55f7269 --- /dev/null +++ b/docs/jose/jwk_set_url.md @@ -0,0 +1,14 @@ +The "jku" (JWK Set URL) Header Parameter is a URI ([RFC 3986][RFC3986]) that refers to a +resource for a set of JSON-encoded public keys, one of which corresponds to +the key used to digitally sign the JWS. The keys MUST be encoded as a JWK +Set ([JWK][]). The protocol used to acquire the resource MUST provide integrity +protection; an HTTP GET request to retrieve the JWK Set MUST use Transport +Layer Security (TLS) ([RFC 2818][RFC2818]) ([RFC 5246][RFC5246]); and the identity of the server +MUST be validated, as per Section 6 of [RFC 6125][RFC6125]. Also, see Section +8 on TLS requirements. Use of this Header Parameter is OPTIONAL. + +[JWK]: https://tools.ietf.org/html/rfc7517 +[RFC2818]: https://tools.ietf.org/html/rfc2818 +[RFC3986]: https://tools.ietf.org/html/rfc3986 +[RFC5246]: https://tools.ietf.org/html/rfc5246 +[RFC6125]: https://tools.ietf.org/html/rfc6125 diff --git a/docs/jose/key_id.md b/docs/jose/key_id.md new file mode 100644 index 0000000..a16cedd --- /dev/null +++ b/docs/jose/key_id.md @@ -0,0 +1,11 @@ +The "kid" (key ID) Header Parameter is a hint indicating which key was used +to secure the [JWS][]. This parameter allows originators to explicitly signal a +change of key to recipients. The structure of the "kid" value is +unspecified. Its value MUST be a case-sensitive string. Use of this Header +Parameter is OPTIONAL. + +When used with a [JWK][], the "kid" value is used to match a [JWK][] "kid" parameter +value. + +[JWK]: https://tools.ietf.org/html/rfc7517 +[JWS]: https://datatracker.ietf.org/doc/html/rfc7515 diff --git a/docs/jose/thumbprint.md b/docs/jose/thumbprint.md new file mode 100644 index 0000000..966f444 --- /dev/null +++ b/docs/jose/thumbprint.md @@ -0,0 +1,7 @@ +The "x5t" (X.509 certificate SHA-1 thumbprint) Header Parameter is a +base64url-encoded SHA-1 thumbprint (a.k.a. digest) of the DER encoding of the +X.509 certificate ([RFC 5280][RFC5280]) corresponding to the key used to digitally sign +the JWS. Note that certificate thumbprints are also sometimes known as +certificate fingerprints. Use of this Header Parameter is OPTIONAL. + +[RFC5280]: https://tools.ietf.org/html/rfc5280 diff --git a/docs/jose/thumbprint_sha256.md b/docs/jose/thumbprint_sha256.md new file mode 100644 index 0000000..15bc8c7 --- /dev/null +++ b/docs/jose/thumbprint_sha256.md @@ -0,0 +1,7 @@ +The "x5t#S256" (X.509 certificate SHA-256 thumbprint) Header Parameter is a +base64url-encoded SHA-256 thumbprint (a.k.a. digest) of the DER encoding of the +X.509 certificate ([RFC 5280][RFC5280]) corresponding to the key used to digitally sign +the JWS. Note that certificate thumbprints are also sometimes known as +certificate fingerprints. Use of this Header Parameter is OPTIONAL. + +[RFC5280]: https://tools.ietf.org/html/rfc5280 diff --git a/docs/jose/type.md b/docs/jose/type.md new file mode 100644 index 0000000..5882bdb --- /dev/null +++ b/docs/jose/type.md @@ -0,0 +1,30 @@ +The "typ" (type) Header Parameter is used by JWS applications to declare the +media type ([IANA.MediaTypes][]) of this complete JWS. This is intended for use +by the application when more than one kind of object could be present in an +application data structure that can contain a JWS; the application can use this +value to disambiguate among the different kinds of objects that might be +present. It will typically not be used by applications when the kind of object +is already known. This parameter is ignored by JWS implementations; any +processing of this parameter is performed by the JWS application. Use of this +Header Parameter is OPTIONAL. + +Per [RFC 2045][RFC2045], all media type values, subtype values, and parameter names are +case insensitive. However, parameter values are case sensitive unless otherwise +specified for the specific parameter + +To keep messages compact in common situations, it is RECOMMENDED that producers +omit an "application/" prefix of a media type value in a "typ" Header Parameter +when no other '/' appears in the media type value. A recipient using the media +type value MUST treat it as if "application/" were prepended to any "typ" value +not containing a '/'. For instance, a "typ" value of "example" SHOULD be used +to represent the "application/example" media type, whereas the media type +"application/example;part="1/2"" cannot be shortened to "example;part="1/2"". + +The "typ" value "JOSE" can be used by applications to indicate that this object +is a JWS or JWE using the JWS Compact Serialization or the JWE Compact +Serialization. The "typ" value "JOSE+JSON" can be used by applications to +indicate that this object is a JWS or JWE using the JWS JSON Serialization or +the JWE JSON Serialization. Other type values can also be used by applications. + +[IANA.MediaTypes]: https://www.iana.org/assignments/media-types/media-types.xhtml +[RFC2045]: https://tools.ietf.org/html/rfc2045 diff --git a/examples/acme-new-account.rs b/examples/acme-new-account.rs index 06fa88b..2d8908c 100644 --- a/examples/acme-new-account.rs +++ b/examples/acme-new-account.rs @@ -66,7 +66,7 @@ fn main() { // Create a token with the default headers, and no custom headers. let mut token = Token::new(payload, header, Compact); // Request that the token header include a JWK field. - token.header_mut().jwk().derived(); + token.header_mut().key().derived(); // Sign the token with the algorithm and key we specified above. let signed = token.sign(&alg).unwrap(); diff --git a/examples/rfc7515a2.rs b/examples/rfc7515a2.rs index a923c98..ffc1b3c 100644 --- a/examples/rfc7515a2.rs +++ b/examples/rfc7515a2.rs @@ -65,7 +65,7 @@ fn main() -> Result<(), Box> { let mut token = Token::new((), claims, Compact); // We can modify the headers freely before signing the JWT. In this case, // we provide the `typ` header, which is optional in the JWT spec. - token.header_mut().registered.r#type = Some("JWT".to_string()); + *token.header_mut().r#type() = Some("JWT".to_string()); // Sign the token with the algorithm, and print the result. let signed = token.sign(&alg).unwrap(); @@ -74,7 +74,7 @@ fn main() -> Result<(), Box> { // but we can access fields and read from them: println!( "Type: {:?}, Algorithm: {:?}", - signed.header().registered.r#type, + signed.header().r#type(), signed.header().algorithm(), ); diff --git a/src/algorithms/ecdsa.rs b/src/algorithms/ecdsa.rs index 4c446e6..c89485b 100644 --- a/src/algorithms/ecdsa.rs +++ b/src/algorithms/ecdsa.rs @@ -51,7 +51,7 @@ //! // but a custom type could be passed if we wanted to have custom header //! // fields. //! let mut token = Token::compact((), claims); -//! token.header_mut().registered.r#type = Some("JWT".to_string()); +//! *token.header_mut().r#type() = Some("JWT".to_string()); //! //! // Sign the token with the ECDSA key, and print the result. //! let signed = token.sign(&key).unwrap(); diff --git a/src/jose.rs b/src/jose.rs index b112ff0..3cdfe8e 100644 --- a/src/jose.rs +++ b/src/jose.rs @@ -22,6 +22,9 @@ use crate::fmt; use crate::key::{JsonWebKey, JsonWebKeyBuilder, KeyDerivedBuilder, Thumbprint, Thumbprinter}; /// Stub type to represent an X.509 certificate +/// +/// This type should be replaced with a proper representation of an X.509 +/// certificate, but that is not yet implemeted for this library. #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct Certificate; @@ -29,9 +32,14 @@ pub struct Certificate; /// when those fields are derived from the signing key. #[derive(Debug, Clone, Default)] pub enum KeyDerivation { + /// Omit this value from the rendered JOSE header. #[default] Omit, + + /// Derive this value from the signing key used to sign the token. Derived, + + /// Provide an explicit value for this field. Explicit(Value), } @@ -72,25 +80,25 @@ where /// to `true` when you'd like to serialize the signing key in the JOSE header /// as a JSON Web Key (JWK). #[derive(Debug, Clone, Default)] -pub struct Unsigned { +pub struct UnsignedHeader { /// Whether to include the signing key in the JOSE header as a JWK. /// - /// See [Rendered::key] for field details. + /// See [RenderedHeader::key] for field details. pub key: KeyDerivation, /// Whether to include the X.509 certificate thumbprint in the JOSE header with the SHA1 digest. /// - /// See [Rendered::thumbprint] for field details. + /// See [RenderedHeader::thumbprint] for field details. pub thumbprint: KeyDerivation>, /// Whether to include the X.509 certificate thumbprint in the JOSE header with the SHA256 digest. /// - /// See [Rendered::thumbprint_sha256] for field details. + /// See [RenderedHeader::thumbprint_sha256] for field details. pub thumbprint_sha256: KeyDerivation>, } #[cfg(feature = "fmt")] -impl Unsigned { +impl UnsignedHeader { fn parameters(&self) -> serde_json::Value { let mut data = json!({}); @@ -111,7 +119,7 @@ impl Unsigned { } #[cfg(feature = "fmt")] -impl fmt::JWTFormat for Unsigned { +impl fmt::JWTFormat for UnsignedHeader { fn fmt(&self, f: &mut fmt::IndentWriter<'_, W>) -> std::fmt::Result { Base64JSON(&self.parameters()).fmt(f) } @@ -124,7 +132,7 @@ impl fmt::JWTFormat for Unsigned { /// of that distinction, allowing a field to be marked as derived /// from the signing key. #[derive(Debug, Default)] -pub enum DerivedKey +enum DerivedKeyValue where Builder: KeyDerivedBuilder, { @@ -134,7 +142,7 @@ where Explicit(Builder::Value), } -impl Clone for DerivedKey +impl Clone for DerivedKeyValue where Builder: KeyDerivedBuilder, >::Value: Clone, @@ -149,19 +157,19 @@ where } } -impl DerivedKey +impl DerivedKeyValue where Builder: KeyDerivedBuilder, { fn is_none(&self) -> bool { - matches!(self, DerivedKey::Omit) + matches!(self, DerivedKeyValue::Omit) } fn build(self) -> Option { match self { - DerivedKey::Omit => None, - DerivedKey::Derived(key) => Some(Builder::from(key).build()), - DerivedKey::Explicit(value) => Some(value), + DerivedKeyValue::Omit => None, + DerivedKeyValue::Derived(key) => Some(Builder::from(key).build()), + DerivedKeyValue::Explicit(value) => Some(value), } } @@ -170,14 +178,14 @@ where Key: Clone, { match derivation { - KeyDerivation::Omit => DerivedKey::Omit, - KeyDerivation::Derived => DerivedKey::Derived(key.clone()), - KeyDerivation::Explicit(value) => DerivedKey::Explicit(value), + KeyDerivation::Omit => DerivedKeyValue::Omit, + KeyDerivation::Derived => DerivedKeyValue::Derived(key.clone()), + KeyDerivation::Explicit(value) => DerivedKeyValue::Explicit(value), } } } -impl ser::Serialize for DerivedKey +impl ser::Serialize for DerivedKeyValue where Builder: KeyDerivedBuilder, >::Value: Serialize + Clone, @@ -189,7 +197,7 @@ where } #[cfg(feature = "fmt")] -impl DerivedKey +impl DerivedKeyValue where Builder: KeyDerivedBuilder, >::Value: Serialize, @@ -197,12 +205,27 @@ where { fn parameter(&self) -> Option { match self { - DerivedKey::Omit => None, - DerivedKey::Derived(key) => Some( + DerivedKeyValue::Omit => None, + DerivedKeyValue::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(), + DerivedKeyValue::Explicit(value) => serde_json::to_value(value).ok(), + } + } +} + +impl DerivedKeyValue +where + Builder: KeyDerivedBuilder, + >::Value: Serialize + Clone, + Key: Clone, +{ + fn value(&self) -> Option { + match self { + DerivedKeyValue::Omit => None, + DerivedKeyValue::Derived(key) => Some(Builder::from(key.clone()).build()), + DerivedKeyValue::Explicit(value) => Some(value.clone()), } } } @@ -211,63 +234,29 @@ where /// with the signing key. #[derive(Debug, Clone, Serialize)] #[serde(bound(serialize = "Key: SerializeJWK + Clone"))] -pub struct Signed +pub struct SignedHeader where Key: SerializeJWK, { - /// The "alg" (algorithm) Header Parameter identifies the cryptographic - /// algorithm used to secure the JWS. The JWS Signature value is not - /// valid if the "alg" value does not represent a supported algorithm or - /// if there is not a key for use with that algorithm associated with the - /// party that digitally signed or MACed the content. "alg" values - /// should either be registered in the IANA "JSON Web Signature and - /// Encryption Algorithms" registry established by [JWA][] or be a value - /// that contains a Collision-Resistant Name. The "alg" value is a case- - /// sensitive ASCII string containing a StringOrURI value. This Header - /// Parameter MUST be present and MUST be understood and processed by - /// implementations. - /// - /// A list of defined "alg" values for this use can be found in the IANA - /// "JSON Web Signature and Encryption Algorithms" registry established - /// by [JWA][]; the initial contents of this registry are the values - /// defined in Section 3.1 of [JWA][]. - /// - /// [JWA]: https://tools.ietf.org/html/rfc7518 + #[doc = include_str!("../docs/jose/algorithm.md")] #[serde(rename = "alg")] - pub algorithm: AlgorithmIdentifier, + algorithm: AlgorithmIdentifier, - /// The "jwk" (JSON Web Key) Header Parameter is the public key that - /// corresponds to the key used to digitally sign the JWS. This key is - /// represented as a JSON Web Key [JWK][]. Use of this Header Parameter is - /// OPTIONAL. - /// - /// [JWK]: https://tools.ietf.org/html/rfc7517 - #[serde(rename = "jwk", skip_serializing_if = "DerivedKey::is_none")] - pub key: DerivedKey, Key>, - - /// The "x5t" (X.509 certificate SHA-1 thumbprint) Header Parameter is a - /// base64url-encoded SHA-1 thumbprint (a.k.a. digest) of the DER encoding of the - /// X.509 certificate ([RFC 5280][RFC5280]) corresponding to the key used to digitally sign - /// the JWS. Note that certificate thumbprints are also sometimes known as - /// certificate fingerprints. Use of this Header Parameter is OPTIONAL. - /// - /// [RFC5280]: https://tools.ietf.org/html/rfc5280 - #[serde(rename = "x5t", skip_serializing_if = "DerivedKey::is_none")] - pub thumbprint: DerivedKey, Key>, - - /// The "x5t#S256" (X.509 certificate SHA-256 thumbprint) Header Parameter is a - /// base64url-encoded SHA-256 thumbprint (a.k.a. digest) of the DER encoding of the - /// X.509 certificate ([RFC 5280][RFC5280]) corresponding to the key used to digitally sign - /// the JWS. Note that certificate thumbprints are also sometimes known as - /// certificate fingerprints. Use of this Header Parameter is OPTIONAL. - /// - /// [RFC5280]: https://tools.ietf.org/html/rfc5280 - #[serde(rename = "x5t#S256", skip_serializing_if = "DerivedKey::is_none")] - pub thumbprint_sha256: DerivedKey, Key>, + #[doc = include_str!("../docs/jose/json_web_key.md")] + #[serde(rename = "jwk", skip_serializing_if = "DerivedKeyValue::is_none")] + key: DerivedKeyValue, Key>, + + #[doc = include_str!("../docs/jose/thumbprint.md")] + #[serde(rename = "x5t", skip_serializing_if = "DerivedKeyValue::is_none")] + thumbprint: DerivedKeyValue, Key>, + + #[doc = include_str!("../docs/jose/thumbprint_sha256.md")] + #[serde(rename = "x5t#S256", skip_serializing_if = "DerivedKeyValue::is_none")] + thumbprint_sha256: DerivedKeyValue, Key>, } #[cfg(feature = "fmt")] -impl Signed +impl SignedHeader where Key: SerializeJWK + Clone, { @@ -293,7 +282,7 @@ where } #[cfg(feature = "fmt")] -impl fmt::JWTFormat for Signed +impl fmt::JWTFormat for SignedHeader where Key: SerializeJWK + Clone, { @@ -305,57 +294,23 @@ where /// The registered fields of a JOSE header, which are interdependent /// with the signing key, rendered into their typed form. /// -/// This is different from [Signed] in that it contains the actual data, +/// This is different from [SignedHeader] in that it contains the actual data, /// and not thd derivation, so the fields may be in inconsistent states. #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Rendered { - /// The "alg" (algorithm) Header Parameter identifies the cryptographic - /// algorithm used to secure the JWS. The JWS Signature value is not - /// valid if the "alg" value does not represent a supported algorithm or - /// if there is not a key for use with that algorithm associated with the - /// party that digitally signed or MACed the content. "alg" values - /// should either be registered in the IANA "JSON Web Signature and - /// Encryption Algorithms" registry established by [JWA][] or be a value - /// that contains a Collision-Resistant Name. The "alg" value is a case- - /// sensitive ASCII string containing a StringOrURI value. This Header - /// Parameter MUST be present and MUST be understood and processed by - /// implementations. - /// - /// A list of defined "alg" values for this use can be found in the IANA - /// "JSON Web Signature and Encryption Algorithms" registry established - /// by [JWA][]; the initial contents of this registry are the values - /// defined in Section 3.1 of [JWA][]. - /// - /// [JWA]: https://tools.ietf.org/html/rfc7518 +pub struct RenderedHeader { + #[doc = include_str!("../docs/jose/algorithm.md")] #[serde(rename = "alg")] pub algorithm: AlgorithmIdentifier, - /// The "jwk" (JSON Web Key) Header Parameter is the public key that - /// corresponds to the key used to digitally sign the JWS. This key is - /// represented as a JSON Web Key [JWK][]. Use of this Header Parameter is - /// OPTIONAL. - /// - /// [JWK]: https://tools.ietf.org/html/rfc7517 + #[doc = include_str!("../docs/jose/json_web_key.md")] #[serde(rename = "jwk", skip_serializing_if = "Option::is_none")] pub key: Option, - /// The "x5t" (X.509 certificate SHA-1 thumbprint) Header Parameter is a - /// base64url-encoded SHA-1 thumbprint (a.k.a. digest) of the DER encoding of the - /// X.509 certificate ([RFC 5280][RFC5280]) corresponding to the key used to digitally sign - /// the JWS. Note that certificate thumbprints are also sometimes known as - /// certificate fingerprints. Use of this Header Parameter is OPTIONAL. - /// - /// [RFC5280]: https://tools.ietf.org/html/rfc5280 + #[doc = include_str!("../docs/jose/thumbprint.md")] #[serde(rename = "x5t", skip_serializing_if = "Option::is_none")] pub thumbprint: Option>, - /// The "x5t#S256" (X.509 certificate SHA-256 thumbprint) Header Parameter is a - /// base64url-encoded SHA-256 thumbprint (a.k.a. digest) of the DER encoding of the - /// X.509 certificate ([RFC 5280][RFC5280]) corresponding to the key used to digitally sign - /// the JWS. Note that certificate thumbprints are also sometimes known as - /// certificate fingerprints. Use of this Header Parameter is OPTIONAL. - /// - /// [RFC5280]: https://tools.ietf.org/html/rfc5280 + #[doc = include_str!("../docs/jose/thumbprint_sha256.md")] #[serde(rename = "x5t#S256", skip_serializing_if = "Option::is_none")] pub thumbprint_sha256: Option>, } @@ -369,162 +324,34 @@ pub struct Rendered { /// [Header::jwk()] method to set the included JWK, or the [Header::thumbprint()] /// and [Header::thumbprint_sha256()] methods to set the included X.509 thumbprint. #[derive(Debug, Clone, Serialize, Default, PartialEq, Eq, Deserialize)] -pub struct RegisteredHeader { - /// The "jku" (JWK Set URL) Header Parameter is a URI ([RFC 3986][RFC3986]) that refers to a - /// resource for a set of JSON-encoded public keys, one of which corresponds to - /// the key used to digitally sign the JWS. The keys MUST be encoded as a JWK - /// Set ([JWK][]). The protocol used to acquire the resource MUST provide integrity - /// protection; an HTTP GET request to retrieve the JWK Set MUST use Transport - /// Layer Security (TLS) ([RFC 2818][RFC2818]) ([RFC 5246][RFC5246]); and the identity of the server - /// MUST be validated, as per Section 6 of [RFC 6125][RFC6125]. Also, see Section - /// 8 on TLS requirements. Use of this Header Parameter is OPTIONAL. - /// - /// [JWK]: https://tools.ietf.org/html/rfc7517 - /// [RFC2818]: https://tools.ietf.org/html/rfc2818 - /// [RFC3986]: https://tools.ietf.org/html/rfc3986 - /// [RFC5246]: https://tools.ietf.org/html/rfc5246 - /// [RFC6125]: https://tools.ietf.org/html/rfc6125 +struct RegisteredHeaderFields { + #[doc = include_str!("../docs/jose/jwk_set_url.md")] #[serde(rename = "jku", skip_serializing_if = "Option::is_none")] - pub jwk_set_url: Option, - - /// The "typ" (type) Header Parameter is used by JWS applications to declare the - /// media type ([IANA.MediaTypes][]) of this complete JWS. This is intended for use - /// by the application when more than one kind of object could be present in an - /// application data structure that can contain a JWS; the application can use this - /// value to disambiguate among the different kinds of objects that might be - /// present. It will typically not be used by applications when the kind of object - /// is already known. This parameter is ignored by JWS implementations; any - /// processing of this parameter is performed by the JWS application. Use of this - /// Header Parameter is OPTIONAL. - /// - /// Per [RFC 2045][RFC2045], all media type values, subtype values, and parameter names are - /// case insensitive. However, parameter values are case sensitive unless otherwise - /// specified for the specific parameter. - /// - /// To keep messages compact in common situations, it is RECOMMENDED that producers - /// omit an "application/" prefix of a media type value in a "typ" Header Parameter - /// when no other '/' appears in the media type value. A recipient using the media - /// type value MUST treat it as if "application/" were prepended to any "typ" value - /// not containing a '/'. For instance, a "typ" value of "example" SHOULD be used - /// to represent the "application/example" media type, whereas the media type - /// "application/example;part="1/2"" cannot be shortened to "example;part="1/2"". - /// - /// The "typ" value "JOSE" can be used by applications to indicate that this object - /// is a JWS or JWE using the JWS Compact Serialization or the JWE Compact - /// Serialization. The "typ" value "JOSE+JSON" can be used by applications to - /// indicate that this object is a JWS or JWE using the JWS JSON Serialization or - /// the JWE JSON Serialization. Other type values can also be used by applications. - /// - /// [IANA.MediaTypes]: https://www.iana.org/assignments/media-types/media-types.xhtml - /// [RFC2045]: https://tools.ietf.org/html/rfc2045 - #[serde(rename = "typ", skip_serializing_if = "Option::is_none")] - pub r#type: Option, + jwk_set_url: Option, - /// The "kid" (key ID) Header Parameter is a hint indicating which key was used - /// to secure the [JWS][]. This parameter allows originators to explicitly signal a - /// change of key to recipients. The structure of the "kid" value is - /// unspecified. Its value MUST be a case-sensitive string. Use of this Header - /// Parameter is OPTIONAL. - /// - /// When used with a [JWK][], the "kid" value is used to match a [JWK][] "kid" parameter - /// value. - /// - /// [JWK]: https://tools.ietf.org/html/rfc7517 - /// [JWS]: https://datatracker.ietf.org/doc/html/rfc7515 + #[doc = include_str!("../docs/jose/type.md")] + #[serde(rename = "typ", skip_serializing_if = "Option::is_none")] + r#type: Option, + #[doc = include_str!("../docs/jose/key_id.md")] #[serde(rename = "kid", skip_serializing_if = "Option::is_none")] - pub key_id: Option, - - /// The "x5u" (X.509 URL) Header Parameter is a URI ([RFC 3986][RFC3986]) that refers to a - /// resource for the X.509 public key certificate or certificate chain - /// ([RFC 5280][RFC5280]) corresponding to the key used to digitally sign the JWS. The - /// identified resource MUST provide a representation of the certificate or - /// certificate chain that conforms to [RFC 5280][RFC5280] in PEM-encoded form, - /// with each certificate delimited as specified in Section 6.1 of [RFC 4945][RFC4945]. - /// The certificate containing the public key corresponding to the - /// key used to digitally sign the [JWS][] MUST be the first certificate. This MAY - /// be followed by additional certificates, with each subsequent certificate - /// being the one used to certify the previous one. The protocol used to - /// acquire the resource MUST provide integrity protection; an HTTP GET request - /// to retrieve the certificate MUST use TLS ([RFC 2818][RFC2818]) ([RFC 5246][RFC5246]); and the - /// identity of the server MUST be validated, as per Section 6 of [RFC 6125][RFC6125]. - /// Also, see Section 8 on TLS requirements. Use of this Header Parameter is OPTIONAL. - /// - /// [JWS]: https://datatracker.ietf.org/doc/html/rfc7515 - /// [RFC2818]: https://tools.ietf.org/html/rfc2818 - /// [RFC3986]: https://tools.ietf.org/html/rfc3986 - /// [RFC4945]: https://tools.ietf.org/html/rfc4945 - /// [RFC5246]: https://tools.ietf.org/html/rfc5246 - /// [RFC5280]: https://tools.ietf.org/html/rfc5280 - /// [RFC6125]: https://tools.ietf.org/html/rfc6125 + key_id: Option, + + #[doc = include_str!("../docs/jose/certificate_url.md")] #[serde(rename = "x5u", skip_serializing_if = "Option::is_none")] pub certificate_url: Option, - /// The "x5c" (X.509 certificate chain) Header Parameter contains the X.509 public - /// key certificate or certificate chain ([RFC 5280][RFC5280]) corresponding to the key used - /// to digitally sign the JWS. The certificate or certificate chain is represented - /// as a JSON array of certificate value strings. Each string in the array is a - /// base64-encoded (Section 4 of [RFC 4648][RFC4648] -- not base64url-encoded) DER - /// ([ITU.X690.2008][]) PKIX certificate value. The certificate containing the public - /// key corresponding to the key used to digitally sign the JWS MUST be the first - /// certificate. This MAY be followed by additional certificates, with each - /// subsequent certificate being the one used to certify the previous one. The - /// recipient MUST validate the certificate chain according to [RFC 5280][RFC5280] - /// and consider the certificate or certificate chain to be invalid if any - /// validation failure occurs. Use of this Header Parameter is OPTIONAL. - /// - /// [RFC4648]: https://tools.ietf.org/html/rfc4648 - /// [RFC5280]: https://tools.ietf.org/html/rfc5280 - /// [ITU.X690.2008]: hhttps://www.itu.int/rec/T-REC-X.680-X.693-200811-S/en + #[doc = include_str!("../docs/jose/certificate_chain.md")] #[serde(rename = "x5c", skip_serializing_if = "Option::is_none")] - pub certificate_chain: Option>, - - /// The "cty" (content type) Header Parameter is used by JWS applications to - /// declare the media type ([IANA.MediaTypes][]) of the secured content (the - /// payload). This is intended for use by the application when more than one kind - /// of object could be present in the JWS Payload; the application can use this - /// value to disambiguate among the different kinds of objects that might be - /// present. It will typically not be used by applications when the kind of object - /// is already known. This parameter is ignored by JWS implementations; any - /// processing of this parameter is performed by the JWS application. Use of this - /// Header Parameter is OPTIONAL. - /// - /// Per [RFC 2045][], all media type values, subtype values, and parameter names - /// are case insensitive. However, parameter values are case sensitive unless - /// otherwise specified for the specific parameter. - /// - /// To keep messages compact in common situations, it is RECOMMENDED that producers - /// omit an "application/" prefix of a media type value in a "cty" Header Parameter - /// when no other '/' appears in the media type value. A recipient using the media - /// type value MUST treat it as if "application/" were prepended to any "cty" value - /// not containing a '/'. For instance, a "cty" value of "example" SHOULD be used - /// to represent the "application/example" media type, whereas the media type - /// "application/example;part="1/2"" cannot be shortened to "example;part="1/2"". - /// - /// [IANA.MediaTypes]: https://www.iana.org/assignments/media-types/media-types.xhtml - /// [RFC2045]: https://tools.ietf.org/html/rfc2045 + certificate_chain: Option>, + + #[doc = include_str!("../docs/jose/content_type.md")] #[serde(rename = "cty", skip_serializing_if = "Option::is_none")] - pub content_type: Option, - - /// The "crit" (critical) Header Parameter indicates that extensions to this - /// specification and/or [JWA][] are being used that MUST be understood and - /// processed. Its value is an array listing the Header Parameter names present in - /// the JOSE Header that use those extensions. If any of the listed extension - /// Header Parameters are not understood and supported by the recipient, then the - /// JWS is invalid. Producers MUST NOT include Header Parameter names defined by - /// this specification or [JWA][] for use with JWS, duplicate names, or names that - /// do not occur as Header Parameter names within the JOSE Header in the "crit" - /// list. Producers MUST NOT use the empty list "[]" as the "crit" value. - /// Recipients MAY consider the JWS to be invalid if the critical list contains any - /// Header Parameter names defined by this specification or [JWA][] for use with - /// JWS or if any other constraints on its use are violated. When used, this Header - /// Parameter MUST be integrity protected; therefore, it MUST occur only within the - /// JWS Protected Header. Use of this Header Parameter is OPTIONAL. This Header - /// Parameter MUST be understood and processed by implementations. - /// - /// [JWA]: https://datatracker.ietf.org/doc/html/rfc7518 + content_type: Option, + + #[doc = include_str!("../docs/jose/critical.md")] #[serde(rename = "crit", skip_serializing_if = "Option::is_none")] - pub critical: Option>, + critical: Option>, } /// The JOSE Header is a JSON object that represents the cryptographic operations @@ -543,7 +370,7 @@ pub struct Header { /// [JWA]: https://datatracker.ietf.org/doc/html/rfc7518 /// [JWS]: https://datatracker.ietf.org/doc/html/rfc7515 #[serde(flatten)] - pub registered: RegisteredHeader, + registered: RegisteredHeaderFields, /// The set of unregistered header parameters, which are custom provided /// by the type parameter H. @@ -551,40 +378,40 @@ pub struct Header { pub custom: H, } -impl Default for Header +impl Default for Header where H: Default, { fn default() -> Self { Self { - state: Unsigned::default(), + state: UnsignedHeader::default(), registered: Default::default(), custom: Default::default(), } } } -impl Header { +impl Header { /// Create a new JOSE header builder with the given custom header. pub fn new(custom: H) -> Self { Self { - state: Unsigned::default(), + state: UnsignedHeader::default(), registered: Default::default(), custom, } } /// Construct the JOSE header from the builder and signing key. - pub(crate) fn sign(self, key: &A::Key) -> Header> + pub(crate) fn sign(self, key: &A::Key) -> Header> where A: crate::algorithms::SigningAlgorithm, A::Key: Clone, { - let state = Signed { + let state = SignedHeader { algorithm: A::IDENTIFIER, - key: DerivedKey::derive(self.state.key, key), - thumbprint: DerivedKey::derive(self.state.thumbprint, key), - thumbprint_sha256: DerivedKey::derive(self.state.thumbprint_sha256, key), + key: DerivedKeyValue::derive(self.state.key, key), + thumbprint: DerivedKeyValue::derive(self.state.thumbprint, key), + thumbprint_sha256: DerivedKeyValue::derive(self.state.thumbprint_sha256, key), }; Header { @@ -610,7 +437,7 @@ impl Header { } } -impl Header> +impl Header> where Key: SerializeJWK, { @@ -622,8 +449,8 @@ where /// Render a signed JWK header into its rendered /// form, where the derived fields have been built /// as necessary. - pub fn render(self) -> Header { - let state = Rendered { + pub fn render(self) -> Header { + let state = RenderedHeader { algorithm: self.state.algorithm, key: self.state.key.build(), thumbprint: self.state.thumbprint.build(), @@ -638,13 +465,13 @@ where } } -impl Header { +impl Header { pub fn algorithm(&self) -> &AlgorithmIdentifier { &self.state.algorithm } #[allow(unused_variables)] - pub(crate) fn verify(self, key: &A::Key) -> Result>, A::Error> + pub(crate) fn verify(self, key: &A::Key) -> Result>, A::Error> where A: crate::algorithms::VerifyAlgorithm, { @@ -652,7 +479,7 @@ impl Header { } } #[cfg(feature = "fmt")] -impl Header +impl Header where H: Serialize, { @@ -683,7 +510,7 @@ where } #[cfg(feature = "fmt")] -impl fmt::JWTFormat for Header +impl fmt::JWTFormat for Header where H: Serialize, { @@ -695,13 +522,13 @@ where } #[cfg(feature = "fmt")] -impl Header> +impl Header> where H: Serialize, Key: SerializeJWK + Clone, { pub(crate) fn value(&self) -> serde_json::Value { - let value = self.state.parameters(); + let parameters = self.state.parameters(); let header = serde_json::to_value(&self.registered).unwrap(); let mut custom = serde_json::to_value(&self.custom).unwrap(); @@ -709,18 +536,28 @@ where let Value::Object(header) = header else { panic!("expected header") }; - map.extend(header); - let Value::Object(value) = value else { + + for (key, value) in header.into_iter() { + if map.insert(key.clone(), value.clone()).is_some() { + panic!("duplicate header key: {}", key); + } + } + + let Value::Object(parameters) = parameters else { panic!("expected algorithm header") }; - map.extend(value); + for (key, value) in parameters { + if map.insert(key.clone(), value.clone()).is_some() { + panic!("duplicate header key: {}", key); + } + } custom } } #[cfg(feature = "fmt")] -impl fmt::JWTFormat for Header> +impl fmt::JWTFormat for Header> where H: Serialize, Key: SerializeJWK + Clone, @@ -732,6 +569,241 @@ where } } +/// Access to the header fields of a JOSE header. +#[derive(Debug)] +pub struct HeaderAccess<'h, H, State> { + header: &'h Header, +} + +impl<'h, H, State> HeaderAccess<'h, H, State> { + pub(crate) fn new(header: &'h Header) -> Self { + Self { header } + } + + /// Custom header values. The type of this field is determined by the + /// type parameter H in the token, and can be used for any arbitrary + /// JSON values which should be included in the signature. + /// + /// Using a custom header value which conflicts with a registered header + /// value will result in an error when signing the token. + pub fn custom(&self) -> &H { + &self.header.custom + } + + #[doc = include_str!("../docs/jose/jwk_set_url.md")] + pub fn jwk_set_url(&self) -> Option<&Url> { + self.header.registered.jwk_set_url.as_ref() + } + + #[doc = include_str!("../docs/jose/type.md")] + pub fn r#type(&self) -> Option<&str> { + self.header.registered.r#type.as_deref() + } + + #[doc = include_str!("../docs/jose/key_id.md")] + pub fn key_id(&self) -> Option<&str> { + self.header.registered.key_id.as_deref() + } + + #[doc = include_str!("../docs/jose/certificate_url.md")] + pub fn certificate_url(&self) -> Option<&Url> { + self.header.registered.certificate_url.as_ref() + } + + #[doc = include_str!("../docs/jose/certificate_chain.md")] + pub fn certificate_chain(&self) -> Option<&[Certificate]> { + self.header.registered.certificate_chain.as_deref() + } + + #[doc = include_str!("../docs/jose/content_type.md")] + pub fn content_type(&self) -> Option<&str> { + self.header.registered.content_type.as_deref() + } + + #[doc = include_str!("../docs/jose/critical.md")] + pub fn critical(&self) -> Option<&[String]> { + self.header.registered.critical.as_deref() + } +} + +impl<'h, H> HeaderAccess<'h, H, UnsignedHeader> { + #[doc = include_str!("../docs/jose/json_web_key.md")] + pub fn key(&self) -> &KeyDerivation { + &self.header.state.key + } + + #[doc = include_str!("../docs/jose/thumbprint.md")] + pub fn thumbprint(&self) -> &KeyDerivation> { + &self.header.state.thumbprint + } + + #[doc = include_str!("../docs/jose/thumbprint_sha256.md")] + pub fn thumbprint_sha256(&self) -> &KeyDerivation> { + &self.header.state.thumbprint_sha256 + } +} + +impl<'h, H, K> HeaderAccess<'h, H, SignedHeader> +where + K: SerializeJWK + Clone, +{ + #[doc = include_str!("../docs/jose/algorithm.md")] + pub fn algorithm(&self) -> &AlgorithmIdentifier { + &self.header.state.algorithm + } + + #[doc = include_str!("../docs/jose/json_web_key.md")] + pub fn key(&self) -> Option { + self.header.state.key.value() + } + + #[doc = include_str!("../docs/jose/thumbprint.md")] + pub fn thumbprint(&self) -> Option> { + self.header.state.thumbprint.value() + } + + #[doc = include_str!("../docs/jose/thumbprint_sha256.md")] + pub fn thumbprint_sha256(&self) -> Option> { + self.header.state.thumbprint_sha256.value() + } +} + +impl<'h, H> HeaderAccess<'h, H, RenderedHeader> { + #[doc = include_str!("../docs/jose/algorithm.md")] + pub fn algorithm(&self) -> &AlgorithmIdentifier { + &self.header.state.algorithm + } + + #[doc = include_str!("../docs/jose/json_web_key.md")] + pub fn key(&self) -> Option<&JsonWebKey> { + self.header.state.key.as_ref() + } + + #[doc = include_str!("../docs/jose/thumbprint.md")] + pub fn thumbprint(&self) -> Option<&Thumbprint> { + self.header.state.thumbprint.as_ref() + } + + #[doc = include_str!("../docs/jose/thumbprint_sha256.md")] + pub fn thumbprint_sha256(&self) -> Option<&Thumbprint> { + self.header.state.thumbprint_sha256.as_ref() + } +} + +/// Mutable access to header fields of a JOSE header. +pub struct HeaderAccessMut<'h, H, State> { + header: &'h mut Header, +} + +impl<'h, H, State> HeaderAccessMut<'h, H, State> { + pub(crate) fn new(header: &'h mut Header) -> Self { + Self { header } + } + + pub fn custom(&mut self) -> &mut H { + &mut self.header.custom + } + + #[doc = include_str!("../docs/jose/jwk_set_url.md")] + pub fn jwk_set_url(&mut self) -> &mut Option { + &mut self.header.registered.jwk_set_url + } + + #[doc = include_str!("../docs/jose/type.md")] + pub fn r#type(&mut self) -> &mut Option { + &mut self.header.registered.r#type + } + + #[doc = include_str!("../docs/jose/key_id.md")] + pub fn key_id(&mut self) -> &mut Option { + &mut self.header.registered.key_id + } + + #[doc = include_str!("../docs/jose/certificate_url.md")] + pub fn certificate_url(&mut self) -> &mut Option { + &mut self.header.registered.certificate_url + } + + #[doc = include_str!("../docs/jose/certificate_chain.md")] + pub fn certificate_chain(&mut self) -> &mut Option> { + &mut self.header.registered.certificate_chain + } + + #[doc = include_str!("../docs/jose/content_type.md")] + pub fn content_type(&mut self) -> &mut Option { + &mut self.header.registered.content_type + } + + #[doc = include_str!("../docs/jose/critical.md")] + pub fn critical(&mut self) -> &mut Option> { + &mut self.header.registered.critical + } +} + +impl<'h, H> HeaderAccessMut<'h, H, UnsignedHeader> { + #[doc = include_str!("../docs/jose/json_web_key.md")] + pub fn key(&mut self) -> &mut KeyDerivation { + &mut self.header.state.key + } + + #[doc = include_str!("../docs/jose/thumbprint.md")] + pub fn thumbprint(&mut self) -> &mut KeyDerivation> { + &mut self.header.state.thumbprint + } + + #[doc = include_str!("../docs/jose/thumbprint_sha256.md")] + pub fn thumbprint_sha256(&mut self) -> &mut KeyDerivation> { + &mut self.header.state.thumbprint_sha256 + } +} + +impl<'h, H, K> HeaderAccessMut<'h, H, SignedHeader> +where + K: SerializeJWK + Clone, +{ + #[doc = include_str!("../docs/jose/algorithm.md")] + pub fn algorithm(&self) -> &AlgorithmIdentifier { + &self.header.state.algorithm + } + + #[doc = include_str!("../docs/jose/json_web_key.md")] + pub fn key(&self) -> Option { + self.header.state.key.value() + } + + #[doc = include_str!("../docs/jose/thumbprint.md")] + pub fn thumbprint(&self) -> Option> { + self.header.state.thumbprint.value() + } + + #[doc = include_str!("../docs/jose/thumbprint_sha256.md")] + pub fn thumbprint_sha256(&self) -> Option> { + self.header.state.thumbprint_sha256.value() + } +} + +impl<'h, H> HeaderAccessMut<'h, H, RenderedHeader> { + #[doc = include_str!("../docs/jose/algorithm.md")] + pub fn algorithm(&mut self) -> &mut AlgorithmIdentifier { + &mut self.header.state.algorithm + } + + #[doc = include_str!("../docs/jose/json_web_key.md")] + pub fn key(&mut self) -> &mut Option { + &mut self.header.state.key + } + + #[doc = include_str!("../docs/jose/thumbprint.md")] + pub fn thumbprint(&mut self) -> &mut Option> { + &mut self.header.state.thumbprint + } + + #[doc = include_str!("../docs/jose/thumbprint_sha256.md")] + pub fn thumbprint_sha256(&mut self) -> &mut Option> { + &mut self.header.state.thumbprint_sha256 + } +} + /// Errors returned when verifying a header. #[derive(Debug, thiserror::Error)] pub enum VerifyHeaderError {} diff --git a/src/lib.rs b/src/lib.rs index 4d5dfe2..12d0c82 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,6 @@ pub mod token; pub use claims::{Claims, RegisteredClaims}; #[cfg(feature = "fmt")] pub use fmt::JWTFormat; -pub use jose::{Header, RegisteredHeader}; +pub use jose::Header; pub use token::Token; pub use token::{Compact, Flat, FlatUnprotected}; diff --git a/src/token/mod.rs b/src/token/mod.rs index db1e441..dc74f59 100644 --- a/src/token/mod.rs +++ b/src/token/mod.rs @@ -20,6 +20,7 @@ use crate::fmt; use crate::{ algorithms::{AlgorithmIdentifier, SigningAlgorithm}, base64data::{Base64Data, Base64JSON}, + jose::{HeaderAccess, HeaderAccessMut}, Header, }; @@ -36,7 +37,7 @@ pub use self::state::{HasSignature, MaybeSigned, Signed, Unsigned, Unverified, V /// It is hard to express this empty type naturally in the Rust type system in a way that interacts /// well with [serde_json]. #[derive(Debug, Clone)] -pub enum Payload

{ +enum Payload

{ /// A payload which will be serialized as JSON and then base64url encoded. Json(Base64JSON

), @@ -171,7 +172,14 @@ pub struct Token, Fmt: TokenFormat = Compac } impl Token { - /// Token header values + /// Access to Token header values. + /// + /// All access is read-only, and the header cannot be modified here, + /// see [`Token::header_mut`] for mutable access. + /// + /// Header fields are accessed as methods on [`HeaderAccess`], and + /// their types will depend on the state of the token. Additionally, + /// the `alg` field will not be availalbe for unsigned token types. /// /// # Example: No custom headers, only registered headers. /// @@ -180,15 +188,15 @@ impl Token { /// /// let token = Token::compact((), ()); /// let header = token.header(); - /// assert_eq!(&header.registered.r#type, &None); + /// assert_eq!(&header.r#type(), &None); /// ``` - pub fn header(&self) -> &Header { - self.state.header() + pub fn header(&self) -> HeaderAccess<'_, State::Header, State::HeaderState> { + HeaderAccess::new(self.state.header()) } /// Mutable access to Token header values - pub fn header_mut(&mut self) -> &mut Header { - self.state.header_mut() + pub fn header_mut(&mut self) -> HeaderAccessMut { + HeaderAccessMut::new(self.state.header_mut()) } } @@ -208,6 +216,17 @@ where fmt, } } + + /// Create an empty token with a given header and format. + pub fn empty(header: H, fmt: Fmt) -> Self { + Token { + payload: Payload::Empty, + state: Unsigned { + header: Header::new(header), + }, + fmt, + } + } } impl Token, Compact> { @@ -399,7 +418,9 @@ where "signature": signature, }); - f.write_str(&token.to_string()) + let rendered = serde_json::to_string_pretty(&token).unwrap(); + + f.write_str(&rendered) } } @@ -421,7 +442,9 @@ where "signature": "", }); - f.write_str(&token.to_string()) + let rendered = serde_json::to_string_pretty(&token).unwrap(); + + f.write_str(&rendered) } } diff --git a/src/token/state.rs b/src/token/state.rs index 0810bee..9b1db01 100644 --- a/src/token/state.rs +++ b/src/token/state.rs @@ -55,11 +55,11 @@ pub trait HasSignature: MaybeSigned { /// input to the cryptographic signature. #[derive(Debug, Clone)] pub struct Unsigned { - pub(super) header: jose::Header, + pub(super) header: jose::Header, } impl MaybeSigned for Unsigned { - type HeaderState = jose::Unsigned; + type HeaderState = jose::UnsignedHeader; type Header = H; fn header(&self) -> &jose::Header { @@ -89,7 +89,7 @@ pub struct Signed where Alg: SigningAlgorithm, { - pub(super) header: jose::Header>, + pub(super) header: jose::Header>, pub(super) signature: Alg::Signature, } @@ -108,7 +108,7 @@ impl MaybeSigned for Signed where Alg: SigningAlgorithm, { - type HeaderState = jose::Signed; + type HeaderState = jose::SignedHeader; type Header = H; fn header(&self) -> &jose::Header { @@ -140,7 +140,7 @@ pub struct Verified where Alg: VerifyAlgorithm, { - pub(super) header: jose::Header>, + pub(super) header: jose::Header>, pub(super) signature: Alg::Signature, } @@ -148,7 +148,7 @@ impl MaybeSigned for Verified where Alg: VerifyAlgorithm, { - type HeaderState = jose::Signed; + type HeaderState = jose::SignedHeader; type Header = H; fn header_mut(&mut self) -> &mut jose::Header { @@ -189,12 +189,12 @@ where deserialize = "H: for<'deh> Deserialize<'deh>" ))] pub struct Unverified { - pub(super) header: jose::Header, + pub(super) header: jose::Header, pub(super) signature: Base64Data, } impl MaybeSigned for Unverified { - type HeaderState = jose::Rendered; + type HeaderState = jose::RenderedHeader; type Header = H; fn header_mut(&mut self) -> &mut jose::Header {