Skip to content

Commit f730806

Browse files
committed
fix 0 disclosures bug
1 parent 38013b0 commit f730806

File tree

3 files changed

+64
-14
lines changed

3 files changed

+64
-14
lines changed

src/key_binding_jwt_claims.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use anyhow::Context as _;
1212
use serde::Deserialize;
1313
use serde::Serialize;
1414
use serde_json::Value;
15+
use std::borrow::Cow;
1516
use std::fmt::Display;
1617
use std::ops::Deref;
1718
use std::str::FromStr;
@@ -72,12 +73,18 @@ impl KeyBindingJwtBuilder {
7273
self.0.insert("iat".to_string(), iat.into());
7374
self
7475
}
75-
pub fn aud(mut self, aud: impl ToOwned<Owned = String>) -> Self {
76-
self.0.insert("aud".to_string(), aud.to_owned().into());
76+
pub fn aud<'a, S>(mut self, aud: S) -> Self
77+
where
78+
S: Into<Cow<'a, str>>,
79+
{
80+
self.0.insert("aud".to_string(), aud.into().into_owned().into());
7781
self
7882
}
79-
pub fn nonce(mut self, nonce: impl ToOwned<Owned = String>) -> Self {
80-
self.0.insert("nonce".to_string(), nonce.to_owned().into());
83+
pub fn nonce<'a, S>(mut self, nonce: S) -> Self
84+
where
85+
S: Into<Cow<'a, str>>,
86+
{
87+
self.0.insert("nonce".to_string(), nonce.into().into_owned().into());
8188
self
8289
}
8390
pub fn insert_property(mut self, name: &str, value: Value) -> Self {

src/sd_jwt.rs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,11 @@ impl SdJwt {
115115
.as_ref()
116116
.map(ToString::to_string)
117117
.unwrap_or_default();
118-
format!("{}~{}~{}", self.jwt, disclosures, key_bindings)
118+
if disclosures.is_empty() {
119+
format!("{}~{}", self.jwt, key_bindings)
120+
} else {
121+
format!("{}~{}~{}", self.jwt, disclosures, key_bindings)
122+
}
119123
}
120124

121125
/// Parses an SD-JWT into its components as [`SdJwt`].
@@ -128,22 +132,17 @@ impl SdJwt {
128132
));
129133
}
130134

131-
let includes_key_binding = sd_jwt.chars().next_back().is_some_and(|char| char != '~');
132-
if includes_key_binding && num_of_segments < 3 {
133-
return Err(Error::DeserializationError(
134-
"SD-JWT format is invalid, less than 3 segments with key binding jwt".to_string(),
135-
));
136-
}
137-
138135
let jwt = sd_segments.first().unwrap().parse()?;
139136

140137
let disclosures = sd_segments[1..num_of_segments - 1]
141138
.iter()
142139
.map(|s| Disclosure::parse(s))
143140
.try_collect()?;
144141

145-
let key_binding_jwt = includes_key_binding
146-
.then(|| sd_segments[num_of_segments - 1].parse())
142+
let key_binding_jwt = sd_segments
143+
.last()
144+
.filter(|segment| !segment.is_empty())
145+
.map(|segment| segment.parse())
147146
.transpose()?;
148147

149148
Ok(Self {

tests/api_test.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ use josekit::jws::JwsHeader;
77
use josekit::jws::HS256;
88
use josekit::jwt;
99
use josekit::jwt::JwtPayload;
10+
use sd_jwt_payload::Hasher;
1011
use sd_jwt_payload::JsonObject;
1112
use sd_jwt_payload::JwsSigner;
13+
use sd_jwt_payload::KeyBindingJwt;
1214
use sd_jwt_payload::Sha256Hasher;
1315
use serde_json::json;
1416
use serde_json::Value;
@@ -43,6 +45,17 @@ async fn make_sd_jwt(object: Value, disclosable_values: impl IntoIterator<Item =
4345
.unwrap()
4446
}
4547

48+
async fn make_kb_jwt(sd_jwt: &SdJwt, hasher: &dyn Hasher) -> KeyBindingJwt {
49+
let signer = HmacSignerAdapter(HS256.signer_from_bytes(HMAC_SECRET).unwrap());
50+
KeyBindingJwt::builder()
51+
.nonce("abcdefghi")
52+
.aud("https://example.com")
53+
.iat(1458304832)
54+
.finish(sd_jwt, hasher, "HS256", &signer)
55+
.await
56+
.unwrap()
57+
}
58+
4659
#[test]
4760
fn simple_sd_jwt() {
4861
// Values taken from https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-06.html#name-example-2-handling-structur
@@ -103,3 +116,34 @@ async fn sd_jwt_is_verifiable() -> anyhow::Result<()> {
103116
josekit::jwt::decode_with_verifier(&jwt, &verifier)?;
104117
Ok(())
105118
}
119+
120+
#[tokio::test]
121+
async fn sd_jwt_without_disclosures_works() -> anyhow::Result<()> {
122+
let hasher = Sha256Hasher::new();
123+
let sd_jwt = make_sd_jwt(json!({"parent": {"property1": "value1", "property2": [1, 2, 3]}}), []).await;
124+
// Try to serialize & deserialize `sd_jwt`.
125+
let sd_jwt = {
126+
let s = sd_jwt.to_string();
127+
s.parse::<SdJwt>()?
128+
};
129+
130+
assert!(sd_jwt.disclosures().is_empty());
131+
assert!(sd_jwt.key_binding_jwt().is_none());
132+
133+
let with_kb = sd_jwt
134+
.clone()
135+
.into_presentation(&hasher)?
136+
.attach_key_binding_jwt(make_kb_jwt(&sd_jwt, &hasher).await)
137+
.finish()
138+
.0;
139+
// Try to serialize & deserialize `with_kb`.
140+
let with_kb = {
141+
let s = with_kb.to_string();
142+
s.parse::<SdJwt>()?
143+
};
144+
145+
assert!(with_kb.disclosures().is_empty());
146+
assert!(with_kb.key_binding_jwt().is_some());
147+
148+
Ok(())
149+
}

0 commit comments

Comments
 (0)