Skip to content

Commit

Permalink
Upgrade to draft version 07 (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
abdulmth authored Jan 8, 2024
1 parent 6670730 commit 5c4a8fc
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 12 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@

# SD-JWT Reference implementation

Rust implementation of the [Selective Disclosure for JWTs (SD-JWT) **version 06**](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-06.html)
Rust implementation of the [Selective Disclosure for JWTs (SD-JWT) **version 07**](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-07.html)

## Overview

Expand Down
54 changes: 53 additions & 1 deletion src/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ impl SdObjectDecoder {
// Decode the object recursively.
let mut decoded = self.decode_object(object, &disclosures_map, &mut processed_digests)?;

if processed_digests.len() != disclosures.len() {
return Err(crate::Error::UnusedDisclosures(
disclosures.len().saturating_sub(processed_digests.len()),
));
}

// Remove `_sd_alg` in case it exists.
decoded.remove(SD_ALG);
Ok(decoded)
Expand Down Expand Up @@ -139,6 +145,7 @@ impl SdObjectDecoder {
if output.contains_key(&claim_name) {
return Err(Error::ClaimCollisionError(claim_name));
}
processed_digests.push(digest_str.clone());

let recursively_decoded = match disclosure.claim_value {
Value::Array(ref sub_arr) => Value::Array(self.decode_array(sub_arr, disclosures, processed_digests)?),
Expand Down Expand Up @@ -199,11 +206,11 @@ impl SdObjectDecoder {
if processed_digests.contains(&digest_in_array) {
return Err(Error::DuplicateDigestError(digest_in_array));
}

if let Some(disclosure) = disclosures.get(&digest_in_array) {
if disclosure.claim_name.is_some() {
return Err(Error::InvalidDisclosure("array length must be 2".to_string()));
}
processed_digests.push(digest_in_array.clone());
// Recursively decoded the disclosed values.
let recursively_decoded = match disclosure.claim_value {
Value::Array(ref sub_arr) => {
Expand Down Expand Up @@ -245,6 +252,7 @@ impl Default for SdObjectDecoder {

#[cfg(test)]
mod test {
use crate::Disclosure;
use crate::Error;
use crate::SdObjectDecoder;
use crate::SdObjectEncoder;
Expand Down Expand Up @@ -281,4 +289,48 @@ mod test {
let decoded = decoder.decode(encoder.object(), &vec![]).unwrap();
assert!(decoded.get("_sd_alg").is_none());
}

#[test]
fn duplicate_digest() {
let object = json!({
"id": "did:value",
});
let mut encoder = SdObjectEncoder::try_from(object).unwrap();
let dislosure: Disclosure = encoder.conceal(&["id"], Some("test".to_string())).unwrap();
// 'obj' contains digest of `id` twice.
let obj = json!({
"_sd":[
"mcKLMnXQdCM0gJ5l4Hb6ignpVgCw4SfienkI8vFgpjE",
"mcKLMnXQdCM0gJ5l4Hb6ignpVgCw4SfienkI8vFgpjE"
]
}
);
let decoder = SdObjectDecoder::new_with_sha256();
let result = decoder.decode(obj.as_object().unwrap(), &vec![dislosure.to_string()]);
assert!(matches!(result.err().unwrap(), crate::Error::DuplicateDigestError(_)));
}

#[test]
fn unused_disclosure() {
let object = json!({
"id": "did:value",
"tst": "tst-value"
});
let mut encoder = SdObjectEncoder::try_from(object).unwrap();
let disclosure_1: Disclosure = encoder.conceal(&["id"], Some("test".to_string())).unwrap();
let disclosure_2: Disclosure = encoder.conceal(&["tst"], Some("test".to_string())).unwrap();
// 'obj' contains only the digest of `id`.
let obj = json!({
"_sd":[
"mcKLMnXQdCM0gJ5l4Hb6ignpVgCw4SfienkI8vFgpjE",
]
}
);
let decoder = SdObjectDecoder::new_with_sha256();
let result = decoder.decode(
obj.as_object().unwrap(),
&vec![disclosure_1.to_string(), disclosure_2.to_string()],
);
assert!(matches!(result.err().unwrap(), crate::Error::UnusedDisclosures(1)));
}
}
8 changes: 4 additions & 4 deletions src/disclosure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use std::fmt::Display;
/// Represents an elements constructing a disclosure.
/// Object properties and array elements disclosures are supported.
///
/// See: https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-06.html#name-disclosures
/// See: https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-07.html#name-disclosures
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Disclosure {
/// The salt value.
Expand Down Expand Up @@ -69,7 +69,7 @@ impl Disclosure {
if decoded.len() == 2 {
Ok(Self {
salt: decoded
.get(0)
.first()
.ok_or(Error::InvalidDisclosure("invalid salt".to_string()))?
.as_str()
.ok_or(Error::InvalidDisclosure(
Expand All @@ -87,7 +87,7 @@ impl Disclosure {
} else if decoded.len() == 3 {
Ok(Self {
salt: decoded
.get(0)
.first()
.ok_or(Error::InvalidDisclosure("invalid salt".to_string()))?
.as_str()
.ok_or(Error::InvalidDisclosure(
Expand Down Expand Up @@ -140,7 +140,7 @@ mod test {
use super::Disclosure;

// Test values from:
// https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-05.html#appendix-A.2-7
// https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-07.html#appendix-A.2-7
#[test]
fn test_parsing() {
let disclosure = Disclosure::new(
Expand Down
3 changes: 3 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,7 @@ pub enum Error {

#[error("salt size must be greater than or equal to 16")]
InvalidSaltSize,

#[error("the validation ended with {0} unused disclosure(s)")]
UnusedDisclosures(usize),
}
4 changes: 2 additions & 2 deletions src/hasher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub const SHA_ALG_NAME: &str = "sha-256";
///
/// 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)
/// See [Hash Function Claim](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-07.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<u8>;
Expand Down Expand Up @@ -60,7 +60,7 @@ 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
// Some test values taken from https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-07.html#name-disclosures
#[cfg(test)]
mod test {
use crate::Hasher;
Expand Down
5 changes: 2 additions & 3 deletions src/key_binding_jwt_claims.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,12 @@ use crate::Hasher;
use serde::Deserialize;
use serde::Serialize;

///
/// Claims set for key binding JWT.
#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
pub struct KeyBindingJwtClaims {
pub iat: i64,
pub aud: String,
pub nonce: String,
#[serde(rename = "_sd_hash")]
pub sd_hash: String,
#[serde(flatten)]
pub properties: BTreeMap<String, Value>,
Expand All @@ -27,7 +26,7 @@ impl KeyBindingJwtClaims {
pub const KB_JWT_HEADER_TYP: &'static str = " kb+jwt";

/// Creates a new [`KeyBindingJwtClaims`].
/// When `issued_at` is left as None, it will automatically default to the current time
/// When `issued_at` is left as None, it will automatically default to the current time.
///
/// # Panic
/// When `issued_at` is set to `None` and the system returns time earlier than `SystemTime::UNIX_EPOCH`.
Expand Down
2 changes: 1 addition & 1 deletion tests/api_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use sd_jwt_payload::SdObjectEncoder;

#[test]
fn test_complex_structure() {
// Values taken from https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-06.html#appendix-A.2
// Values taken from https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-07.html#appendix-A.2
let object = json!({
"verified_claims": {
"verification": {
Expand Down

0 comments on commit 5c4a8fc

Please sign in to comment.