From d01c6e4a702593aa6329a30e60f5ca9059a2b7ef Mon Sep 17 00:00:00 2001 From: Joey Silberman Date: Thu, 31 Oct 2024 12:44:13 -0700 Subject: [PATCH 1/7] Add temporary support for unencoded JWT authorization requests --- src/core/authorization_request/verification/did.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/core/authorization_request/verification/did.rs b/src/core/authorization_request/verification/did.rs index cd69956..0a0adb3 100644 --- a/src/core/authorization_request/verification/did.rs +++ b/src/core/authorization_request/verification/did.rs @@ -40,6 +40,13 @@ pub async fn verify_with_resolver( bail!("request was signed with unsupported algorithm: {alg}") } + // This bypass is for unencoded JWT requests, but we will need to change this later + // so that trust is preserved when receiving unencoded requests + if alg.contains("none") { + // bail!("UNSIGNED JWT") + return Ok(()); + } + let Json::String(kid) = headers .remove("kid") .context("'kid' was missing from jwt headers")? From c589019ea3b8790eb6c94eab643ec5d27a44d02e Mon Sep 17 00:00:00 2001 From: Joey Silberman Date: Thu, 31 Oct 2024 12:53:17 -0700 Subject: [PATCH 2/7] Remove unnecessary comment --- src/core/authorization_request/verification/did.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/authorization_request/verification/did.rs b/src/core/authorization_request/verification/did.rs index 0a0adb3..0bc0928 100644 --- a/src/core/authorization_request/verification/did.rs +++ b/src/core/authorization_request/verification/did.rs @@ -43,7 +43,6 @@ pub async fn verify_with_resolver( // This bypass is for unencoded JWT requests, but we will need to change this later // so that trust is preserved when receiving unencoded requests if alg.contains("none") { - // bail!("UNSIGNED JWT") return Ok(()); } From 73e3a136ab1714a1e67f378b82c833b12479f6f8 Mon Sep 17 00:00:00 2001 From: Ryan Tate Date: Thu, 31 Oct 2024 11:59:31 -0700 Subject: [PATCH 3/7] use serde deserialize serialize derive for authorization response Signed-off-by: Ryan Tate --- src/core/object/mod.rs | 17 --------------- src/core/response/mod.rs | 45 ++++++++++++---------------------------- 2 files changed, 13 insertions(+), 49 deletions(-) diff --git a/src/core/object/mod.rs b/src/core/object/mod.rs index 02447c5..02ad16f 100644 --- a/src/core/object/mod.rs +++ b/src/core/object/mod.rs @@ -1,5 +1,3 @@ -use std::collections::BTreeMap; - use anyhow::{Context, Error, Result}; use serde::{Deserialize, Serialize}; use serde_json::{Map, Value as Json}; @@ -61,21 +59,6 @@ impl UntypedObject { ), } } - - /// Flatten the structure for posting as a form. - pub(crate) fn flatten_for_form(self) -> Result> { - self.0 - .into_iter() - .map(|(k, v)| { - if let Json::String(s) = v { - return Ok((k, s)); - } - serde_json::to_string(&v) - .map(|v| (k, v)) - .map_err(Error::from) - }) - .collect() - } } impl From for Json { diff --git a/src/core/response/mod.rs b/src/core/response/mod.rs index a6e1b1b..12781a7 100644 --- a/src/core/response/mod.rs +++ b/src/core/response/mod.rs @@ -1,13 +1,7 @@ -use super::{ - object::{ParsingErrorContext, UntypedObject}, - presentation_submission::PresentationSubmission, -}; - -use std::collections::BTreeMap; +use super::{object::UntypedObject, presentation_submission::PresentationSubmission}; use anyhow::{Context, Error, Result}; use serde::{Deserialize, Serialize}; -use serde_json::Value; use url::Url; use self::parameters::VpToken; @@ -26,45 +20,34 @@ impl AuthorizationResponse { return Ok(Self::Jwt(jwt)); } - let flattened = serde_urlencoded::from_bytes::>(bytes) + let unencoded = serde_urlencoded::from_bytes::(bytes) .context("failed to construct flat map")?; - let map = flattened - .into_iter() - .map(|(k, v)| { - let v = serde_json::from_str::(&v).unwrap_or(Value::String(v)); - (k, v) - }) - .collect(); - - Ok(Self::Unencoded(UntypedObject(map).try_into()?)) + + Ok(Self::Unencoded(unencoded)) } } -#[derive(Debug, Clone)] -pub struct UnencodedAuthorizationResponse( - pub UntypedObject, - pub VpToken, - pub PresentationSubmission, -); +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct UnencodedAuthorizationResponse { + pub vp_token: VpToken, + pub presentation_submission: PresentationSubmission, +} impl UnencodedAuthorizationResponse { /// Encode the Authorization Response as 'application/x-www-form-urlencoded'. pub fn into_x_www_form_urlencoded(self) -> Result { - let mut inner = self.0; - inner.insert(self.1); - inner.insert(self.2); - serde_urlencoded::to_string(inner.flatten_for_form()?) + serde_urlencoded::to_string(&self) .context("failed to encode response as 'application/x-www-form-urlencoded'") } /// Return the Verifiable Presentation Token. pub fn vp_token(&self) -> &VpToken { - &self.1 + &self.vp_token } /// Return the Presentation Submission. pub fn presentation_submission(&self) -> &PresentationSubmission { - &self.2 + &self.presentation_submission } } @@ -91,9 +74,7 @@ impl TryFrom for UnencodedAuthorizationResponse { type Error = Error; fn try_from(value: UntypedObject) -> Result { - let vp_token = value.get().parsing_error()?; - let presentation_submission = value.get().parsing_error()?; - Ok(Self(value, vp_token, presentation_submission)) + Ok(serde_json::from_value(serde_json::Value::Object(value.0))?) } } From 4f9c3fc43d9e2ce0a577b8ce27a692d9b2303b38 Mon Sep 17 00:00:00 2001 From: Ryan Tate Date: Thu, 31 Oct 2024 12:56:39 -0700 Subject: [PATCH 4/7] update url encoding for auth response Signed-off-by: Ryan Tate --- src/core/response/mod.rs | 20 +++++++++++++++++--- tests/e2e.rs | 9 ++++----- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/core/response/mod.rs b/src/core/response/mod.rs index 12781a7..e888a08 100644 --- a/src/core/response/mod.rs +++ b/src/core/response/mod.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use super::{object::UntypedObject, presentation_submission::PresentationSubmission}; use anyhow::{Context, Error, Result}; @@ -36,8 +38,20 @@ pub struct UnencodedAuthorizationResponse { impl UnencodedAuthorizationResponse { /// Encode the Authorization Response as 'application/x-www-form-urlencoded'. pub fn into_x_www_form_urlencoded(self) -> Result { - serde_urlencoded::to_string(&self) - .context("failed to encode response as 'application/x-www-form-urlencoded'") + let mut map = HashMap::<&str, String>::new(); + let vp_token = + serde_json::to_string(&self.vp_token).context("failed to parse JSON vp token")?; + let presentation_submission = serde_json::to_string(&self.presentation_submission) + .context("failed to encode presentation_submission as JSON")?; + + map.insert("presentation_submission", presentation_submission); + map.insert("vp_token", vp_token); + + let encoded = serde_urlencoded::to_string(&map).context( + "failed to encode presentation_submission as 'application/x-www-form-urlencoded'", + )?; + + Ok(encoded) } /// Return the Verifiable Presentation Token. @@ -113,7 +127,7 @@ mod test { let response = UnencodedAuthorizationResponse::try_from(object).unwrap(); assert_eq!( response.into_x_www_form_urlencoded().unwrap(), - "presentation_submission=%7B%22id%22%3A%22d05a7f51-ac09-43af-8864-e00f0175f2c7%22%2C%22definition_id%22%3A%22f619e64a-8f80-4b71-8373-30cf07b1e4f2%22%2C%22descriptor_map%22%3A%5B%5D%7D&vp_token=string", + "presentation_submission=%7B%22id%22%3A%22d05a7f51-ac09-43af-8864-e00f0175f2c7%22%2C%22definition_id%22%3A%22f619e64a-8f80-4b71-8373-30cf07b1e4f2%22%2C%22descriptor_map%22%3A%5B%5D%7D&vp_token=%22string%22", ) } } diff --git a/tests/e2e.rs b/tests/e2e.rs index 1792988..1c3c9a0 100644 --- a/tests/e2e.rs +++ b/tests/e2e.rs @@ -140,11 +140,10 @@ async fn w3c_vc_did_client_direct_post() { .await .expect("failed to create verifiable presentation"); - let response = AuthorizationResponse::Unencoded(UnencodedAuthorizationResponse( - Default::default(), - vp.into(), - presentation_submission.try_into().unwrap(), - )); + let response = AuthorizationResponse::Unencoded(UnencodedAuthorizationResponse { + vp_token: vp.into(), + presentation_submission: presentation_submission.try_into().unwrap(), + }); let status = verifier.poll_status(id).await.unwrap(); assert_eq!(Status::SentRequest, status); From 7641fb9671c7f56bf4a37a2b87bb2686a8f1cfae Mon Sep 17 00:00:00 2001 From: Ryan Tate Date: Thu, 31 Oct 2024 13:25:46 -0700 Subject: [PATCH 5/7] fix unit tests Signed-off-by: Ryan Tate --- src/core/response/mod.rs | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/src/core/response/mod.rs b/src/core/response/mod.rs index e888a08..7c983f3 100644 --- a/src/core/response/mod.rs +++ b/src/core/response/mod.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use super::{object::UntypedObject, presentation_submission::PresentationSubmission}; -use anyhow::{Context, Error, Result}; +use anyhow::{bail, Context, Error, Result}; use serde::{Deserialize, Serialize}; use url::Url; @@ -22,10 +22,29 @@ impl AuthorizationResponse { return Ok(Self::Jwt(jwt)); } - let unencoded = serde_urlencoded::from_bytes::(bytes) + let unencoded = serde_urlencoded::from_bytes::>(bytes) .context("failed to construct flat map")?; - Ok(Self::Unencoded(unencoded)) + let Some(vp_token) = unencoded + .get("vp_token") + .map(|v| serde_json::from_str(&v)) + .transpose()? + else { + bail!("failed to find vp token"); + }; + + let Some(presentation_submission) = unencoded + .get("presentation_submission") + .map(|v| serde_json::from_str(&v)) + .transpose()? + else { + bail!("failed to find presentation submission"); + }; + + Ok(Self::Unencoded(UnencodedAuthorizationResponse { + vp_token, + presentation_submission, + })) } } @@ -125,9 +144,9 @@ mod test { )) .unwrap(); let response = UnencodedAuthorizationResponse::try_from(object).unwrap(); - assert_eq!( - response.into_x_www_form_urlencoded().unwrap(), - "presentation_submission=%7B%22id%22%3A%22d05a7f51-ac09-43af-8864-e00f0175f2c7%22%2C%22definition_id%22%3A%22f619e64a-8f80-4b71-8373-30cf07b1e4f2%22%2C%22descriptor_map%22%3A%5B%5D%7D&vp_token=%22string%22", - ) + let url_encoded = response.into_x_www_form_urlencoded().unwrap(); + + assert!(url_encoded.contains("presentation_submission=%7B%22id%22%3A%22d05a7f51-ac09-43af-8864-e00f0175f2c7%22%2C%22definition_id%22%3A%22f619e64a-8f80-4b71-8373-30cf07b1e4f2%22%2C%22descriptor_map%22%3A%5B%5D%7D")); + assert!(url_encoded.contains("vp_token=%22string%22")); } } From eef7f70fee5aa7a380721c5781a37554ee456054 Mon Sep 17 00:00:00 2001 From: Ryan Tate Date: Thu, 31 Oct 2024 13:39:11 -0700 Subject: [PATCH 6/7] use custom struct for json string encoded authorization response inner values Signed-off-by: Ryan Tate --- src/core/response/mod.rs | 65 +++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/src/core/response/mod.rs b/src/core/response/mod.rs index 7c983f3..b4a8c92 100644 --- a/src/core/response/mod.rs +++ b/src/core/response/mod.rs @@ -1,8 +1,6 @@ -use std::collections::HashMap; - use super::{object::UntypedObject, presentation_submission::PresentationSubmission}; -use anyhow::{bail, Context, Error, Result}; +use anyhow::{Context, Error, Result}; use serde::{Deserialize, Serialize}; use url::Url; @@ -22,24 +20,15 @@ impl AuthorizationResponse { return Ok(Self::Jwt(jwt)); } - let unencoded = serde_urlencoded::from_bytes::>(bytes) + let unencoded = serde_urlencoded::from_bytes::(bytes) .context("failed to construct flat map")?; - let Some(vp_token) = unencoded - .get("vp_token") - .map(|v| serde_json::from_str(&v)) - .transpose()? - else { - bail!("failed to find vp token"); - }; + let vp_token: VpToken = + serde_json::from_str(&unencoded.vp_token).context("failed to decode vp token")?; - let Some(presentation_submission) = unencoded - .get("presentation_submission") - .map(|v| serde_json::from_str(&v)) - .transpose()? - else { - bail!("failed to find presentation submission"); - }; + let presentation_submission: PresentationSubmission = + serde_json::from_str(&unencoded.presentation_submission) + .context("failed to decode presentation submission")?; Ok(Self::Unencoded(UnencodedAuthorizationResponse { vp_token, @@ -48,6 +37,14 @@ impl AuthorizationResponse { } } +#[derive(Debug, Deserialize, Serialize)] +struct JsonEncodedAuthorizationResponse { + /// `vp_token` is JSON string encoded. + pub(crate) vp_token: String, + /// `presentation_submission` is JSON string encoded. + pub(crate) presentation_submission: String, +} + #[derive(Debug, Clone, Deserialize, Serialize)] pub struct UnencodedAuthorizationResponse { pub vp_token: VpToken, @@ -57,18 +54,10 @@ pub struct UnencodedAuthorizationResponse { impl UnencodedAuthorizationResponse { /// Encode the Authorization Response as 'application/x-www-form-urlencoded'. pub fn into_x_www_form_urlencoded(self) -> Result { - let mut map = HashMap::<&str, String>::new(); - let vp_token = - serde_json::to_string(&self.vp_token).context("failed to parse JSON vp token")?; - let presentation_submission = serde_json::to_string(&self.presentation_submission) - .context("failed to encode presentation_submission as JSON")?; - - map.insert("presentation_submission", presentation_submission); - map.insert("vp_token", vp_token); - - let encoded = serde_urlencoded::to_string(&map).context( - "failed to encode presentation_submission as 'application/x-www-form-urlencoded'", - )?; + let encoded = serde_urlencoded::to_string(JsonEncodedAuthorizationResponse::from(self)) + .context( + "failed to encode presentation_submission as 'application/x-www-form-urlencoded'", + )?; Ok(encoded) } @@ -84,6 +73,22 @@ impl UnencodedAuthorizationResponse { } } +impl From for JsonEncodedAuthorizationResponse { + fn from(value: UnencodedAuthorizationResponse) -> Self { + let vp_token = serde_json::to_string(&value.vp_token) + // SAFTEY: VP Token will always be a valid JSON object. + .unwrap(); + let presentation_submission = serde_json::to_string(&value.presentation_submission) + // SAFETY: presentation submission will always be a valid JSON object. + .unwrap(); + + Self { + vp_token, + presentation_submission, + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct JwtAuthorizationResponse { /// Can be JWT or JWE. From f1ecd8b4ad70efe447a7327bd25d60bed3582476 Mon Sep 17 00:00:00 2001 From: Ryan Tate Date: Mon, 18 Nov 2024 09:30:32 -0800 Subject: [PATCH 7/7] allow unencoded authorization request Signed-off-by: Ryan Tate --- src/core/authorization_request/verification/did.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/core/authorization_request/verification/did.rs b/src/core/authorization_request/verification/did.rs index cd69956..42fba26 100644 --- a/src/core/authorization_request/verification/did.rs +++ b/src/core/authorization_request/verification/did.rs @@ -40,6 +40,14 @@ pub async fn verify_with_resolver( bail!("request was signed with unsupported algorithm: {alg}") } + // This bypass is for unencoded JWT requests, but we will need to change this later + // so that trust is preserved when receiving unencoded requests + // NOTE: This requires that `Algorithm::None` is permitted in the wallet metadata + // Otherwise, this function will error in the previous assertion. + if alg.contains("none") { + return Ok(()); + } + let Json::String(kid) = headers .remove("kid") .context("'kid' was missing from jwt headers")?