Skip to content

Commit

Permalink
update vp token definition
Browse files Browse the repository at this point in the history
Signed-off-by: Ryan Tate <ryan.michael.tate@gmail.com>

use base64 url encoding

Signed-off-by: Ryan Tate <ryan.michael.tate@gmail.com>

remove vp builder, use AnyJsonPresentation

Signed-off-by: Ryan Tate <ryan.michael.tate@gmail.com>

parse VC from VerifiableCredential to SpeicalizedJsonCredential to use JsonPresentation

Signed-off-by: Ryan Tate <ryan.michael.tate@gmail.com>

update comments

Signed-off-by: Ryan Tate <ryan.michael.tate@gmail.com>

fix from any json presentation for vp token impl

Signed-off-by: Ryan Tate <ryan.michael.tate@gmail.com>
  • Loading branch information
Ryanmtate committed Sep 20, 2024
1 parent 575df19 commit 3dcdb7e
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 216 deletions.
77 changes: 61 additions & 16 deletions src/core/response/parameters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ use crate::core::object::TypedParameter;
use crate::core::presentation_submission::PresentationSubmission as PresentationSubmissionParsed;

use anyhow::Error;
use serde_json::Value as Json;
use base64::prelude::*;
use serde_json::{Map, Value as Json};
use ssi::prelude::AnyJsonPresentation;

#[derive(Debug, Clone)]
pub struct IdToken(pub String);
Expand All @@ -26,18 +28,26 @@ impl From<IdToken> for Json {
}
}

// TODO: Update this type to something like:
//
// enum VpToken {
// Single(String),
// SingleAsMap(Map<String, Value>),
// Many(Vec<VpToken>),
// }
//
// 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(Vec<u8>),
SingleAsMap(Map<String, Json>),
Many(Vec<VpToken>),
}

impl TypedParameter for VpToken {
const KEY: &'static str = "vp_token";
Expand All @@ -47,13 +57,48 @@ impl TryFrom<Json> for VpToken {
type Error = Error;

fn try_from(value: Json) -> Result<Self, Self::Error> {
serde_json::from_value(value).map(Self).map_err(Into::into)
match value {
// NOTE: When parsing a Json string, it must be base64Url encoded,
// therefore the base64 encoded string is decoded for internal representation
// of the VP token.
Json::String(s) => Ok(Self::Single(BASE64_URL_SAFE_NO_PAD.decode(s)?)),
// NOTE: When the Json is an object, it must be a map.
Json::Object(map) => Ok(Self::SingleAsMap(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<VpToken> for Json {
fn from(value: VpToken) -> Self {
value.0.into()
impl TryFrom<VpToken> for Json {
type Error = Error;

fn try_from(value: VpToken) -> Result<Self, Self::Error> {
match value {
VpToken::Single(s) => Ok(serde_json::Value::String(BASE64_URL_SAFE_NO_PAD.encode(s))),
VpToken::SingleAsMap(map) => Ok(serde_json::Value::Object(map)),
VpToken::Many(tokens) => {
let mut arr: Vec<Json> = Vec::new();
for token in tokens {
arr.push(token.try_into()?);
}
Ok(arr.into())
}
}
}
}

impl TryFrom<AnyJsonPresentation> for VpToken {
type Error = Error;

fn try_from(vp: AnyJsonPresentation) -> Result<Self, Self::Error> {
Ok(VpToken::Single(serde_json::to_vec(&vp)?))
}
}

Expand Down
1 change: 0 additions & 1 deletion src/holder/mod.rs

This file was deleted.

164 changes: 0 additions & 164 deletions src/holder/verifiable_presentation_builder.rs

This file was deleted.

1 change: 0 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
pub mod core;
pub mod holder;
#[cfg(test)]
pub(crate) mod tests;
mod utils;
Expand Down
13 changes: 6 additions & 7 deletions tests/e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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(),
));

Expand Down
72 changes: 45 additions & 27 deletions tests/jwt_vp.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
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::VerifiableCredential;
use ssi::claims::vc::v1::{Context, JsonPresentation};
use ssi::dids::DIDKey;
use ssi::jwk::JWK;

pub async fn create_test_verifiable_presentation() -> Result<String> {
let verifier = JWK::from_str(include_str!("examples/verifier.jwk"))?;
use ssi::json_ld::iref::UriBuf;
use ssi::prelude::AnyJsonPresentation;
use uuid::Uuid;

pub async fn create_test_verifiable_presentation() -> Result<AnyJsonPresentation> {
let signer = P256Signer::new(
p256::SecretKey::from_jwk_str(include_str!("examples/subject.jwk"))
.unwrap()
Expand All @@ -21,28 +16,51 @@ pub async fn create_test_verifiable_presentation() -> Result<String> {
.unwrap();

let holder_did = DIDKey::generate_url(signer.jwk())?;
let verifier_did = DIDKey::generate_url(&verifier)?;

println!("holder_did: {:?}", holder_did);

// Create a verifiable presentation using the `examples/vc.jwt` file
// The signer information is the holder's key, also found in the `examples/subject.jwk` file.
let verifiable_credential: jwt::VerifiableCredential =
let verifiable_credential: VerifiableCredential =
ssi::claims::jwt::decode_unverified(include_str!("examples/vc.jwt"))?;

let verifiable_presentation =
VerifiablePresentationBuilder::from_options(VerifiablePresentationBuilderOptions {
issuer: holder_did.clone(),
subject: holder_did.clone(),
audience: verifier_did.clone(),
expiration_secs: 3600,
credentials: vec![verifiable_credential],
nonce: "random_nonce".into(),
});
let parsed_vc = &verifiable_credential
.0
.as_object()
.expect("Failed to parse credential")
.get("vc")
.next()
.expect("Failed to parse credential")
.to_owned();

// Encode the verifiable presentation as base64 encoded payload.
let vp_token = verifiable_presentation.0.to_string();
let mut json_credential = parsed_vc.clone().into_serde_json();

// encode as base64.
let base64_encoded_vp = BASE64_STANDARD.encode(vp_token);
// NOTE: the `id` in the VC is a UUID string, but it should be a URI
// according to the `SpecializedJsonCredential` type.
json_credential.as_object_mut().map(|obj| {
// Update the ID to be a UriBuf.
let id = obj
.get("id")
.expect("failed to parse vc id")
.as_str()
.expect("failed to parse id into string");

let id_urn = format!("urn:uuid:{id}").as_bytes().to_vec();
let id_url = UriBuf::new(id_urn).expect("failed to parse id into UriBuf");
obj.insert("id".to_string(), serde_json::json!(id_url));
});

let mut vp = JsonPresentation::default();
vp.context = Context::default();
vp.verifiable_credentials
.push(serde_json::from_value(json_credential)?);
vp.holder = Some(holder_did.into());
vp.id = UriBuf::new(
format!("urn:uuid:{}", Uuid::new_v4().to_string())
.as_bytes()
.to_vec(),
)
.ok();

Ok(base64_encoded_vp)
Ok(AnyJsonPresentation::V1(vp))
}

0 comments on commit 3dcdb7e

Please sign in to comment.