From d06336463a478243f546b4febc22b4bd6166d751 Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Mon, 4 Dec 2023 13:04:47 +0100 Subject: [PATCH 01/13] use fix salt size/don't return disclosure for decoys --- src/encoder.rs | 17 +++++++---------- src/hasher.rs | 12 ++++++++---- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/encoder.rs b/src/encoder.rs index 135bafc..19f02f2 100644 --- a/src/encoder.rs +++ b/src/encoder.rs @@ -1,8 +1,6 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use std::ops::Range; - use super::Disclosure; use super::Hasher; use super::Sha256Hasher; @@ -17,7 +15,7 @@ use serde_json::Value; pub(crate) const DIGESTS_KEY: &str = "_sd"; pub(crate) const ARRAY_DIGEST_KEY: &str = "..."; -pub(crate) const DEFAULT_SALT_RANGE: Range = 24..34; +pub(crate) const DEFAULT_SALT_SIZE: usize = 30; /// Transforms a JSON object into an SD-JWT object by substituting selected values /// with their corresponding disclosure digests. @@ -39,7 +37,7 @@ impl SdObjectEncoder { pub fn new(object: &str) -> Result> { Ok(SdObjectEncoder { object: serde_json::from_str(object).map_err(|e| Error::DeserializationError(e.to_string()))?, - salt_length: rand::thread_rng().gen_range(DEFAULT_SALT_RANGE), + salt_length: DEFAULT_SALT_SIZE, hasher: Sha256Hasher::new(), }) } @@ -52,7 +50,7 @@ impl TryFrom for SdObjectEncoder { match value { Value::Object(object) => Ok(SdObjectEncoder { object, - salt_length: rand::thread_rng().gen_range(DEFAULT_SALT_RANGE), + salt_length: DEFAULT_SALT_SIZE, hasher: Sha256Hasher::new(), }), _ => Err(Error::DataTypeMismatch("expected object".to_owned())), @@ -65,7 +63,7 @@ impl SdObjectEncoder { pub fn with_custom_hasher(object: &str, hasher: H) -> Result { Ok(Self { object: serde_json::from_str(object).map_err(|e| Error::DeserializationError(e.to_string()))?, - salt_length: rand::thread_rng().gen_range(DEFAULT_SALT_RANGE), + salt_length: DEFAULT_SALT_SIZE, hasher, }) } @@ -198,12 +196,11 @@ impl SdObjectEncoder { /// Adds a decoy digest to the specified path. /// If path is an empty slice, decoys will be added to the top level. - pub fn add_decoys(&mut self, path: &[&str], number_of_decoys: usize) -> Result> { - let mut disclosures = vec![]; + pub fn add_decoys(&mut self, path: &[&str], number_of_decoys: usize) -> Result<()> { for _ in 0..number_of_decoys { - disclosures.push(self.add_decoy(path)?); + self.add_decoy(path)?; } - Ok(disclosures) + Ok(()) } fn add_decoy(&mut self, path: &[&str]) -> Result { diff --git a/src/hasher.rs b/src/hasher.rs index a9073c6..cb99070 100644 --- a/src/hasher.rs +++ b/src/hasher.rs @@ -5,6 +5,12 @@ use crypto::hashes::sha::SHA256; use crypto::hashes::sha::SHA256_LEN; /// Used to implement hash functions to be used for encoding/decoding. +/// +/// ## Note +/// +/// Implementations of this trait are expected only for algorithms listed in +/// the IANA "Named Information Hash Algorithm" registry. +/// See [Hash Function Claim](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-06.html#name-hash-function-claim) pub trait Hasher: Sync + Send { /// Digests input to produce unique fixed-size hash value in bytes. fn digest(&self, input: &[u8]) -> Vec; @@ -13,10 +19,8 @@ pub trait Hasher: Sync + Send { /// /// ## Note /// - /// The hash algorithm identifier MUST be a hash algorithm value - /// from the "Hash Name String" column in the IANA "Named Information - /// Hash Algorithm" registry [IANA.Hash.Algorithms](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-06.html#IANA.Hash.Algorithms) - /// or a value defined in another specification and/or profile of this specification. + /// The hash algorithm identifier MUST be a hash algorithm value from the + /// "Hash Name String" column in the IANA "Named Information Hash Algorithm" fn alg_name(&self) -> &'static str; } From 93a6eb09095b4548bee61fea2ace1629d18019ca Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Mon, 4 Dec 2023 21:20:40 +0100 Subject: [PATCH 02/13] add constant for `_sd_alg` --- src/decoder.rs | 5 +++-- src/encoder.rs | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/decoder.rs b/src/decoder.rs index 2b11172..3ca9a2e 100644 --- a/src/decoder.rs +++ b/src/decoder.rs @@ -4,6 +4,7 @@ use crate::Utils; use crate::ARRAY_DIGEST_KEY; use crate::DIGESTS_KEY; +use crate::SD_ALG; use super::Disclosure; use super::Hasher; @@ -76,13 +77,13 @@ impl SdObjectDecoder { let mut decoded = self.decode_object(object, &disclosures_map, &mut processed_digests)?; // Remove `_sd_alg` in case it exists. - decoded.remove("_sd_alg"); + decoded.remove(SD_ALG); Ok(decoded) } pub fn determine_hasher(&self, object: &Map) -> Result<&dyn Hasher, Error> { //If the _sd_alg claim is not present at the top level, a default value of sha-256 MUST be used. - let alg: &str = if let Some(alg) = object.get("_sd_alg") { + let alg: &str = if let Some(alg) = object.get(SD_ALG) { alg.as_str().ok_or(Error::DataTypeMismatch( "the value of `_sd_alg` is not a string".to_string(), ))? diff --git a/src/encoder.rs b/src/encoder.rs index 19f02f2..51debcb 100644 --- a/src/encoder.rs +++ b/src/encoder.rs @@ -16,6 +16,7 @@ use serde_json::Value; pub(crate) const DIGESTS_KEY: &str = "_sd"; pub(crate) const ARRAY_DIGEST_KEY: &str = "..."; pub(crate) const DEFAULT_SALT_SIZE: usize = 30; +pub(crate) const SD_ALG: &str = "_sd_alg"; /// Transforms a JSON object into an SD-JWT object by substituting selected values /// with their corresponding disclosure digests. @@ -185,7 +186,7 @@ impl SdObjectEncoder { pub fn add_sd_alg_property(&mut self) -> Option { self .object - .insert("_sd_alg".to_string(), Value::String(self.hasher.alg_name().to_string())) + .insert(SD_ALG.to_string(), Value::String(self.hasher.alg_name().to_string())) } /// Returns the modified object as a string. From d5491ea76001dcb22386699ad56d911eb7326a2b Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Mon, 4 Dec 2023 21:54:19 +0100 Subject: [PATCH 03/13] add `try_from_serializable` to encoder --- src/encoder.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/encoder.rs b/src/encoder.rs index 51debcb..c43e870 100644 --- a/src/encoder.rs +++ b/src/encoder.rs @@ -42,6 +42,15 @@ impl SdObjectEncoder { hasher: Sha256Hasher::new(), }) } + + /// Creates a new [`SdObjectEncoder`] with `sha-256` hash function from a serializable object. + /// + /// ## Error + /// Returns [`Error::DeserializationError`] if `object` can not be serialized into a valid JSON object. + pub fn try_from_serializable(object: T) -> std::result::Result { + let value: String = serde_json::to_string(&object).map_err(|e| Error::DeserializationError(e.to_string()))?; + Self::new(&value) + } } impl TryFrom for SdObjectEncoder { @@ -299,11 +308,19 @@ impl SdObjectEncoder { #[cfg(test)] mod test { + use super::SdObjectEncoder; use crate::Error; + use serde::Serialize; use serde_json::json; use serde_json::Value; + #[derive(Serialize)] + struct TestStruct { + id: String, + claim2: Vec, + } + fn object() -> Value { json!({ "id": "did:value", @@ -348,4 +365,19 @@ mod test { Error::InvalidPath(_) )); } + + #[test] + fn test_from_serializable() { + let test_value = TestStruct { + id: "did:value".to_string(), + claim2: vec!["arr-value1".to_string(), "arr-vlaue2".to_string()], + }; + let mut encoder = SdObjectEncoder::try_from_serializable(test_value).unwrap(); + encoder.conceal(&["id"], None).unwrap(); + encoder.add_decoys(&[], 10).unwrap(); + encoder.add_decoys(&["claim2"], 10).unwrap(); + assert!(encoder.object().get("id").is_none()); + assert_eq!(encoder.object.get("_sd").unwrap().as_array().unwrap().len(), 11); + assert_eq!(encoder.object.get("claim2").unwrap().as_array().unwrap().len(), 12); + } } From c6d407764475474cd7afb0283285e34d1e127009 Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Mon, 4 Dec 2023 23:52:45 +0100 Subject: [PATCH 04/13] fix salt generation --- src/encoder.rs | 42 ++++++++++++++++++++++-------------------- src/error.rs | 3 +++ 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/encoder.rs b/src/encoder.rs index c43e870..a3dfdb8 100644 --- a/src/encoder.rs +++ b/src/encoder.rs @@ -7,7 +7,6 @@ use super::Sha256Hasher; use crate::Error; use crate::Result; use crate::Utils; -use rand::distributions::DistString; use rand::Rng; use serde_json::json; use serde_json::Map; @@ -23,9 +22,9 @@ pub(crate) const SD_ALG: &str = "_sd_alg"; pub struct SdObjectEncoder { /// The object in JSON format. object: Map, - /// Length of the salts that generated for disclosures. + /// Size of random data used to generate the salts for disclosures in bytes. /// Constant length for readability considerations. - salt_length: usize, + salt_size: usize, /// The hash function used to create digests. hasher: H, } @@ -38,7 +37,7 @@ impl SdObjectEncoder { pub fn new(object: &str) -> Result> { Ok(SdObjectEncoder { object: serde_json::from_str(object).map_err(|e| Error::DeserializationError(e.to_string()))?, - salt_length: DEFAULT_SALT_SIZE, + salt_size: DEFAULT_SALT_SIZE, hasher: Sha256Hasher::new(), }) } @@ -60,7 +59,7 @@ impl TryFrom for SdObjectEncoder { match value { Value::Object(object) => Ok(SdObjectEncoder { object, - salt_length: DEFAULT_SALT_SIZE, + salt_size: DEFAULT_SALT_SIZE, hasher: Sha256Hasher::new(), }), _ => Err(Error::DataTypeMismatch("expected object".to_owned())), @@ -73,7 +72,7 @@ impl SdObjectEncoder { pub fn with_custom_hasher(object: &str, hasher: H) -> Result { Ok(Self { object: serde_json::from_str(object).map_err(|e| Error::DeserializationError(e.to_string()))?, - salt_length: DEFAULT_SALT_SIZE, + salt_size: DEFAULT_SALT_SIZE, hasher, }) } @@ -97,7 +96,7 @@ impl SdObjectEncoder { } // Determine salt. - let salt = salt.unwrap_or(Self::gen_rand(self.salt_length)); + let salt = salt.unwrap_or(Self::gen_rand(self.salt_size)); // Obtain the parent of the property specified by the provided path. let (target_key, parent_value) = Self::get_target_property_and_its_parent(&mut self.object, path)?; @@ -141,7 +140,7 @@ impl SdObjectEncoder { } // Determine salt. - let salt = salt.unwrap_or(Self::gen_rand(self.salt_length)); + let salt = salt.unwrap_or(Self::gen_rand(self.salt_size)); // Obtain the parent of the property specified by the provided path. let (target_key, parent_value) = Self::get_target_property_and_its_parent(&mut self.object, path)?; @@ -215,7 +214,7 @@ impl SdObjectEncoder { fn add_decoy(&mut self, path: &[&str]) -> Result { if path.is_empty() { - let (disclosure, hash) = Self::random_digest(&self.hasher, self.salt_length, true); + let (disclosure, hash) = Self::random_digest(&self.hasher, self.salt_size, true); Self::add_digest_to_object(&mut self.object, hash)?; Ok(disclosure) } else { @@ -226,11 +225,11 @@ impl SdObjectEncoder { .ok_or(Error::InvalidPath(format!("{} does not exist", target_key)))?; if let Some(object) = value.as_object_mut() { - let (disclosure, hash) = Self::random_digest(&self.hasher, self.salt_length, true); + let (disclosure, hash) = Self::random_digest(&self.hasher, self.salt_size, true); Self::add_digest_to_object(object, hash)?; Ok(disclosure) } else if let Some(array) = value.as_array_mut() { - let (disclosure, hash) = Self::random_digest(&self.hasher, self.salt_length, true); + let (disclosure, hash) = Self::random_digest(&self.hasher, self.salt_size, true); let tripledot = json!({ARRAY_DIGEST_KEY: hash}); array.push(tripledot); Ok(disclosure) @@ -276,8 +275,9 @@ impl SdObjectEncoder { } fn gen_rand(len: usize) -> String { - // todo: check if random is cryptographically secure. - rand::distributions::Alphanumeric.sample_string(&mut rand::thread_rng(), len) + let mut rng = rand::thread_rng(); + let random_bytes: Vec = (0..len).map(|_| rng.gen()).collect(); + multibase::Base::Base64Url.encode(random_bytes) } /// Returns a reference to the internal object. @@ -291,17 +291,19 @@ impl SdObjectEncoder { } /// Returns the used salt length. - pub fn salt_length(&self) -> usize { - self.salt_length + pub fn salt_size(&self) -> usize { + self.salt_size } - /// Sets the used salt length. + /// Sets size of random data used to generate the salts for disclosures in bytes. /// /// ## Warning - /// If the new value is 0, it will not be set. - pub fn set_salt_length(&mut self, salt_length: usize) { - if salt_length > 0 { - self.salt_length = salt_length + /// Salt size must be >= 16. + pub fn set_salt_size(&mut self, salt_size: usize) -> Result<()> { + if salt_size < 16 { + Err(Error::InvalidSaltSize) + } else { + Ok(self.salt_size = salt_size) } } } diff --git a/src/error.rs b/src/error.rs index cdfa6eb..957418d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -36,4 +36,7 @@ pub enum Error { #[error("{0}")] Unspecified(String), + + #[error("salt size must be greater or equal 16")] + InvalidSaltSize, } From 1410c39935105c5f34438881df9de080d056d3a3 Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Tue, 5 Dec 2023 10:45:01 +0100 Subject: [PATCH 05/13] remove unnecessary ASCII filtering --- src/decoder.rs | 3 +-- src/encoder.rs | 7 +++-- src/hasher.rs | 37 ++++++++++++++++++++++++++ src/key_binding_jwt_claims.rs | 3 +-- src/lib.rs | 2 -- src/utils.rs | 49 ----------------------------------- 6 files changed, 42 insertions(+), 59 deletions(-) delete mode 100644 src/utils.rs diff --git a/src/decoder.rs b/src/decoder.rs index 3ca9a2e..8183114 100644 --- a/src/decoder.rs +++ b/src/decoder.rs @@ -1,7 +1,6 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use crate::Utils; use crate::ARRAY_DIGEST_KEY; use crate::DIGESTS_KEY; use crate::SD_ALG; @@ -65,7 +64,7 @@ impl SdObjectDecoder { let mut disclosures_map: BTreeMap = BTreeMap::new(); for disclosure in disclosures { let parsed_disclosure = Disclosure::parse(disclosure.to_string())?; - let digest = Utils::digest_b64_url_only_ascii(hasher, disclosure.as_str()); + let digest = hasher.encoded_digest(disclosure.as_str()); disclosures_map.insert(digest, parsed_disclosure); } diff --git a/src/encoder.rs b/src/encoder.rs index a3dfdb8..13a9b3e 100644 --- a/src/encoder.rs +++ b/src/encoder.rs @@ -6,7 +6,6 @@ use super::Hasher; use super::Sha256Hasher; use crate::Error; use crate::Result; -use crate::Utils; use rand::Rng; use serde_json::json; use serde_json::Map; @@ -111,7 +110,7 @@ impl SdObjectEncoder { ); // Hash the disclosure. - let hash = Utils::digest_b64_url_only_ascii(&self.hasher, disclosure.as_str()); + let hash = self.hasher.encoded_digest(disclosure.as_str()); // Add the hash to the "_sd" array if exists; otherwise, create the array and insert the hash. Self::add_digest_to_object(parent_value, hash)?; @@ -155,7 +154,7 @@ impl SdObjectEncoder { // of form "{"...": ""}". if let Some(element_value) = array.get_mut(element_index) { let disclosure = Disclosure::new(salt, None, element_value.clone()); - let hash = Utils::digest_b64_url_only_ascii(&self.hasher, disclosure.as_str()); + let hash = self.hasher.encoded_digest(disclosure.as_str()); let tripledot = json!({ARRAY_DIGEST_KEY: hash}); *element_value = tripledot; Ok(disclosure) @@ -270,7 +269,7 @@ impl SdObjectEncoder { }; let decoy_value = Self::gen_rand(decoy_value_length); let disclosure = Disclosure::new(salt, decoy_claim_name, Value::String(decoy_value)); - let hash = Utils::digest_b64_url_only_ascii(hasher, disclosure.as_str()); + let hash = hasher.encoded_digest(disclosure.as_str()); (disclosure, hash) } diff --git a/src/hasher.rs b/src/hasher.rs index cb99070..f9b588f 100644 --- a/src/hasher.rs +++ b/src/hasher.rs @@ -22,6 +22,12 @@ pub trait Hasher: Sync + Send { /// The hash algorithm identifier MUST be a hash algorithm value from the /// "Hash Name String" column in the IANA "Named Information Hash Algorithm" fn alg_name(&self) -> &'static str; + + /// Returns the base64url-encoded digest of a `disclosure`. + fn encoded_digest(&self, disclosure: &str) -> String { + let hash = self.digest(disclosure.as_bytes()); + multibase::Base::Base64Url.encode(hash) + } } /// An implementation of [`Hasher`] that uses the `sha-256` hash function. @@ -47,3 +53,34 @@ impl Hasher for Sha256Hasher { Sha256Hasher::ALG_NAME } } + +// Some test values taken from https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-05.html#name-hashing-disclosures +#[cfg(test)] +mod test { + use crate::{Hasher, Sha256Hasher}; + + #[test] + fn test1() { + let disclosure = "WyI2cU1RdlJMNWhhaiIsICJmYW1pbHlfbmFtZSIsICJNw7ZiaXVzIl0"; + let hasher = Sha256Hasher::new(); + let hash = hasher.encoded_digest(disclosure); + assert_eq!("uutlBuYeMDyjLLTpf6Jxi7yNkEF35jdyWMn9U7b_RYY", hash); + } + + #[test] + fn test2() { + let disclosure = + "WyJlSThaV205UW5LUHBOUGVOZW5IZGhRIiwgImVtYWlsIiwgIlwidW51c3VhbCBlbWFpbCBhZGRyZXNzXCJAZXhhbXBsZS5qcCJd"; + let hasher = Sha256Hasher::new(); + let hash = hasher.encoded_digest(disclosure); + assert_eq!("Kuet1yAa0HIQvYnOVd59hcViO9Ug6J2kSfqYRBeowvE", hash); + } + + #[test] + fn test3() { + let disclosure = "WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgIkZSIl0"; + let hasher = Sha256Hasher::new(); + let hash = hasher.encoded_digest(disclosure); + assert_eq!("w0I8EKcdCtUPkGCNUrfwVp2xEgNjtoIDlOxc9-PlOhs", hash); + } +} diff --git a/src/key_binding_jwt_claims.rs b/src/key_binding_jwt_claims.rs index 8379c66..e83acda 100644 --- a/src/key_binding_jwt_claims.rs +++ b/src/key_binding_jwt_claims.rs @@ -7,7 +7,6 @@ use std::time::SystemTime; use itertools::Itertools; use serde_json::Value; -use crate::utils::Utils; use crate::Hasher; use serde::Deserialize; use serde::Serialize; @@ -40,7 +39,7 @@ impl KeyBindingJwtClaims { ) -> Self { let disclosures = disclosures.iter().join("~"); let sd_jwt = format!("{}~{}~", jwt, disclosures); - let hash = Utils::digest_b64_url_only_ascii(hasher, &sd_jwt); + let hash = hasher.encoded_digest(&sd_jwt); let iat = issued_at.unwrap_or( SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) diff --git a/src/lib.rs b/src/lib.rs index 85a2345..4d0cb27 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,6 @@ mod error; mod hasher; mod key_binding_jwt_claims; mod sd_jwt; -mod utils; pub use decoder::*; pub use disclosure::*; @@ -22,4 +21,3 @@ pub use sd_jwt::*; pub use serde_json::json; pub use serde_json::Map; pub use serde_json::Value; -pub use utils::*; diff --git a/src/utils.rs b/src/utils.rs deleted file mode 100644 index ef5f38d..0000000 --- a/src/utils.rs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2020-2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use crate::Hasher; - -pub struct Utils {} - -impl Utils { - pub fn digest_b64_url_only_ascii(hasher: &dyn Hasher, input: &str) -> String { - // "The digest MUST be taken over the US-ASCII bytes of the base64url-encoded Disclosure". - let ascii_bytes: Vec = input.as_bytes().iter().cloned().filter(|&byte| byte <= 127).collect(); - let hash = hasher.digest(&ascii_bytes); - // "The bytes of the digest MUST then be base64url-encoded". - multibase::Base::Base64Url.encode(hash) - } -} - -// Some test values taken from https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-05.html#name-hashing-disclosures -#[cfg(test)] -mod test { - use crate::Sha256Hasher; - - use super::Utils; - - #[test] - fn test1() { - let disclosure = "WyI2cU1RdlJMNWhhaiIsICJmYW1pbHlfbmFtZSIsICJNw7ZiaXVzIl0"; - let hasher = Sha256Hasher::new(); - let hash = Utils::digest_b64_url_only_ascii(&hasher, disclosure); - assert_eq!("uutlBuYeMDyjLLTpf6Jxi7yNkEF35jdyWMn9U7b_RYY", hash); - } - - #[test] - fn test2() { - let disclosure = - "WyJlSThaV205UW5LUHBOUGVOZW5IZGhRIiwgImVtYWlsIiwgIlwidW51c3VhbCBlbWFpbCBhZGRyZXNzXCJAZXhhbXBsZS5qcCJd"; - let hasher = Sha256Hasher::new(); - let hash = Utils::digest_b64_url_only_ascii(&hasher, disclosure); - assert_eq!("Kuet1yAa0HIQvYnOVd59hcViO9Ug6J2kSfqYRBeowvE", hash); - } - - #[test] - fn test3() { - let disclosure = "WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgIkZSIl0"; - let hasher = Sha256Hasher::new(); - let hash = Utils::digest_b64_url_only_ascii(&hasher, disclosure); - assert_eq!("w0I8EKcdCtUPkGCNUrfwVp2xEgNjtoIDlOxc9-PlOhs", hash); - } -} From 92f966fdbca85b8fe25cd150e8b78d479935e883 Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Tue, 5 Dec 2023 11:30:33 +0100 Subject: [PATCH 06/13] feature gate sha --- Cargo.toml | 5 ++++- examples/sd_jwt.rs | 2 +- src/api_test.rs | 4 ++-- src/decoder.rs | 20 +++++++++++++++----- src/encoder.rs | 17 +++++++++++++++++ src/hasher.rs | 12 +++++++++--- 6 files changed, 48 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0758c6a..6588f4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ rand = { version = "0.8.5", default-features = false, features = ["std", "std_rn thiserror = { version = "1.0", default-features = false } strum = { version = "0.25", default-features = false, features = ["std", "derive"] } itertools = { version = "0.12", default-features = false, features = ["use_std"] } -iota-crypto = { version = "0.23", default-features = false, features = ["std", "sha"] } +iota-crypto = { version = "0.23", default-features = false, features = ["sha"], optional = true } serde = { version = "1.0", default-features = false, features = ["derive"] } [dev-dependencies] @@ -27,3 +27,6 @@ josekit = "0.8.4" [[example]] name = "sd_jwt" +[features] +default = ["sha"] +sha = ["iota-crypto"] diff --git a/examples/sd_jwt.rs b/examples/sd_jwt.rs index 70ff61c..be0017d 100644 --- a/examples/sd_jwt.rs +++ b/examples/sd_jwt.rs @@ -78,7 +78,7 @@ fn main() { let (payload, _header) = jwt::decode_with_verifier(&sd_jwt.jwt, &verifier).unwrap(); // Decode the payload by providing the disclosures that were parsed from the SD-JWT. - let decoder = SdObjectDecoder::new(); + let decoder = SdObjectDecoder::new_with_sha256(); let decoded = decoder.decode(payload.claims_set(), &sd_jwt.disclosures).unwrap(); println!("decoded object: {}", serde_json::to_string_pretty(&decoded).unwrap()); } diff --git a/src/api_test.rs b/src/api_test.rs index 69e8e8d..908cd67 100644 --- a/src/api_test.rs +++ b/src/api_test.rs @@ -115,7 +115,7 @@ fn test_complex_structure() { let (payload, _header) = jwt::decode_with_verifier(&sd_jwt.jwt, &verifier).unwrap(); // Decode the payload by providing the disclosures that were parsed from the SD-JWT. - let decoder = SdObjectDecoder::new(); + let decoder = SdObjectDecoder::new_with_sha256(); let decoded = decoder.decode(payload.claims_set(), &sd_jwt.disclosures).unwrap(); println!("decoded object: {}", serde_json::to_string_pretty(&decoded).unwrap()); assert_eq!(Value::Object(decoded), object); @@ -156,7 +156,7 @@ fn concealed_object_in_array() { .into_iter() .map(|disclosure| disclosure.to_string()) .collect(); - let decoder = SdObjectDecoder::new(); + let decoder = SdObjectDecoder::new_with_sha256(); let decoded = decoder.decode(encoder.object(), &disclosures).unwrap(); assert_eq!(Value::Object(decoded), expected); } diff --git a/src/decoder.rs b/src/decoder.rs index 8183114..e535125 100644 --- a/src/decoder.rs +++ b/src/decoder.rs @@ -4,9 +4,11 @@ use crate::ARRAY_DIGEST_KEY; use crate::DIGESTS_KEY; use crate::SD_ALG; +use crate::SHA_ALG_NAME; use super::Disclosure; use super::Hasher; +#[cfg(feature = "sha")] use super::Sha256Hasher; use crate::Error; use serde_json::Map; @@ -20,13 +22,20 @@ pub struct SdObjectDecoder { impl SdObjectDecoder { /// Creates a new [`SdObjectDecoder`] with `sha-256` hasher. - pub fn new() -> Self { + #[cfg(feature = "sha")] + pub fn new_with_sha256() -> Self { let hashers: BTreeMap> = BTreeMap::new(); let mut hasher = Self { hashers }; hasher.add_hasher(Box::new(Sha256Hasher::new())); hasher } + /// Creates a new [`SdObjectDecoder`] without any hashers. + pub fn new() -> Self { + let hashers: BTreeMap> = BTreeMap::new(); + Self { hashers } + } + /// Adds a hasher. /// /// If a hasher for the same algorithm [`Hasher::alg_name`] already exists, it will be replaced and @@ -87,7 +96,7 @@ impl SdObjectDecoder { "the value of `_sd_alg` is not a string".to_string(), ))? } else { - Sha256Hasher::ALG_NAME + SHA_ALG_NAME }; self .hashers @@ -227,9 +236,10 @@ impl SdObjectDecoder { } } +#[cfg(feature = "sha")] impl Default for SdObjectDecoder { fn default() -> Self { - Self::new() + Self::new_with_sha256() } } @@ -251,7 +261,7 @@ mod test { encoder .object_mut() .insert("id".to_string(), Value::String("id-value".to_string())); - let decoder = SdObjectDecoder::new(); + let decoder = SdObjectDecoder::new_with_sha256(); let decoded = decoder.decode(encoder.object(), &vec![dis.to_string()]).unwrap_err(); assert!(matches!(decoded, Error::ClaimCollisionError(_))); } @@ -267,7 +277,7 @@ mod test { let mut encoder = SdObjectEncoder::try_from(object).unwrap(); encoder.add_sd_alg_property(); assert_eq!(encoder.object().get("_sd_alg").unwrap(), "sha-256"); - let decoder = SdObjectDecoder::new(); + let decoder = SdObjectDecoder::new_with_sha256(); let decoded = decoder.decode(encoder.object(), &vec![]).unwrap(); assert!(decoded.get("_sd_alg").is_none()); } diff --git a/src/encoder.rs b/src/encoder.rs index 13a9b3e..ff95fae 100644 --- a/src/encoder.rs +++ b/src/encoder.rs @@ -3,6 +3,7 @@ use super::Disclosure; use super::Hasher; +#[cfg(feature = "sha")] use super::Sha256Hasher; use crate::Error; use crate::Result; @@ -18,6 +19,20 @@ pub(crate) const SD_ALG: &str = "_sd_alg"; /// Transforms a JSON object into an SD-JWT object by substituting selected values /// with their corresponding disclosure digests. +#[cfg(not(feature = "sha"))] +pub struct SdObjectEncoder { + /// The object in JSON format. + object: Map, + /// Size of random data used to generate the salts for disclosures in bytes. + /// Constant length for readability considerations. + salt_size: usize, + /// The hash function used to create digests. + hasher: H, +} + +/// Transforms a JSON object into an SD-JWT object by substituting selected values +/// with their corresponding disclosure digests. +#[cfg(feature = "sha")] pub struct SdObjectEncoder { /// The object in JSON format. object: Map, @@ -28,6 +43,7 @@ pub struct SdObjectEncoder { hasher: H, } +#[cfg(feature = "sha")] impl SdObjectEncoder { /// Creates a new [`SdObjectEncoder`] with `sha-256` hash function. /// @@ -51,6 +67,7 @@ impl SdObjectEncoder { } } +#[cfg(feature = "sha")] impl TryFrom for SdObjectEncoder { type Error = crate::Error; diff --git a/src/hasher.rs b/src/hasher.rs index f9b588f..2e8ace4 100644 --- a/src/hasher.rs +++ b/src/hasher.rs @@ -1,9 +1,14 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +#[cfg(feature = "sha")] use crypto::hashes::sha::SHA256; + +#[cfg(feature = "sha")] use crypto::hashes::sha::SHA256_LEN; +pub const SHA_ALG_NAME: &'static str = "sha-256"; + /// Used to implement hash functions to be used for encoding/decoding. /// /// ## Note @@ -32,16 +37,17 @@ pub trait Hasher: Sync + Send { /// An implementation of [`Hasher`] that uses the `sha-256` hash function. #[derive(Default)] +#[cfg(feature = "sha")] pub struct Sha256Hasher; +#[cfg(feature = "sha")] impl Sha256Hasher { - pub const ALG_NAME: &'static str = "sha-256"; /// Creates a new [`ShaHasher`] pub fn new() -> Self { Sha256Hasher {} } } - +#[cfg(feature = "sha")] impl Hasher for Sha256Hasher { fn digest(&self, input: &[u8]) -> Vec { let mut digest: [u8; SHA256_LEN] = Default::default(); @@ -50,7 +56,7 @@ impl Hasher for Sha256Hasher { } fn alg_name(&self) -> &'static str { - Sha256Hasher::ALG_NAME + SHA_ALG_NAME } } From 97bb8d218c8611df612a5c20e4dfe8790d001349 Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Tue, 5 Dec 2023 12:31:24 +0100 Subject: [PATCH 07/13] remove `object_mut` from API --- src/encoder.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/encoder.rs b/src/encoder.rs index ff95fae..b54207a 100644 --- a/src/encoder.rs +++ b/src/encoder.rs @@ -302,7 +302,8 @@ impl SdObjectEncoder { } /// Returns a mutable reference to the internal object. - pub fn object_mut(&mut self) -> &mut Map { + #[cfg(test)] + pub(crate) fn object_mut(&mut self) -> &mut Map { &mut self.object } From 8114adbf93a116b507f48add2f7788c32ed4bf89 Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Tue, 5 Dec 2023 13:00:12 +0100 Subject: [PATCH 08/13] clippy --- src/encoder.rs | 3 ++- src/hasher.rs | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/encoder.rs b/src/encoder.rs index b54207a..ca9270c 100644 --- a/src/encoder.rs +++ b/src/encoder.rs @@ -320,7 +320,8 @@ impl SdObjectEncoder { if salt_size < 16 { Err(Error::InvalidSaltSize) } else { - Ok(self.salt_size = salt_size) + self.salt_size = salt_size; + Ok(()) } } } diff --git a/src/hasher.rs b/src/hasher.rs index 2e8ace4..07b62cf 100644 --- a/src/hasher.rs +++ b/src/hasher.rs @@ -7,7 +7,7 @@ use crypto::hashes::sha::SHA256; #[cfg(feature = "sha")] use crypto::hashes::sha::SHA256_LEN; -pub const SHA_ALG_NAME: &'static str = "sha-256"; +pub const SHA_ALG_NAME: &str = "sha-256"; /// Used to implement hash functions to be used for encoding/decoding. /// @@ -63,7 +63,8 @@ impl Hasher for Sha256Hasher { // Some test values taken from https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-05.html#name-hashing-disclosures #[cfg(test)] mod test { - use crate::{Hasher, Sha256Hasher}; + use crate::Hasher; + use crate::Sha256Hasher; #[test] fn test1() { From db5f45bf9de648e892ae8f5157db61fa1df9e1f1 Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Tue, 5 Dec 2023 22:13:25 +0100 Subject: [PATCH 09/13] use ? instead of unwrap in example --- examples/sd_jwt.rs | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/examples/sd_jwt.rs b/examples/sd_jwt.rs index be0017d..eecd1d6 100644 --- a/examples/sd_jwt.rs +++ b/examples/sd_jwt.rs @@ -1,6 +1,8 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use std::error::Error; + use josekit::jws::JwsHeader; use josekit::jws::HS256; use josekit::jwt::JwtPayload; @@ -11,7 +13,7 @@ use sd_jwt::SdObjectDecoder; use sd_jwt::SdObjectEncoder; use serde_json::json; -fn main() { +fn main() -> Result<(), Box> { let object = json!({ "sub": "user_42", "given_name": "John", @@ -34,23 +36,20 @@ fn main() { }); let mut disclosures: Vec = vec![]; - let mut encoder: SdObjectEncoder = object.try_into().unwrap(); - let disclosure = encoder.conceal(&["email"], None).unwrap(); + let mut encoder: SdObjectEncoder = object.try_into()?; + let disclosure = encoder.conceal(&["email"], None)?; disclosures.push(disclosure); let disclosure = encoder.conceal(&["phone_number"], None); - disclosures.push(disclosure.unwrap()); + disclosures.push(disclosure?); let disclosure = encoder.conceal(&["address", "street_address"], None); - disclosures.push(disclosure.unwrap()); + disclosures.push(disclosure?); let disclosure = encoder.conceal(&["address"], None); - disclosures.push(disclosure.unwrap()); + disclosures.push(disclosure?); let disclosure = encoder.conceal_array_entry(&["nationalities"], 0, None); - disclosures.push(disclosure.unwrap()); + disclosures.push(disclosure?); encoder.add_sd_alg_property(); - println!( - "encoded object: {}", - serde_json::to_string_pretty(encoder.object()).unwrap() - ); + println!("encoded object: {}", serde_json::to_string_pretty(encoder.object())?); // Create the JWT. // Creating JWTs is out of the scope of this library, josekit is used here as an example @@ -58,10 +57,10 @@ fn main() { header.set_token_type("sd-jwt"); // Use the encoded object as a payload for the JWT. - let payload = JwtPayload::from_map(encoder.object().clone()).unwrap(); + let payload = JwtPayload::from_map(encoder.object().clone())?; let key = b"0123456789ABCDEF0123456789ABCDEF"; - let signer = HS256.signer_from_bytes(key).unwrap(); - let jwt = jwt::encode_with_signer(&payload, &header, &signer).unwrap(); + let signer = HS256.signer_from_bytes(key)?; + let jwt = jwt::encode_with_signer(&payload, &header, &signer)?; // Create an SD_JWT by collecting the disclosures and creating an `SdJwt` instance. let disclosures: Vec = disclosures @@ -73,12 +72,13 @@ fn main() { // Decoding the SD-JWT // Extract the payload from the JWT of the SD-JWT after verifying the signature. - let sd_jwt: SdJwt = SdJwt::parse(&sd_jwt).unwrap(); - let verifier = HS256.verifier_from_bytes(key).unwrap(); - let (payload, _header) = jwt::decode_with_verifier(&sd_jwt.jwt, &verifier).unwrap(); + let sd_jwt: SdJwt = SdJwt::parse(&sd_jwt)?; + let verifier = HS256.verifier_from_bytes(key)?; + let (payload, _header) = jwt::decode_with_verifier(&sd_jwt.jwt, &verifier)?; // Decode the payload by providing the disclosures that were parsed from the SD-JWT. let decoder = SdObjectDecoder::new_with_sha256(); - let decoded = decoder.decode(payload.claims_set(), &sd_jwt.disclosures).unwrap(); - println!("decoded object: {}", serde_json::to_string_pretty(&decoded).unwrap()); + let decoded = decoder.decode(payload.claims_set(), &sd_jwt.disclosures)?; + println!("decoded object: {}", serde_json::to_string_pretty(&decoded)?); + Ok(()) } From 0215fc54cf286fa50bdd409182eb49d1219a1d7d Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Tue, 5 Dec 2023 22:13:44 +0100 Subject: [PATCH 10/13] derive clone for hasher --- src/encoder.rs | 1 + src/hasher.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/encoder.rs b/src/encoder.rs index ca9270c..97abb44 100644 --- a/src/encoder.rs +++ b/src/encoder.rs @@ -33,6 +33,7 @@ pub struct SdObjectEncoder { /// Transforms a JSON object into an SD-JWT object by substituting selected values /// with their corresponding disclosure digests. #[cfg(feature = "sha")] +#[derive(Debug, Clone)] pub struct SdObjectEncoder { /// The object in JSON format. object: Map, diff --git a/src/hasher.rs b/src/hasher.rs index 07b62cf..6c1e6b9 100644 --- a/src/hasher.rs +++ b/src/hasher.rs @@ -36,7 +36,7 @@ pub trait Hasher: Sync + Send { } /// An implementation of [`Hasher`] that uses the `sha-256` hash function. -#[derive(Default)] +#[derive(Default, Clone, Copy)] #[cfg(feature = "sha")] pub struct Sha256Hasher; From e03caa8a858e5f53e73535daa322362d9275b57b Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Tue, 5 Dec 2023 23:28:51 +0100 Subject: [PATCH 11/13] add test for decoy/decode --- src/api_test.rs | 60 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/api_test.rs b/src/api_test.rs index 908cd67..8539d10 100644 --- a/src/api_test.rs +++ b/src/api_test.rs @@ -1,11 +1,14 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use josekit::jws::JwsAlgorithm; use josekit::jws::JwsHeader; +use josekit::jws::JwsVerifier; use josekit::jws::HS256; use josekit::jwt::JwtPayload; use josekit::jwt::{self}; use serde_json::json; +use serde_json::Map; use serde_json::Value; use crate::Disclosure; @@ -160,3 +163,60 @@ fn concealed_object_in_array() { let decoded = decoder.decode(encoder.object(), &disclosures).unwrap(); assert_eq!(Value::Object(decoded), expected); } + +#[test] +fn decode() { + // Values taken from https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-06.html#name-example-2-handling-structur + let sd_jwt = "eyJhbGciOiAiRVMyNTYifQ.eyJfc2QiOiBbIkM5aW5wNllvUmFFWFI0Mjd6WUpQN1FyazFXSF84YmR3T0FfWVVyVW5HUVUiLCAiS3VldDF5QWEwSElRdlluT1ZkNTloY1ZpTzlVZzZKMmtTZnFZUkJlb3d2RSIsICJNTWxkT0ZGekIyZDB1bWxtcFRJYUdlcmhXZFVfUHBZZkx2S2hoX2ZfOWFZIiwgIlg2WkFZT0lJMnZQTjQwVjd4RXhad1Z3ejd5Um1MTmNWd3Q1REw4Ukx2NGciLCAiWTM0em1JbzBRTExPdGRNcFhHd2pCZ0x2cjE3eUVoaFlUMEZHb2ZSLWFJRSIsICJmeUdwMFdUd3dQdjJKRFFsbjFsU2lhZW9iWnNNV0ExMGJRNTk4OS05RFRzIiwgIm9tbUZBaWNWVDhMR0hDQjB1eXd4N2ZZdW8zTUhZS08xNWN6LVJaRVlNNVEiLCAiczBCS1lzTFd4UVFlVTh0VmxsdE03TUtzSVJUckVJYTFQa0ptcXhCQmY1VSJdLCAiaXNzIjogImh0dHBzOi8vaXNzdWVyLmV4YW1wbGUuY29tIiwgImlhdCI6IDE2ODMwMDAwMDAsICJleHAiOiAxODgzMDAwMDAwLCAiYWRkcmVzcyI6IHsiX3NkIjogWyI2YVVoelloWjdTSjFrVm1hZ1FBTzN1MkVUTjJDQzFhSGhlWnBLbmFGMF9FIiwgIkF6TGxGb2JrSjJ4aWF1cFJFUHlvSnotOS1OU2xkQjZDZ2pyN2ZVeW9IemciLCAiUHp6Y1Z1MHFiTXVCR1NqdWxmZXd6a2VzRDl6dXRPRXhuNUVXTndrclEtayIsICJiMkRrdzBqY0lGOXJHZzhfUEY4WmN2bmNXN3p3Wmo1cnlCV3ZYZnJwemVrIiwgImNQWUpISVo4VnUtZjlDQ3lWdWIyVWZnRWs4anZ2WGV6d0sxcF9KbmVlWFEiLCAiZ2xUM2hyU1U3ZlNXZ3dGNVVEWm1Xd0JUdzMyZ25VbGRJaGk4aEdWQ2FWNCIsICJydkpkNmlxNlQ1ZWptc0JNb0d3dU5YaDlxQUFGQVRBY2k0MG9pZEVlVnNBIiwgInVOSG9XWWhYc1poVkpDTkUyRHF5LXpxdDd0NjlnSkt5NVFhRnY3R3JNWDQiXX0sICJfc2RfYWxnIjogInNoYS0yNTYifQ.IjE4EfnYu1RZ1uz6yqtFh5Lppq36VC4VeSr-hLDFpZ9zqBNmMrT5JHLLXTuMJqKQp3NIzDsLaft4GK5bYyfqhg~WyJHMDJOU3JRZmpGWFE3SW8wOXN5YWpBIiwgInJlZ2lvbiIsICJcdTZlMmZcdTUzM2EiXQ~WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgImNvdW50cnkiLCAiSlAiXQ~"; + let sd_jwt: SdJwt = SdJwt::parse(&sd_jwt).unwrap(); + let (payload, _header) = jwt::decode_with_verifier(&sd_jwt.jwt, &DeocyJwsVerifier {}).unwrap(); + let decoder = SdObjectDecoder::new_with_sha256(); + let decoded: Map = decoder.decode(payload.claims_set(), &sd_jwt.disclosures).unwrap(); + let expected_object = json!({ + "address": { + "country": "JP", + "region": "港区" + }, + "iss": "https://issuer.example.com", + "iat": 1683000000, + "exp": 1883000000 + } + ) + .as_object() + .unwrap() + .clone(); + assert_eq!(expected_object, decoded); +} + +// Boilerplate to allow extracting JWS payload without verifying the signature. +#[derive(Debug, Clone)] +struct DecoyJwsAlgorithm; +impl JwsAlgorithm for DecoyJwsAlgorithm { + fn name(&self) -> &str { + "ES256" + } + + fn box_clone(&self) -> Box { + Box::new(self.clone()) + } +} + +#[derive(Debug, Clone)] +struct DeocyJwsVerifier; +impl JwsVerifier for DeocyJwsVerifier { + fn algorithm(&self) -> &dyn josekit::jws::JwsAlgorithm { + &DecoyJwsAlgorithm {} + } + + fn key_id(&self) -> Option<&str> { + None + } + + fn verify(&self, _message: &[u8], _signature: &[u8]) -> Result<(), josekit::JoseError> { + Ok(()) + } + + fn box_clone(&self) -> Box { + Box::new(self.clone()) + } +} From 4b707c26c5a1d0c24ed1a5f3e7053c0ba3b565ef Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Tue, 5 Dec 2023 23:33:41 +0100 Subject: [PATCH 12/13] clippy --- src/api_test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api_test.rs b/src/api_test.rs index 8539d10..fb89046 100644 --- a/src/api_test.rs +++ b/src/api_test.rs @@ -168,7 +168,7 @@ fn concealed_object_in_array() { fn decode() { // Values taken from https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-06.html#name-example-2-handling-structur let sd_jwt = "eyJhbGciOiAiRVMyNTYifQ.eyJfc2QiOiBbIkM5aW5wNllvUmFFWFI0Mjd6WUpQN1FyazFXSF84YmR3T0FfWVVyVW5HUVUiLCAiS3VldDF5QWEwSElRdlluT1ZkNTloY1ZpTzlVZzZKMmtTZnFZUkJlb3d2RSIsICJNTWxkT0ZGekIyZDB1bWxtcFRJYUdlcmhXZFVfUHBZZkx2S2hoX2ZfOWFZIiwgIlg2WkFZT0lJMnZQTjQwVjd4RXhad1Z3ejd5Um1MTmNWd3Q1REw4Ukx2NGciLCAiWTM0em1JbzBRTExPdGRNcFhHd2pCZ0x2cjE3eUVoaFlUMEZHb2ZSLWFJRSIsICJmeUdwMFdUd3dQdjJKRFFsbjFsU2lhZW9iWnNNV0ExMGJRNTk4OS05RFRzIiwgIm9tbUZBaWNWVDhMR0hDQjB1eXd4N2ZZdW8zTUhZS08xNWN6LVJaRVlNNVEiLCAiczBCS1lzTFd4UVFlVTh0VmxsdE03TUtzSVJUckVJYTFQa0ptcXhCQmY1VSJdLCAiaXNzIjogImh0dHBzOi8vaXNzdWVyLmV4YW1wbGUuY29tIiwgImlhdCI6IDE2ODMwMDAwMDAsICJleHAiOiAxODgzMDAwMDAwLCAiYWRkcmVzcyI6IHsiX3NkIjogWyI2YVVoelloWjdTSjFrVm1hZ1FBTzN1MkVUTjJDQzFhSGhlWnBLbmFGMF9FIiwgIkF6TGxGb2JrSjJ4aWF1cFJFUHlvSnotOS1OU2xkQjZDZ2pyN2ZVeW9IemciLCAiUHp6Y1Z1MHFiTXVCR1NqdWxmZXd6a2VzRDl6dXRPRXhuNUVXTndrclEtayIsICJiMkRrdzBqY0lGOXJHZzhfUEY4WmN2bmNXN3p3Wmo1cnlCV3ZYZnJwemVrIiwgImNQWUpISVo4VnUtZjlDQ3lWdWIyVWZnRWs4anZ2WGV6d0sxcF9KbmVlWFEiLCAiZ2xUM2hyU1U3ZlNXZ3dGNVVEWm1Xd0JUdzMyZ25VbGRJaGk4aEdWQ2FWNCIsICJydkpkNmlxNlQ1ZWptc0JNb0d3dU5YaDlxQUFGQVRBY2k0MG9pZEVlVnNBIiwgInVOSG9XWWhYc1poVkpDTkUyRHF5LXpxdDd0NjlnSkt5NVFhRnY3R3JNWDQiXX0sICJfc2RfYWxnIjogInNoYS0yNTYifQ.IjE4EfnYu1RZ1uz6yqtFh5Lppq36VC4VeSr-hLDFpZ9zqBNmMrT5JHLLXTuMJqKQp3NIzDsLaft4GK5bYyfqhg~WyJHMDJOU3JRZmpGWFE3SW8wOXN5YWpBIiwgInJlZ2lvbiIsICJcdTZlMmZcdTUzM2EiXQ~WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgImNvdW50cnkiLCAiSlAiXQ~"; - let sd_jwt: SdJwt = SdJwt::parse(&sd_jwt).unwrap(); + let sd_jwt: SdJwt = SdJwt::parse(sd_jwt).unwrap(); let (payload, _header) = jwt::decode_with_verifier(&sd_jwt.jwt, &DeocyJwsVerifier {}).unwrap(); let decoder = SdObjectDecoder::new_with_sha256(); let decoded: Map = decoder.decode(payload.claims_set(), &sd_jwt.disclosures).unwrap(); From d8db8d424e1e7eb59fb54eecd875ad4f3495cc17 Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Wed, 6 Dec 2023 11:56:55 +0100 Subject: [PATCH 13/13] typos + move `api_test.rs` --- src/encoder.rs | 6 ++++-- src/error.rs | 2 +- src/lib.rs | 2 -- {src => tests}/api_test.rs | 14 +++++++------- 4 files changed, 12 insertions(+), 12 deletions(-) rename {src => tests}/api_test.rs (97%) diff --git a/src/encoder.rs b/src/encoder.rs index 97abb44..ad391d5 100644 --- a/src/encoder.rs +++ b/src/encoder.rs @@ -292,9 +292,11 @@ impl SdObjectEncoder { } fn gen_rand(len: usize) -> String { + let mut bytes = vec![0; len]; let mut rng = rand::thread_rng(); - let random_bytes: Vec = (0..len).map(|_| rng.gen()).collect(); - multibase::Base::Base64Url.encode(random_bytes) + rng.fill(&mut bytes[..]); + + multibase::Base::Base64Url.encode(bytes) } /// Returns a reference to the internal object. diff --git a/src/error.rs b/src/error.rs index 957418d..f67f942 100644 --- a/src/error.rs +++ b/src/error.rs @@ -37,6 +37,6 @@ pub enum Error { #[error("{0}")] Unspecified(String), - #[error("salt size must be greater or equal 16")] + #[error("salt size must be greater than or equal to 16")] InvalidSaltSize, } diff --git a/src/lib.rs b/src/lib.rs index 4d0cb27..f7686ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,6 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -#[cfg(test)] -mod api_test; mod decoder; mod disclosure; mod encoder; diff --git a/src/api_test.rs b/tests/api_test.rs similarity index 97% rename from src/api_test.rs rename to tests/api_test.rs index fb89046..9f02987 100644 --- a/src/api_test.rs +++ b/tests/api_test.rs @@ -11,10 +11,10 @@ use serde_json::json; use serde_json::Map; use serde_json::Value; -use crate::Disclosure; -use crate::SdJwt; -use crate::SdObjectDecoder; -use crate::SdObjectEncoder; +use sd_jwt::Disclosure; +use sd_jwt::SdJwt; +use sd_jwt::SdObjectDecoder; +use sd_jwt::SdObjectEncoder; #[test] fn test_complex_structure() { @@ -169,7 +169,7 @@ fn decode() { // Values taken from https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-06.html#name-example-2-handling-structur let sd_jwt = "eyJhbGciOiAiRVMyNTYifQ.eyJfc2QiOiBbIkM5aW5wNllvUmFFWFI0Mjd6WUpQN1FyazFXSF84YmR3T0FfWVVyVW5HUVUiLCAiS3VldDF5QWEwSElRdlluT1ZkNTloY1ZpTzlVZzZKMmtTZnFZUkJlb3d2RSIsICJNTWxkT0ZGekIyZDB1bWxtcFRJYUdlcmhXZFVfUHBZZkx2S2hoX2ZfOWFZIiwgIlg2WkFZT0lJMnZQTjQwVjd4RXhad1Z3ejd5Um1MTmNWd3Q1REw4Ukx2NGciLCAiWTM0em1JbzBRTExPdGRNcFhHd2pCZ0x2cjE3eUVoaFlUMEZHb2ZSLWFJRSIsICJmeUdwMFdUd3dQdjJKRFFsbjFsU2lhZW9iWnNNV0ExMGJRNTk4OS05RFRzIiwgIm9tbUZBaWNWVDhMR0hDQjB1eXd4N2ZZdW8zTUhZS08xNWN6LVJaRVlNNVEiLCAiczBCS1lzTFd4UVFlVTh0VmxsdE03TUtzSVJUckVJYTFQa0ptcXhCQmY1VSJdLCAiaXNzIjogImh0dHBzOi8vaXNzdWVyLmV4YW1wbGUuY29tIiwgImlhdCI6IDE2ODMwMDAwMDAsICJleHAiOiAxODgzMDAwMDAwLCAiYWRkcmVzcyI6IHsiX3NkIjogWyI2YVVoelloWjdTSjFrVm1hZ1FBTzN1MkVUTjJDQzFhSGhlWnBLbmFGMF9FIiwgIkF6TGxGb2JrSjJ4aWF1cFJFUHlvSnotOS1OU2xkQjZDZ2pyN2ZVeW9IemciLCAiUHp6Y1Z1MHFiTXVCR1NqdWxmZXd6a2VzRDl6dXRPRXhuNUVXTndrclEtayIsICJiMkRrdzBqY0lGOXJHZzhfUEY4WmN2bmNXN3p3Wmo1cnlCV3ZYZnJwemVrIiwgImNQWUpISVo4VnUtZjlDQ3lWdWIyVWZnRWs4anZ2WGV6d0sxcF9KbmVlWFEiLCAiZ2xUM2hyU1U3ZlNXZ3dGNVVEWm1Xd0JUdzMyZ25VbGRJaGk4aEdWQ2FWNCIsICJydkpkNmlxNlQ1ZWptc0JNb0d3dU5YaDlxQUFGQVRBY2k0MG9pZEVlVnNBIiwgInVOSG9XWWhYc1poVkpDTkUyRHF5LXpxdDd0NjlnSkt5NVFhRnY3R3JNWDQiXX0sICJfc2RfYWxnIjogInNoYS0yNTYifQ.IjE4EfnYu1RZ1uz6yqtFh5Lppq36VC4VeSr-hLDFpZ9zqBNmMrT5JHLLXTuMJqKQp3NIzDsLaft4GK5bYyfqhg~WyJHMDJOU3JRZmpGWFE3SW8wOXN5YWpBIiwgInJlZ2lvbiIsICJcdTZlMmZcdTUzM2EiXQ~WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgImNvdW50cnkiLCAiSlAiXQ~"; let sd_jwt: SdJwt = SdJwt::parse(sd_jwt).unwrap(); - let (payload, _header) = jwt::decode_with_verifier(&sd_jwt.jwt, &DeocyJwsVerifier {}).unwrap(); + let (payload, _header) = jwt::decode_with_verifier(&sd_jwt.jwt, &DecoyJwsVerifier {}).unwrap(); let decoder = SdObjectDecoder::new_with_sha256(); let decoded: Map = decoder.decode(payload.claims_set(), &sd_jwt.disclosures).unwrap(); let expected_object = json!({ @@ -202,8 +202,8 @@ impl JwsAlgorithm for DecoyJwsAlgorithm { } #[derive(Debug, Clone)] -struct DeocyJwsVerifier; -impl JwsVerifier for DeocyJwsVerifier { +struct DecoyJwsVerifier; +impl JwsVerifier for DecoyJwsVerifier { fn algorithm(&self) -> &dyn josekit::jws::JwsAlgorithm { &DecoyJwsAlgorithm {} }