Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade to draft version 07 #9

Merged
merged 4 commits into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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