Skip to content

Commit

Permalink
move validation to auth response impl instead of presentation definit…
Browse files Browse the repository at this point in the history
…ion impl

Signed-off-by: Ryan Tate <[email protected]>
  • Loading branch information
Ryanmtate committed Aug 28, 2024
1 parent 51e11e8 commit 65e1b11
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 90 deletions.
35 changes: 19 additions & 16 deletions src/core/credential_format/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ use std::collections::HashMap;

use serde::{Deserialize, Serialize};

// TODO: Does the `isomdl` crate provide this constant value, or another crate?
const ORG_ISO_18013_5_1_MDL: &str = "org.iso.18013.5.1.mDL";

/// A Json object of claim formats.
pub type ClaimFormatMap = HashMap<ClaimFormatDesignation, ClaimFormatPayload>;

Expand All @@ -20,53 +23,53 @@ pub type ClaimFormatMap = HashMap<ClaimFormatDesignation, ClaimFormatPayload>;
pub enum ClaimFormat {
#[serde(rename = "jwt")]
Jwt {
// The algorithm used to sign the JWT.
/// The algorithm used to sign the JWT.
alg: Vec<String>,
},
#[serde(rename = "jwt_vc")]
JwtVc {
// The algorithm used to sign the JWT verifiable credential.
/// The algorithm used to sign the JWT verifiable credential.
alg: Vec<String>,
},
#[serde(rename = "jwt_vp")]
JwtVp {
// The algorithm used to sign the JWT verifiable presentation.
/// The algorithm used to sign the JWT verifiable presentation.
alg: Vec<String>,
},
#[serde(rename = "jwt_vc_json")]
JwtVcJson {
// Used in the OID4VP specification for wallet methods supported.
/// Used in the OID4VP specification for wallet methods supported.
alg_values_supported: Vec<String>,
},
#[serde(rename = "jwt_vp_json")]
JwtVpJson {
// Used in the OID4VP specification for wallet methods supported.
/// Used in the OID4VP specification for wallet methods supported.
alg_values_supported: Vec<String>,
},
#[serde(rename = "ldp")]
Ldp {
// The proof type used to sign the linked data proof.
// e.g., "JsonWebSignature2020", "Ed25519Signature2018", "EcdsaSecp256k1Signature2019", "RsaSignature2018"
/// The proof type used to sign the linked data proof.
/// e.g., "JsonWebSignature2020", "Ed25519Signature2018", "EcdsaSecp256k1Signature2019", "RsaSignature2018"
proof_type: Vec<String>,
},
#[serde(rename = "ldp_vc")]
LdpVc {
// The proof type used to sign the linked data proof verifiable credential.
/// The proof type used to sign the linked data proof verifiable credential.
proof_type: Vec<String>,
},
#[serde(rename = "ldp_vp")]
LdpVp {
// The proof type used to sign the linked data proof verifiable presentation.
/// The proof type used to sign the linked data proof verifiable presentation.
proof_type: Vec<String>,
},
#[serde(rename = "ac_vc")]
AcVc {
// The proof type used to sign the anoncreds verifiable credential.
/// The proof type used to sign the anoncreds verifiable credential.
proof_type: Vec<String>,
},
#[serde(rename = "ac_vp")]
AcVp {
// The proof type used to sign the anoncreds verifiable presentation.
/// The proof type used to sign the anoncreds verifiable presentation.
proof_type: Vec<String>,
},
#[serde(rename = "mso_mdoc")]
Expand Down Expand Up @@ -259,18 +262,18 @@ pub enum CredentialType {
///
/// Given there is no universal standard for how to present a vehicle title credential,
/// the inner String provides a dynamic way to represent a vehicle title credential.
// TODO: is there a standard identifier for a vehicle title credential instead of `vehicle_title`?
#[serde(rename = "vehicle_title")]
VehicleTitle(String),
// Add additional credential types here.
//
// Fallback to a string for any other credential type.
// TODO: Add additional credential types here. Fallback to a string for any other credential type.
/// Other credential types not covered by the above.
Other(String),
}

impl From<&str> for CredentialType {
fn from(s: &str) -> Self {
match s {
s if s.contains("org.iso.18013.5.1.mDL") => Self::Iso18013_5_1mDl,
s if s.contains(ORG_ISO_18013_5_1_MDL) => Self::Iso18013_5_1mDl,
s if s.contains("vehicle_title.") => Self::VehicleTitle(s.to_string()),
s => Self::Other(s.to_string()),
}
Expand All @@ -280,7 +283,7 @@ impl From<&str> for CredentialType {
impl From<CredentialType> for String {
fn from(cred_type: CredentialType) -> Self {
match cred_type {
CredentialType::Iso18013_5_1mDl => "org.iso.18013.5.1.mDL".to_string(),
CredentialType::Iso18013_5_1mDl => ORG_ISO_18013_5_1_MDL.to_string(),
CredentialType::VehicleTitle(title) => format!("vehicle_title.{title}"),
CredentialType::Other(s) => s,
}
Expand Down
65 changes: 1 addition & 64 deletions src/core/presentation_definition.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use super::credential_format::*;
use super::input_descriptor::*;
use super::presentation_submission::*;
use super::response::AuthorizationResponse;

use std::collections::HashMap;

Expand Down Expand Up @@ -153,69 +152,7 @@ impl PresentationDefinition {
}

/// Validate a presentation submission against the presentation definition.
///
/// Checks the underlying presentation submission parsed from the authorization response,
/// against the input descriptors of the presentation definition.
#[deprecated(
note = "This method is to be replaced by a top-level function that takes a presentation definition and a presentation submission."
)]
pub async fn validate_authorization_response(
&self,
auth_response: &AuthorizationResponse,
) -> Result<()> {
match auth_response {
AuthorizationResponse::Jwt(_jwt) => {
// TODO: Handle JWT Encoded authorization response.

bail!("Authorization Response Presentation Definition validation not implemented.")
}
AuthorizationResponse::Unencoded(response) => {
let presentation_submission = response.presentation_submission().parsed();

// Ensure the definition id matches the submission's definition id.
if presentation_submission.definition_id() != self.id() {
bail!("Presentation Definition ID does not match the Presentation Submission.")
}

// Parse the descriptor map into a HashMap for easier access
let descriptor_map: HashMap<String, DescriptorMap> = presentation_submission
.descriptor_map()
.iter()
.map(|descriptor_map| (descriptor_map.id().to_owned(), descriptor_map.clone()))
.collect();

// Parse the VP Token according to the Spec, here:
// https://openid.net/specs/openid-4-verifiable-presentations-1_0.html#section-6.1-2.2
let vp_payload = response.vp_token().parse()?;

// Check if the vp_payload is an array of VPs
match vp_payload.as_array() {
None => {
// handle a single verifiable presentation
self.validate_definition_map(
VerifiablePresentation(json_syntax::Value::from(vp_payload)),
&descriptor_map,
)
}
Some(vps) => {
// Each item in the array is a VP
for vp in vps {
// handle the verifiable presentation
self.validate_definition_map(
VerifiablePresentation(json_syntax::Value::from(vp.clone())),
&descriptor_map,
)?;
}

Ok(())
}
}
}
}
}

/// Validate a presentation submission against the presentation definition.
fn validate_definition_map(
pub fn validate_definition_map(
&self,
verifiable_presentation: VerifiablePresentation,
descriptor_map: &HashMap<String, DescriptorMap>,
Expand Down
84 changes: 80 additions & 4 deletions src/core/response/mod.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
use std::collections::BTreeMap;
use super::{
object::{ParsingErrorContext, UntypedObject},
presentation_definition::PresentationDefinition,
presentation_submission::DescriptorMap,
};

use anyhow::{Context, Error, Result};
use std::collections::{BTreeMap, HashMap};

use anyhow::{bail, Context, Error, Result};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use ssi_claims::jwt::VerifiablePresentation;
use url::Url;

use self::parameters::{PresentationSubmission, VpToken};

use super::object::{ParsingErrorContext, UntypedObject};

pub mod parameters;

#[derive(Debug, Clone)]
Expand All @@ -35,6 +40,77 @@ impl AuthorizationResponse {

Ok(Self::Unencoded(UntypedObject(map).try_into()?))
}

/// Validate an authorization response against a presentation definition.
///
/// This method will parse the presentation submission from the auth response and
/// validate it against the provided presentation definition.
///
/// # Parameters
///
/// - `self` - The authorization response to validate.
/// - `presentation_definition` - The presentation definition to validate against.
///
/// # Errors
///
/// This method will return an error if the presentation submission does not match the
/// presentation definition.
///
/// # Returns
///
/// This method will return `Ok(())` if the presentation submission matches the presentation
/// definition.
pub fn validate(&self, presentation_definition: &PresentationDefinition) -> Result<()> {
match self {
AuthorizationResponse::Jwt(_jwt) => {
// TODO: Handle JWT Encoded authorization response.

bail!("Authorization Response Presentation Definition validation not implemented.")
}
AuthorizationResponse::Unencoded(response) => {
let presentation_submission = response.presentation_submission().parsed();

// Ensure the definition id matches the submission's definition id.
if presentation_submission.definition_id() != presentation_definition.id() {
bail!("Presentation Definition ID does not match the Presentation Submission.")
}

// Parse the descriptor map into a HashMap for easier access
let descriptor_map: HashMap<String, DescriptorMap> = presentation_submission
.descriptor_map()
.iter()
.map(|descriptor_map| (descriptor_map.id().to_owned(), descriptor_map.clone()))
.collect();

// Parse the VP Token according to the Spec, here:
// https://openid.net/specs/openid-4-verifiable-presentations-1_0.html#section-6.1-2.2
let vp_payload = response.vp_token().parse()?;

// Check if the vp_payload is an array of VPs
match vp_payload.as_array() {
None => {
// handle a single verifiable presentation
presentation_definition.validate_definition_map(
VerifiablePresentation(json_syntax::Value::from(vp_payload)),
&descriptor_map,
)
}
Some(vps) => {
// Each item in the array is a VP
for vp in vps {
// handle the verifiable presentation
presentation_definition.validate_definition_map(
VerifiablePresentation(json_syntax::Value::from(vp.clone())),
&descriptor_map,
)?;
}

Ok(())
}
}
}
}
}
}

#[derive(Debug, Clone)]
Expand Down
6 changes: 1 addition & 5 deletions tests/jwt_vc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,7 @@ impl AsyncHttpClient for MockHttpClient {
.context("failed to parse authorization response request")?,
|session, auth_response| {
Box::pin(async move {
match session
.presentation_definition
.validate_authorization_response(&auth_response)
.await
{
match auth_response.validate(&session.presentation_definition) {
Ok(_) => Outcome::Success {
info: serde_json::Value::Null,
},
Expand Down
2 changes: 1 addition & 1 deletion tests/jwt_vp.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::str::FromStr;

use anyhow::Result;
use base64::{encode_engine_string, prelude::*};
use base64::prelude::*;
use oid4vp::holder::verifiable_presentation_builder::{
VerifiablePresentationBuilder, VerifiablePresentationBuilderOptions,
};
Expand Down

0 comments on commit 65e1b11

Please sign in to comment.