Skip to content

Commit

Permalink
Improvements (#4)
Browse files Browse the repository at this point in the history
* use fix salt size/don't return disclosure for decoys

* add constant for `_sd_alg`

* add `try_from_serializable` to encoder

* fix salt generation

* remove unnecessary ASCII filtering

* feature gate sha

* remove `object_mut` from API

* clippy

* use ? instead of unwrap in example

* derive clone for hasher

* add test for decoy/decode

* clippy

* typos + move `api_test.rs`
  • Loading branch information
abdulmth authored Dec 6, 2023
1 parent 2942a98 commit a17b639
Show file tree
Hide file tree
Showing 10 changed files with 255 additions and 132 deletions.
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -27,3 +27,6 @@ josekit = "0.8.4"
[[example]]
name = "sd_jwt"

[features]
default = ["sha"]
sha = ["iota-crypto"]
40 changes: 20 additions & 20 deletions examples/sd_jwt.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -11,7 +13,7 @@ use sd_jwt::SdObjectDecoder;
use sd_jwt::SdObjectEncoder;
use serde_json::json;

fn main() {
fn main() -> Result<(), Box<dyn Error>> {
let object = json!({
"sub": "user_42",
"given_name": "John",
Expand All @@ -34,34 +36,31 @@ fn main() {
});

let mut disclosures: Vec<Disclosure> = 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
let mut header = JwsHeader::new();
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<String> = disclosures
Expand All @@ -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();
let decoded = decoder.decode(payload.claims_set(), &sd_jwt.disclosures).unwrap();
println!("decoded object: {}", serde_json::to_string_pretty(&decoded).unwrap());
let decoder = SdObjectDecoder::new_with_sha256();
let decoded = decoder.decode(payload.claims_set(), &sd_jwt.disclosures)?;
println!("decoded object: {}", serde_json::to_string_pretty(&decoded)?);
Ok(())
}
28 changes: 19 additions & 9 deletions src/decoder.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
// 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;
use crate::SHA_ALG_NAME;

use super::Disclosure;
use super::Hasher;
#[cfg(feature = "sha")]
use super::Sha256Hasher;
use crate::Error;
use serde_json::Map;
Expand All @@ -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<String, Box<dyn Hasher>> = 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<String, Box<dyn Hasher>> = BTreeMap::new();
Self { hashers }
}

/// Adds a hasher.
///
/// If a hasher for the same algorithm [`Hasher::alg_name`] already exists, it will be replaced and
Expand Down Expand Up @@ -64,7 +73,7 @@ impl SdObjectDecoder {
let mut disclosures_map: BTreeMap<String, Disclosure> = 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);
}

Expand All @@ -76,18 +85,18 @@ 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<String, Value>) -> 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(),
))?
} else {
Sha256Hasher::ALG_NAME
SHA_ALG_NAME
};
self
.hashers
Expand Down Expand Up @@ -227,9 +236,10 @@ impl SdObjectDecoder {
}
}

#[cfg(feature = "sha")]
impl Default for SdObjectDecoder {
fn default() -> Self {
Self::new()
Self::new_with_sha256()
}
}

Expand All @@ -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(_)));
}
Expand All @@ -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());
}
Expand Down
Loading

0 comments on commit a17b639

Please sign in to comment.