diff --git a/src/core/response/parameters.rs b/src/core/response/parameters.rs index 8fd36e2..bef23ac 100644 --- a/src/core/response/parameters.rs +++ b/src/core/response/parameters.rs @@ -3,7 +3,9 @@ use crate::core::object::TypedParameter; use crate::core::presentation_submission::PresentationSubmission as PresentationSubmissionParsed; use anyhow::Error; +use base64::prelude::*; use serde_json::Value as Json; +use ssi::claims::jwt::VerifiablePresentation; #[derive(Debug, Clone)] pub struct IdToken(pub String); @@ -26,18 +28,26 @@ impl From for Json { } } -// TODO: Update this type to something like: -// -// enum VpToken { -// Single(String), -// SingleAsMap(Map), -// Many(Vec), -// } -// -// See: https://github.com/spruceid/oid4vp-rs/pull/8#discussion_r1750274969 -// +/// OpenID Connect for Verifiable Presentations specification defines `vp_token` parameter: +/// +/// > JSON String or JSON object that MUST contain a single Verifiable Presentation or +/// > an array of JSON Strings and JSON objects each of them containing a Verifiable Presentations. +/// > +/// > Each Verifiable Presentation MUST be represented as a JSON string (that is a Base64url encoded value) +/// > or a JSON object depending on a format as defined in Appendix A of [OpenID.VCI]. +/// > +/// > If Appendix A of [OpenID.VCI] defines a rule for encoding the respective Credential +/// > format in the Credential Response, this rules MUST also be followed when encoding Credentials of +/// > this format in the vp_token response parameter. Otherwise, this specification does not require +/// > any additional encoding when a Credential format is already represented as a JSON object or a JSON string. +/// +/// See: [OpenID.VP#section-6.1-2.2](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html#section-6.1-2.2) #[derive(Debug, Clone)] -pub struct VpToken(pub String); +pub enum VpToken { + Single(String), + SingleAsMap(Json), + Many(Vec), +} impl TypedParameter for VpToken { const KEY: &'static str = "vp_token"; @@ -47,13 +57,80 @@ impl TryFrom for VpToken { type Error = Error; fn try_from(value: Json) -> Result { - serde_json::from_value(value).map(Self).map_err(Into::into) + match value { + // NOTE: When parsing a Json string object, it must be base64 encoded. + Json::String(s) => Ok(Self::Single(BASE64_STANDARD.encode(s.as_bytes()))), + // NOTE: When the Json is an object, it must be a map. + Json::Object(map) => Ok(Self::SingleAsMap(Json::Object(map))), + Json::Array(arr) => { + let mut tokens = Vec::new(); + for value in arr { + tokens.push(Self::try_from(value)?); + } + Ok(Self::Many(tokens)) + } + _ => Err(Error::msg("Invalid vp_token")), + } } } -impl From for Json { - fn from(value: VpToken) -> Self { - value.0.into() +impl TryFrom for Json { + type Error = Error; + + fn try_from(value: VpToken) -> Result { + match value { + VpToken::Single(s) => { + // Decode base64 string. + let bytes = BASE64_STANDARD.decode(s.as_bytes())?; + let s = String::from_utf8(bytes)?; + + Ok(s.into()) + } + VpToken::SingleAsMap(map) => Ok(map), + VpToken::Many(tokens) => { + let mut arr: Vec = Vec::new(); + for token in tokens { + arr.push(token.try_into()?); + } + Ok(arr.into()) + } + } + } +} + +impl TryFrom for VpToken { + type Error = Error; + + fn try_from(vp: VerifiablePresentation) -> Result { + Self::try_from(vp.0.into_serde_json()) + } +} + +impl TryFrom for Vec { + type Error = Error; + + fn try_from(token: VpToken) -> Result { + let mut vps = Vec::new(); + + match token { + VpToken::Single(s) => { + let bytes = BASE64_STANDARD.decode(s.as_bytes())?; + let s = String::from_utf8(bytes)?; + let value = json_syntax::Value::from_serde_json(s.try_into()?); + vps.push(VerifiablePresentation(value)) + } + VpToken::SingleAsMap(map) => { + let value = json_syntax::Value::from_serde_json(map.try_into()?); + vps.push(VerifiablePresentation(value)) + } + VpToken::Many(tokens) => { + for token in tokens { + vps.extend(Self::try_from(token)?); + } + } + } + + Ok(vps) } } diff --git a/tests/e2e.rs b/tests/e2e.rs index b2ad0e7..931ae32 100644 --- a/tests/e2e.rs +++ b/tests/e2e.rs @@ -7,7 +7,7 @@ use oid4vp::{ object::UntypedObject, presentation_definition::*, presentation_submission::*, - response::{parameters::VpToken, AuthorizationResponse, UnencodedAuthorizationResponse}, + response::{AuthorizationResponse, UnencodedAuthorizationResponse}, }, verifier::session::{Outcome, Status}, wallet::Wallet, @@ -129,14 +129,13 @@ async fn w3c_vc_did_client_direct_post() { descriptor_map, ); + let vp = create_test_verifiable_presentation() + .await + .expect("failed to create verifiable presentation"); + let response = AuthorizationResponse::Unencoded(UnencodedAuthorizationResponse( Default::default(), - VpToken( - create_test_verifiable_presentation() - .await - .expect("failed to create verifiable presentation") - .to_string(), - ), + vp.try_into().expect("failed to convert vp to vp token"), presentation_submission.try_into().unwrap(), )); diff --git a/tests/jwt_vp.rs b/tests/jwt_vp.rs index 1adcead..eaee46f 100644 --- a/tests/jwt_vp.rs +++ b/tests/jwt_vp.rs @@ -1,16 +1,15 @@ use std::str::FromStr; use anyhow::Result; -use base64::prelude::*; use oid4vp::holder::verifiable_presentation_builder::{ VerifiablePresentationBuilder, VerifiablePresentationBuilderOptions, }; use oid4vp::verifier::request_signer::P256Signer; -use ssi::claims::jwt; +use ssi::claims::jwt::{self, VerifiablePresentation}; use ssi::dids::DIDKey; use ssi::jwk::JWK; -pub async fn create_test_verifiable_presentation() -> Result { +pub async fn create_test_verifiable_presentation() -> Result { let verifier = JWK::from_str(include_str!("examples/verifier.jwk"))?; let signer = P256Signer::new( @@ -38,11 +37,5 @@ pub async fn create_test_verifiable_presentation() -> Result { nonce: "random_nonce".into(), }); - // Encode the verifiable presentation as base64 encoded payload. - let vp_token = verifiable_presentation.0.to_string(); - - // encode as base64. - let base64_encoded_vp = BASE64_STANDARD.encode(vp_token); - - Ok(base64_encoded_vp) + Ok(verifiable_presentation) }