diff --git a/CHANGELOG.md b/CHANGELOG.md index 38f1eec2..abce1a01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ - Add conversions between `Algorithm` and `KeyAlgorithm` - JWKs can now be created from `DecodingKey`s, creation from `EncodingKey` and `DecodingKey` now supports Ed25519 as well -- BREAKING: `Algorithm` and `EllipticCurve` are now `non_exhaustive` +- JWKs with unknown key types are now deserializable +- BREAKING: `Algorithm`, `KeyAlgorithm`, `EllipticCurve` and `ThumbprintHash` are now `non_exhaustive` - BREAKING: `Jwk.thumbprint` now returns a `Result<_>` - BREAKING: `Header.extras` is now a struct that allows for deserialization to any `T` - BREAKING: Implicit features resulting from optional crates have been removed diff --git a/examples/custom_time.rs b/examples/custom_time.rs index 1ae8bacb..e1c7ae93 100644 --- a/examples/custom_time.rs +++ b/examples/custom_time.rs @@ -147,7 +147,7 @@ fn main() -> Result<(), Box> { &EncodingKey::from_secret(SECRET.as_ref()), )?; - println!("serialized token: {}", &token); + println!("serialized token: {}", token); let token_data = jsonwebtoken::decode::( &token, @@ -155,6 +155,6 @@ fn main() -> Result<(), Box> { &Validation::new(Algorithm::HS256), )?; - println!("token data:\n{:#?}", &token_data); + println!("token data:\n{:#?}", token_data); Ok(()) } diff --git a/src/decoding.rs b/src/decoding.rs index c0815a6d..154cf2b3 100644 --- a/src/decoding.rs +++ b/src/decoding.rs @@ -226,6 +226,7 @@ impl DecodingKey { kind: DecodingKeyKind::SecretOrDer(out), }) } + AlgorithmParameters::Other(_) => Err(ErrorKind::UnsupportedAlgorithm.into()), } } diff --git a/src/jwk.rs b/src/jwk.rs index 89b5fb03..c15d83a2 100644 --- a/src/jwk.rs +++ b/src/jwk.rs @@ -3,6 +3,7 @@ //! Most of the code in this file is taken from but //! tweaked to remove the private bits as it's not the goal for this crate currently. +use std::collections::BTreeMap; use std::{fmt, str::FromStr}; use serde::{Deserialize, Deserializer, Serialize, Serializer, de}; @@ -149,6 +150,7 @@ impl<'de> Deserialize<'de> for KeyOperations { /// The algorithms of the keys #[allow(non_camel_case_types, clippy::upper_case_acronyms)] #[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize)] +#[non_exhaustive] pub enum KeyAlgorithm { /// HMAC using SHA-256 HS256, @@ -439,20 +441,31 @@ pub struct OctetKeyPairParameters { pub x: String, } +/// Parameters for unknown keys +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Default, Hash)] +pub struct OtherKeyParameters { + #[serde(flatten)] + #[allow(missing_docs)] + pub fields: BTreeMap, +} + /// Algorithm specific parameters #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)] #[serde(untagged)] #[allow(missing_docs)] +#[non_exhaustive] pub enum AlgorithmParameters { EllipticCurve(EllipticCurveKeyParameters), RSA(RSAKeyParameters), OctetKey(OctetKeyParameters), OctetKeyPair(OctetKeyPairParameters), + Other(OtherKeyParameters), } /// The function to use to hash the intermediate thumbprint data. #[derive(Debug, Clone, Eq, PartialEq)] #[allow(missing_docs)] +#[non_exhaustive] pub enum ThumbprintHash { SHA256, SHA384, @@ -643,6 +656,7 @@ impl Jwk { ) } }, + AlgorithmParameters::Other(_) => return Err(ErrorKind::UnsupportedAlgorithm.into()), }; Ok(b64_encode((CryptoProvider::get_default().key_utils.compute_digest)( @@ -670,6 +684,8 @@ impl JwkSet { #[cfg(test)] mod tests { + use std::collections::BTreeMap; + use serde_json::json; use wasm_bindgen_test::wasm_bindgen_test; @@ -721,6 +737,42 @@ mod tests { assert_eq!(key_alg_result, KeyAlgorithm::UNKNOWN_ALGORITHM); } + #[test] + fn deserialize_unknown_kty() { + let parameters_json = json!({ + "kty": "AKP", + "foo": "bar", + "solution": 42 + }); + let parameters_result: AlgorithmParameters = + serde_json::from_value(parameters_json).expect("Could not deserialize json"); + match parameters_result { + AlgorithmParameters::Other(other_key_parameters) => { + let mut expected = BTreeMap::new(); + expected.insert("kty".to_owned(), serde_json::to_value("AKP").unwrap()); + expected.insert("foo".to_owned(), serde_json::to_value("bar").unwrap()); + expected.insert("solution".to_owned(), serde_json::to_value(42).unwrap()); + assert_eq!(other_key_parameters.fields, expected); + } + _ => { + panic!("Unexpected deserialization result"); + } + } + + // RFC 9964 Appendix A.1 JWK + let jwk: Jwk = serde_json::from_value(json!({ + "kid": "T4xl70S7MT6Zeq6r9V9fPJGVn76wfnXJ21-gyo0Gu6o", + "kty": "AKP", + "alg": "ML-DSA-44", + "pub": "...", + "priv": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + })) + .expect("Could not deserialize json"); + + assert!(!jwk.is_supported()); + assert!(matches!(jwk.algorithm, AlgorithmParameters::Other(_))); + } + #[test] #[wasm_bindgen_test] fn check_thumbprint() {