From 11d0c8546751c2ef326991b76b68a65a528403bc Mon Sep 17 00:00:00 2001 From: Enrico Marconi Date: Thu, 26 Sep 2024 16:11:00 +0200 Subject: [PATCH] fail to create presentation if missing requied KB-JWT --- examples/sd_jwt.rs | 2 +- src/builder.rs | 2 +- src/error.rs | 3 +++ src/key_binding_jwt_claims.rs | 37 +++++++++++++++++++++++------------ src/sd_jwt.rs | 12 +++++++++--- tests/api_test.rs | 6 +++--- 6 files changed, 41 insertions(+), 21 deletions(-) diff --git a/examples/sd_jwt.rs b/examples/sd_jwt.rs index 5ea3942..9c594a4 100644 --- a/examples/sd_jwt.rs +++ b/examples/sd_jwt.rs @@ -79,7 +79,7 @@ async fn main() -> Result<(), Box> { .into_presentation(&hasher)? .conceal("/email")? .conceal("/nationalities/0")? - .finish(); + .finish()?; // The holder send its token to a verifier. let received_sd_jwt = presented_sd_jwt.presentation(); diff --git a/src/builder.rs b/src/builder.rs index a6f6e2c..41b04f5 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -97,7 +97,7 @@ impl SdJwtBuilder { /// Sets the JWT header. /// ## Notes - /// - if [`SdJwtBuilder::header`] is not called, a default below header is used: ```json { "typ": "sd-jwt", "alg": + /// - if [`SdJwtBuilder::header`] is not called, the default header is used: ```json { "typ": "sd-jwt", "alg": /// "" } ``` /// - `alg` is always replaced with the value passed to [`SdJwtBuilder::finish`]. pub fn header(mut self, header: JsonObject) -> Self { diff --git a/src/error.rs b/src/error.rs index 5eca1fb..45b17d9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -42,4 +42,7 @@ pub enum Error { #[error("JWS creation failure: {0}")] JwsSignerFailure(String), + + #[error("Missing required KB-JWT")] + MissingKeyBindingJwt, } diff --git a/src/key_binding_jwt_claims.rs b/src/key_binding_jwt_claims.rs index 65b9409..5475753 100644 --- a/src/key_binding_jwt_claims.rs +++ b/src/key_binding_jwt_claims.rs @@ -60,35 +60,47 @@ impl KeyBindingJwt { } #[derive(Debug, Default, Clone)] -pub struct KeyBindingJwtBuilder(JsonObject); +pub struct KeyBindingJwtBuilder { + header: JsonObject, + payload: JsonObject, +} impl KeyBindingJwtBuilder { pub fn new() -> Self { Self::default() } pub fn from_object(object: JsonObject) -> Self { - Self(object) + Self { + header: JsonObject::default(), + payload: object, + } + } + pub fn header(mut self, header: JsonObject) -> Self { + self.header = header; + self } pub fn iat(mut self, iat: i64) -> Self { - self.0.insert("iat".to_string(), iat.into()); + self.payload.insert("iat".to_string(), iat.into()); self } pub fn aud<'a, S>(mut self, aud: S) -> Self where S: Into>, { - self.0.insert("aud".to_string(), aud.into().into_owned().into()); + self.payload.insert("aud".to_string(), aud.into().into_owned().into()); self } pub fn nonce<'a, S>(mut self, nonce: S) -> Self where S: Into>, { - self.0.insert("nonce".to_string(), nonce.into().into_owned().into()); + self + .payload + .insert("nonce".to_string(), nonce.into().into_owned().into()); self } pub fn insert_property(mut self, name: &str, value: Value) -> Self { - self.0.insert(name.to_string(), value); + self.payload.insert(name.to_string(), value); self } pub async fn finish( @@ -101,7 +113,7 @@ impl KeyBindingJwtBuilder { where S: JwsSigner, { - let mut claims = self.0; + let mut claims = self.payload; if alg == "none" { return Err(Error::DataTypeMismatch( "A KeyBindingJwt cannot use algorithm \"none\"".to_string(), @@ -121,12 +133,11 @@ impl KeyBindingJwtBuilder { let sd_hash = hasher.encoded_digest(&sd_jwt.to_string()); claims.insert("sd_hash".to_string(), sd_hash.into()); - let Value::Object(header) = serde_json::json!({ - "alg": alg, - "typ": KB_JWT_HEADER_TYP, - }) else { - unreachable!(); - }; + let mut header = self.header; + header.insert("alg".to_string(), alg.to_owned().into()); + header + .entry("typ") + .or_insert_with(|| KB_JWT_HEADER_TYP.to_owned().into()); // Validate claims let parsed_claims = serde_json::from_value::(claims.clone().into()) diff --git a/src/sd_jwt.rs b/src/sd_jwt.rs index 282fffd..927c643 100644 --- a/src/sd_jwt.rs +++ b/src/sd_jwt.rs @@ -267,7 +267,13 @@ impl SdJwtPresentationBuilder { } /// Returns the resulting [`SdJwt`] together with all removed disclosures. - pub fn finish(self) -> (SdJwt, Vec) { + /// ## Errors + /// - Fails with [`Error::MissingKeyBindingJwt`] if this [`SdJwt`] requires a key binding but none was provided. + pub fn finish(self) -> Result<(SdJwt, Vec)> { + if self.sd_jwt.required_key_bind().is_some() && self.key_binding_jwt.is_none() { + return Err(Error::MissingKeyBindingJwt); + } + // Put everything back in its place. let SdJwtPresentationBuilder { mut sd_jwt, @@ -281,7 +287,7 @@ impl SdJwtPresentationBuilder { let Value::Object(mut obj) = object else { unreachable!(); }; - let Value::Array(sd) = obj.remove(DIGESTS_KEY).unwrap() else { + let Value::Array(sd) = obj.remove(DIGESTS_KEY).unwrap_or(Value::Array(vec![])) else { unreachable!() }; sd_jwt.jwt.claims._sd = sd @@ -296,7 +302,7 @@ impl SdJwtPresentationBuilder { .collect(); sd_jwt.jwt.claims.properties = obj; - (sd_jwt, removed_disclosures) + Ok((sd_jwt, removed_disclosures)) } } diff --git a/tests/api_test.rs b/tests/api_test.rs index 4de4892..29af608 100644 --- a/tests/api_test.rs +++ b/tests/api_test.rs @@ -84,7 +84,7 @@ async fn concealing_parent_also_removes_all_sub_disclosures() -> anyhow::Result< ) .await; - let removed_disclosures = sd_jwt.into_presentation(&hasher)?.conceal("/parent")?.finish().1; + let removed_disclosures = sd_jwt.into_presentation(&hasher)?.conceal("/parent")?.finish()?.1; assert_eq!(removed_disclosures.len(), 3); Ok(()) @@ -102,7 +102,7 @@ async fn concealing_property_of_concealable_value_works() -> anyhow::Result<()> sd_jwt .into_presentation(&hasher)? .conceal("/parent/property2/0")? - .finish(); + .finish()?; Ok(()) } @@ -134,7 +134,7 @@ async fn sd_jwt_without_disclosures_works() -> anyhow::Result<()> { .clone() .into_presentation(&hasher)? .attach_key_binding_jwt(make_kb_jwt(&sd_jwt, &hasher).await) - .finish() + .finish()? .0; // Try to serialize & deserialize `with_kb`. let with_kb = {