From 6accc2eea4a605bfaef2a6ef71e26c75d5fd3221 Mon Sep 17 00:00:00 2001 From: Abdulbois Date: Wed, 13 Dec 2023 14:53:31 +0500 Subject: [PATCH] fixes: Fix holder selective disclosure - Enable issuing and verifying nested array SDs - Fix parts related to disclosing arrayed types - Refactoring Signed-off-by: Abdulbois --- .gitignore | 1 + Cargo.toml | 3 +++ src/holder.rs | 62 +++++++++++++++++++++++++++++++++++-------------- src/issuer.rs | 21 +++++------------ src/lib.rs | 19 +++++++-------- src/verifier.rs | 2 +- 6 files changed, 63 insertions(+), 45 deletions(-) diff --git a/.gitignore b/.gitignore index 6985cf1..a38b19f 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ Cargo.lock # MSVC Windows builds of rustc generate these, which store debugging information *.pdb +.idea/ \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 179438a..b75052a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,6 @@ log = "0.4" rand = "0.8" serde_json = { version = "1.0", features = ["preserve_order"] } sha2 = "0.10" + +[dev-dependencies] +rstest = "0.18.2" diff --git a/src/holder.rs b/src/holder.rs index a1c938c..c444a89 100644 --- a/src/holder.rs +++ b/src/holder.rs @@ -93,31 +93,40 @@ impl SDJWTHolder { claims_to_disclose: Map, ) -> Vec { let mut hash_to_disclosure = Vec::new(); - let default_list = Vec::new(); - let sd_map: HashMap<&str, (&Value, &str)> = sd_jwt_claims[SD_DIGESTS_KEY].as_array().unwrap_or(&default_list).iter().map(|digest| { + let sd_map: HashMap<&str, (&Value, &str)> = sd_jwt_claims.get(SD_DIGESTS_KEY).and_then(Value::as_array).unwrap_or(&default_list).iter().filter_map(|digest| { let digest = digest.as_str().unwrap(); - let disclosure = self.sd_jwt_engine.hash_to_decoded_disclosure[digest].as_array().unwrap(); - (disclosure[1].as_str().unwrap(), (&disclosure[2], digest)) + if let Some(Value::Array(disclosure)) = self.sd_jwt_engine.hash_to_decoded_disclosure.get(digest) { + return Some((disclosure[1].as_str().unwrap(), (&disclosure[2], digest))); + } + None }).collect(); //TODO split to 2 maps for (key_to_disclose, value_to_disclose) in claims_to_disclose { match value_to_disclose { - Value::Null | Value::Bool(true) | Value::Number(_) | Value::String(_) => { /* disclose without children */ } - Value::Array(arr_to_disclose) => { - if let Some(arr) = sd_jwt_claims.get(&key_to_disclose).and_then(Value::as_array) { - hash_to_disclosure.append(&mut self.select_disclosures_from_disclosed_list(&arr, &arr_to_disclose)) + Value::Bool(true) | Value::Number(_) | Value::String(_) => { /* disclose without children */ } + Value::Array(claims_to_disclose) => { + if let Some(sd_jwt_claims) = sd_jwt_claims + .get(&key_to_disclose) + .and_then(Value::as_array) + { + hash_to_disclosure.append(&mut self.select_disclosures_from_disclosed_list(&sd_jwt_claims, &claims_to_disclose)) + } else if let Some(sd_jwt_claims) = sd_map + .get(key_to_disclose.as_str()) + .and_then(|(sd, _)| sd.as_array()) + { + hash_to_disclosure.append(&mut self.select_disclosures_from_disclosed_list(&sd_jwt_claims, &claims_to_disclose)) } } - Value::Object(next_disclosure) if (!next_disclosure.is_empty()) => { - let next_sd_jwt_claims = if let Some(next) = sd_jwt_claims.get(&key_to_disclose).and_then(Value::as_object) { + Value::Object(claims_to_disclose) if (!claims_to_disclose.is_empty()) => { + let sd_jwt_claims = if let Some(next) = sd_jwt_claims.get(&key_to_disclose).and_then(Value::as_object) { next } else { sd_map[key_to_disclose.as_str()].0.as_object().unwrap() }; - hash_to_disclosure.append(&mut self.select_disclosures(next_sd_jwt_claims, next_disclosure)); + hash_to_disclosure.append(&mut self.select_disclosures(sd_jwt_claims, claims_to_disclose)); } Value::Object(_) => { /* disclose without children */ } - Value::Bool(false) => { + Value::Bool(false) | Value::Null => { // skip unrevealed continue } @@ -134,15 +143,32 @@ impl SDJWTHolder { fn select_disclosures_from_disclosed_list(&self, sd_jwt_claims: &Vec, claims_to_disclose: &Vec) -> Vec { let mut hash_to_disclosure: Vec = Vec::new(); - for (claim_to_disclose, claim) in claims_to_disclose.iter().zip(sd_jwt_claims) { - match (claim_to_disclose, claim) { - (Value::Bool(true), Value::Object(claim)) => { - if let Some(Value::String(digest)) = claim.get(SD_LIST_PREFIX) { + for (claim_to_disclose, sd_jwt_claims) in claims_to_disclose.iter().zip(sd_jwt_claims) { + match (claim_to_disclose, sd_jwt_claims) { + (Value::Bool(true), Value::Object(sd_jwt_claims)) => { + if let Some(Value::String(digest)) = sd_jwt_claims.get(SD_LIST_PREFIX) { hash_to_disclosure.push(self.sd_jwt_engine.hash_to_disclosure[digest].to_owned()); } } - (Value::Array(new_claims_to_disclose), Value::Array(claim)) => { - self.select_disclosures_from_disclosed_list(claim, new_claims_to_disclose); + (claim_to_disclose, Value::Object(sd_jwt_claims)) => { + if let Some(Value::String(digest)) = sd_jwt_claims.get(SD_LIST_PREFIX) { + let disclosure = self.sd_jwt_engine.hash_to_decoded_disclosure[digest].as_array().unwrap(); + match (claim_to_disclose, disclosure.get(1)) { + (Value::Array(claim_to_disclose), Some(Value::Array(sd_jwt_claims))) => { + hash_to_disclosure.append(&mut self.select_disclosures_from_disclosed_list(&sd_jwt_claims, claim_to_disclose)); + } + (Value::Object(claim_to_disclose), Some(Value::Object(sd_jwt_claims))) => { + hash_to_disclosure.push(self.sd_jwt_engine.hash_to_disclosure[digest].to_owned()); + hash_to_disclosure.append(&mut self.select_disclosures(&sd_jwt_claims, claim_to_disclose.to_owned())); + } + _ => {} + } + } else if let Some(claim_to_disclose) = claim_to_disclose.as_object() { + hash_to_disclosure.append(&mut self.select_disclosures(sd_jwt_claims, claim_to_disclose.to_owned())); + } + } + (Value::Array(claim_to_disclose), Value::Array(sd_jwt_claims)) => { + hash_to_disclosure.append(&mut self.select_disclosures_from_disclosed_list(sd_jwt_claims, claim_to_disclose)); } _ => {} } diff --git a/src/issuer.rs b/src/issuer.rs index be90224..0107c51 100644 --- a/src/issuer.rs +++ b/src/issuer.rs @@ -25,9 +25,9 @@ pub struct SDJWTIssuer { // internal fields inner: SDJWTCommon, all_disclosures: Vec, - sd_jwt_payload: SJMap, - signed_sd_jwt: String, - pub(crate) serialized_sd_jwt: String, + pub sd_jwt_payload: SJMap, + pub signed_sd_jwt: String, + pub serialized_sd_jwt: String, } pub enum SDJWTClaimsStrategy<'a> { @@ -72,21 +72,12 @@ impl<'a> SDJWTClaimsStrategy<'a> { str.strip_prefix(key).as_mut().and_then(|claim| { if let Some(next_claim) = claim.strip_prefix('.') { Some(next_claim) - } else if let Some(next_claim) = claim.strip_prefix('[').and_then(|str| str.strip_suffix(']')) { - Some(next_claim) } else { + // FIXME Replace to non-leackable impl // Removes "[", "]" symbols form "index" and returns "next_claim" as "index.remained_claims.." // For example: [0].street -> 0.street - if let Some(remainder) = claim.strip_prefix('[') { - *claim = remainder; - let remainder: Vec<&str> = claim.splitn(2, ']').collect(); - //FIXME Change to safe impl - *claim = remainder.join("").leak(); - - Some(claim) - } else { - None - } + *claim = claim.replace("[", "").replace("]","").leak(); + Some(claim) } }) }).collect(); diff --git a/src/lib.rs b/src/lib.rs index 5312865..186efe0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,30 +1,27 @@ use std::collections::HashMap; -use std::sync::Mutex; - -use base64::{Engine, engine::general_purpose}; +use base64::{engine::general_purpose, Engine}; use lazy_static::lazy_static; use rand::prelude::ThreadRng; use rand::RngCore; use serde_json::{Map, Value}; use sha2::Digest; - +use std::sync::Mutex; pub use {holder::SDJWTHolder, issuer::SDJWTIssuer, verifier::SDJWTVerifier}; - -mod issuer; -mod verifier; -mod holder; mod disclosure; +pub mod holder; +pub mod issuer; +pub mod verifier; -const DEFAULT_SIGNING_ALG: &str = "ES256"; +pub const DEFAULT_SIGNING_ALG: &str = "ES256"; const SD_DIGESTS_KEY: &str = "_sd"; const DIGEST_ALG_KEY: &str = "_sd_alg"; -const DEFAULT_DIGEST_ALG: &str = "sha-256"; +pub const DEFAULT_DIGEST_ALG: &str = "sha-256"; const SD_LIST_PREFIX: &str = "..."; const _SD_JWT_TYP_HEADER: &str = "sd+jwt"; const KB_JWT_TYP_HEADER: &str = "kb+jwt"; const JWS_KEY_DISCLOSURES: &str = "disclosures"; const JWS_KEY_KB_JWT: &str = "kb_jwt"; -const COMBINED_SERIALIZATION_FORMAT_SEPARATOR: &str = "~"; +pub const COMBINED_SERIALIZATION_FORMAT_SEPARATOR: &str = "~"; const JWT_SEPARATOR: &str = "."; #[derive(Debug)] diff --git a/src/verifier.rs b/src/verifier.rs index 26d64ae..1282d62 100644 --- a/src/verifier.rs +++ b/src/verifier.rs @@ -17,7 +17,7 @@ pub struct SDJWTVerifier { sd_jwt_payload: Map, _holder_public_key_payload: Option>, duplicate_hash_check: Vec, - verified_claims: Value, + pub verified_claims: Value, cb_get_issuer_key: Box, }