Skip to content

Commit

Permalink
Fix the presentation submission in the oid4vp-rs e2e test. (#8)
Browse files Browse the repository at this point in the history
* wip: use ssi 0.8.1, debugging e2e test using did resolver

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

* wip: use VerificationMethodDIDResolver for DIDClient constructor

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

* remove unused imports

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

* wip: debugging did resolver jwk not found in e2e flow

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

* feat/improve-presentation-exchange-support-in-oid4vp-rs

add implementation methods for Presentation Definition.

WIP: Need to continue work for Presentation Submission and the rest
of the structs used in the presentation exchange flow.

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

Co-authored-by: Todd Showalter <todd.showalter@spruce.id>

* add getter methods for presentation definition member fields

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

* fix broken links in documentation

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

* update presentation submission implementation

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

* fix test cases. todo: update test cases to use newly created interface for presentation exchange

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

* update json schema validator to use anyhow result type

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

* wip: use latest implementation changes, update tests

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

* fix verification method did resolver tests

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

* wip: remove unused imports

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

* add ClaimFormat type

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

* add regex support for string pattern matching

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

* revert uuid presentation definition id type to string

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

* fix: ensure negation of regex pattern match for error

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

* add 'other' variante to claim format type

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

* remove commented out code

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

* use ssi sub-crates instead of main ssi dependency

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

* add jwt_vc_json and jwt_vp_json claim formats

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

* wip: construct verifiable presentation for e2e test

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

* wip: perform validation on presentation submission

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

* ensure range exclusive values are checked; fix inclusive range values

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

* ensure enum values are parsed in alpha descending order

This is a fix for a bug where ClaimFormat::JwtVc was being parsed
when ClaimFormat::JwtVp should have been instead. The fix is to
order the enum fields in alphabetical descending order, such that
VP comes BEFORE VC, and so on, for the other formats.

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

* wip: verify authorized response presentation submission

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

* ensure json schema validator adheres to the specification

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

* ensure json schema validator adheres to the specification

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

* add unit tests for schema validator

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

* use serde default value for constraints field if not found during deserialization

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

* remove unused imports

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

* remove unsed imports in test files

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

* update vp token

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

* rebase with main

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

* remove unused dependencies

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

* add rand crate and provide random nonce method using Rng trait

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

* remove todos and update comments, use JWKResolver instead of VerificationMethodDIDResolver

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

* fix clippy warnings

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

* verify jwt in validate_authorization_response presentation definition method

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

* Update tests/e2e.rs

Co-authored-by: Jacob <jacob.ward@spruceid.com>

* update descriptor map nested path in e2e example

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

* remove dependency patches

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

* add paths to example for input descriptor constraints field

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

* use top level json path for jwt_vp_json

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

* Update src/verifier/client.rs

Co-authored-by: Jacob <jacob.ward@spruceid.com>

* rebase

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

* revert validation function async signature to use boxed pin future

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

* add helper methods

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

* debug: jwt claim signing does not include public key

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

* make request signer methods return a result

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

* refactor presentation exchange file into smaller modules

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

* fix clippy warnings

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

* fix outcome error cause

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

* add credential format and add requested fields helper method to input descriptor

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

* Update src/core/presentation_definition.rs

Co-authored-by: Jacob <jacob.ward@spruceid.com>

* Update src/core/presentation_definition.rs

Co-authored-by: Jacob <jacob.ward@spruceid.com>

* wip: add notes on required fields parsing

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

* update vp token base64 encoding and check for multiple vp payloads

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

* move validation to auth response impl instead of presentation definition impl

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

* remove feature gated non-optional deps

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

* remove cfg features

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

* wip: handle groups in presentation definition, input descriptor tests

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

* remove cfg feature tags

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

* remove extraneous metadata helper methods; use UntypedObject for dereferencing

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

* add submission requirement check for presentation validation

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

* add validate method to vp token; ensure submission requirement all rule is enforced.

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

* add vp token validate unencoded method. fix minor todos.

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

* Update Cargo.toml

Co-authored-by: Jacob <jacob.ward@spruceid.com>

* fix other claim format serde

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

* use Vec::is_empty versus Option::is_none for various serialization fields

This commit also removes validation logic from vp token response struct.

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

* revert to use of ClaimFormatMap to pass presentation defintion test suite

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

---------

Signed-off-by: Ryan Tate <ryan.michael.tate@gmail.com>
Co-authored-by: Todd Showalter <todd.showalter@spruce.id>
Co-authored-by: Jacob <jacob.ward@spruceid.com>
  • Loading branch information
3 people authored Sep 16, 2024
1 parent 2842f2b commit 13c4733
Show file tree
Hide file tree
Showing 41 changed files with 2,500 additions and 1,082 deletions.
21 changes: 10 additions & 11 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,28 @@ repository = "https://github.com/spruceid/oidc4vp-rs/"
documentation = "https://docs.rs/oid4vp/"

[features]
reqwest = ["dep:reqwest"]
p256 = ["dep:p256"]
default = []

[dependencies]
anyhow = "1.0.75"
async-trait = "0.1.73"
base64 = "0.21.4"
did-web = "0.2.2"
http = "1.1.0"
# NOTE: ssi-jwk uses syntax_json, but does not use the `serde_json` feature for serialization/deserialization.
json-syntax = { version = "0.12.5", features = ["serde_json"] }
jsonpath_lib = "0.3.0"
jsonschema = "0.18.0"
oid4vp-frontend = { version = "0.1.0", path = "oid4vp-frontend" }
p256 = { version = "0.13.2", features = ["jwk"], optional = true }
regex = "1.10.6"
reqwest = { version = "0.12.5", features = ["rustls-tls"], optional = true }
p256 = { version = "0.13.2", features = ["jwk"] }
rand = { version = "0.8.5" }
reqwest = { version = "0.12.5", features = ["rustls-tls"] }
serde = "1.0.188"
serde_cbor = "0.11.2"
serde_json = "1.0.107"
serde_qs = "0.12.0"
serde_urlencoded = "0.7.1"
ssi = "0.7"
thiserror = "1.0.49"
ssi-claims = "0.1.0"
ssi-dids = "0.2.0"
ssi-jwk = { version = "0.2.1", features = ["secp256r1"] }
ssi-verification-methods = "0.1.1"
tokio = "1.32.0"
tracing = "0.1.37"
url = { version = "2.4.1", features = ["serde"] }
Expand All @@ -40,7 +40,6 @@ x509-cert = "0.2.4"
serde_path_to_error = "0.1.8"
tokio = { version = "1.32.0", features = ["macros"] }
did-method-key = "0.2"
oid4vp = { path = ".", features = ["p256"] }

[target.'cfg(target_arch = "wasm32")'.dependencies]
uuid = { version = "1.2", features = ["v4", "serde", "js"] }
Expand Down
52 changes: 43 additions & 9 deletions src/core/authorization_request/parameters.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use std::fmt;
use std::{fmt, ops::Deref};

use crate::core::{
object::{ParsingErrorContext, TypedParameter, UntypedObject},
presentation_definition::PresentationDefinition as PresentationDefinitionParsed,
util::{base_request, AsyncHttpClient},
};
use anyhow::{bail, Context, Error, Ok};
Expand Down Expand Up @@ -193,7 +194,42 @@ impl TryFrom<Json> for ClientMetadataUri {
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Nonce(pub String);
pub struct Nonce(String);

impl From<String> for Nonce {
fn from(value: String) -> Self {
Self(value)
}
}

impl From<&str> for Nonce {
fn from(value: &str) -> Self {
Self(value.to_string())
}
}

impl Deref for Nonce {
type Target = String;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl std::fmt::Display for Nonce {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}

impl Nonce {
/// Crate a new `Nonce` with a random value of the given length.
pub fn random(rng: &mut impl rand::Rng, length: usize) -> Self {
use rand::distributions::{Alphanumeric, DistString};

Self(Alphanumeric.sample_string(rng, length))
}
}

impl TypedParameter for Nonce {
const KEY: &'static str = "nonce";
Expand Down Expand Up @@ -432,25 +468,23 @@ impl From<State> for Json {
#[derive(Debug, Clone)]
pub struct PresentationDefinition {
raw: Json,
parsed: crate::presentation_exchange::PresentationDefinition,
parsed: PresentationDefinitionParsed,
}

impl PresentationDefinition {
pub fn into_parsed(self) -> crate::presentation_exchange::PresentationDefinition {
pub fn into_parsed(self) -> PresentationDefinitionParsed {
self.parsed
}

pub fn parsed(&self) -> &crate::presentation_exchange::PresentationDefinition {
pub fn parsed(&self) -> &PresentationDefinitionParsed {
&self.parsed
}
}

impl TryFrom<crate::presentation_exchange::PresentationDefinition> for PresentationDefinition {
impl TryFrom<PresentationDefinitionParsed> for PresentationDefinition {
type Error = Error;

fn try_from(
parsed: crate::presentation_exchange::PresentationDefinition,
) -> Result<Self, Self::Error> {
fn try_from(parsed: PresentationDefinitionParsed) -> Result<Self, Self::Error> {
let raw = serde_json::to_value(parsed.clone())?;
Ok(Self { raw, parsed })
}
Expand Down
14 changes: 8 additions & 6 deletions src/core/authorization_request/verification/did.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,18 @@ use crate::core::{
use anyhow::{bail, Context, Result};
use base64::prelude::*;
use serde_json::{Map, Value as Json};
use ssi::did_resolve::{resolve_key, DIDResolver};

use ssi_jwk::JWKResolver;

/// Default implementation of request validation for `client_id_scheme` `did`.
pub async fn verify_with_resolver(
wallet_metadata: &WalletMetadata,
request_object: &AuthorizationRequestObject,
request_jwt: String,
trusted_dids: Option<&[String]>,
resolver: &dyn DIDResolver,
resolver: impl JWKResolver,
) -> Result<()> {
let (headers_b64, _, _) = ssi::jws::split_jws(&request_jwt)?;
let (headers_b64, _, _) = ssi_claims::jws::split_jws(&request_jwt)?;

let headers_json_bytes = BASE64_URL_SAFE_NO_PAD
.decode(headers_b64)
Expand Down Expand Up @@ -64,11 +65,12 @@ pub async fn verify_with_resolver(
}
}

let jwk = resolve_key(&kid, resolver)
let jwk = resolver
.fetch_public_jwk(Some(&kid))
.await
.context("unable to resolve verification method from 'kid' header")?;
.context("unable to resolve key from verification method")?;

let _: Json = ssi::jwt::decode_verify(&request_jwt, &jwk)
let _: Json = ssi_claims::jwt::decode_verify(&request_jwt, &jwk)
.context("request signature could not be verified")?;

Ok(())
Expand Down
7 changes: 4 additions & 3 deletions src/core/authorization_request/verification/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,10 @@ pub(crate) async fn verify_request<W: Wallet + ?Sized>(
wallet: &W,
jwt: String,
) -> Result<AuthorizationRequestObject> {
let request: AuthorizationRequestObject = ssi::jwt::decode_unverified::<UntypedObject>(&jwt)
.context("unable to decode Authorization Request Object JWT")?
.try_into()?;
let request: AuthorizationRequestObject =
ssi_claims::jwt::decode_unverified::<UntypedObject>(&jwt)
.context("unable to decode Authorization Request Object JWT")?
.try_into()?;

validate_request_against_metadata(wallet, &request).await?;

Expand Down
4 changes: 0 additions & 4 deletions src/core/authorization_request/verification/verifier.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
use anyhow::Result;
#[cfg(feature = "p256")]
use anyhow::{bail, Error};
#[cfg(feature = "p256")]
use p256::ecdsa::signature::Verifier as _;
use x509_cert::spki::SubjectPublicKeyInfoRef;

Expand All @@ -15,11 +13,9 @@ pub trait Verifier: Sized {
fn verify(&self, payload: &[u8], signature: &[u8]) -> Result<()>;
}

#[cfg(feature = "p256")]
#[derive(Debug, Clone)]
pub struct P256Verifier(p256::ecdsa::VerifyingKey);

#[cfg(feature = "p256")]
impl Verifier for P256Verifier {
fn from_spki(spki: SubjectPublicKeyInfoRef<'_>, algorithm: String) -> Result<Self> {
if algorithm != "ES256" {
Expand Down
2 changes: 1 addition & 1 deletion src/core/authorization_request/verification/x509_san.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pub fn validate<V: Verifier>(
trusted_roots: Option<&[Certificate]>,
) -> Result<()> {
let client_id = request_object.client_id().0.as_str();
let (headers_b64, body_b64, sig_b64) = ssi::jws::split_jws(&request_jwt)?;
let (headers_b64, body_b64, sig_b64) = ssi_claims::jws::split_jws(&request_jwt)?;

let headers_json_bytes = BASE64_URL_SAFE_NO_PAD
.decode(headers_b64)
Expand Down
Loading

0 comments on commit 13c4733

Please sign in to comment.