Skip to content

Commit

Permalink
Add oid4vci exchange options to allow verification to be optional (#60)
Browse files Browse the repository at this point in the history
Signed-off-by: Tiago Nascimento <[email protected]>
  • Loading branch information
theosirian authored Dec 12, 2024
1 parent 4adc360 commit 0e771c8
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 60 deletions.
84 changes: 77 additions & 7 deletions MobileSdkRs/Sources/MobileSdkRs/mobile_sdk_rs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2757,7 +2757,7 @@ public protocol Oid4vciProtocol : AnyObject {

func clearContextMap() throws

func exchangeCredential(proofsOfPossession: [String]) async throws -> [CredentialResponse]
func exchangeCredential(proofsOfPossession: [String], options: Oid4vciExchangeOptions) async throws -> [CredentialResponse]

func exchangeToken() async throws -> String?

Expand Down Expand Up @@ -2857,13 +2857,13 @@ open func clearContextMap()throws {try rustCallWithError(FfiConverterTypeOid4vc
}
}

open func exchangeCredential(proofsOfPossession: [String])async throws -> [CredentialResponse] {
open func exchangeCredential(proofsOfPossession: [String], options: Oid4vciExchangeOptions)async throws -> [CredentialResponse] {
return
try await uniffiRustCallAsync(
rustFutureFunc: {
uniffi_mobile_sdk_rs_fn_method_oid4vci_exchange_credential(
self.uniffiClonePointer(),
FfiConverterSequenceString.lower(proofsOfPossession)
FfiConverterSequenceString.lower(proofsOfPossession),FfiConverterTypeOid4vciExchangeOptions.lower(options)
)
},
pollFunc: ffi_mobile_sdk_rs_rust_future_poll_rust_buffer,
Expand Down Expand Up @@ -6048,6 +6048,55 @@ public func FfiConverterTypeMDLReaderSessionData_lower(_ value: MdlReaderSession
}


public struct Oid4vciExchangeOptions {
public var verifyAfterExchange: Bool?

// Default memberwise initializers are never public by default, so we
// declare one manually.
public init(verifyAfterExchange: Bool?) {
self.verifyAfterExchange = verifyAfterExchange
}
}



extension Oid4vciExchangeOptions: Equatable, Hashable {
public static func ==(lhs: Oid4vciExchangeOptions, rhs: Oid4vciExchangeOptions) -> Bool {
if lhs.verifyAfterExchange != rhs.verifyAfterExchange {
return false
}
return true
}

public func hash(into hasher: inout Hasher) {
hasher.combine(verifyAfterExchange)
}
}


public struct FfiConverterTypeOid4vciExchangeOptions: FfiConverterRustBuffer {
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Oid4vciExchangeOptions {
return
try Oid4vciExchangeOptions(
verifyAfterExchange: FfiConverterOptionBool.read(from: &buf)
)
}

public static func write(_ value: Oid4vciExchangeOptions, into buf: inout [UInt8]) {
FfiConverterOptionBool.write(value.verifyAfterExchange, into: &buf)
}
}


public func FfiConverterTypeOid4vciExchangeOptions_lift(_ buf: RustBuffer) throws -> Oid4vciExchangeOptions {
return try FfiConverterTypeOid4vciExchangeOptions.lift(buf)
}

public func FfiConverterTypeOid4vciExchangeOptions_lower(_ value: Oid4vciExchangeOptions) -> RustBuffer {
return FfiConverterTypeOid4vciExchangeOptions.lower(value)
}


public struct StatusMessage {
/**
* The value of the entry in the status list
Expand Down Expand Up @@ -9659,6 +9708,27 @@ fileprivate struct FfiConverterOptionInt64: FfiConverterRustBuffer {
}
}

fileprivate struct FfiConverterOptionBool: FfiConverterRustBuffer {
typealias SwiftType = Bool?

public static func write(_ value: SwiftType, into buf: inout [UInt8]) {
guard let value = value else {
writeInt(&buf, Int8(0))
return
}
writeInt(&buf, Int8(1))
FfiConverterBool.write(value, into: &buf)
}

public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType {
switch try readInt(&buf) as Int8 {
case 0: return nil
case 1: return try FfiConverterBool.read(from: &buf)
default: throw UniffiInternalError.unexpectedOptionalTag
}
}
}

fileprivate struct FfiConverterOptionString: FfiConverterRustBuffer {
typealias SwiftType = String?

Expand Down Expand Up @@ -10908,11 +10978,11 @@ public func initializeMdlPresentationFromBytes(mdoc: Mdoc, uuid: Uuid)throws ->
)
})
}
public func oid4vciExchangeCredential(session: Oid4vciSession, proofsOfPossession: [String], contextMap: [String: String]?, httpClient: IHttpClient)async throws -> [CredentialResponse] {
public func oid4vciExchangeCredential(session: Oid4vciSession, proofsOfPossession: [String], options: Oid4vciExchangeOptions, contextMap: [String: String]?, httpClient: IHttpClient)async throws -> [CredentialResponse] {
return
try await uniffiRustCallAsync(
rustFutureFunc: {
uniffi_mobile_sdk_rs_fn_func_oid4vci_exchange_credential(FfiConverterTypeOid4vciSession.lower(session),FfiConverterSequenceString.lower(proofsOfPossession),FfiConverterOptionDictionaryStringString.lower(contextMap),FfiConverterTypeIHttpClient.lower(httpClient)
uniffi_mobile_sdk_rs_fn_func_oid4vci_exchange_credential(FfiConverterTypeOid4vciSession.lower(session),FfiConverterSequenceString.lower(proofsOfPossession),FfiConverterTypeOid4vciExchangeOptions.lower(options),FfiConverterOptionDictionaryStringString.lower(contextMap),FfiConverterTypeIHttpClient.lower(httpClient)
)
},
pollFunc: ffi_mobile_sdk_rs_rust_future_poll_rust_buffer,
Expand Down Expand Up @@ -11078,7 +11148,7 @@ private var initializationResult: InitializationResult = {
if (uniffi_mobile_sdk_rs_checksum_func_initialize_mdl_presentation_from_bytes() != 26972) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_mobile_sdk_rs_checksum_func_oid4vci_exchange_credential() != 59343) {
if (uniffi_mobile_sdk_rs_checksum_func_oid4vci_exchange_credential() != 25671) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_mobile_sdk_rs_checksum_func_oid4vci_exchange_token() != 3394) {
Expand Down Expand Up @@ -11207,7 +11277,7 @@ private var initializationResult: InitializationResult = {
if (uniffi_mobile_sdk_rs_checksum_method_oid4vci_clear_context_map() != 165) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_mobile_sdk_rs_checksum_method_oid4vci_exchange_credential() != 17336) {
if (uniffi_mobile_sdk_rs_checksum_method_oid4vci_exchange_credential() != 53636) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_mobile_sdk_rs_checksum_method_oid4vci_exchange_token() != 35585) {
Expand Down
145 changes: 95 additions & 50 deletions src/oid4vci/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pub use context_loader::context_loader_from_map;
pub use error::*;
pub use http_client::*;
pub use metadata::*;
pub use options::*;
pub use session::*;
pub use wrapper::*;

Expand All @@ -40,6 +41,7 @@ mod context_loader;
mod error;
mod http_client;
mod metadata;
mod options;
mod session;
mod wrapper;

Expand Down Expand Up @@ -265,6 +267,7 @@ pub async fn oid4vci_exchange_token(
pub async fn oid4vci_exchange_credential(
session: Arc<Oid4vciSession>,
proofs_of_possession: Vec<String>,
options: Oid4vciExchangeOptions,
context_map: Option<HashMap<String, String>>,
http_client: Arc<IHttpClient>,
) -> Result<Vec<CredentialResponse>, Oid4vciError> {
Expand Down Expand Up @@ -348,56 +351,98 @@ pub async fn oid4vci_exchange_credential(
.collect()
};

log::trace!("create vm_resolver");
let vm_resolver = AnyDidMethod::default().into_vm_resolver();
log::trace!("create verification params");
let params = match context_map {
Some(context_map) => VerificationParameters::from_resolver(vm_resolver)
.with_json_ld_loader(context_loader_from_map(context_map)?),
None => VerificationParameters::from_resolver(vm_resolver),
};

log::trace!("verify and convert http response into credential response");
futures::future::try_join_all(credential_responses.into_iter().map(
|credential_response| async {
use oid4vci::core::profiles::CoreProfilesCredentialResponseType::*;

match credential_response {
JwtVcJson(response) => {
log::trace!("processing a JwtVcJson");
let rt = tokio::runtime::Runtime::new().unwrap();
let ret = response.as_bytes().to_vec();

Ok(CredentialResponse {
format: CredentialFormat::JwtVcJson,
payload: rt
.block_on(async { response.verify_jwt(&params).await.map(|_| ret) })?,
})
}
JwtVcJsonLd(response) => {
log::trace!("processing a JwtVcJsonLd");
let vc = serde_json::to_string(&response)?;
let ret = serde_json::to_vec(&response)?;
Ok(CredentialResponse {
format: CredentialFormat::JwtVcJsonLd,
payload: any_credential_from_json_str(&vc)?
.verify(&params)
.await
.map(|_| ret)?,
})
if options.verify_after_exchange.unwrap_or(false) {
futures::future::try_join_all(credential_responses.into_iter().map(
|credential_response| async {
use oid4vci::core::profiles::CoreProfilesCredentialResponseType::*;

match credential_response {
JwtVcJson(response) => {
log::trace!("processing a JwtVcJson");
let ret = response.as_bytes().to_vec();

Ok(CredentialResponse {
format: CredentialFormat::JwtVcJson,
payload: ret,
})
}
JwtVcJsonLd(response) => {
log::trace!("processing a JwtVcJsonLd");
let ret = serde_json::to_vec(&response)?;
Ok(CredentialResponse {
format: CredentialFormat::JwtVcJsonLd,
payload: ret,
})
}
LdpVc(response) => {
log::trace!("processing an LdpVc");
let vc: AnyDataIntegrity<AnyJsonCredential> =
serde_json::from_value(response)?;
let ret = serde_json::to_vec(&vc)?;
Ok(CredentialResponse {
format: CredentialFormat::LdpVc,
payload: ret,
})
}
MsoMdoc(_) => todo!(),
}
LdpVc(response) => {
log::trace!("processing an LdpVc");
let vc: AnyDataIntegrity<AnyJsonCredential> = serde_json::from_value(response)?;
let ret = serde_json::to_vec(&vc)?;
Ok(CredentialResponse {
format: CredentialFormat::LdpVc,
payload: vc.verify(&params).await.map(|_| ret)?,
})
},
))
.await
} else {
log::trace!("create vm_resolver");
let vm_resolver = AnyDidMethod::default().into_vm_resolver();
log::trace!("create verification params");
let params = match context_map {
Some(context_map) => VerificationParameters::from_resolver(vm_resolver)
.with_json_ld_loader(context_loader_from_map(context_map)?),
None => VerificationParameters::from_resolver(vm_resolver),
};

log::trace!("verify and convert http response into credential response");
futures::future::try_join_all(credential_responses.into_iter().map(
|credential_response| async {
use oid4vci::core::profiles::CoreProfilesCredentialResponseType::*;

match credential_response {
JwtVcJson(response) => {
log::trace!("processing a JwtVcJson");
let rt = tokio::runtime::Runtime::new().unwrap();
let ret = response.as_bytes().to_vec();

Ok(CredentialResponse {
format: CredentialFormat::JwtVcJson,
payload: rt.block_on(async {
response.verify_jwt(&params).await.map(|_| ret)
})?,
})
}
JwtVcJsonLd(response) => {
log::trace!("processing a JwtVcJsonLd");
let vc = serde_json::to_string(&response)?;
let ret = serde_json::to_vec(&response)?;
Ok(CredentialResponse {
format: CredentialFormat::JwtVcJsonLd,
payload: any_credential_from_json_str(&vc)?
.verify(&params)
.await
.map(|_| ret)?,
})
}
LdpVc(response) => {
log::trace!("processing an LdpVc");
let vc: AnyDataIntegrity<AnyJsonCredential> =
serde_json::from_value(response)?;
let ret = serde_json::to_vec(&vc)?;
Ok(CredentialResponse {
format: CredentialFormat::LdpVc,
payload: vc.verify(&params).await.map(|_| ret)?,
})
}
MsoMdoc(_) => todo!(),
}
MsoMdoc(_) => todo!(),
}
},
))
.await
},
))
.await
}
}
4 changes: 4 additions & 0 deletions src/oid4vci/options.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#[derive(uniffi::Record, Clone, Debug, Default)]
pub struct Oid4vciExchangeOptions {
pub verify_after_exchange: Option<bool>,
}
4 changes: 3 additions & 1 deletion src/oid4vci/wrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::{
use super::{
oid4vci_exchange_credential, oid4vci_exchange_token, oid4vci_get_metadata, oid4vci_initiate,
oid4vci_initiate_with_offer, AsyncHttpClient, CredentialResponse, IHttpClient, Oid4vciError,
Oid4vciMetadata, Oid4vciSession, SyncHttpClient,
Oid4vciExchangeOptions, Oid4vciMetadata, Oid4vciSession, SyncHttpClient,
};

#[derive(uniffi::Object)]
Expand Down Expand Up @@ -167,10 +167,12 @@ impl Oid4vci {
pub async fn exchange_credential(
&self,
proofs_of_possession: Vec<String>,
options: Oid4vciExchangeOptions,
) -> Result<Vec<CredentialResponse>, Oid4vciError> {
oid4vci_exchange_credential(
self.session()?,
proofs_of_possession,
options,
self.context_map()?,
self.http_client.clone(),
)
Expand Down
6 changes: 4 additions & 2 deletions src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use ssi::{
};
use uniffi::deps::anyhow::Result;

use crate::oid4vci::Oid4vci;
use crate::oid4vci::{Oid4vci, Oid4vciExchangeOptions};

const OID4VCI_CREDENTIAL_OFFER_URI: &str = "openid-credential-offer://?credential_offer_uri=https%3A%2F%2Fqa.veresexchanger.dev%2Fexchangers%2Fz1A68iKqcX2HbQGQfVSfFnjkM%2Fexchanges%2Fz1ADKJLFpFtovZkxXHbQz47f5%2Fopenid%2Fcredential-offer";
const OID4VP_URI: &str = "openid4vp://?client_id=https%3A%2F%2Fqa.veresexchanger.dev%2Fexchangers%2Fz19vRLNoFaBKDeDaMzRjUj8hi%2Fexchanges%2Fz19prZuVakk5Rzt1tE12evjQX%2Fopenid%2Fclient%2Fauthorization%2Fresponse&request_uri=https%3A%2F%2Fqa.veresexchanger.dev%2Fexchangers%2Fz19vRLNoFaBKDeDaMzRjUj8hi%2Fexchanges%2Fz19prZuVakk5Rzt1tE12evjQX%2Fopenid%2Fclient%2Fauthorization%2Frequest";
Expand Down Expand Up @@ -138,7 +138,9 @@ pub async fn test_vc_playground_oid4vci() -> Result<()> {
// Load VC Playground Context
session.set_context_map(vc_playground_context())?;

let credentials = session.exchange_credential(vec![pop]).await?;
let credentials = session
.exchange_credential(vec![pop], Oid4vciExchangeOptions::default())
.await?;

println!("Credentials: {credentials:?}");

Expand Down

0 comments on commit 0e771c8

Please sign in to comment.