Skip to content

Commit

Permalink
fixes: Fix holder selective disclosure
Browse files Browse the repository at this point in the history
- Enable issuing and verifying nested array SDs
- Fix parts related to disclosing arrayed types
- Refactoring

Signed-off-by: Abdulbois <[email protected]>
  • Loading branch information
Abdulbois committed Dec 13, 2023
1 parent 89c05e9 commit 6accc2e
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 45 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ Cargo.lock

# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
.idea/
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
62 changes: 44 additions & 18 deletions src/holder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,31 +93,40 @@ impl SDJWTHolder {
claims_to_disclose: Map<String, Value>,
) -> Vec<String> {
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
}
Expand All @@ -134,15 +143,32 @@ impl SDJWTHolder {

fn select_disclosures_from_disclosed_list(&self, sd_jwt_claims: &Vec<Value>, claims_to_disclose: &Vec<Value>) -> Vec<String> {
let mut hash_to_disclosure: Vec<String> = 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));
}
_ => {}
}
Expand Down
21 changes: 6 additions & 15 deletions src/issuer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ pub struct SDJWTIssuer {
// internal fields
inner: SDJWTCommon,
all_disclosures: Vec<SDJWTDisclosure>,
sd_jwt_payload: SJMap<String, Value>,
signed_sd_jwt: String,
pub(crate) serialized_sd_jwt: String,
pub sd_jwt_payload: SJMap<String, Value>,
pub signed_sd_jwt: String,
pub serialized_sd_jwt: String,
}

pub enum SDJWTClaimsStrategy<'a> {
Expand Down Expand Up @@ -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();
Expand Down
19 changes: 8 additions & 11 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down
2 changes: 1 addition & 1 deletion src/verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub struct SDJWTVerifier {
sd_jwt_payload: Map<String, Value>,
_holder_public_key_payload: Option<HashMap<String, Value>>,
duplicate_hash_check: Vec<String>,
verified_claims: Value,
pub verified_claims: Value,

cb_get_issuer_key: Box<KeyResolver>,
}
Expand Down

0 comments on commit 6accc2e

Please sign in to comment.