From c801206936028f7030600dddd4e8af3496dd0773 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Wed, 8 Feb 2023 09:31:32 +0000 Subject: [PATCH 01/87] Initial FFI with flutter rust bridge --- trustchain-ion/Cargo.toml | 3 +- trustchain-ion/src/api.rs | 16 +++++ trustchain-ion/src/bridge_generated.io.rs | 72 +++++++++++++++++++ trustchain-ion/src/bridge_generated.rs | 88 +++++++++++++++++++++++ trustchain-ion/src/lib.rs | 2 + 5 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 trustchain-ion/src/api.rs create mode 100644 trustchain-ion/src/bridge_generated.io.rs create mode 100644 trustchain-ion/src/bridge_generated.rs diff --git a/trustchain-ion/Cargo.toml b/trustchain-ion/Cargo.toml index 2bfadcb1..ff906c69 100644 --- a/trustchain-ion/Cargo.toml +++ b/trustchain-ion/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Trustchain Devs"] edition = "2018" [lib] -crate-type = ["cdylib", "rlib"] +crate-type = ["staticlib", "cdylib", "rlib"] [[bin]] name = "trustchain-cli" @@ -15,6 +15,7 @@ path = "src/bin/main.rs" default = ["console_error_panic_hook"] [dependencies] +flutter_rust_bridge = "1" trustchain-core = { path = "../trustchain-core" } diff --git a/trustchain-ion/src/api.rs b/trustchain-ion/src/api.rs new file mode 100644 index 00000000..a3f066dd --- /dev/null +++ b/trustchain-ion/src/api.rs @@ -0,0 +1,16 @@ +use crate::get_ion_resolver; +use serde_json::to_string_pretty; + +/// Example greet function. +pub fn greet() -> String { + "Hello from Rust! 🦀".into() +} + +/// Example resolve interface. +pub fn resolve(did: String) -> String { + // Trustchain Resolver with android localhost + let resolver = get_ion_resolver("http://10.0.2.2:3000/"); + // Result metadata, Document, Document metadata + let (_, doc, _) = resolver.resolve_as_result(&did).unwrap(); + to_string_pretty(&doc.unwrap()).expect("Cannot convert to JSON.") +} diff --git a/trustchain-ion/src/bridge_generated.io.rs b/trustchain-ion/src/bridge_generated.io.rs new file mode 100644 index 00000000..de86cfac --- /dev/null +++ b/trustchain-ion/src/bridge_generated.io.rs @@ -0,0 +1,72 @@ +use super::*; +// Section: wire functions + +#[no_mangle] +pub extern "C" fn wire_greet(port_: i64) { + wire_greet_impl(port_) +} + +#[no_mangle] +pub extern "C" fn wire_resolve(port_: i64, did: *mut wire_uint_8_list) { + wire_resolve_impl(port_, did) +} + +// Section: allocate functions + +#[no_mangle] +pub extern "C" fn new_uint_8_list_0(len: i32) -> *mut wire_uint_8_list { + let ans = wire_uint_8_list { + ptr: support::new_leak_vec_ptr(Default::default(), len), + len, + }; + support::new_leak_box_ptr(ans) +} + +// Section: related functions + +// Section: impl Wire2Api + +impl Wire2Api for *mut wire_uint_8_list { + fn wire2api(self) -> String { + let vec: Vec = self.wire2api(); + String::from_utf8_lossy(&vec).into_owned() + } +} + +impl Wire2Api> for *mut wire_uint_8_list { + fn wire2api(self) -> Vec { + unsafe { + let wrap = support::box_from_leak_ptr(self); + support::vec_from_leak_ptr(wrap.ptr, wrap.len) + } + } +} +// Section: wire structs + +#[repr(C)] +#[derive(Clone)] +pub struct wire_uint_8_list { + ptr: *mut u8, + len: i32, +} + +// Section: impl NewWithNullPtr + +pub trait NewWithNullPtr { + fn new_with_null_ptr() -> Self; +} + +impl NewWithNullPtr for *mut T { + fn new_with_null_ptr() -> Self { + std::ptr::null_mut() + } +} + +// Section: sync execution mode utility + +#[no_mangle] +pub extern "C" fn free_WireSyncReturn(ptr: support::WireSyncReturn) { + unsafe { + let _ = support::box_from_leak_ptr(ptr); + }; +} diff --git a/trustchain-ion/src/bridge_generated.rs b/trustchain-ion/src/bridge_generated.rs new file mode 100644 index 00000000..febb3c79 --- /dev/null +++ b/trustchain-ion/src/bridge_generated.rs @@ -0,0 +1,88 @@ +#![allow( + non_camel_case_types, + unused, + clippy::redundant_closure, + clippy::useless_conversion, + clippy::unit_arg, + clippy::double_parens, + non_snake_case, + clippy::too_many_arguments +)] +// AUTO GENERATED FILE, DO NOT EDIT. +// Generated by `flutter_rust_bridge`@ 1.62.1. + +use crate::api::*; +use core::panic::UnwindSafe; +use flutter_rust_bridge::*; +use std::ffi::c_void; +use std::sync::Arc; + +// Section: imports + +// Section: wire functions + +fn wire_greet_impl(port_: MessagePort) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap( + WrapInfo { + debug_name: "greet", + port: Some(port_), + mode: FfiCallMode::Normal, + }, + move || move |task_callback| Ok(greet()), + ) +} +fn wire_resolve_impl(port_: MessagePort, did: impl Wire2Api + UnwindSafe) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap( + WrapInfo { + debug_name: "resolve", + port: Some(port_), + mode: FfiCallMode::Normal, + }, + move || { + let api_did = did.wire2api(); + move |task_callback| Ok(resolve(api_did)) + }, + ) +} +// Section: wrapper structs + +// Section: static checks + +// Section: allocate functions + +// Section: related functions + +// Section: impl Wire2Api + +pub trait Wire2Api { + fn wire2api(self) -> T; +} + +impl Wire2Api> for *mut S +where + *mut S: Wire2Api, +{ + fn wire2api(self) -> Option { + (!self.is_null()).then(|| self.wire2api()) + } +} + +impl Wire2Api for u8 { + fn wire2api(self) -> u8 { + self + } +} + +// Section: impl IntoDart + +// Section: executor + +support::lazy_static! { + pub static ref FLUTTER_RUST_BRIDGE_HANDLER: support::DefaultHandler = Default::default(); +} + +#[cfg(not(target_family = "wasm"))] +#[path = "bridge_generated.io.rs"] +mod io; +#[cfg(not(target_family = "wasm"))] +pub use io::*; diff --git a/trustchain-ion/src/lib.rs b/trustchain-ion/src/lib.rs index d83be8ab..367e3dd2 100644 --- a/trustchain-ion/src/lib.rs +++ b/trustchain-ion/src/lib.rs @@ -1,5 +1,7 @@ +pub mod api; pub mod attest; pub mod attestor; +mod bridge_generated; /* AUTO INJECTED BY flutter_rust_bridge. This line may not be accurate, and you can change it according to your needs. */ pub mod controller; pub mod verifier; From e914df2d7275a3a9fea182faf983cfe73e3ccc74 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Wed, 29 Mar 2023 17:03:00 +0100 Subject: [PATCH 02/87] Use trustchain-ffi crate from #63 --- Cargo.toml | 1 + trustchain-ffi/Cargo.toml | 17 +++++++++++++++++ trustchain-ffi/src/lib.rs | 1 + 3 files changed, 19 insertions(+) create mode 100644 trustchain-ffi/Cargo.toml create mode 100644 trustchain-ffi/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 388a1dc2..ec59ddf6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,4 +2,5 @@ members = [ "trustchain-core", "trustchain-ion", + "trustchain-ffi" ] diff --git a/trustchain-ffi/Cargo.toml b/trustchain-ffi/Cargo.toml new file mode 100644 index 00000000..0cecb149 --- /dev/null +++ b/trustchain-ffi/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "trustchain-ffi" +version = "0.1.0" +authors = ["Trustchain Devs"] +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +trustchain-core = { path = "../trustchain-core" } +trustchain-ion = { path = "../trustchain-ion" } + +serde_json = "1.0" +serde = { version = "1.0", features = ["derive"] } diff --git a/trustchain-ffi/src/lib.rs b/trustchain-ffi/src/lib.rs new file mode 100644 index 00000000..10051c76 --- /dev/null +++ b/trustchain-ffi/src/lib.rs @@ -0,0 +1 @@ +// Placeholder From 91ab006742cfc7ad042ecd873dc23e268a222da5 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Wed, 29 Mar 2023 17:14:31 +0100 Subject: [PATCH 03/87] Move mobile bridge to FFI crate --- trustchain-ffi/Cargo.toml | 2 +- trustchain-ffi/src/lib.rs | 2 +- trustchain-ion/src/api.rs => trustchain-ffi/src/mobile.rs | 2 +- trustchain-ion/src/lib.rs | 2 -- 4 files changed, 3 insertions(+), 5 deletions(-) rename trustchain-ion/src/api.rs => trustchain-ffi/src/mobile.rs (92%) diff --git a/trustchain-ffi/Cargo.toml b/trustchain-ffi/Cargo.toml index 0cecb149..eb31cf7c 100644 --- a/trustchain-ffi/Cargo.toml +++ b/trustchain-ffi/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Trustchain Devs"] edition = "2021" [lib] -crate-type = ["cdylib"] +crate-type = ["cdylib", "staticlib"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/trustchain-ffi/src/lib.rs b/trustchain-ffi/src/lib.rs index 10051c76..999ea403 100644 --- a/trustchain-ffi/src/lib.rs +++ b/trustchain-ffi/src/lib.rs @@ -1 +1 @@ -// Placeholder +pub mod mobile; diff --git a/trustchain-ion/src/api.rs b/trustchain-ffi/src/mobile.rs similarity index 92% rename from trustchain-ion/src/api.rs rename to trustchain-ffi/src/mobile.rs index a3f066dd..ee164a20 100644 --- a/trustchain-ion/src/api.rs +++ b/trustchain-ffi/src/mobile.rs @@ -1,5 +1,5 @@ -use crate::get_ion_resolver; use serde_json::to_string_pretty; +use trustchain_ion::get_ion_resolver; /// Example greet function. pub fn greet() -> String { diff --git a/trustchain-ion/src/lib.rs b/trustchain-ion/src/lib.rs index 367e3dd2..d83be8ab 100644 --- a/trustchain-ion/src/lib.rs +++ b/trustchain-ion/src/lib.rs @@ -1,7 +1,5 @@ -pub mod api; pub mod attest; pub mod attestor; -mod bridge_generated; /* AUTO INJECTED BY flutter_rust_bridge. This line may not be accurate, and you can change it according to your needs. */ pub mod controller; pub mod verifier; From 587be7828371181f5f2e1b71b6447ec9f0661640 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Wed, 29 Mar 2023 17:34:32 +0100 Subject: [PATCH 04/87] Add dep --- trustchain-ffi/Cargo.toml | 1 + trustchain-ffi/src/lib.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/trustchain-ffi/Cargo.toml b/trustchain-ffi/Cargo.toml index eb31cf7c..bd2e0cbf 100644 --- a/trustchain-ffi/Cargo.toml +++ b/trustchain-ffi/Cargo.toml @@ -13,5 +13,6 @@ crate-type = ["cdylib", "staticlib"] trustchain-core = { path = "../trustchain-core" } trustchain-ion = { path = "../trustchain-ion" } +flutter_rust_bridge = "1" serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } diff --git a/trustchain-ffi/src/lib.rs b/trustchain-ffi/src/lib.rs index 999ea403..cf171489 100644 --- a/trustchain-ffi/src/lib.rs +++ b/trustchain-ffi/src/lib.rs @@ -1 +1,2 @@ pub mod mobile; +mod mobile_bridge; /* AUTO INJECTED BY flutter_rust_bridge. This line may not be accurate, and you can change it according to your needs. */ From 5ab83ce8802a6e4ee14fe6b91ad5711bcf5328df Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Thu, 27 Apr 2023 18:30:04 +0100 Subject: [PATCH 05/87] WIP: verify DID and credential --- trustchain-api/src/api.rs | 4 ++-- trustchain-ffi/src/lib.rs | 1 + trustchain-ffi/src/mobile.rs | 36 +++++++++++++++++++++++++++++++----- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/trustchain-api/src/api.rs b/trustchain-api/src/api.rs index c7ed3506..fe25099e 100644 --- a/trustchain-api/src/api.rs +++ b/trustchain-api/src/api.rs @@ -76,14 +76,14 @@ pub trait TrustchainVCAPI { signature_only: bool, root_event_time: u32, ) -> (VerificationResult, Option>) { - let resolver = get_ion_resolver("http://localhost:3000/"); + let resolver = get_ion_resolver("http://10.0.2.2:3000/"); let verification_result = resolver .runtime .block_on(async { credential.verify(None, &resolver).await }); if signature_only { (verification_result, None) } else { - let mut verifier = IONVerifier::new(get_ion_resolver("http://localhost:3000/")); + let mut verifier = IONVerifier::new(get_ion_resolver("http://10.0.2.2:3000/")); let issuer = match credential.issuer.as_ref() { Some(ssi::vc::Issuer::URI(URI::String(did))) => did, _ => panic!("No issuer present in credential."), diff --git a/trustchain-ffi/src/lib.rs b/trustchain-ffi/src/lib.rs index 431a06fc..3032c62d 100644 --- a/trustchain-ffi/src/lib.rs +++ b/trustchain-ffi/src/lib.rs @@ -1,2 +1,3 @@ pub mod gui; pub mod mobile; +mod mobile_bridge; /* AUTO INJECTED BY flutter_rust_bridge. This line may not be accurate, and you can change it according to your needs. */ diff --git a/trustchain-ffi/src/mobile.rs b/trustchain-ffi/src/mobile.rs index 2d495f17..de38cbdf 100644 --- a/trustchain-ffi/src/mobile.rs +++ b/trustchain-ffi/src/mobile.rs @@ -1,7 +1,12 @@ use anyhow::{anyhow, Result}; use serde_json::to_string_pretty; -use trustchain_api::{api::TrustchainDIDAPI, TrustchainAPI}; -use trustchain_ion::get_ion_resolver; +use ssi::vc::Credential; +use trustchain_api::{ + api::{TrustchainDIDAPI, TrustchainVCAPI}, + TrustchainAPI, +}; +use trustchain_core::{config::core_config, verifier::Verifier}; +use trustchain_ion::{get_ion_resolver, verifier::IONVerifier}; /// Android localhost endpoint. const ANDROID_ENDPOINT: &str = "http://10.0.2.2:3000/"; @@ -29,8 +34,14 @@ pub fn did_resolve(did: String) -> Result { .and_then(|(_, doc, _)| serde_json::to_string_pretty(&doc).map_err(|e| anyhow!(e))) } /// Verifies a given DID assuming trust in endpoint. -pub fn did_verify(did: String) -> Result { - todo!() +pub fn did_verify(did: String, endpoint: String) -> Result<()> { + // Trustchain Resolver with android localhost + let mut verifier = IONVerifier::new(get_ion_resolver(ANDROID_ENDPOINT)); + verifier.fetch_bundle(&did, Some(endpoint)); + verifier + .verify(&did, core_config().root_event_time) + .map_err(|err| anyhow!(err.to_string())); + Ok(()) } /// Verifies a given DID bundle providing complete verification without trust in endpoint. pub fn did_verify_bundle(bundle_json: String) -> Result { @@ -38,7 +49,22 @@ pub fn did_verify_bundle(bundle_json: String) -> Result { } /// Verifies a verifiable credential. Analogous with [didkit](https://docs.rs/didkit/latest/didkit/c/fn.didkit_vc_verify_credential.html). pub fn vc_verify_credential(credential_json: String, proof_options_json: String) -> Result { - todo!() + let credential: Credential = serde_json::from_str(&credential_json).unwrap(); + let (verify_result, did_chain) = + TrustchainAPI::verify_credential(&credential, false, core_config().root_event_time); + if verify_result.errors.is_empty() { + } else { + return Err(anyhow!("Invalid signature")); + } + if let Some(did_chain) = did_chain { + if did_chain.is_ok() { + Ok("OK".to_string()) + } else { + Err(anyhow!("Invalid DID chain of issuer.")) + } + } else { + Err(anyhow!("No DID chain returned, failed to verify issuer.")) + } } /// Issues a verifiable presentation. Analogous with [didkit](https://docs.rs/didkit/latest/didkit/c/fn.didkit_vc_issue_presentation.html). pub fn vc_issue_presentation( From d6766cabfa4bd6223a13bcb05cc6229ad60f79ec Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Wed, 10 May 2023 10:05:09 +0100 Subject: [PATCH 06/87] Update to ssi-0.6.0 --- trustchain-core/Cargo.toml | 7 +++++-- trustchain-core/src/issuer.rs | 21 +++++++++++++++------ trustchain-core/src/utils.rs | 4 ++-- trustchain-ion/Cargo.toml | 10 +++++++--- trustchain-ion/src/attestor.rs | 12 +++++++++--- trustchain-ion/src/bin/main.rs | 9 +++++++-- trustchain-ion/tests/vc.rs | 14 ++++++++++---- 7 files changed, 55 insertions(+), 22 deletions(-) diff --git a/trustchain-core/Cargo.toml b/trustchain-core/Cargo.toml index 715dff67..0b5c546b 100644 --- a/trustchain-core/Cargo.toml +++ b/trustchain-core/Cargo.toml @@ -12,7 +12,6 @@ async-trait = "0.1" base64 = "0.13" canonical_json = "0.4.0" chrono = "0.4" -clap = { version = "~4.0", features=["derive", "cargo"] } futures = "0.3.21" lazy_static="1.4.0" petgraph = {version = "0.6"} @@ -20,7 +19,11 @@ serde = { version = "1.0", features = ["derive"] } serde_jcs = "0.1.0" serde_json = "1.0" sha2 = "0.10" -ssi = { version = "0.4", features = ["http-did", "secp256k1"] } +ssi = { version = "0.6", features = ["http-did", "secp256k1"] } +ssi-jws = "0.1" +ssi-jwt = "0.1.1" +ssi-ldp = "0.2.1" +ssi-vc = "0.1.3" tempfile = { version = "3.3" } thiserror = "1.0" tokio = {version = "1.20.1", features = ["full"]} diff --git a/trustchain-core/src/issuer.rs b/trustchain-core/src/issuer.rs index 4275be70..3302807f 100644 --- a/trustchain-core/src/issuer.rs +++ b/trustchain-core/src/issuer.rs @@ -8,17 +8,26 @@ use thiserror::Error; /// An error relating to a Trustchain Issuer. #[derive(Error, Debug)] pub enum IssuerError { - /// Wrapped error for SSI error. - #[error("A wrapped variant for an SSI error.")] - SSI(ssi::error::Error), + /// Wrapped error for ssi-vc error. + #[error("A wrapped variant for an SSI VC error.")] + VC(ssi::vc::Error), + /// Wrapped error for ssi-ldp error. + #[error("A wrapped variant for an SSI LDP error.")] + LDP(ssi::ldp::Error), /// Wrapped error for key manager error. #[error("A wrapped variant for a key manager error.")] KeyManager(KeyManagerError), } -impl From for IssuerError { - fn from(err: ssi::error::Error) -> Self { - IssuerError::SSI(err) +impl From for IssuerError { + fn from(err: ssi::vc::Error) -> Self { + IssuerError::VC(err) + } +} + +impl From for IssuerError { + fn from(err: ssi::ldp::Error) -> Self { + IssuerError::LDP(err) } } diff --git a/trustchain-core/src/utils.rs b/trustchain-core/src/utils.rs index 4e965ded..09cee7f8 100644 --- a/trustchain-core/src/utils.rs +++ b/trustchain-core/src/utils.rs @@ -105,12 +105,12 @@ pub fn hash(data: &str) -> String { } /// Extracts payload from JWT and verifies signature. -pub fn decode_verify(jwt: &str, key: &JWK) -> Result { +pub fn decode_verify(jwt: &str, key: &JWK) -> Result { ssi::jwt::decode_verify(jwt, key) } /// Extracts and decodes the payload from the JWT. -pub fn decode(jwt: &str) -> Result { +pub fn decode(jwt: &str) -> Result { ssi::jwt::decode_unverified(jwt) } diff --git a/trustchain-ion/Cargo.toml b/trustchain-ion/Cargo.toml index 19fead58..0eddac48 100644 --- a/trustchain-ion/Cargo.toml +++ b/trustchain-ion/Cargo.toml @@ -17,8 +17,8 @@ async-trait = "0.1" bitcoin = "0.29.2" bitcoincore-rpc = "0.16.0" canonical_json = "0.4.0" -clap = { version = "~4.0", features=["derive", "cargo"] } -did-ion="0.1.0" +clap = { version = "^4.1", features=["derive", "cargo"] } +did-ion="0.2.0" flate2 = "1.0.24" futures = "0.3.21" hex = "0.4.3" @@ -31,7 +31,11 @@ serde = { version = "1.0", features = ["derive"] } serde_jcs = "0.1.0" serde_json = "1.0" sha256 = "1.1.1" -ssi = { version = "0.4", features = ["http-did", "secp256k1"] } +ssi = { version = "0.6", features = ["http-did", "secp256k1"] } +ssi-jws = "0.1" +ssi-jwt = "0.1.1" +ssi-ldp = "0.2.1" +ssi-vc = "0.1.3" thiserror = "1.0" toml="0.7.2" tokio = {version = "1.20.1", features = ["full"]} diff --git a/trustchain-ion/src/attestor.rs b/trustchain-ion/src/attestor.rs index b2a72862..cc9cd211 100644 --- a/trustchain-ion/src/attestor.rs +++ b/trustchain-ion/src/attestor.rs @@ -3,6 +3,7 @@ use did_ion::sidetree::Sidetree; use did_ion::ION; use ssi::did::Document; use ssi::did_resolve::DIDResolver; +use ssi::jsonld::ContextLoader; use ssi::vc::{Credential, LinkedDataProofOptions}; use ssi::{jwk::JWK, one_or_many::OneOrMany}; use std::convert::TryFrom; @@ -152,7 +153,12 @@ impl Issuer for IONAttestor { // Generate proof let proof = credential - .generate_proof(&signing_key, &LinkedDataProofOptions::default(), resolver) + .generate_proof( + &signing_key, + &LinkedDataProofOptions::default(), + resolver, + &mut ContextLoader::default(), + ) .await?; // Add proof to credential @@ -214,9 +220,9 @@ mod tests { // Check signature let proof_result = result?; - let valid_decoded: Result = + let valid_decoded: Result = ssi::jwt::decode_verify(&proof_result, valid_key); - let invalid_decoded: Result = + let invalid_decoded: Result = ssi::jwt::decode_verify(&proof_result, invalid_key); assert!(valid_decoded.is_ok()); assert!(invalid_decoded.is_err()); diff --git a/trustchain-ion/src/bin/main.rs b/trustchain-ion/src/bin/main.rs index 9f95d17e..7cae4543 100644 --- a/trustchain-ion/src/bin/main.rs +++ b/trustchain-ion/src/bin/main.rs @@ -1,7 +1,10 @@ //! Trustchain CLI binary use clap::{arg, ArgAction, Command}; use serde_json::to_string_pretty; -use ssi::vc::{Credential, URI}; +use ssi::{ + jsonld::ContextLoader, + vc::{Credential, URI}, +}; use std::{ fs::File, io::{stdin, BufReader}, @@ -157,7 +160,9 @@ async fn main() -> Result<(), Box> { serde_json::from_reader(buffer).unwrap() }; - let verify_result = credential.verify(None, &resolver).await; + let verify_result = credential + .verify(None, &resolver, &mut ContextLoader::default()) + .await; if verify_result.errors.is_empty() { println!("Proof... ✅") } else { diff --git a/trustchain-ion/tests/vc.rs b/trustchain-ion/tests/vc.rs index 8fa42488..dd982a4a 100644 --- a/trustchain-ion/tests/vc.rs +++ b/trustchain-ion/tests/vc.rs @@ -1,4 +1,4 @@ -use ssi::ldp::now_ms; +use ssi::{jsonld::ContextLoader, ldp::now_ms}; use std::convert::TryFrom; use trustchain_core::issuer::{Issuer, IssuerError}; use trustchain_ion::attestor::IONAttestor; @@ -57,7 +57,9 @@ async fn test_sign_credential() { let mut vc_with_proof = attestor.sign(&vc, None, &resolver).await.unwrap(); // Verify: expect no warnings or errors - let verification_result = vc_with_proof.verify(None, &resolver).await; + let verification_result = vc_with_proof + .verify(None, &resolver, &mut ContextLoader::default()) + .await; assert!(verification_result.warnings.is_empty()); assert!(verification_result.errors.is_empty()); @@ -65,7 +67,9 @@ async fn test_sign_credential() { vc_with_proof.expiration_date = Some(VCDateTime::try_from(now_ms()).unwrap()); // Verify: expect no warnings and a signature error as VC has changed - let verification_result = vc_with_proof.verify(None, &resolver).await; + let verification_result = vc_with_proof + .verify(None, &resolver, &mut ContextLoader::default()) + .await; assert!(verification_result.warnings.is_empty()); assert_eq!(verification_result.errors, vec!["signature error"]); } @@ -92,6 +96,8 @@ async fn test_sign_credential_failure() { assert!(vc_with_proof.is_err()); assert!(matches!( vc_with_proof, - Err(IssuerError::SSI(ssi::error::Error::KeyMismatch)) + Err(IssuerError::LDP( + ssi::ldp::Error::VerificationMethodMismatch + )) )); } From d61b634481af090ebada71a21c73b560a0cab9ed Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Wed, 10 May 2023 10:17:17 +0100 Subject: [PATCH 07/87] Update ssi_jws qualification as ssi::jws --- trustchain-core/src/utils.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/trustchain-core/src/utils.rs b/trustchain-core/src/utils.rs index 09cee7f8..5fe97ee8 100644 --- a/trustchain-core/src/utils.rs +++ b/trustchain-core/src/utils.rs @@ -105,12 +105,12 @@ pub fn hash(data: &str) -> String { } /// Extracts payload from JWT and verifies signature. -pub fn decode_verify(jwt: &str, key: &JWK) -> Result { +pub fn decode_verify(jwt: &str, key: &JWK) -> Result { ssi::jwt::decode_verify(jwt, key) } /// Extracts and decodes the payload from the JWT. -pub fn decode(jwt: &str) -> Result { +pub fn decode(jwt: &str) -> Result { ssi::jwt::decode_unverified(jwt) } From 23a11b1e04718e2dc4d97df69b7becdcb3205a79 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Wed, 10 May 2023 10:26:52 +0100 Subject: [PATCH 08/87] Only retain ssi package not individual ssi crates in Cargo.toml --- trustchain-core/Cargo.toml | 4 ---- trustchain-ion/Cargo.toml | 4 ---- 2 files changed, 8 deletions(-) diff --git a/trustchain-core/Cargo.toml b/trustchain-core/Cargo.toml index 0b5c546b..77e5db79 100644 --- a/trustchain-core/Cargo.toml +++ b/trustchain-core/Cargo.toml @@ -20,10 +20,6 @@ serde_jcs = "0.1.0" serde_json = "1.0" sha2 = "0.10" ssi = { version = "0.6", features = ["http-did", "secp256k1"] } -ssi-jws = "0.1" -ssi-jwt = "0.1.1" -ssi-ldp = "0.2.1" -ssi-vc = "0.1.3" tempfile = { version = "3.3" } thiserror = "1.0" tokio = {version = "1.20.1", features = ["full"]} diff --git a/trustchain-ion/Cargo.toml b/trustchain-ion/Cargo.toml index 0eddac48..6e765fc8 100644 --- a/trustchain-ion/Cargo.toml +++ b/trustchain-ion/Cargo.toml @@ -32,10 +32,6 @@ serde_jcs = "0.1.0" serde_json = "1.0" sha256 = "1.1.1" ssi = { version = "0.6", features = ["http-did", "secp256k1"] } -ssi-jws = "0.1" -ssi-jwt = "0.1.1" -ssi-ldp = "0.2.1" -ssi-vc = "0.1.3" thiserror = "1.0" toml="0.7.2" tokio = {version = "1.20.1", features = ["full"]} From 0a9fd6bf8bc5e7942333e61d80aea66b18c5b077 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Thu, 11 May 2023 10:03:06 +0100 Subject: [PATCH 09/87] Fix updated error variant --- trustchain-ion/tests/vc.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/trustchain-ion/tests/vc.rs b/trustchain-ion/tests/vc.rs index dd982a4a..1bfd5a9b 100644 --- a/trustchain-ion/tests/vc.rs +++ b/trustchain-ion/tests/vc.rs @@ -96,8 +96,8 @@ async fn test_sign_credential_failure() { assert!(vc_with_proof.is_err()); assert!(matches!( vc_with_proof, - Err(IssuerError::LDP( - ssi::ldp::Error::VerificationMethodMismatch - )) + Err(IssuerError::LDP(ssi::ldp::Error::DID( + ssi::did::Error::KeyMismatch + ))) )); } From bced36f0c4e788b135605be7448af0b5032259e6 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Thu, 11 May 2023 10:05:58 +0100 Subject: [PATCH 10/87] Add type for ION test network --- trustchain-ion/Cargo.toml | 1 + trustchain-ion/src/attest.rs | 2 +- trustchain-ion/src/attestor.rs | 2 +- trustchain-ion/src/controller.rs | 2 +- trustchain-ion/src/create.rs | 2 +- trustchain-ion/src/ion.rs | 32 ++++++++++++++++++++++++++++++++ trustchain-ion/src/lib.rs | 4 +++- trustchain-ion/src/sidetree.rs | 6 ++---- 8 files changed, 42 insertions(+), 9 deletions(-) create mode 100644 trustchain-ion/src/ion.rs diff --git a/trustchain-ion/Cargo.toml b/trustchain-ion/Cargo.toml index 6e765fc8..4d942915 100644 --- a/trustchain-ion/Cargo.toml +++ b/trustchain-ion/Cargo.toml @@ -13,6 +13,7 @@ path = "src/bin/main.rs" [dependencies] trustchain-core = { path = "../trustchain-core" } +anyhow = "1.0" async-trait = "0.1" bitcoin = "0.29.2" bitcoincore-rpc = "0.16.0" diff --git a/trustchain-ion/src/attest.rs b/trustchain-ion/src/attest.rs index fd6c0efa..68724499 100644 --- a/trustchain-ion/src/attest.rs +++ b/trustchain-ion/src/attest.rs @@ -1,7 +1,7 @@ +use crate::ion::IONTest as ION; use did_ion::sidetree::DIDStatePatch; use did_ion::sidetree::PublicKeyJwk; use did_ion::sidetree::{DIDSuffix, Operation, Sidetree}; -use did_ion::ION; use serde_json::to_string_pretty as to_json; use std::convert::TryFrom; use trustchain_core::controller::Controller; diff --git a/trustchain-ion/src/attestor.rs b/trustchain-ion/src/attestor.rs index cc9cd211..b264db96 100644 --- a/trustchain-ion/src/attestor.rs +++ b/trustchain-ion/src/attestor.rs @@ -1,6 +1,6 @@ +use crate::ion::IONTest as ION; use async_trait::async_trait; use did_ion::sidetree::Sidetree; -use did_ion::ION; use ssi::did::Document; use ssi::did_resolve::DIDResolver; use ssi::jsonld::ContextLoader; diff --git a/trustchain-ion/src/controller.rs b/trustchain-ion/src/controller.rs index 875ff346..6caab082 100644 --- a/trustchain-ion/src/controller.rs +++ b/trustchain-ion/src/controller.rs @@ -1,7 +1,7 @@ use crate::attestor::IONAttestor; +use crate::ion::IONTest as ION; use crate::TrustchainIONError; use did_ion::sidetree::{DIDStatePatch, PublicKeyJwk, ServiceEndpointEntry, Sidetree}; -use did_ion::ION; use serde_json::{Map, Value}; use ssi::did::ServiceEndpoint; use ssi::did_resolve::{DocumentMetadata, Metadata}; diff --git a/trustchain-ion/src/create.rs b/trustchain-ion/src/create.rs index 34c0b2db..7b8cc247 100644 --- a/trustchain-ion/src/create.rs +++ b/trustchain-ion/src/create.rs @@ -1,9 +1,9 @@ use crate::attestor::{AttestorData, IONAttestor}; use crate::controller::{ControllerData, IONController}; +use crate::ion::IONTest as ION; use did_ion::sidetree::DIDStatePatch; use did_ion::sidetree::{DocumentState, PublicKeyEntry, PublicKeyJwk}; use did_ion::sidetree::{Operation, Sidetree, SidetreeDID, SidetreeOperation}; -use did_ion::ION; use serde_json::to_string_pretty as to_json; use ssi::jwk::JWK; use ssi::one_or_many::OneOrMany; diff --git a/trustchain-ion/src/ion.rs b/trustchain-ion/src/ion.rs new file mode 100644 index 00000000..2e9900fe --- /dev/null +++ b/trustchain-ion/src/ion.rs @@ -0,0 +1,32 @@ +use anyhow::{anyhow, Context, Result}; +use did_ion::sidetree::{is_secp256k1, Sidetree, SidetreeClient, SidetreeError}; +use ssi::jwk::{Algorithm, JWK}; + +pub const USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")); + +/// Type for ION test network given [did-ion-0.2.0](https://github.com/spruceid/ssi/blob/79ad4a679933d1b4f6af93193408cd0a24e68f74/did-ion/src/lib.rs) +/// now uses main network. +#[derive(Clone)] +pub struct IONTest; + +/// did:ion:test Method +pub type DIDIONTest = SidetreeClient; + +impl Sidetree for IONTest { + fn generate_key() -> Result { + let key = JWK::generate_secp256k1().context("Generate secp256k1 key")?; + Ok(key) + } + + fn validate_key(key: &JWK) -> Result<(), SidetreeError> { + if !is_secp256k1(key) { + return Err(anyhow!("Key must be Secp256k1").into()); + } + Ok(()) + } + + const SIGNATURE_ALGORITHM: Algorithm = Algorithm::ES256K; + const METHOD: &'static str = "ion"; + // Specify "test" network. + const NETWORK: Option<&'static str> = Some("test"); +} diff --git a/trustchain-ion/src/lib.rs b/trustchain-ion/src/lib.rs index 995e588b..b89ded68 100644 --- a/trustchain-ion/src/lib.rs +++ b/trustchain-ion/src/lib.rs @@ -5,13 +5,15 @@ pub mod config; pub mod controller; pub mod create; pub mod data; +pub mod ion; pub mod resolve; pub mod sidetree; pub mod utils; pub mod verifier; use std::num::ParseIntError; -use did_ion::{sidetree::SidetreeClient, ION}; +use crate::ion::IONTest as ION; +use did_ion::sidetree::SidetreeClient; use std::io; use thiserror::Error; use trustchain_core::resolver::{DIDMethodWrapper, Resolver}; diff --git a/trustchain-ion/src/sidetree.rs b/trustchain-ion/src/sidetree.rs index ebe1fcde..cebf5d25 100644 --- a/trustchain-ion/src/sidetree.rs +++ b/trustchain-ion/src/sidetree.rs @@ -1,7 +1,5 @@ -use did_ion::{ - sidetree::{Delta, Sidetree, SuffixData}, - ION, -}; +use crate::ion::IONTest as ION; +use did_ion::sidetree::{Delta, Sidetree, SuffixData}; use serde::{Deserialize, Serialize}; use trustchain_core::{commitment::CommitmentError, utils::get_did_suffix}; From 0ef6224e1d4f3ddc7cf52a7036830d03fa81fc6a Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Mon, 22 May 2023 11:41:43 +0100 Subject: [PATCH 11/87] Use forked ssi with modified encode_sign for ION header compat --- trustchain-core/Cargo.toml | 2 +- trustchain-ion/Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/trustchain-core/Cargo.toml b/trustchain-core/Cargo.toml index 77e5db79..8bb927b4 100644 --- a/trustchain-core/Cargo.toml +++ b/trustchain-core/Cargo.toml @@ -19,7 +19,7 @@ serde = { version = "1.0", features = ["derive"] } serde_jcs = "0.1.0" serde_json = "1.0" sha2 = "0.10" -ssi = { version = "0.6", features = ["http-did", "secp256k1"] } +ssi = {git="https://github.com/alan-turing-institute/ssi.git", branch="modify-encode-sign-jwt", features = ["http-did", "secp256k1"]} tempfile = { version = "3.3" } thiserror = "1.0" tokio = {version = "1.20.1", features = ["full"]} diff --git a/trustchain-ion/Cargo.toml b/trustchain-ion/Cargo.toml index 4d942915..9d928e43 100644 --- a/trustchain-ion/Cargo.toml +++ b/trustchain-ion/Cargo.toml @@ -19,7 +19,7 @@ bitcoin = "0.29.2" bitcoincore-rpc = "0.16.0" canonical_json = "0.4.0" clap = { version = "^4.1", features=["derive", "cargo"] } -did-ion="0.2.0" +did-ion = {git="https://github.com/alan-turing-institute/ssi.git", branch="modify-encode-sign-jwt"} flate2 = "1.0.24" futures = "0.3.21" hex = "0.4.3" @@ -32,7 +32,7 @@ serde = { version = "1.0", features = ["derive"] } serde_jcs = "0.1.0" serde_json = "1.0" sha256 = "1.1.1" -ssi = { version = "0.6", features = ["http-did", "secp256k1"] } +ssi = {git="https://github.com/alan-turing-institute/ssi.git", branch="modify-encode-sign-jwt", features = ["http-did", "secp256k1"]} thiserror = "1.0" toml="0.7.2" tokio = {version = "1.20.1", features = ["full"]} From 9a4d070398434709cdd5a13882e02c23fdc7fee6 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Mon, 22 May 2023 14:42:15 +0100 Subject: [PATCH 12/87] WIP: implementing a credential verifier --- trustchain-http/src/resolver.rs | 2 +- trustchain-http/src/verifier.rs | 37 +++++++++++++++++++++++++++++---- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/trustchain-http/src/resolver.rs b/trustchain-http/src/resolver.rs index 68b4ce1f..9b959a35 100644 --- a/trustchain-http/src/resolver.rs +++ b/trustchain-http/src/resolver.rs @@ -84,7 +84,7 @@ impl TrustchainHTTP for TrustchainHTTPHandler { } } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Serialize, Debug)] /// Struct for deserializing `root_event_time` from handler's query param. pub struct RootEventTime { root_event_time: Timestamp, diff --git a/trustchain-http/src/verifier.rs b/trustchain-http/src/verifier.rs index 40366463..0d381c27 100644 --- a/trustchain-http/src/verifier.rs +++ b/trustchain-http/src/verifier.rs @@ -1,13 +1,18 @@ use crate::qrcode::str_to_qr_code_html; +use crate::resolver::RootEventTime; use crate::state::AppState; use crate::EXAMPLE_VP_REQUEST; -use axum::extract::{Path, State}; +use axum::extract::{Path, Query, State}; use axum::http::StatusCode; use axum::response::{Html, IntoResponse}; use axum::Json; use log::info; +use serde::{Deserialize, Serialize}; +use ssi::did_resolve::DIDResolver; use ssi::vc::{Credential, Presentation}; use std::sync::Arc; +use trustchain_ion::verifier::IONVerifier; +use trustchain_ion::IONResolver; // TODO: implement in core? pub struct PresentationRequest; @@ -26,7 +31,10 @@ pub trait TrustchainVerifierHTTP { /// Verifies verifiable presentation fn verify_presentation(presentation: &Presentation) -> Result<(), PresentationError>; /// Verifies verifiable credential - fn verify_credential(credential: &Credential) -> Result<(), PresentationError>; + fn verify_credential( + credential: &Credential, + verifier: &IONVerifier, + ) -> Result<(), PresentationError>; } pub struct TrustchainVerifierHTTPHandler; @@ -40,11 +48,24 @@ impl TrustchainVerifierHTTP for TrustchainVerifierHTTPHandler { todo!() } - fn verify_credential(credential: &Credential) -> Result<(), PresentationError> { + fn verify_credential( + credential: &Credential, + verifier: &IONVerifier, + ) -> Result<(), PresentationError> { + // 1. Verify signature on credential is valid given key + // Use the resolver from the verifier inside: + // credential.verify(None, verifier.resolver()) + // 2. Verify did of issuer is valide (same as chain resolution) todo!() } } +#[derive(Debug, Serialize, Deserialize)] +struct PostVerifier { + pub credential: Credential, + pub root_event_time: RootEventTime, +} + impl TrustchainVerifierHTTPHandler { /// API endpoint taking the UUID of a VC. Response is the VC JSON. // TODO: identify how to handle multiple string variables @@ -54,11 +75,19 @@ impl TrustchainVerifierHTTPHandler { Html(EXAMPLE_VP_REQUEST.to_string()) } - pub async fn post_verifier(Json(info): Json) -> impl IntoResponse { + // pub async fn post_verifier(Json(info): Json) -> impl IntoResponse { + pub async fn post_verifier( + Json(info): Json, + // TODO: replace + // Json(info): Json, + // Query(root_event_time): Query, + State(app_state): State>, + ) -> impl IntoResponse { info!( "Received credential at presentation:\n{}", serde_json::to_string_pretty(&info).unwrap() ); + // TODO: check whether a specific response body is required // See [here](https://w3c-ccg.github.io/vc-api/#prove-presentation) (StatusCode::OK, "Received!") From 281a226a9362380c4803e2433fb58a2655051966 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Tue, 30 May 2023 17:26:18 +0100 Subject: [PATCH 13/87] Refactor PresentationError into TrustchainHTTPError, fix verifier post --- trustchain-http/src/errors.rs | 5 +++++ trustchain-http/src/server.rs | 10 ++++++++-- trustchain-http/src/verifier.rs | 22 ++++++---------------- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/trustchain-http/src/errors.rs b/trustchain-http/src/errors.rs index 78589960..42c5101d 100644 --- a/trustchain-http/src/errors.rs +++ b/trustchain-http/src/errors.rs @@ -24,6 +24,8 @@ pub enum TrustchainHTTPError { CredentialDoesNotExist, #[error("No issuer available.")] NoCredentialIssuer, + #[error("Failed to verify credential.")] + FailedToVerifyCredential, } impl From for TrustchainHTTPError { @@ -80,6 +82,9 @@ impl IntoResponse for TrustchainHTTPError { err @ TrustchainHTTPError::NoCredentialIssuer => { (StatusCode::BAD_REQUEST, err.to_string()) } + err @ TrustchainHTTPError::FailedToVerifyCredential => { + (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) + } }; let body = Json(json!({ "error": err_message })); (status, body).into_response() diff --git a/trustchain-http/src/server.rs b/trustchain-http/src/server.rs index 4076bef3..51c4e11d 100644 --- a/trustchain-http/src/server.rs +++ b/trustchain-http/src/server.rs @@ -50,8 +50,14 @@ impl TrustchainRouter { ) .route( "/vc/verifier", - get(verifier::TrustchainVerifierHTTPHandler::get_verifier) - .post(verifier::TrustchainVerifierHTTPHandler::post_verifier), + get(verifier::TrustchainVerifierHTTPHandler::get_verifier).post({ + let state = shared_state.clone(); + move |credential| { + verifier::TrustchainVerifierHTTPHandler::post_verifier( + credential, state, + ) + } + }), ) .route( "/did/:id", diff --git a/trustchain-http/src/verifier.rs b/trustchain-http/src/verifier.rs index c733bf2d..e6743914 100644 --- a/trustchain-http/src/verifier.rs +++ b/trustchain-http/src/verifier.rs @@ -1,3 +1,4 @@ +use crate::errors::TrustchainHTTPError; use crate::qrcode::str_to_qr_code_html; use crate::resolver::RootEventTime; use crate::state::AppState; @@ -14,27 +15,19 @@ use std::sync::Arc; use trustchain_ion::verifier::IONVerifier; use trustchain_ion::IONResolver; -// TODO: implement in core? pub struct PresentationRequest; -// TODO: implement in core? -/// An error type for presentation failures -pub enum PresentationError { - FailedToVerify, - // TODO: add other variants -} - /// An API for a Trustchain verifier server. pub trait TrustchainVerifierHTTP { /// Constructs a presentation request (given some `presentiation_id`) to send to a credential holder from request wallet by ID fn generate_presentation_request(presentation_id: &str) -> PresentationRequest; /// Verifies verifiable presentation - fn verify_presentation(presentation: &Presentation) -> Result<(), PresentationError>; + fn verify_presentation(presentation: &Presentation) -> Result<(), TrustchainHTTPError>; /// Verifies verifiable credential fn verify_credential( credential: &Credential, verifier: &IONVerifier, - ) -> Result<(), PresentationError>; + ) -> Result<(), TrustchainHTTPError>; } pub struct TrustchainVerifierHTTPHandler; @@ -44,14 +37,14 @@ impl TrustchainVerifierHTTP for TrustchainVerifierHTTPHandler { todo!() } - fn verify_presentation(presentation: &Presentation) -> Result<(), PresentationError> { + fn verify_presentation(presentation: &Presentation) -> Result<(), TrustchainHTTPError> { todo!() } fn verify_credential( credential: &Credential, verifier: &IONVerifier, - ) -> Result<(), PresentationError> { + ) -> Result<(), TrustchainHTTPError> { // 1. Verify signature on credential is valid given key // Use the resolver from the verifier inside: // credential.verify(None, verifier.resolver()) @@ -78,10 +71,7 @@ impl TrustchainVerifierHTTPHandler { // pub async fn post_verifier(Json(info): Json) -> impl IntoResponse { pub async fn post_verifier( Json(info): Json, - // TODO: replace - // Json(info): Json, - // Query(root_event_time): Query, - State(app_state): State>, + app_state: Arc, ) -> impl IntoResponse { info!( "Received credential at presentation:\n{}", From 371b4e3e5da7bc78d03e5a55c97f5dfee1a32621 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Tue, 30 May 2023 17:50:24 +0100 Subject: [PATCH 14/87] Make async_trait, refactor post_verifier handler --- trustchain-http/src/verifier.rs | 43 ++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/trustchain-http/src/verifier.rs b/trustchain-http/src/verifier.rs index e6743914..1509684c 100644 --- a/trustchain-http/src/verifier.rs +++ b/trustchain-http/src/verifier.rs @@ -2,7 +2,8 @@ use crate::errors::TrustchainHTTPError; use crate::qrcode::str_to_qr_code_html; use crate::resolver::RootEventTime; use crate::state::AppState; -use crate::EXAMPLE_VP_REQUEST; +use crate::{verifier, EXAMPLE_VP_REQUEST}; +use async_trait::async_trait; use axum::extract::{Path, Query, State}; use axum::http::StatusCode; use axum::response::{Html, IntoResponse}; @@ -12,36 +13,38 @@ use serde::{Deserialize, Serialize}; use ssi::did_resolve::DIDResolver; use ssi::vc::{Credential, Presentation}; use std::sync::Arc; +use trustchain_core::verifier::Verifier; use trustchain_ion::verifier::IONVerifier; use trustchain_ion::IONResolver; pub struct PresentationRequest; /// An API for a Trustchain verifier server. +#[async_trait] pub trait TrustchainVerifierHTTP { /// Constructs a presentation request (given some `presentiation_id`) to send to a credential holder from request wallet by ID fn generate_presentation_request(presentation_id: &str) -> PresentationRequest; /// Verifies verifiable presentation - fn verify_presentation(presentation: &Presentation) -> Result<(), TrustchainHTTPError>; + async fn verify_presentation(presentation: &Presentation) -> Result<(), TrustchainHTTPError>; /// Verifies verifiable credential - fn verify_credential( + async fn verify_credential( credential: &Credential, verifier: &IONVerifier, ) -> Result<(), TrustchainHTTPError>; } pub struct TrustchainVerifierHTTPHandler; - +#[async_trait] impl TrustchainVerifierHTTP for TrustchainVerifierHTTPHandler { fn generate_presentation_request(presentation_id: &str) -> PresentationRequest { todo!() } - fn verify_presentation(presentation: &Presentation) -> Result<(), TrustchainHTTPError> { + async fn verify_presentation(presentation: &Presentation) -> Result<(), TrustchainHTTPError> { todo!() } - fn verify_credential( + async fn verify_credential( credential: &Credential, verifier: &IONVerifier, ) -> Result<(), TrustchainHTTPError> { @@ -49,6 +52,7 @@ impl TrustchainVerifierHTTP for TrustchainVerifierHTTPHandler { // Use the resolver from the verifier inside: // credential.verify(None, verifier.resolver()) // 2. Verify did of issuer is valide (same as chain resolution) + // verifier.verify(did, root) todo!() } } @@ -61,26 +65,27 @@ struct PostVerifier { impl TrustchainVerifierHTTPHandler { /// API endpoint taking the UUID of a VC. Response is the VC JSON. - // TODO: identify how to handle multiple string variables - pub async fn get_verifier() -> Html { - // Return the presentation request - // (StatusCode::OK, Json(EXAMPLE_VP_REQUEST)) - Html(EXAMPLE_VP_REQUEST.to_string()) + // TODO: refine to allow a specific ID for the request to be passed + pub async fn get_verifier() -> impl IntoResponse { + (StatusCode::OK, Json(EXAMPLE_VP_REQUEST)) } - - // pub async fn post_verifier(Json(info): Json) -> impl IntoResponse { + /// Handler for credential received from POST. pub async fn post_verifier( - Json(info): Json, + Json(credential): Json, app_state: Arc, ) -> impl IntoResponse { info!( "Received credential at presentation:\n{}", - serde_json::to_string_pretty(&info).unwrap() + serde_json::to_string_pretty(&credential).unwrap() ); - - // TODO: check whether a specific response body is required - // See [here](https://w3c-ccg.github.io/vc-api/#prove-presentation) - (StatusCode::OK, "Received!") + TrustchainVerifierHTTPHandler::verify_credential(&credential, &app_state.verifier) + .await + .map(|_| { + ( + StatusCode::CREATED, + Html("Presentation successfully proved!"), + ) + }) } pub async fn get_verifier_qrcode(State(app_state): State>) -> Html { From ff956406406284a1f50cb8f8ddabe54e8ceda091 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Tue, 30 May 2023 18:39:45 +0100 Subject: [PATCH 15/87] Initial implementation of verifier, add error variant --- trustchain-http/src/errors.rs | 5 +++ trustchain-http/src/resolver.rs | 2 +- trustchain-http/src/verifier.rs | 79 +++++++++++++++++---------------- 3 files changed, 47 insertions(+), 39 deletions(-) diff --git a/trustchain-http/src/errors.rs b/trustchain-http/src/errors.rs index 42c5101d..90f92639 100644 --- a/trustchain-http/src/errors.rs +++ b/trustchain-http/src/errors.rs @@ -26,6 +26,8 @@ pub enum TrustchainHTTPError { NoCredentialIssuer, #[error("Failed to verify credential.")] FailedToVerifyCredential, + #[error("Invalid signature.")] + InvalidSignature, } impl From for TrustchainHTTPError { @@ -85,6 +87,9 @@ impl IntoResponse for TrustchainHTTPError { err @ TrustchainHTTPError::FailedToVerifyCredential => { (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) } + err @ TrustchainHTTPError::InvalidSignature => { + (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) + } }; let body = Json(json!({ "error": err_message })); (status, body).into_response() diff --git a/trustchain-http/src/resolver.rs b/trustchain-http/src/resolver.rs index b79b0e34..5aa2bd01 100644 --- a/trustchain-http/src/resolver.rs +++ b/trustchain-http/src/resolver.rs @@ -87,7 +87,7 @@ impl TrustchainHTTP for TrustchainHTTPHandler { #[derive(Deserialize, Serialize, Debug)] /// Struct for deserializing `root_event_time` from handler's query param. pub struct RootEventTime { - root_event_time: Timestamp, + pub root_event_time: Timestamp, } impl TrustchainHTTPHandler { diff --git a/trustchain-http/src/verifier.rs b/trustchain-http/src/verifier.rs index 1509684c..1d0de3e5 100644 --- a/trustchain-http/src/verifier.rs +++ b/trustchain-http/src/verifier.rs @@ -2,20 +2,19 @@ use crate::errors::TrustchainHTTPError; use crate::qrcode::str_to_qr_code_html; use crate::resolver::RootEventTime; use crate::state::AppState; -use crate::{verifier, EXAMPLE_VP_REQUEST}; +use crate::EXAMPLE_VP_REQUEST; use async_trait::async_trait; -use axum::extract::{Path, Query, State}; +use axum::extract::State; use axum::http::StatusCode; use axum::response::{Html, IntoResponse}; use axum::Json; use log::info; use serde::{Deserialize, Serialize}; use ssi::did_resolve::DIDResolver; -use ssi::vc::{Credential, Presentation}; +use ssi::vc::{Credential, Issuer, Presentation, URI}; use std::sync::Arc; -use trustchain_core::verifier::Verifier; +use trustchain_core::verifier::{Timestamp, Verifier}; use trustchain_ion::verifier::IONVerifier; -use trustchain_ion::IONResolver; pub struct PresentationRequest; @@ -23,69 +22,73 @@ pub struct PresentationRequest; #[async_trait] pub trait TrustchainVerifierHTTP { /// Constructs a presentation request (given some `presentiation_id`) to send to a credential holder from request wallet by ID - fn generate_presentation_request(presentation_id: &str) -> PresentationRequest; - /// Verifies verifiable presentation - async fn verify_presentation(presentation: &Presentation) -> Result<(), TrustchainHTTPError>; - /// Verifies verifiable credential - async fn verify_credential( - credential: &Credential, - verifier: &IONVerifier, - ) -> Result<(), TrustchainHTTPError>; -} - -pub struct TrustchainVerifierHTTPHandler; -#[async_trait] -impl TrustchainVerifierHTTP for TrustchainVerifierHTTPHandler { fn generate_presentation_request(presentation_id: &str) -> PresentationRequest { todo!() } - + /// Verifies verifiable presentation async fn verify_presentation(presentation: &Presentation) -> Result<(), TrustchainHTTPError> { todo!() } - + /// Verifies verifiable credential async fn verify_credential( credential: &Credential, + root_event_time: Timestamp, verifier: &IONVerifier, ) -> Result<(), TrustchainHTTPError> { - // 1. Verify signature on credential is valid given key - // Use the resolver from the verifier inside: - // credential.verify(None, verifier.resolver()) - // 2. Verify did of issuer is valide (same as chain resolution) - // verifier.verify(did, root) - todo!() + let verify_credential_result = credential.verify(None, verifier.resolver()).await; + if !verify_credential_result.errors.is_empty() { + return Err(TrustchainHTTPError::InvalidSignature); + } + match credential.issuer { + Some(Issuer::URI(URI::String(ref issuer))) => { + Ok(verifier.verify(issuer, root_event_time).await.map(|_| ())?) + } + _ => Err(TrustchainHTTPError::NoCredentialIssuer), + } } } +/// TODO +pub struct TrustchainVerifierHTTPHandler; + +impl TrustchainVerifierHTTP for TrustchainVerifierHTTPHandler {} + #[derive(Debug, Serialize, Deserialize)] -struct PostVerifier { +#[serde(rename_all = "camelCase")] +/// Struct for deserializing credential and corresponding root event time. +pub struct PostVerifier { pub credential: Credential, pub root_event_time: RootEventTime, } impl TrustchainVerifierHTTPHandler { /// API endpoint taking the UUID of a VC. Response is the VC JSON. - // TODO: refine to allow a specific ID for the request to be passed + // TODO: refine to allow a specific ID for the request to be passed and extracted from path pub async fn get_verifier() -> impl IntoResponse { (StatusCode::OK, Json(EXAMPLE_VP_REQUEST)) } /// Handler for credential received from POST. pub async fn post_verifier( - Json(credential): Json, + Json(verification_info): Json, app_state: Arc, ) -> impl IntoResponse { info!( "Received credential at presentation:\n{}", - serde_json::to_string_pretty(&credential).unwrap() + serde_json::to_string_pretty(&verification_info).unwrap() ); - TrustchainVerifierHTTPHandler::verify_credential(&credential, &app_state.verifier) - .await - .map(|_| { - ( - StatusCode::CREATED, - Html("Presentation successfully proved!"), - ) - }) + + TrustchainVerifierHTTPHandler::verify_credential( + &verification_info.credential, + verification_info.root_event_time.root_event_time, + &app_state.verifier, + ) + .await + .map(|_| { + ( + StatusCode::CREATED, + Html("Presentation successfully proved!"), + ) + }) } pub async fn get_verifier_qrcode(State(app_state): State>) -> Html { From dc8995c821064600da445a3910b0935dc226f56f Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Tue, 30 May 2023 18:41:42 +0100 Subject: [PATCH 16/87] Refactor verification info --- trustchain-http/src/server.rs | 5 +++-- trustchain-http/src/verifier.rs | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/trustchain-http/src/server.rs b/trustchain-http/src/server.rs index 51c4e11d..7f6b202c 100644 --- a/trustchain-http/src/server.rs +++ b/trustchain-http/src/server.rs @@ -52,9 +52,10 @@ impl TrustchainRouter { "/vc/verifier", get(verifier::TrustchainVerifierHTTPHandler::get_verifier).post({ let state = shared_state.clone(); - move |credential| { + move |verification_info| { verifier::TrustchainVerifierHTTPHandler::post_verifier( - credential, state, + verification_info, + state, ) } }), diff --git a/trustchain-http/src/verifier.rs b/trustchain-http/src/verifier.rs index 1d0de3e5..d68a6510 100644 --- a/trustchain-http/src/verifier.rs +++ b/trustchain-http/src/verifier.rs @@ -58,7 +58,7 @@ impl TrustchainVerifierHTTP for TrustchainVerifierHTTPHandler {} /// Struct for deserializing credential and corresponding root event time. pub struct PostVerifier { pub credential: Credential, - pub root_event_time: RootEventTime, + pub root_event_time: Timestamp, } impl TrustchainVerifierHTTPHandler { @@ -79,7 +79,7 @@ impl TrustchainVerifierHTTPHandler { TrustchainVerifierHTTPHandler::verify_credential( &verification_info.credential, - verification_info.root_event_time.root_event_time, + verification_info.root_event_time, &app_state.verifier, ) .await From cc79f144f9d0a1684b5d61ae2cdd116a5a1a4134 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Tue, 20 Jun 2023 18:34:55 +0100 Subject: [PATCH 17/87] WIP: Add presentation request cache and type --- trustchain-http/src/issuer.rs | 4 +++- trustchain-http/src/state.rs | 19 ++++++++++++++++--- trustchain-http/src/verifier.rs | 2 ++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/trustchain-http/src/issuer.rs b/trustchain-http/src/issuer.rs index 6802dd4a..ec5726f3 100644 --- a/trustchain-http/src/issuer.rs +++ b/trustchain-http/src/issuer.rs @@ -189,7 +189,7 @@ mod tests { one_or_many::OneOrMany, vc::{Credential, CredentialSubject, Issuer, URI}, }; - use std::sync::Arc; + use std::{collections::HashMap, sync::Arc}; use trustchain_core::{utils::canonicalize, verifier::Verifier}; use trustchain_ion::{get_ion_resolver, verifier::IONVerifier}; @@ -231,6 +231,7 @@ mod tests { let state = Arc::new(AppState::new_with_cache( TEST_HTTP_CONFIG.to_owned(), serde_json::from_str(CREDENTIALS).unwrap(), + HashMap::new(), )); let app = TrustchainRouter::from(state.clone()).into_router(); // Get offer for valid credential @@ -275,6 +276,7 @@ mod tests { let app = TrustchainRouter::from(Arc::new(AppState::new_with_cache( TEST_HTTP_CONFIG.to_owned(), serde_json::from_str(CREDENTIALS).unwrap(), + HashMap::new(), ))) .into_router(); let uid = "46cb84e2-fa10-11ed-a0d4-bbb4e61d1556".to_string(); diff --git a/trustchain-http/src/state.rs b/trustchain-http/src/state.rs index 13b1ad01..f138bbe7 100644 --- a/trustchain-http/src/state.rs +++ b/trustchain-http/src/state.rs @@ -1,4 +1,4 @@ -use crate::config::HTTPConfig; +use crate::{config::HTTPConfig, verifier::PresentationRequest}; use ssi::vc::Credential; use std::collections::HashMap; use trustchain_core::{resolver::Resolver, TRUSTCHAIN_DATA}; @@ -11,6 +11,7 @@ pub struct AppState { pub config: HTTPConfig, pub verifier: IONVerifier, pub credentials: HashMap, + pub presentation_requests: HashMap, } impl AppState { @@ -23,18 +24,30 @@ impl AppState { .as_slice(), ) .expect("Credential cache could not be deserialized."); + let presentation_requests: HashMap = serde_json::from_reader( + std::fs::read(std::path::Path::new(&path).join("presentations/requests/cache.json")) + .expect("Credential cache does not exist.") + .as_slice(), + ) + .expect("Credential cache could not be deserialized."); Self { config, verifier, credentials, + presentation_requests, } } - pub fn new_with_cache(config: HTTPConfig, credentials: HashMap) -> Self { + pub fn new_with_cache( + config: HTTPConfig, + credentials: HashMap, + presentation_requests: HashMap, + ) -> Self { let verifier = IONVerifier::new(Resolver::new(get_ion_resolver(DEFAULT_VERIFIER_ENDPOINT))); Self { config, verifier, credentials, + presentation_requests, } } } @@ -47,6 +60,6 @@ mod tests { #[test] fn test_create_app_state() { AppState::new(HTTPConfig::default()); - AppState::new_with_cache(HTTPConfig::default(), HashMap::new()); + AppState::new_with_cache(HTTPConfig::default(), HashMap::new(), HashMap::new()); } } diff --git a/trustchain-http/src/verifier.rs b/trustchain-http/src/verifier.rs index d68a6510..4da2829e 100644 --- a/trustchain-http/src/verifier.rs +++ b/trustchain-http/src/verifier.rs @@ -16,6 +16,8 @@ use std::sync::Arc; use trustchain_core::verifier::{Timestamp, Verifier}; use trustchain_ion::verifier::IONVerifier; +// https://w3c-ccg.github.io/vp-request-spec/ +#[derive(Debug, Serialize, Deserialize)] pub struct PresentationRequest; /// An API for a Trustchain verifier server. From dc54bf7d13a3d07a5bc953f422ca707255a59e5d Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Fri, 7 Jul 2023 16:01:51 +0100 Subject: [PATCH 18/87] WIP: mobile vc verification --- trustchain-ffi/src/mobile.rs | 81 ++++++++++++++++++++++++++++-------- 1 file changed, 64 insertions(+), 17 deletions(-) diff --git a/trustchain-ffi/src/mobile.rs b/trustchain-ffi/src/mobile.rs index 96e8deab..41bc41a5 100644 --- a/trustchain-ffi/src/mobile.rs +++ b/trustchain-ffi/src/mobile.rs @@ -1,12 +1,16 @@ use anyhow::{anyhow, Result}; use serde_json::to_string_pretty; -use ssi::vc::Credential; +use ssi::vc::{Credential, URI}; use tokio::runtime::Runtime; use trustchain_api::{ api::{TrustchainDIDAPI, TrustchainVCAPI}, TrustchainAPI, }; -use trustchain_core::{config::core_config, verifier::Verifier}; +use trustchain_core::{ + chain::{Chain, DIDChain}, + config::core_config, + verifier::Verifier, +}; use trustchain_ion::{get_ion_resolver, verifier::IONVerifier}; /// Android localhost endpoint. @@ -46,12 +50,15 @@ pub fn did_verify(did: String, endpoint: String) -> Result<()> { let rt = Runtime::new().unwrap(); rt.block_on(async { // Trustchain Resolver with android localhost - let mut verifier = IONVerifier::new(get_ion_resolver(ANDROID_ENDPOINT)); - verifier.fetch_bundle(&did, Some(endpoint)); + let verifier = IONVerifier::new(get_ion_resolver(ANDROID_ENDPOINT)); + verifier + .fetch_bundle(&did, Some(endpoint)) + .await + .map_err(|err| anyhow!(err.to_string()))?; verifier .verify(&did, core_config().root_event_time) .await - .map_err(|err| anyhow!(err.to_string())); + .map_err(|err| anyhow!(err.to_string()))?; Ok(()) }) } @@ -63,19 +70,52 @@ pub fn did_verify_bundle(bundle_json: String) -> Result { pub fn vc_verify_credential(credential_json: String, proof_options_json: String) -> Result { let rt = Runtime::new().unwrap(); rt.block_on(async { - let credential: Credential = serde_json::from_str(&credential_json).unwrap(); - let (verify_result, did_chain) = - TrustchainAPI::verify_credential(&credential, false, core_config().root_event_time) - .await; - if verify_result.errors.is_empty() { + let credential: Credential = serde_json::from_str(&credential_json)?; + let resolver = get_ion_resolver("http://10.0.2.2:3000/"); + + // TODO: refactor to use API once functional + // TrustchainAPI::verify_credential(&credential, false, 1).await; + // With signature only as `true`, functions + // let signature_only = true; + let signature_only = false; + let root_event_time = 1666971942; + let verification_result = credential.verify(None, &resolver).await; + let (verify_result, did_chain) = if signature_only { + (verification_result, None) } else { - return Err(anyhow!("Invalid signature")); - } - if let Some(did_chain) = did_chain { - if did_chain.is_ok() { - Ok("OK".to_string()) - } else { - Err(anyhow!("Invalid DID chain of issuer.")) + // Issuer + let issuer = match credential.issuer { + Some(issuer) => issuer.get_id(), + _ => return Err(anyhow!("No issuer present in credential.")), + }; + // TODO: identify why verifier new call fails over FFI + let verifier = IONVerifier::new(get_ion_resolver("http://10.0.2.2:3000/")); + // Get root of chain so can fetch bundle + let did_chain_prov = DIDChain::new(&issuer, &resolver).await?; + // Fetch root bundle + verifier + .fetch_bundle( + did_chain_prov.root(), + Some("http://10.0.2.2:8081/did/bundle/".to_string()), + ) + .await + .map_err(|err| anyhow!(err.to_string()))?; + // Verify + ( + verification_result, + Some(verifier.verify(&issuer, root_event_time).await), + ) + }; + + // Returns + if !verify_result.errors.is_empty() { + Err(anyhow!("Invalid signature")) + } else if signature_only { + Ok("OK: signature only".to_string()) + } else if let Some(did_chain) = did_chain { + match did_chain { + Ok(_) => Ok("OK".to_string()), + Err(e) => Err(anyhow!(e.to_string())), } } else { Err(anyhow!("No DID chain returned, failed to verify issuer.")) @@ -97,3 +137,10 @@ pub fn vc_verify_presentation( ) -> Result { todo!() } +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_vc_verify_credential() {} +} From a35ec7350418d1a89079dbfd846469cf72386e5c Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Fri, 14 Jul 2023 16:02:35 +0100 Subject: [PATCH 19/87] Initial VC verification with light verifier --- Cargo.toml | 1 + trustchain-ffi/src/mobile.rs | 75 ++++++++++++++++++++++------------ trustchain-ion/src/verifier.rs | 17 ++++++++ 3 files changed, 66 insertions(+), 27 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 056bbf06..13190fbd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "trustchain-core", "trustchain-ion", + "trustchain-http", "trustchain-api", "trustchain-cli", "trustchain-ffi" diff --git a/trustchain-ffi/src/mobile.rs b/trustchain-ffi/src/mobile.rs index 41bc41a5..76c52962 100644 --- a/trustchain-ffi/src/mobile.rs +++ b/trustchain-ffi/src/mobile.rs @@ -1,6 +1,9 @@ use anyhow::{anyhow, Result}; use serde_json::to_string_pretty; -use ssi::vc::{Credential, URI}; +use ssi::{ + one_or_many::OneOrMany, + vc::{Credential, LinkedDataProofOptions, Proof}, +}; use tokio::runtime::Runtime; use trustchain_api::{ api::{TrustchainDIDAPI, TrustchainVCAPI}, @@ -50,9 +53,9 @@ pub fn did_verify(did: String, endpoint: String) -> Result<()> { let rt = Runtime::new().unwrap(); rt.block_on(async { // Trustchain Resolver with android localhost - let verifier = IONVerifier::new(get_ion_resolver(ANDROID_ENDPOINT)); + let verifier = IONVerifier::with_endpoint(get_ion_resolver(ANDROID_ENDPOINT), endpoint); verifier - .fetch_bundle(&did, Some(endpoint)) + .fetch_bundle(&did) .await .map_err(|err| anyhow!(err.to_string()))?; verifier @@ -70,46 +73,64 @@ pub fn did_verify_bundle(bundle_json: String) -> Result { pub fn vc_verify_credential(credential_json: String, proof_options_json: String) -> Result { let rt = Runtime::new().unwrap(); rt.block_on(async { + // let host = "127.0.0.1"; + let host = "10.0.2.2"; let credential: Credential = serde_json::from_str(&credential_json)?; - let resolver = get_ion_resolver("http://10.0.2.2:3000/"); - // TODO: refactor to use API once functional + let verifier = IONVerifier::with_endpoint( + get_ion_resolver(&format!("http://{host}:3000/")), + format!("http://{host}:8081/did/bundle/"), + ); + // TODO: refactor to use TrustchainAPI once functional // TrustchainAPI::verify_credential(&credential, false, 1).await; - // With signature only as `true`, functions - // let signature_only = true; let signature_only = false; let root_event_time = 1666971942; - let verification_result = credential.verify(None, &resolver).await; - let (verify_result, did_chain) = if signature_only { - (verification_result, None) + + // NB. When using android emulator, the time is less than the created time on + // the credential. This leads to a failure upon the proofs being checked: + // https://docs.rs/ssi/0.4.0/src/ssi/vc.rs.html#1243 (filtered here) + // https://docs.rs/ssi/0.4.0/src/ssi/vc.rs.html#1973-1975 (created time checked here) + // + // A workaround is to set the "created" time from the credential directly with + // LinkedDataProofOptions + let ldpo = match credential.proof { + Some(OneOrMany::One(Proof { + created: created_time, + .. + })) => LinkedDataProofOptions { + created: created_time, + ..Default::default() + }, + _ => return Err(anyhow!("No proof or created time available in proof.")), + }; + // Verify credential signature with LinkedDataProofOptions + let verification_result = credential.verify(Some(ldpo), verifier.resolver()).await; + + // Get DID chain if not signature only + let did_chain = if signature_only { + None } else { - // Issuer let issuer = match credential.issuer { Some(issuer) => issuer.get_id(), _ => return Err(anyhow!("No issuer present in credential.")), }; - // TODO: identify why verifier new call fails over FFI - let verifier = IONVerifier::new(get_ion_resolver("http://10.0.2.2:3000/")); - // Get root of chain so can fetch bundle - let did_chain_prov = DIDChain::new(&issuer, &resolver).await?; - // Fetch root bundle + // Get root of chain to fetch bundle for root verification + let did_chain = DIDChain::new(&issuer, verifier.resolver()) + .await + .map_err(|err| anyhow!(err))?; verifier - .fetch_bundle( - did_chain_prov.root(), - Some("http://10.0.2.2:8081/did/bundle/".to_string()), - ) + .fetch_bundle(did_chain.root()) .await .map_err(|err| anyhow!(err.to_string()))?; - // Verify - ( - verification_result, - Some(verifier.verify(&issuer, root_event_time).await), - ) + Some(verifier.verify(&issuer, root_event_time).await) }; // Returns - if !verify_result.errors.is_empty() { - Err(anyhow!("Invalid signature")) + if !verification_result.errors.is_empty() { + Err(anyhow!( + "Invalid signature:\n{}", + serde_json::to_string_pretty(&verification_result.errors).unwrap() + )) } else if signature_only { Ok("OK: signature only".to_string()) } else if let Some(did_chain) = did_chain { diff --git a/trustchain-ion/src/verifier.rs b/trustchain-ion/src/verifier.rs index 0fe1fa05..835480fb 100644 --- a/trustchain-ion/src/verifier.rs +++ b/trustchain-ion/src/verifier.rs @@ -23,6 +23,7 @@ use std::convert::TryInto; use std::marker::PhantomData; use std::str::FromStr; use std::sync::{Arc, Mutex}; +use trustchain_core::chain::{Chain, DIDChain}; use trustchain_core::commitment::{CommitmentError, DIDCommitment}; use trustchain_core::resolver::{Resolver, ResolverError}; use trustchain_core::utils::get_did_suffix; @@ -510,6 +511,22 @@ where fn resolver(&self) -> &Resolver { &self.resolver } + + // TODO: temporary override until timestamp verification complete + async fn verify( + &self, + did: &str, + _root_timestamp: Timestamp, + ) -> Result { + // Build a chain from the given DID to the root. + let resolver = self.resolver(); + let chain = DIDChain::new(did, resolver).await?; + + // Verify the proofs in the chain. + chain.verify_proofs()?; + + Ok(chain) + } } #[cfg(test)] From f0b7aee85a5aa47698dde56eabd8e9faf3f181fe Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Mon, 17 Jul 2023 16:56:30 +0100 Subject: [PATCH 20/87] Remove override of verify for light client --- trustchain-ion/src/verifier.rs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/trustchain-ion/src/verifier.rs b/trustchain-ion/src/verifier.rs index 20191757..7c6696cc 100644 --- a/trustchain-ion/src/verifier.rs +++ b/trustchain-ion/src/verifier.rs @@ -23,7 +23,6 @@ use std::convert::TryInto; use std::marker::PhantomData; use std::str::FromStr; use std::sync::{Arc, Mutex}; -use trustchain_core::chain::{Chain, DIDChain}; use trustchain_core::commitment::{CommitmentError, DIDCommitment}; use trustchain_core::resolver::{Resolver, ResolverError}; use trustchain_core::utils::get_did_suffix; @@ -519,22 +518,6 @@ where fn resolver(&self) -> &Resolver { &self.resolver } - - // TODO: temporary override until timestamp verification complete - async fn verify( - &self, - did: &str, - _root_timestamp: Timestamp, - ) -> Result { - // Build a chain from the given DID to the root. - let resolver = self.resolver(); - let chain = DIDChain::new(did, resolver).await?; - - // Verify the proofs in the chain. - chain.verify_proofs()?; - - Ok(chain) - } } #[cfg(test)] From 042ea3344d7623eb33d663530da82a778ca00ec3 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Mon, 17 Jul 2023 17:02:35 +0100 Subject: [PATCH 21/87] Add FFI bridge modules --- trustchain-ffi/src/lib.rs | 1 + trustchain-ffi/src/mobile_bridge.io.rs | 119 +++++++++++++++ trustchain-ffi/src/mobile_bridge.rs | 199 +++++++++++++++++++++++++ 3 files changed, 319 insertions(+) create mode 100644 trustchain-ffi/src/mobile_bridge.io.rs create mode 100644 trustchain-ffi/src/mobile_bridge.rs diff --git a/trustchain-ffi/src/lib.rs b/trustchain-ffi/src/lib.rs index 431a06fc..3032c62d 100644 --- a/trustchain-ffi/src/lib.rs +++ b/trustchain-ffi/src/lib.rs @@ -1,2 +1,3 @@ pub mod gui; pub mod mobile; +mod mobile_bridge; /* AUTO INJECTED BY flutter_rust_bridge. This line may not be accurate, and you can change it according to your needs. */ diff --git a/trustchain-ffi/src/mobile_bridge.io.rs b/trustchain-ffi/src/mobile_bridge.io.rs new file mode 100644 index 00000000..bf66f8ad --- /dev/null +++ b/trustchain-ffi/src/mobile_bridge.io.rs @@ -0,0 +1,119 @@ +use super::*; +// Section: wire functions + +#[no_mangle] +pub extern "C" fn wire_greet(port_: i64) { + wire_greet_impl(port_) +} + +#[no_mangle] +pub extern "C" fn wire_resolve(port_: i64, did: *mut wire_uint_8_list) { + wire_resolve_impl(port_, did) +} + +#[no_mangle] +pub extern "C" fn wire_did_resolve(port_: i64, did: *mut wire_uint_8_list) { + wire_did_resolve_impl(port_, did) +} + +#[no_mangle] +pub extern "C" fn wire_did_verify( + port_: i64, + did: *mut wire_uint_8_list, + endpoint: *mut wire_uint_8_list, +) { + wire_did_verify_impl(port_, did, endpoint) +} + +#[no_mangle] +pub extern "C" fn wire_did_verify_bundle(port_: i64, bundle_json: *mut wire_uint_8_list) { + wire_did_verify_bundle_impl(port_, bundle_json) +} + +#[no_mangle] +pub extern "C" fn wire_vc_verify_credential( + port_: i64, + credential_json: *mut wire_uint_8_list, + proof_options_json: *mut wire_uint_8_list, +) { + wire_vc_verify_credential_impl(port_, credential_json, proof_options_json) +} + +#[no_mangle] +pub extern "C" fn wire_vc_issue_presentation( + port_: i64, + presentation_json: *mut wire_uint_8_list, + proof_options_json: *mut wire_uint_8_list, + key_json: *mut wire_uint_8_list, +) { + wire_vc_issue_presentation_impl(port_, presentation_json, proof_options_json, key_json) +} + +#[no_mangle] +pub extern "C" fn wire_vc_verify_presentation( + port_: i64, + presentation_json: *mut wire_uint_8_list, + proof_options_json: *mut wire_uint_8_list, +) { + wire_vc_verify_presentation_impl(port_, presentation_json, proof_options_json) +} + +// Section: allocate functions + +#[no_mangle] +pub extern "C" fn new_uint_8_list_0(len: i32) -> *mut wire_uint_8_list { + let ans = wire_uint_8_list { + ptr: support::new_leak_vec_ptr(Default::default(), len), + len, + }; + support::new_leak_box_ptr(ans) +} + +// Section: related functions + +// Section: impl Wire2Api + +impl Wire2Api for *mut wire_uint_8_list { + fn wire2api(self) -> String { + let vec: Vec = self.wire2api(); + String::from_utf8_lossy(&vec).into_owned() + } +} + +impl Wire2Api> for *mut wire_uint_8_list { + fn wire2api(self) -> Vec { + unsafe { + let wrap = support::box_from_leak_ptr(self); + support::vec_from_leak_ptr(wrap.ptr, wrap.len) + } + } +} +// Section: wire structs + +#[repr(C)] +#[derive(Clone)] +pub struct wire_uint_8_list { + ptr: *mut u8, + len: i32, +} + +// Section: impl NewWithNullPtr + +pub trait NewWithNullPtr { + fn new_with_null_ptr() -> Self; +} + +impl NewWithNullPtr for *mut T { + fn new_with_null_ptr() -> Self { + std::ptr::null_mut() + } +} + +// Section: sync execution mode utility + +#[no_mangle] +pub extern "C" fn free_WireSyncReturn(ptr: support::WireSyncReturn) { + unsafe { + let _ = support::box_from_leak_ptr(ptr); + }; +} diff --git a/trustchain-ffi/src/mobile_bridge.rs b/trustchain-ffi/src/mobile_bridge.rs new file mode 100644 index 00000000..cc4e881d --- /dev/null +++ b/trustchain-ffi/src/mobile_bridge.rs @@ -0,0 +1,199 @@ +#![allow( + non_camel_case_types, + unused, + clippy::redundant_closure, + clippy::useless_conversion, + clippy::unit_arg, + clippy::double_parens, + non_snake_case, + clippy::too_many_arguments +)] +// AUTO GENERATED FILE, DO NOT EDIT. +// Generated by `flutter_rust_bridge`@ 1.64.0. + +use crate::mobile::*; +use core::panic::UnwindSafe; +use flutter_rust_bridge::*; +use std::ffi::c_void; +use std::sync::Arc; + +// Section: imports + +// Section: wire functions + +fn wire_greet_impl(port_: MessagePort) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap( + WrapInfo { + debug_name: "greet", + port: Some(port_), + mode: FfiCallMode::Normal, + }, + move || move |task_callback| Ok(greet()), + ) +} +fn wire_resolve_impl(port_: MessagePort, did: impl Wire2Api + UnwindSafe) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap( + WrapInfo { + debug_name: "resolve", + port: Some(port_), + mode: FfiCallMode::Normal, + }, + move || { + let api_did = did.wire2api(); + move |task_callback| resolve(api_did) + }, + ) +} +fn wire_did_resolve_impl(port_: MessagePort, did: impl Wire2Api + UnwindSafe) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap( + WrapInfo { + debug_name: "did_resolve", + port: Some(port_), + mode: FfiCallMode::Normal, + }, + move || { + let api_did = did.wire2api(); + move |task_callback| did_resolve(api_did) + }, + ) +} +fn wire_did_verify_impl( + port_: MessagePort, + did: impl Wire2Api + UnwindSafe, + endpoint: impl Wire2Api + UnwindSafe, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap( + WrapInfo { + debug_name: "did_verify", + port: Some(port_), + mode: FfiCallMode::Normal, + }, + move || { + let api_did = did.wire2api(); + let api_endpoint = endpoint.wire2api(); + move |task_callback| did_verify(api_did, api_endpoint) + }, + ) +} +fn wire_did_verify_bundle_impl( + port_: MessagePort, + bundle_json: impl Wire2Api + UnwindSafe, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap( + WrapInfo { + debug_name: "did_verify_bundle", + port: Some(port_), + mode: FfiCallMode::Normal, + }, + move || { + let api_bundle_json = bundle_json.wire2api(); + move |task_callback| did_verify_bundle(api_bundle_json) + }, + ) +} +fn wire_vc_verify_credential_impl( + port_: MessagePort, + credential_json: impl Wire2Api + UnwindSafe, + proof_options_json: impl Wire2Api + UnwindSafe, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap( + WrapInfo { + debug_name: "vc_verify_credential", + port: Some(port_), + mode: FfiCallMode::Normal, + }, + move || { + let api_credential_json = credential_json.wire2api(); + let api_proof_options_json = proof_options_json.wire2api(); + move |task_callback| vc_verify_credential(api_credential_json, api_proof_options_json) + }, + ) +} +fn wire_vc_issue_presentation_impl( + port_: MessagePort, + presentation_json: impl Wire2Api + UnwindSafe, + proof_options_json: impl Wire2Api + UnwindSafe, + key_json: impl Wire2Api + UnwindSafe, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap( + WrapInfo { + debug_name: "vc_issue_presentation", + port: Some(port_), + mode: FfiCallMode::Normal, + }, + move || { + let api_presentation_json = presentation_json.wire2api(); + let api_proof_options_json = proof_options_json.wire2api(); + let api_key_json = key_json.wire2api(); + move |task_callback| { + Ok(vc_issue_presentation( + api_presentation_json, + api_proof_options_json, + api_key_json, + )) + } + }, + ) +} +fn wire_vc_verify_presentation_impl( + port_: MessagePort, + presentation_json: impl Wire2Api + UnwindSafe, + proof_options_json: impl Wire2Api + UnwindSafe, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap( + WrapInfo { + debug_name: "vc_verify_presentation", + port: Some(port_), + mode: FfiCallMode::Normal, + }, + move || { + let api_presentation_json = presentation_json.wire2api(); + let api_proof_options_json = proof_options_json.wire2api(); + move |task_callback| { + vc_verify_presentation(api_presentation_json, api_proof_options_json) + } + }, + ) +} +// Section: wrapper structs + +// Section: static checks + +// Section: allocate functions + +// Section: related functions + +// Section: impl Wire2Api + +pub trait Wire2Api { + fn wire2api(self) -> T; +} + +impl Wire2Api> for *mut S +where + *mut S: Wire2Api, +{ + fn wire2api(self) -> Option { + (!self.is_null()).then(|| self.wire2api()) + } +} + +impl Wire2Api for u8 { + fn wire2api(self) -> u8 { + self + } +} + +// Section: impl IntoDart + +// Section: executor + +support::lazy_static! { + pub static ref FLUTTER_RUST_BRIDGE_HANDLER: support::DefaultHandler = Default::default(); +} + +#[cfg(not(target_family = "wasm"))] +#[path = "mobile_bridge.io.rs"] +mod io; +#[cfg(not(target_family = "wasm"))] +pub use io::*; From a214f2a24483ac898ae0564e418032daf177ab59 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Mon, 17 Jul 2023 17:21:55 +0100 Subject: [PATCH 22/87] Remove unnecessary fetch --- trustchain-ffi/src/mobile.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/trustchain-ffi/src/mobile.rs b/trustchain-ffi/src/mobile.rs index 76c52962..074e83d0 100644 --- a/trustchain-ffi/src/mobile.rs +++ b/trustchain-ffi/src/mobile.rs @@ -69,6 +69,7 @@ pub fn did_verify(did: String, endpoint: String) -> Result<()> { pub fn did_verify_bundle(bundle_json: String) -> Result { todo!() } + /// Verifies a verifiable credential. Analogous with [didkit](https://docs.rs/didkit/latest/didkit/c/fn.didkit_vc_verify_credential.html). pub fn vc_verify_credential(credential_json: String, proof_options_json: String) -> Result { let rt = Runtime::new().unwrap(); @@ -114,14 +115,6 @@ pub fn vc_verify_credential(credential_json: String, proof_options_json: String) Some(issuer) => issuer.get_id(), _ => return Err(anyhow!("No issuer present in credential.")), }; - // Get root of chain to fetch bundle for root verification - let did_chain = DIDChain::new(&issuer, verifier.resolver()) - .await - .map_err(|err| anyhow!(err))?; - verifier - .fetch_bundle(did_chain.root()) - .await - .map_err(|err| anyhow!(err.to_string()))?; Some(verifier.verify(&issuer, root_event_time).await) }; From 1238585010eff152ce42b97441349c34cc1a517f Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Mon, 17 Jul 2023 17:32:18 +0100 Subject: [PATCH 23/87] Add FFI module for passing endpoint and proof options --- trustchain-ffi/src/lib.rs | 3 +- trustchain-ffi/src/options.rs | 70 +++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 trustchain-ffi/src/options.rs diff --git a/trustchain-ffi/src/lib.rs b/trustchain-ffi/src/lib.rs index 3032c62d..640d5f8a 100644 --- a/trustchain-ffi/src/lib.rs +++ b/trustchain-ffi/src/lib.rs @@ -1,3 +1,4 @@ pub mod gui; pub mod mobile; -mod mobile_bridge; /* AUTO INJECTED BY flutter_rust_bridge. This line may not be accurate, and you can change it according to your needs. */ +mod mobile_bridge; +pub mod options; diff --git a/trustchain-ffi/src/options.rs b/trustchain-ffi/src/options.rs new file mode 100644 index 00000000..b8c2e532 --- /dev/null +++ b/trustchain-ffi/src/options.rs @@ -0,0 +1,70 @@ +use serde::{Deserialize, Serialize}; +use trustchain_ion::URL; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct Endpoint { + pub url: URL, + pub port: u16, +} + +impl Endpoint { + pub fn new(url: URL, port: u16) -> Self { + Self { url, port } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct EndpointOptions { + resolver_endpoint: Endpoint, + bundle_endpoint: Endpoint, +} + +impl Default for EndpointOptions { + fn default() -> Self { + Self { + resolver_endpoint: Endpoint::new(URL::from("http://127.0.0.1"), 3000), + bundle_endpoint: Endpoint::new(URL::from("http://127.0.0.1"), 8081), + } + } +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct ProofOptions { + signature_only: bool, +} + +#[cfg(test)] +mod tests { + use super::*; + + const TEST_ENDPOINT_OPTIONS: &str = r#" + { + "resolverEndpoint": { + "url": "http://127.0.0.1", + "port": 3000 + }, + "bundleEndpoint": { + "url": "http://127.0.0.1", + "port": 8081 + } + } + "#; + + const TEST_PROOF_OPTIONS: &str = r#" + { + "signatureOnly": false + } + "#; + + #[test] + fn test_endpoint_options() { + serde_json::from_str::(TEST_ENDPOINT_OPTIONS).unwrap(); + } + #[test] + fn test_proof_options() { + serde_json::from_str::(TEST_PROOF_OPTIONS).unwrap(); + } +} From 769b5beb587566c9547eb4921c4e32058c9c68f2 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Mon, 17 Jul 2023 20:34:16 +0100 Subject: [PATCH 24/87] Remove function, add todo --- trustchain-ffi/src/mobile.rs | 5 +---- trustchain-ffi/src/mobile_bridge.io.rs | 5 ----- trustchain-ffi/src/mobile_bridge.rs | 16 ---------------- 3 files changed, 1 insertion(+), 25 deletions(-) diff --git a/trustchain-ffi/src/mobile.rs b/trustchain-ffi/src/mobile.rs index 074e83d0..7d009022 100644 --- a/trustchain-ffi/src/mobile.rs +++ b/trustchain-ffi/src/mobile.rs @@ -65,10 +65,6 @@ pub fn did_verify(did: String, endpoint: String) -> Result<()> { Ok(()) }) } -/// Verifies a given DID bundle providing complete verification without trust in endpoint. -pub fn did_verify_bundle(bundle_json: String) -> Result { - todo!() -} /// Verifies a verifiable credential. Analogous with [didkit](https://docs.rs/didkit/latest/didkit/c/fn.didkit_vc_verify_credential.html). pub fn vc_verify_credential(credential_json: String, proof_options_json: String) -> Result { @@ -104,6 +100,7 @@ pub fn vc_verify_credential(credential_json: String, proof_options_json: String) }, _ => return Err(anyhow!("No proof or created time available in proof.")), }; + // TODO: try setting time as now from emulator to check time used from now_ms() call // Verify credential signature with LinkedDataProofOptions let verification_result = credential.verify(Some(ldpo), verifier.resolver()).await; diff --git a/trustchain-ffi/src/mobile_bridge.io.rs b/trustchain-ffi/src/mobile_bridge.io.rs index bf66f8ad..bec4ef32 100644 --- a/trustchain-ffi/src/mobile_bridge.io.rs +++ b/trustchain-ffi/src/mobile_bridge.io.rs @@ -25,11 +25,6 @@ pub extern "C" fn wire_did_verify( wire_did_verify_impl(port_, did, endpoint) } -#[no_mangle] -pub extern "C" fn wire_did_verify_bundle(port_: i64, bundle_json: *mut wire_uint_8_list) { - wire_did_verify_bundle_impl(port_, bundle_json) -} - #[no_mangle] pub extern "C" fn wire_vc_verify_credential( port_: i64, diff --git a/trustchain-ffi/src/mobile_bridge.rs b/trustchain-ffi/src/mobile_bridge.rs index cc4e881d..b376bd05 100644 --- a/trustchain-ffi/src/mobile_bridge.rs +++ b/trustchain-ffi/src/mobile_bridge.rs @@ -75,22 +75,6 @@ fn wire_did_verify_impl( }, ) } -fn wire_did_verify_bundle_impl( - port_: MessagePort, - bundle_json: impl Wire2Api + UnwindSafe, -) { - FLUTTER_RUST_BRIDGE_HANDLER.wrap( - WrapInfo { - debug_name: "did_verify_bundle", - port: Some(port_), - mode: FfiCallMode::Normal, - }, - move || { - let api_bundle_json = bundle_json.wire2api(); - move |task_callback| did_verify_bundle(api_bundle_json) - }, - ) -} fn wire_vc_verify_credential_impl( port_: MessagePort, credential_json: impl Wire2Api + UnwindSafe, From a6ee872e1c0fa58927720ef7c0ff793a719f9596 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Mon, 17 Jul 2023 21:13:19 +0100 Subject: [PATCH 25/87] Refactor mobile API with options --- trustchain-ffi/src/mobile.rs | 70 +++++++++++++------------- trustchain-ffi/src/mobile_bridge.io.rs | 25 ++++----- trustchain-ffi/src/mobile_bridge.rs | 44 ++++++++-------- trustchain-ffi/src/options.rs | 19 ++++--- trustchain-ion/src/verifier.rs | 2 +- 5 files changed, 83 insertions(+), 77 deletions(-) diff --git a/trustchain-ffi/src/mobile.rs b/trustchain-ffi/src/mobile.rs index 7d009022..48b86a53 100644 --- a/trustchain-ffi/src/mobile.rs +++ b/trustchain-ffi/src/mobile.rs @@ -16,6 +16,8 @@ use trustchain_core::{ }; use trustchain_ion::{get_ion_resolver, verifier::IONVerifier}; +use crate::options::{EndpointOptions, ProofOptions}; + /// Android localhost endpoint. const ANDROID_ENDPOINT: &str = "http://10.0.2.2:3000/"; @@ -24,40 +26,27 @@ pub fn greet() -> String { "Hello from Rust! 🦀".into() } -// TODO: update to use TrustchainCLI once endpoint can be passed -/// Example resolve interface. -pub fn resolve(did: String) -> Result { - let rt = Runtime::new().unwrap(); - rt.block_on(async { - // Trustchain Resolver with android localhost - let resolver = get_ion_resolver(ANDROID_ENDPOINT); - // Result metadata, Document, Document metadata - let (_, doc, _) = resolver.resolve_as_result(&did).await.unwrap(); - Ok(to_string_pretty(&doc.unwrap())?) - }) -} - /// Resolves a given DID document assuming trust in endpoint. -pub fn did_resolve(did: String) -> Result { +pub fn did_resolve(did: String, endpoint_opts: String) -> Result { + let endpoint_options: EndpointOptions = serde_json::from_str(&endpoint_opts)?; let rt = Runtime::new().unwrap(); rt.block_on(async { - // Trustchain Resolver with android localhost - TrustchainAPI::resolve(&did, ANDROID_ENDPOINT.into()) + TrustchainAPI::resolve(&did, endpoint_options.resolver_endpoint.to_address()) .await .map_err(|e| anyhow!(e)) .and_then(|(_, doc, _)| serde_json::to_string_pretty(&doc).map_err(|e| anyhow!(e))) }) } /// Verifies a given DID assuming trust in endpoint. -pub fn did_verify(did: String, endpoint: String) -> Result<()> { +pub fn did_verify(did: String, endpoint_opts: String, proof_opts: String) -> Result<()> { + let endpoint_options: EndpointOptions = serde_json::from_str(&endpoint_opts)?; + let proof_options: ProofOptions = serde_json::from_str(&proof_opts)?; let rt = Runtime::new().unwrap(); rt.block_on(async { - // Trustchain Resolver with android localhost - let verifier = IONVerifier::with_endpoint(get_ion_resolver(ANDROID_ENDPOINT), endpoint); - verifier - .fetch_bundle(&did) - .await - .map_err(|err| anyhow!(err.to_string()))?; + let verifier = IONVerifier::with_endpoint( + get_ion_resolver(&endpoint_options.resolver_endpoint.to_address()), + endpoint_options.bundle_endpoint.to_address(), + ); verifier .verify(&did, core_config().root_event_time) .await @@ -67,21 +56,22 @@ pub fn did_verify(did: String, endpoint: String) -> Result<()> { } /// Verifies a verifiable credential. Analogous with [didkit](https://docs.rs/didkit/latest/didkit/c/fn.didkit_vc_verify_credential.html). -pub fn vc_verify_credential(credential_json: String, proof_options_json: String) -> Result { +pub fn vc_verify_credential( + credential: String, + endpoint_opts: String, + proof_opts: String, +) -> Result { + let endpoint_options: EndpointOptions = serde_json::from_str(&endpoint_opts)?; + let proof_options: ProofOptions = serde_json::from_str(&proof_opts)?; let rt = Runtime::new().unwrap(); rt.block_on(async { - // let host = "127.0.0.1"; - let host = "10.0.2.2"; - let credential: Credential = serde_json::from_str(&credential_json)?; - + let credential: Credential = serde_json::from_str(&credential)?; let verifier = IONVerifier::with_endpoint( - get_ion_resolver(&format!("http://{host}:3000/")), - format!("http://{host}:8081/did/bundle/"), + get_ion_resolver(&endpoint_options.resolver_endpoint.to_address()), + endpoint_options.bundle_endpoint.to_address(), ); - // TODO: refactor to use TrustchainAPI once functional - // TrustchainAPI::verify_credential(&credential, false, 1).await; - let signature_only = false; - let root_event_time = 1666971942; + let signature_only = proof_options.signature_only; + let root_event_time = proof_options.root_event_time; // NB. When using android emulator, the time is less than the created time on // the credential. This leads to a failure upon the proofs being checked: @@ -152,6 +142,18 @@ pub fn vc_verify_presentation( mod tests { use super::*; + #[test] + fn test_did_resolve() {} + + #[test] + fn test_did_verify() {} + #[test] fn test_vc_verify_credential() {} + + #[test] + fn test_vc_issue_presentation() {} + + #[test] + fn test_vc_verify_presentation() {} } diff --git a/trustchain-ffi/src/mobile_bridge.io.rs b/trustchain-ffi/src/mobile_bridge.io.rs index bec4ef32..a0e56021 100644 --- a/trustchain-ffi/src/mobile_bridge.io.rs +++ b/trustchain-ffi/src/mobile_bridge.io.rs @@ -7,31 +7,32 @@ pub extern "C" fn wire_greet(port_: i64) { } #[no_mangle] -pub extern "C" fn wire_resolve(port_: i64, did: *mut wire_uint_8_list) { - wire_resolve_impl(port_, did) -} - -#[no_mangle] -pub extern "C" fn wire_did_resolve(port_: i64, did: *mut wire_uint_8_list) { - wire_did_resolve_impl(port_, did) +pub extern "C" fn wire_did_resolve( + port_: i64, + did: *mut wire_uint_8_list, + endpoint_opts: *mut wire_uint_8_list, +) { + wire_did_resolve_impl(port_, did, endpoint_opts) } #[no_mangle] pub extern "C" fn wire_did_verify( port_: i64, did: *mut wire_uint_8_list, - endpoint: *mut wire_uint_8_list, + endpoint_opts: *mut wire_uint_8_list, + proof_opts: *mut wire_uint_8_list, ) { - wire_did_verify_impl(port_, did, endpoint) + wire_did_verify_impl(port_, did, endpoint_opts, proof_opts) } #[no_mangle] pub extern "C" fn wire_vc_verify_credential( port_: i64, - credential_json: *mut wire_uint_8_list, - proof_options_json: *mut wire_uint_8_list, + credential: *mut wire_uint_8_list, + endpoint_opts: *mut wire_uint_8_list, + proof_opts: *mut wire_uint_8_list, ) { - wire_vc_verify_credential_impl(port_, credential_json, proof_options_json) + wire_vc_verify_credential_impl(port_, credential, endpoint_opts, proof_opts) } #[no_mangle] diff --git a/trustchain-ffi/src/mobile_bridge.rs b/trustchain-ffi/src/mobile_bridge.rs index b376bd05..be1e5893 100644 --- a/trustchain-ffi/src/mobile_bridge.rs +++ b/trustchain-ffi/src/mobile_bridge.rs @@ -31,20 +31,11 @@ fn wire_greet_impl(port_: MessagePort) { move || move |task_callback| Ok(greet()), ) } -fn wire_resolve_impl(port_: MessagePort, did: impl Wire2Api + UnwindSafe) { - FLUTTER_RUST_BRIDGE_HANDLER.wrap( - WrapInfo { - debug_name: "resolve", - port: Some(port_), - mode: FfiCallMode::Normal, - }, - move || { - let api_did = did.wire2api(); - move |task_callback| resolve(api_did) - }, - ) -} -fn wire_did_resolve_impl(port_: MessagePort, did: impl Wire2Api + UnwindSafe) { +fn wire_did_resolve_impl( + port_: MessagePort, + did: impl Wire2Api + UnwindSafe, + endpoint_opts: impl Wire2Api + UnwindSafe, +) { FLUTTER_RUST_BRIDGE_HANDLER.wrap( WrapInfo { debug_name: "did_resolve", @@ -53,14 +44,16 @@ fn wire_did_resolve_impl(port_: MessagePort, did: impl Wire2Api + Unwind }, move || { let api_did = did.wire2api(); - move |task_callback| did_resolve(api_did) + let api_endpoint_opts = endpoint_opts.wire2api(); + move |task_callback| did_resolve(api_did, api_endpoint_opts) }, ) } fn wire_did_verify_impl( port_: MessagePort, did: impl Wire2Api + UnwindSafe, - endpoint: impl Wire2Api + UnwindSafe, + endpoint_opts: impl Wire2Api + UnwindSafe, + proof_opts: impl Wire2Api + UnwindSafe, ) { FLUTTER_RUST_BRIDGE_HANDLER.wrap( WrapInfo { @@ -70,15 +63,17 @@ fn wire_did_verify_impl( }, move || { let api_did = did.wire2api(); - let api_endpoint = endpoint.wire2api(); - move |task_callback| did_verify(api_did, api_endpoint) + let api_endpoint_opts = endpoint_opts.wire2api(); + let api_proof_opts = proof_opts.wire2api(); + move |task_callback| did_verify(api_did, api_endpoint_opts, api_proof_opts) }, ) } fn wire_vc_verify_credential_impl( port_: MessagePort, - credential_json: impl Wire2Api + UnwindSafe, - proof_options_json: impl Wire2Api + UnwindSafe, + credential: impl Wire2Api + UnwindSafe, + endpoint_opts: impl Wire2Api + UnwindSafe, + proof_opts: impl Wire2Api + UnwindSafe, ) { FLUTTER_RUST_BRIDGE_HANDLER.wrap( WrapInfo { @@ -87,9 +82,12 @@ fn wire_vc_verify_credential_impl( mode: FfiCallMode::Normal, }, move || { - let api_credential_json = credential_json.wire2api(); - let api_proof_options_json = proof_options_json.wire2api(); - move |task_callback| vc_verify_credential(api_credential_json, api_proof_options_json) + let api_credential = credential.wire2api(); + let api_endpoint_opts = endpoint_opts.wire2api(); + let api_proof_opts = proof_opts.wire2api(); + move |task_callback| { + vc_verify_credential(api_credential, api_endpoint_opts, api_proof_opts) + } }, ) } diff --git a/trustchain-ffi/src/options.rs b/trustchain-ffi/src/options.rs index b8c2e532..89aafa3e 100644 --- a/trustchain-ffi/src/options.rs +++ b/trustchain-ffi/src/options.rs @@ -3,7 +3,7 @@ use trustchain_ion::URL; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -struct Endpoint { +pub struct Endpoint { pub url: URL, pub port: u16, } @@ -12,13 +12,16 @@ impl Endpoint { pub fn new(url: URL, port: u16) -> Self { Self { url, port } } + pub fn to_address(&self) -> URL { + format!("{}:{}/", self.url, self.port) + } } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -struct EndpointOptions { - resolver_endpoint: Endpoint, - bundle_endpoint: Endpoint, +pub struct EndpointOptions { + pub resolver_endpoint: Endpoint, + pub bundle_endpoint: Endpoint, } impl Default for EndpointOptions { @@ -32,8 +35,9 @@ impl Default for EndpointOptions { #[derive(Debug, Default, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -struct ProofOptions { - signature_only: bool, +pub struct ProofOptions { + pub signature_only: bool, + pub root_event_time: u32, } #[cfg(test)] @@ -55,7 +59,8 @@ mod tests { const TEST_PROOF_OPTIONS: &str = r#" { - "signatureOnly": false + "signatureOnly": false, + "rootEventTime": 1666971942 } "#; diff --git a/trustchain-ion/src/verifier.rs b/trustchain-ion/src/verifier.rs index 7c6696cc..affab0fb 100644 --- a/trustchain-ion/src/verifier.rs +++ b/trustchain-ion/src/verifier.rs @@ -370,7 +370,7 @@ where // in-memory verifier HashMap. // If running on a Trustchain light client, make an API call to a full node to request the bundle. pub async fn fetch_bundle(&self, did: &str) -> Result<(), VerifierError> { - let response = reqwest::get(format!("{}{did}", self.endpoint())) + let response = reqwest::get(format!("{}did/bundle/{did}", self.endpoint())) .await .map_err(|e| { VerifierError::ErrorFetchingVerificationMaterial( From 651c1d7a9929adf50f4684d10f40723df85fa0bb Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Tue, 18 Jul 2023 09:50:16 +0100 Subject: [PATCH 26/87] Refactor mobile FFI interface with mobile options --- trustchain-ffi/src/mobile.rs | 93 ++++++++++++-------------- trustchain-ffi/src/mobile_bridge.io.rs | 26 ++++--- trustchain-ffi/src/mobile_bridge.rs | 48 ++++++------- trustchain-ffi/src/options.rs | 59 +++++++++++++++- 4 files changed, 129 insertions(+), 97 deletions(-) diff --git a/trustchain-ffi/src/mobile.rs b/trustchain-ffi/src/mobile.rs index 48b86a53..6e555c9e 100644 --- a/trustchain-ffi/src/mobile.rs +++ b/trustchain-ffi/src/mobile.rs @@ -1,54 +1,47 @@ +use crate::options::MobileOptions; use anyhow::{anyhow, Result}; -use serde_json::to_string_pretty; use ssi::{ one_or_many::OneOrMany, vc::{Credential, LinkedDataProofOptions, Proof}, }; use tokio::runtime::Runtime; -use trustchain_api::{ - api::{TrustchainDIDAPI, TrustchainVCAPI}, - TrustchainAPI, -}; +use trustchain_api::{api::TrustchainDIDAPI, TrustchainAPI}; use trustchain_core::{ chain::{Chain, DIDChain}, - config::core_config, verifier::Verifier, }; use trustchain_ion::{get_ion_resolver, verifier::IONVerifier}; -use crate::options::{EndpointOptions, ProofOptions}; - -/// Android localhost endpoint. -const ANDROID_ENDPOINT: &str = "http://10.0.2.2:3000/"; - /// Example greet function. pub fn greet() -> String { "Hello from Rust! 🦀".into() } /// Resolves a given DID document assuming trust in endpoint. -pub fn did_resolve(did: String, endpoint_opts: String) -> Result { - let endpoint_options: EndpointOptions = serde_json::from_str(&endpoint_opts)?; +pub fn did_resolve(did: String, opts: String) -> Result { + let mobile_opts: MobileOptions = serde_json::from_str(&opts)?; + let endpoint_opts = mobile_opts.endpoint()?; let rt = Runtime::new().unwrap(); rt.block_on(async { - TrustchainAPI::resolve(&did, endpoint_options.resolver_endpoint.to_address()) + TrustchainAPI::resolve(&did, endpoint_opts.resolver_endpoint.to_address()) .await .map_err(|e| anyhow!(e)) .and_then(|(_, doc, _)| serde_json::to_string_pretty(&doc).map_err(|e| anyhow!(e))) }) } /// Verifies a given DID assuming trust in endpoint. -pub fn did_verify(did: String, endpoint_opts: String, proof_opts: String) -> Result<()> { - let endpoint_options: EndpointOptions = serde_json::from_str(&endpoint_opts)?; - let proof_options: ProofOptions = serde_json::from_str(&proof_opts)?; +pub fn did_verify(did: String, opts: String) -> Result<()> { + let mobile_opts: MobileOptions = serde_json::from_str(&opts)?; + let endpoint_opts = mobile_opts.endpoint()?; + let trustchain_opts = mobile_opts.trustchain()?; let rt = Runtime::new().unwrap(); rt.block_on(async { let verifier = IONVerifier::with_endpoint( - get_ion_resolver(&endpoint_options.resolver_endpoint.to_address()), - endpoint_options.bundle_endpoint.to_address(), + get_ion_resolver(&endpoint_opts.resolver_endpoint.to_address()), + endpoint_opts.bundle_endpoint.to_address(), ); verifier - .verify(&did, core_config().root_event_time) + .verify(&did, trustchain_opts.root_event_time) .await .map_err(|err| anyhow!(err.to_string()))?; Ok(()) @@ -56,43 +49,46 @@ pub fn did_verify(did: String, endpoint_opts: String, proof_opts: String) -> Res } /// Verifies a verifiable credential. Analogous with [didkit](https://docs.rs/didkit/latest/didkit/c/fn.didkit_vc_verify_credential.html). -pub fn vc_verify_credential( - credential: String, - endpoint_opts: String, - proof_opts: String, -) -> Result { - let endpoint_options: EndpointOptions = serde_json::from_str(&endpoint_opts)?; - let proof_options: ProofOptions = serde_json::from_str(&proof_opts)?; +pub fn vc_verify_credential(credential: String, opts: String) -> Result { + let mobile_opts: MobileOptions = serde_json::from_str(&opts)?; + let endpoint_opts = mobile_opts.endpoint()?; + let trustchain_opts = mobile_opts.trustchain()?; + let ldp_opts = mobile_opts.linked_data_proof()?.to_owned(); + let credential: Credential = serde_json::from_str(&credential)?; let rt = Runtime::new().unwrap(); rt.block_on(async { - let credential: Credential = serde_json::from_str(&credential)?; let verifier = IONVerifier::with_endpoint( - get_ion_resolver(&endpoint_options.resolver_endpoint.to_address()), - endpoint_options.bundle_endpoint.to_address(), + get_ion_resolver(&endpoint_opts.resolver_endpoint.to_address()), + endpoint_opts.bundle_endpoint.to_address(), ); - let signature_only = proof_options.signature_only; - let root_event_time = proof_options.root_event_time; + let signature_only = trustchain_opts.signature_only; + let root_event_time = trustchain_opts.root_event_time; // NB. When using android emulator, the time is less than the created time on // the credential. This leads to a failure upon the proofs being checked: // https://docs.rs/ssi/0.4.0/src/ssi/vc.rs.html#1243 (filtered here) // https://docs.rs/ssi/0.4.0/src/ssi/vc.rs.html#1973-1975 (created time checked here) // + // TODO: remove once confirmed time passed from mobile is functional // A workaround is to set the "created" time from the credential directly with // LinkedDataProofOptions - let ldpo = match credential.proof { - Some(OneOrMany::One(Proof { - created: created_time, - .. - })) => LinkedDataProofOptions { - created: created_time, - ..Default::default() - }, - _ => return Err(anyhow!("No proof or created time available in proof.")), - }; + // let ldpo = match credential.proof { + // Some(OneOrMany::One(Proof { + // created: created_time, + // .. + // })) => LinkedDataProofOptions { + // created: created_time, + // ..Default::default() + // }, + // _ => return Err(anyhow!("No proof or created time available in proof.")), + // }; + // TODO: try setting time as now from emulator to check time used from now_ms() call + + // TODO: refactor below + // Verify credential signature with LinkedDataProofOptions - let verification_result = credential.verify(Some(ldpo), verifier.resolver()).await; + let verification_result = credential.verify(Some(ldp_opts), verifier.resolver()).await; // Get DID chain if not signature only let did_chain = if signature_only { @@ -124,18 +120,11 @@ pub fn vc_verify_credential( }) } /// Issues a verifiable presentation. Analogous with [didkit](https://docs.rs/didkit/latest/didkit/c/fn.didkit_vc_issue_presentation.html). -pub fn vc_issue_presentation( - presentation_json: String, - proof_options_json: String, - key_json: String, -) { +pub fn vc_issue_presentation(presentation: String, opts: String, key_json: String) { todo!() } /// Verifies a verifiable presentation. Analogous with [didkit](https://docs.rs/didkit/latest/didkit/c/fn.didkit_vc_verify_presentation.html). -pub fn vc_verify_presentation( - presentation_json: String, - proof_options_json: String, -) -> Result { +pub fn vc_verify_presentation(presentation: String, opts: String) -> Result { todo!() } #[cfg(test)] diff --git a/trustchain-ffi/src/mobile_bridge.io.rs b/trustchain-ffi/src/mobile_bridge.io.rs index a0e56021..693005fa 100644 --- a/trustchain-ffi/src/mobile_bridge.io.rs +++ b/trustchain-ffi/src/mobile_bridge.io.rs @@ -10,48 +10,46 @@ pub extern "C" fn wire_greet(port_: i64) { pub extern "C" fn wire_did_resolve( port_: i64, did: *mut wire_uint_8_list, - endpoint_opts: *mut wire_uint_8_list, + opts: *mut wire_uint_8_list, ) { - wire_did_resolve_impl(port_, did, endpoint_opts) + wire_did_resolve_impl(port_, did, opts) } #[no_mangle] pub extern "C" fn wire_did_verify( port_: i64, did: *mut wire_uint_8_list, - endpoint_opts: *mut wire_uint_8_list, - proof_opts: *mut wire_uint_8_list, + opts: *mut wire_uint_8_list, ) { - wire_did_verify_impl(port_, did, endpoint_opts, proof_opts) + wire_did_verify_impl(port_, did, opts) } #[no_mangle] pub extern "C" fn wire_vc_verify_credential( port_: i64, credential: *mut wire_uint_8_list, - endpoint_opts: *mut wire_uint_8_list, - proof_opts: *mut wire_uint_8_list, + opts: *mut wire_uint_8_list, ) { - wire_vc_verify_credential_impl(port_, credential, endpoint_opts, proof_opts) + wire_vc_verify_credential_impl(port_, credential, opts) } #[no_mangle] pub extern "C" fn wire_vc_issue_presentation( port_: i64, - presentation_json: *mut wire_uint_8_list, - proof_options_json: *mut wire_uint_8_list, + presentation: *mut wire_uint_8_list, + opts: *mut wire_uint_8_list, key_json: *mut wire_uint_8_list, ) { - wire_vc_issue_presentation_impl(port_, presentation_json, proof_options_json, key_json) + wire_vc_issue_presentation_impl(port_, presentation, opts, key_json) } #[no_mangle] pub extern "C" fn wire_vc_verify_presentation( port_: i64, - presentation_json: *mut wire_uint_8_list, - proof_options_json: *mut wire_uint_8_list, + presentation: *mut wire_uint_8_list, + opts: *mut wire_uint_8_list, ) { - wire_vc_verify_presentation_impl(port_, presentation_json, proof_options_json) + wire_vc_verify_presentation_impl(port_, presentation, opts) } // Section: allocate functions diff --git a/trustchain-ffi/src/mobile_bridge.rs b/trustchain-ffi/src/mobile_bridge.rs index be1e5893..5231556e 100644 --- a/trustchain-ffi/src/mobile_bridge.rs +++ b/trustchain-ffi/src/mobile_bridge.rs @@ -34,7 +34,7 @@ fn wire_greet_impl(port_: MessagePort) { fn wire_did_resolve_impl( port_: MessagePort, did: impl Wire2Api + UnwindSafe, - endpoint_opts: impl Wire2Api + UnwindSafe, + opts: impl Wire2Api + UnwindSafe, ) { FLUTTER_RUST_BRIDGE_HANDLER.wrap( WrapInfo { @@ -44,16 +44,15 @@ fn wire_did_resolve_impl( }, move || { let api_did = did.wire2api(); - let api_endpoint_opts = endpoint_opts.wire2api(); - move |task_callback| did_resolve(api_did, api_endpoint_opts) + let api_opts = opts.wire2api(); + move |task_callback| did_resolve(api_did, api_opts) }, ) } fn wire_did_verify_impl( port_: MessagePort, did: impl Wire2Api + UnwindSafe, - endpoint_opts: impl Wire2Api + UnwindSafe, - proof_opts: impl Wire2Api + UnwindSafe, + opts: impl Wire2Api + UnwindSafe, ) { FLUTTER_RUST_BRIDGE_HANDLER.wrap( WrapInfo { @@ -63,17 +62,15 @@ fn wire_did_verify_impl( }, move || { let api_did = did.wire2api(); - let api_endpoint_opts = endpoint_opts.wire2api(); - let api_proof_opts = proof_opts.wire2api(); - move |task_callback| did_verify(api_did, api_endpoint_opts, api_proof_opts) + let api_opts = opts.wire2api(); + move |task_callback| did_verify(api_did, api_opts) }, ) } fn wire_vc_verify_credential_impl( port_: MessagePort, credential: impl Wire2Api + UnwindSafe, - endpoint_opts: impl Wire2Api + UnwindSafe, - proof_opts: impl Wire2Api + UnwindSafe, + opts: impl Wire2Api + UnwindSafe, ) { FLUTTER_RUST_BRIDGE_HANDLER.wrap( WrapInfo { @@ -83,18 +80,15 @@ fn wire_vc_verify_credential_impl( }, move || { let api_credential = credential.wire2api(); - let api_endpoint_opts = endpoint_opts.wire2api(); - let api_proof_opts = proof_opts.wire2api(); - move |task_callback| { - vc_verify_credential(api_credential, api_endpoint_opts, api_proof_opts) - } + let api_opts = opts.wire2api(); + move |task_callback| vc_verify_credential(api_credential, api_opts) }, ) } fn wire_vc_issue_presentation_impl( port_: MessagePort, - presentation_json: impl Wire2Api + UnwindSafe, - proof_options_json: impl Wire2Api + UnwindSafe, + presentation: impl Wire2Api + UnwindSafe, + opts: impl Wire2Api + UnwindSafe, key_json: impl Wire2Api + UnwindSafe, ) { FLUTTER_RUST_BRIDGE_HANDLER.wrap( @@ -104,13 +98,13 @@ fn wire_vc_issue_presentation_impl( mode: FfiCallMode::Normal, }, move || { - let api_presentation_json = presentation_json.wire2api(); - let api_proof_options_json = proof_options_json.wire2api(); + let api_presentation = presentation.wire2api(); + let api_opts = opts.wire2api(); let api_key_json = key_json.wire2api(); move |task_callback| { Ok(vc_issue_presentation( - api_presentation_json, - api_proof_options_json, + api_presentation, + api_opts, api_key_json, )) } @@ -119,8 +113,8 @@ fn wire_vc_issue_presentation_impl( } fn wire_vc_verify_presentation_impl( port_: MessagePort, - presentation_json: impl Wire2Api + UnwindSafe, - proof_options_json: impl Wire2Api + UnwindSafe, + presentation: impl Wire2Api + UnwindSafe, + opts: impl Wire2Api + UnwindSafe, ) { FLUTTER_RUST_BRIDGE_HANDLER.wrap( WrapInfo { @@ -129,11 +123,9 @@ fn wire_vc_verify_presentation_impl( mode: FfiCallMode::Normal, }, move || { - let api_presentation_json = presentation_json.wire2api(); - let api_proof_options_json = proof_options_json.wire2api(); - move |task_callback| { - vc_verify_presentation(api_presentation_json, api_proof_options_json) - } + let api_presentation = presentation.wire2api(); + let api_opts = opts.wire2api(); + move |task_callback| vc_verify_presentation(api_presentation, api_opts) }, ) } diff --git a/trustchain-ffi/src/options.rs b/trustchain-ffi/src/options.rs index 89aafa3e..ccd051b4 100644 --- a/trustchain-ffi/src/options.rs +++ b/trustchain-ffi/src/options.rs @@ -1,4 +1,6 @@ +use anyhow::anyhow; use serde::{Deserialize, Serialize}; +use ssi::vc::LinkedDataProofOptions; use trustchain_ion::URL; #[derive(Debug, Clone, Serialize, Deserialize)] @@ -35,11 +37,37 @@ impl Default for EndpointOptions { #[derive(Debug, Default, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ProofOptions { +pub struct TrustchainOptions { pub signature_only: bool, pub root_event_time: u32, } +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MobileOptions { + pub endpoint_options: Option, + pub trustchain_options: Option, + pub linked_data_proof_options: Option, +} + +impl MobileOptions { + pub fn endpoint(&self) -> anyhow::Result<&EndpointOptions> { + self.endpoint_options + .as_ref() + .ok_or_else(|| anyhow!("Expected endpoint options.")) + } + pub fn trustchain(&self) -> anyhow::Result<&TrustchainOptions> { + self.trustchain_options + .as_ref() + .ok_or_else(|| anyhow!("Expected trustchain options.")) + } + pub fn linked_data_proof(&self) -> anyhow::Result<&LinkedDataProofOptions> { + self.linked_data_proof_options + .as_ref() + .ok_or_else(|| anyhow!("Expected linked data proof options.")) + } +} + #[cfg(test)] mod tests { use super::*; @@ -57,19 +85,44 @@ mod tests { } "#; - const TEST_PROOF_OPTIONS: &str = r#" + const TEST_TRUSTCHAIN_OPTIONS: &str = r#" { "signatureOnly": false, "rootEventTime": 1666971942 } "#; + const TEST_LINKED_DATA_PROOF_OPTIONS: &str = r#" + { + "proofPurpose": "assertionMethod", + "created": "2023-07-18T08:42:50Z" + } + "#; + #[test] fn test_endpoint_options() { serde_json::from_str::(TEST_ENDPOINT_OPTIONS).unwrap(); } #[test] + fn test_trustchain_options() { + serde_json::from_str::(TEST_TRUSTCHAIN_OPTIONS).unwrap(); + } + #[test] fn test_proof_options() { - serde_json::from_str::(TEST_PROOF_OPTIONS).unwrap(); + serde_json::from_str::(TEST_LINKED_DATA_PROOF_OPTIONS).unwrap(); + } + #[test] + fn test_mobile_options() { + let test_mobile_options: String = format!( + r#" + {{ + "endpointOptions": {}, + "trustchainOptions": {}, + "linkedDataProofOptions": {} + }} + "#, + TEST_ENDPOINT_OPTIONS, TEST_TRUSTCHAIN_OPTIONS, TEST_LINKED_DATA_PROOF_OPTIONS + ); + serde_json::from_str::(&test_mobile_options).unwrap(); } } From 8f1b123b1bf2ce3ad8b3d908a5cf781f8f1d0fe9 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Fri, 28 Jul 2023 14:04:49 +0100 Subject: [PATCH 27/87] Add tests, use config module, remove options module --- trustchain-ffi/src/config.rs | 2 +- trustchain-ffi/src/lib.rs | 1 - trustchain-ffi/src/mobile.rs | 78 ++++++++++++++++++--- trustchain-ffi/src/options.rs | 128 ---------------------------------- 4 files changed, 69 insertions(+), 140 deletions(-) delete mode 100644 trustchain-ffi/src/options.rs diff --git a/trustchain-ffi/src/config.rs b/trustchain-ffi/src/config.rs index cdf5cd0c..f252af4d 100644 --- a/trustchain-ffi/src/config.rs +++ b/trustchain-ffi/src/config.rs @@ -14,7 +14,7 @@ lazy_static! { } /// Parses and maps ION subfields to a new type. -fn parse_toml(toml_str: &str) -> FFIConfig { +pub(crate) fn parse_toml(toml_str: &str) -> FFIConfig { toml::from_str::(toml_str) .expect("Error parsing trustchain_config.toml") .ffi diff --git a/trustchain-ffi/src/lib.rs b/trustchain-ffi/src/lib.rs index b8af76c6..58400d38 100644 --- a/trustchain-ffi/src/lib.rs +++ b/trustchain-ffi/src/lib.rs @@ -2,4 +2,3 @@ pub mod config; pub mod gui; pub mod mobile; mod mobile_bridge; -pub mod options; diff --git a/trustchain-ffi/src/mobile.rs b/trustchain-ffi/src/mobile.rs index 3415f319..430dbb66 100644 --- a/trustchain-ffi/src/mobile.rs +++ b/trustchain-ffi/src/mobile.rs @@ -1,4 +1,4 @@ -use crate::options::FFIConfig; +use crate::config::FFIConfig; use anyhow::{anyhow, Result}; use ssi::{ one_or_many::OneOrMany, @@ -23,7 +23,7 @@ pub fn did_resolve(did: String, opts: String) -> Result { let endpoint_opts = mobile_opts.endpoint()?; let rt = Runtime::new().unwrap(); rt.block_on(async { - TrustchainAPI::resolve(&did, &endpoint_opts.resolver_endpoint.to_address()) + TrustchainAPI::resolve(&did, &endpoint_opts.ion_endpoint().to_address()) .await .map_err(|e| anyhow!(e)) .and_then(|(_, doc, _)| serde_json::to_string_pretty(&doc).map_err(|e| anyhow!(e))) @@ -37,8 +37,8 @@ pub fn did_verify(did: String, opts: String) -> Result<()> { let rt = Runtime::new().unwrap(); rt.block_on(async { let verifier = IONVerifier::with_endpoint( - get_ion_resolver(&endpoint_opts.resolver_endpoint.to_address()), - endpoint_opts.bundle_endpoint.to_address(), + get_ion_resolver(&endpoint_opts.ion_endpoint().to_address()), + endpoint_opts.trustchain_endpoint()?.to_address(), ); verifier .verify(&did, trustchain_opts.root_event_time) @@ -53,13 +53,13 @@ pub fn vc_verify_credential(credential: String, opts: String) -> Result let mobile_opts: FFIConfig = serde_json::from_str(&opts)?; let endpoint_opts = mobile_opts.endpoint()?; let trustchain_opts = mobile_opts.trustchain()?; - let ldp_opts = mobile_opts.linked_data_proof()?.to_owned(); + let ldp_opts = mobile_opts.linked_data_proof().cloned().unwrap_or_default(); let credential: Credential = serde_json::from_str(&credential)?; let rt = Runtime::new().unwrap(); rt.block_on(async { let verifier = IONVerifier::with_endpoint( - get_ion_resolver(&endpoint_opts.resolver_endpoint.to_address()), - endpoint_opts.bundle_endpoint.to_address(), + get_ion_resolver(&endpoint_opts.ion_endpoint().to_address()), + endpoint_opts.trustchain_endpoint()?.to_address(), ); let signature_only = trustchain_opts.signature_only; let root_event_time = trustchain_opts.root_event_time; @@ -129,16 +129,74 @@ pub fn vc_verify_presentation(presentation: String, opts: String) -> Result Self { - Self { url, port } - } - pub fn to_address(&self) -> URL { - format!("{}:{}/", self.url, self.port) - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct EndpointOptions { - pub resolver_endpoint: Endpoint, - pub bundle_endpoint: Endpoint, -} - -impl Default for EndpointOptions { - fn default() -> Self { - Self { - resolver_endpoint: Endpoint::new(URL::from("http://127.0.0.1"), 3000), - bundle_endpoint: Endpoint::new(URL::from("http://127.0.0.1"), 8081), - } - } -} - -#[derive(Debug, Default, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TrustchainOptions { - pub signature_only: bool, - pub root_event_time: u32, -} - -#[derive(Debug, Default, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct FFIConfig { - pub endpoint_options: Option, - pub trustchain_options: Option, - pub linked_data_proof_options: Option, -} - -impl FFIConfig { - pub fn endpoint(&self) -> anyhow::Result<&EndpointOptions> { - self.endpoint_options - .as_ref() - .ok_or_else(|| anyhow!("Expected endpoint options.")) - } - pub fn trustchain(&self) -> anyhow::Result<&TrustchainOptions> { - self.trustchain_options - .as_ref() - .ok_or_else(|| anyhow!("Expected trustchain options.")) - } - pub fn linked_data_proof(&self) -> anyhow::Result<&LinkedDataProofOptions> { - self.linked_data_proof_options - .as_ref() - .ok_or_else(|| anyhow!("Expected linked data proof options.")) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - const TEST_ENDPOINT_OPTIONS: &str = r#" - { - "resolverEndpoint": { - "url": "http://127.0.0.1", - "port": 3000 - }, - "bundleEndpoint": { - "url": "http://127.0.0.1", - "port": 8081 - } - } - "#; - - const TEST_TRUSTCHAIN_OPTIONS: &str = r#" - { - "signatureOnly": false, - "rootEventTime": 1666971942 - } - "#; - - const TEST_LINKED_DATA_PROOF_OPTIONS: &str = r#" - { - "proofPurpose": "assertionMethod", - "created": "2023-07-18T08:42:50Z" - } - "#; - - #[test] - fn test_endpoint_options() { - serde_json::from_str::(TEST_ENDPOINT_OPTIONS).unwrap(); - } - #[test] - fn test_trustchain_options() { - serde_json::from_str::(TEST_TRUSTCHAIN_OPTIONS).unwrap(); - } - #[test] - fn test_proof_options() { - serde_json::from_str::(TEST_LINKED_DATA_PROOF_OPTIONS).unwrap(); - } - #[test] - fn test_mobile_options() { - let test_mobile_options: String = format!( - r#" - {{ - "endpointOptions": {}, - "trustchainOptions": {}, - "linkedDataProofOptions": {} - }} - "#, - TEST_ENDPOINT_OPTIONS, TEST_TRUSTCHAIN_OPTIONS, TEST_LINKED_DATA_PROOF_OPTIONS - ); - serde_json::from_str::(&test_mobile_options).unwrap(); - } -} From 607e7a2c031e8b94bf57b5e43bb715151a173ffb Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Fri, 28 Jul 2023 14:05:33 +0100 Subject: [PATCH 28/87] Remove route from bundle endpoint test --- trustchain-http/src/resolver.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trustchain-http/src/resolver.rs b/trustchain-http/src/resolver.rs index f3a31436..018f08ec 100644 --- a/trustchain-http/src/resolver.rs +++ b/trustchain-http/src/resolver.rs @@ -289,7 +289,7 @@ mod tests { // Make a verifier instance and fetch bundle from server bundle endpoint let verifier = IONVerifier::with_endpoint( get_ion_resolver("http://localhost:3000/"), - format!("http://127.0.0.1:{}/did/bundle/", port), + format!("http://127.0.0.1:{}/", port), ); let did = "did:ion:test:EiBcLZcELCKKtmun_CUImSlb2wcxK5eM8YXSq3MrqNe5wA"; // Check verification From 989826ee96e21f5e90d9a78fa1c94a63acf1b948 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Tue, 1 Aug 2023 10:39:58 +0100 Subject: [PATCH 29/87] Refactor with trustchain-api --- trustchain-ffi/src/mobile.rs | 108 ++++++++----------------- trustchain-ffi/src/mobile_bridge.io.rs | 19 ----- trustchain-ffi/src/mobile_bridge.rs | 44 ---------- 3 files changed, 32 insertions(+), 139 deletions(-) diff --git a/trustchain-ffi/src/mobile.rs b/trustchain-ffi/src/mobile.rs index 287d1435..fd922a0c 100644 --- a/trustchain-ffi/src/mobile.rs +++ b/trustchain-ffi/src/mobile.rs @@ -1,23 +1,20 @@ +// TODO: add module doc comments for mobile FFI use crate::config::FFIConfig; use anyhow::{anyhow, Result}; -use ssi::{ - one_or_many::OneOrMany, - vc::{Credential, LinkedDataProofOptions, Proof}, -}; +use ssi::{ldp::now_ms, vc::Credential}; use tokio::runtime::Runtime; -use trustchain_api::{api::TrustchainDIDAPI, TrustchainAPI}; -use trustchain_core::{ - chain::{Chain, DIDChain}, - verifier::Verifier, +use trustchain_api::{ + api::{TrustchainDIDAPI, TrustchainVCAPI}, + TrustchainAPI, }; use trustchain_ion::{get_ion_resolver, verifier::IONVerifier}; /// Example greet function. pub fn greet() -> String { - "Hello from Rust! 🦀".into() + format!("Hello from Rust at time: {}", now_ms()) } -/// Resolves a given DID document assuming trust in endpoint. +/// Resolves a given DID document. pub fn did_resolve(did: String, opts: String) -> Result { let mobile_opts: FFIConfig = serde_json::from_str(&opts)?; let endpoint_opts = mobile_opts.endpoint()?; @@ -30,31 +27,31 @@ pub fn did_resolve(did: String, opts: String) -> Result { .and_then(|(_, doc, _)| serde_json::to_string_pretty(&doc).map_err(|e| anyhow!(e))) }) } -/// Verifies a given DID assuming trust in endpoint. +/// Verifies a given DID. pub fn did_verify(did: String, opts: String) -> Result<()> { let mobile_opts: FFIConfig = serde_json::from_str(&opts)?; let endpoint_opts = mobile_opts.endpoint()?; let trustchain_opts = mobile_opts.trustchain()?; + let root_event_time = trustchain_opts.root_event_time; let rt = Runtime::new().unwrap(); rt.block_on(async { let verifier = IONVerifier::with_endpoint( get_ion_resolver(&endpoint_opts.ion_endpoint().to_address()), endpoint_opts.trustchain_endpoint()?.to_address(), ); - verifier - .verify(&did, trustchain_opts.root_event_time) + TrustchainAPI::verify(&did, root_event_time, &verifier) .await .map_err(|err| anyhow!(err.to_string()))?; Ok(()) }) } -/// Verifies a verifiable credential. Analogous with [didkit](https://docs.rs/didkit/latest/didkit/c/fn.didkit_vc_verify_credential.html). +/// Verifies a verifiable credential. pub fn vc_verify_credential(credential: String, opts: String) -> Result { let mobile_opts: FFIConfig = serde_json::from_str(&opts)?; let endpoint_opts = mobile_opts.endpoint()?; let trustchain_opts = mobile_opts.trustchain()?; - let ldp_opts = mobile_opts.linked_data_proof().cloned().unwrap_or_default(); + let ldp_opts = mobile_opts.linked_data_proof().cloned().ok(); let credential: Credential = serde_json::from_str(&credential)?; let rt = Runtime::new().unwrap(); rt.block_on(async { @@ -62,7 +59,6 @@ pub fn vc_verify_credential(credential: String, opts: String) -> Result get_ion_resolver(&endpoint_opts.ion_endpoint().to_address()), endpoint_opts.trustchain_endpoint()?.to_address(), ); - let signature_only = trustchain_opts.signature_only; let root_event_time = trustchain_opts.root_event_time; // NB. When using android emulator, the time is less than the created time on @@ -70,64 +66,24 @@ pub fn vc_verify_credential(credential: String, opts: String) -> Result // https://docs.rs/ssi/0.4.0/src/ssi/vc.rs.html#1243 (filtered here) // https://docs.rs/ssi/0.4.0/src/ssi/vc.rs.html#1973-1975 (created time checked here) // - // TODO: remove once confirmed time passed from mobile is functional - // A workaround is to set the "created" time from the credential directly with - // LinkedDataProofOptions - // let ldpo = match credential.proof { - // Some(OneOrMany::One(Proof { - // created: created_time, - // .. - // })) => LinkedDataProofOptions { - // created: created_time, - // ..Default::default() - // }, - // _ => return Err(anyhow!("No proof or created time available in proof.")), - // }; - // TODO: try setting time as now from emulator to check time used from now_ms() call - - // TODO: refactor below - - // Verify credential signature with LinkedDataProofOptions - let verification_result = credential.verify(Some(ldp_opts), verifier.resolver()).await; - - // Get DID chain if not signature only - let did_chain = if signature_only { - None - } else { - let issuer = match credential.issuer { - Some(issuer) => issuer.get_id(), - _ => return Err(anyhow!("No issuer present in credential.")), - }; - Some(verifier.verify(&issuer, root_event_time).await) - }; - - // Returns - if !verification_result.errors.is_empty() { - Err(anyhow!( - "Invalid signature:\n{}", - serde_json::to_string_pretty(&verification_result.errors).unwrap() - )) - } else if signature_only { - Ok("OK: signature only".to_string()) - } else if let Some(did_chain) = did_chain { - match did_chain { - Ok(_) => Ok("OK".to_string()), - Err(e) => Err(anyhow!(e.to_string())), - } - } else { - Err(anyhow!("No DID chain returned, failed to verify issuer.")) - } + TrustchainAPI::verify_credential(&credential, ldp_opts, root_event_time, &verifier) + .await + .map_err(|err| anyhow!(err.to_string()))?; + Ok("Ok".to_string()) }) } -/// Issues a verifiable presentation. Analogous with [didkit](https://docs.rs/didkit/latest/didkit/c/fn.didkit_vc_issue_presentation.html). -pub fn vc_issue_presentation(presentation: String, opts: String, key_json: String) { - todo!() -} -/// Verifies a verifiable presentation. Analogous with [didkit](https://docs.rs/didkit/latest/didkit/c/fn.didkit_vc_verify_presentation.html). -pub fn vc_verify_presentation(presentation: String, opts: String) -> Result { - todo!() -} + +// // TODO: implement once verifiable presentations are included in API +// /// Issues a verifiable presentation. Analogous with [didkit](https://docs.rs/didkit/latest/didkit/c/fn.didkit_vc_issue_presentation.html). +// pub fn vc_issue_presentation(presentation: String, opts: String, key_json: String) { +// todo!() +// } +// /// Verifies a verifiable presentation. Analogous with [didkit](https://docs.rs/didkit/latest/didkit/c/fn.didkit_vc_verify_presentation.html). +// pub fn vc_verify_presentation(presentation: String, opts: String) -> Result { +// todo!() +// } + #[cfg(test)] mod tests { use crate::config::parse_toml; @@ -145,7 +101,6 @@ mod tests { trustchainEndpoint.port = 8081 "#; - // TODO: add test credential issued by did:ion:testEiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q const TEST_CREDENTIAL: &str = r#" { "@context": [ @@ -199,9 +154,10 @@ mod tests { vc_verify_credential(serde_json::to_string(&credential).unwrap(), ffi_opts).unwrap(); } - #[test] - fn test_vc_issue_presentation() {} + // // TODO: implement once verifiable presentations are included in API + // #[test] + // fn test_vc_issue_presentation() {} - #[test] - fn test_vc_verify_presentation() {} + // #[test] + // fn test_vc_verify_presentation() {} } diff --git a/trustchain-ffi/src/mobile_bridge.io.rs b/trustchain-ffi/src/mobile_bridge.io.rs index 693005fa..100321d7 100644 --- a/trustchain-ffi/src/mobile_bridge.io.rs +++ b/trustchain-ffi/src/mobile_bridge.io.rs @@ -33,25 +33,6 @@ pub extern "C" fn wire_vc_verify_credential( wire_vc_verify_credential_impl(port_, credential, opts) } -#[no_mangle] -pub extern "C" fn wire_vc_issue_presentation( - port_: i64, - presentation: *mut wire_uint_8_list, - opts: *mut wire_uint_8_list, - key_json: *mut wire_uint_8_list, -) { - wire_vc_issue_presentation_impl(port_, presentation, opts, key_json) -} - -#[no_mangle] -pub extern "C" fn wire_vc_verify_presentation( - port_: i64, - presentation: *mut wire_uint_8_list, - opts: *mut wire_uint_8_list, -) { - wire_vc_verify_presentation_impl(port_, presentation, opts) -} - // Section: allocate functions #[no_mangle] diff --git a/trustchain-ffi/src/mobile_bridge.rs b/trustchain-ffi/src/mobile_bridge.rs index 5231556e..418b8e27 100644 --- a/trustchain-ffi/src/mobile_bridge.rs +++ b/trustchain-ffi/src/mobile_bridge.rs @@ -85,50 +85,6 @@ fn wire_vc_verify_credential_impl( }, ) } -fn wire_vc_issue_presentation_impl( - port_: MessagePort, - presentation: impl Wire2Api + UnwindSafe, - opts: impl Wire2Api + UnwindSafe, - key_json: impl Wire2Api + UnwindSafe, -) { - FLUTTER_RUST_BRIDGE_HANDLER.wrap( - WrapInfo { - debug_name: "vc_issue_presentation", - port: Some(port_), - mode: FfiCallMode::Normal, - }, - move || { - let api_presentation = presentation.wire2api(); - let api_opts = opts.wire2api(); - let api_key_json = key_json.wire2api(); - move |task_callback| { - Ok(vc_issue_presentation( - api_presentation, - api_opts, - api_key_json, - )) - } - }, - ) -} -fn wire_vc_verify_presentation_impl( - port_: MessagePort, - presentation: impl Wire2Api + UnwindSafe, - opts: impl Wire2Api + UnwindSafe, -) { - FLUTTER_RUST_BRIDGE_HANDLER.wrap( - WrapInfo { - debug_name: "vc_verify_presentation", - port: Some(port_), - mode: FfiCallMode::Normal, - }, - move || { - let api_presentation = presentation.wire2api(); - let api_opts = opts.wire2api(); - move |task_callback| vc_verify_presentation(api_presentation, api_opts) - }, - ) -} // Section: wrapper structs // Section: static checks From 723c8d1fbef1167b9a3027f1e55020d74ccd06e0 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Wed, 2 Aug 2023 17:28:45 +0100 Subject: [PATCH 30/87] Add Send + Sync bound for error variant --- trustchain-core/src/verifier.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trustchain-core/src/verifier.rs b/trustchain-core/src/verifier.rs index 73d2cda0..895af10c 100644 --- a/trustchain-core/src/verifier.rs +++ b/trustchain-core/src/verifier.rs @@ -119,7 +119,7 @@ pub enum VerifierError { TimestampVerificationError(String), /// Error fetching verification material. #[error("Error fetching verification material: {0}. Error: {1}")] - ErrorFetchingVerificationMaterial(String, Box), + ErrorFetchingVerificationMaterial(String, Box), /// Failed to fetch verification material. #[error("Failed to fetch verification material: {0}")] FailureToFetchVerificationMaterial(String), From 394fbb0b004609fba7d84ab6dfedb285fe02f4d4 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Wed, 2 Aug 2023 17:31:46 +0100 Subject: [PATCH 31/87] Add FFI mobile error --- trustchain-ffi/src/mobile.rs | 72 ++++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 20 deletions(-) diff --git a/trustchain-ffi/src/mobile.rs b/trustchain-ffi/src/mobile.rs index fd922a0c..753b33d8 100644 --- a/trustchain-ffi/src/mobile.rs +++ b/trustchain-ffi/src/mobile.rs @@ -1,37 +1,62 @@ // TODO: add module doc comments for mobile FFI use crate::config::FFIConfig; -use anyhow::{anyhow, Result}; +use anyhow::Result; use ssi::{ldp::now_ms, vc::Credential}; +use thiserror::Error; use tokio::runtime::Runtime; use trustchain_api::{ api::{TrustchainDIDAPI, TrustchainVCAPI}, TrustchainAPI, }; +use trustchain_core::{resolver::ResolverError, vc::CredentialError, verifier::VerifierError}; use trustchain_ion::{get_ion_resolver, verifier::IONVerifier}; +/// A speicfic error for FFI mobile making handling easier. +#[derive(Error, Debug)] +enum FFIMobileError { + /// Failed to deserialize JSON. + #[error("JSON Deserialization Error: {0}.")] + FailedToDeserialize(serde_json::Error), + /// Failed to serialize. + #[error("JSON Serialization Error: {0}.")] + FailedToSerialize(serde_json::Error), + #[error("Missing config error: {0}")] + NoConfig(anyhow::Error), + #[error("DID Resolve Error: {0}.")] + FailedToResolveDID(ResolverError), + #[error("DID Verify Error: {0}.")] + FailedToVerifyDID(VerifierError), + #[error("Failed to verify credential error: {0}.")] + FailedToVerifyCredential(CredentialError), +} + /// Example greet function. pub fn greet() -> String { format!("Hello from Rust at time: {}", now_ms()) } -/// Resolves a given DID document. +/// Resolves a given DID document returning the serialized DID document as a JSON string. pub fn did_resolve(did: String, opts: String) -> Result { - let mobile_opts: FFIConfig = serde_json::from_str(&opts)?; - let endpoint_opts = mobile_opts.endpoint()?; + let mobile_opts: FFIConfig = + serde_json::from_str(&opts).map_err(FFIMobileError::FailedToDeserialize)?; + let endpoint_opts = mobile_opts.endpoint().map_err(FFIMobileError::NoConfig)?; let resolver = get_ion_resolver(&endpoint_opts.ion_endpoint().to_address()); let rt = Runtime::new().unwrap(); rt.block_on(async { - TrustchainAPI::resolve(&did, &resolver) + Ok(TrustchainAPI::resolve(&did, &resolver) .await - .map_err(|e| anyhow!(e)) - .and_then(|(_, doc, _)| serde_json::to_string_pretty(&doc).map_err(|e| anyhow!(e))) + .map_err(FFIMobileError::FailedToResolveDID) + .and_then(|(_, doc, _)| { + serde_json::to_string_pretty(&doc).map_err(FFIMobileError::FailedToSerialize) + })?) }) } -/// Verifies a given DID. -pub fn did_verify(did: String, opts: String) -> Result<()> { - let mobile_opts: FFIConfig = serde_json::from_str(&opts)?; - let endpoint_opts = mobile_opts.endpoint()?; - let trustchain_opts = mobile_opts.trustchain()?; +/// Verifies a given DID returning the serialized DIDChain as a JSON string. +pub fn did_verify(did: String, opts: String) -> Result { + let mobile_opts: FFIConfig = + serde_json::from_str(&opts).map_err(FFIMobileError::FailedToDeserialize)?; + let endpoint_opts = mobile_opts.endpoint().map_err(FFIMobileError::NoConfig)?; + let trustchain_opts = mobile_opts.trustchain().map_err(FFIMobileError::NoConfig)?; let root_event_time = trustchain_opts.root_event_time; let rt = Runtime::new().unwrap(); rt.block_on(async { @@ -39,14 +64,16 @@ pub fn did_verify(did: String, opts: String) -> Result<()> { get_ion_resolver(&endpoint_opts.ion_endpoint().to_address()), endpoint_opts.trustchain_endpoint()?.to_address(), ); - TrustchainAPI::verify(&did, root_event_time, &verifier) + Ok(TrustchainAPI::verify(&did, root_event_time, &verifier) .await - .map_err(|err| anyhow!(err.to_string()))?; - Ok(()) + .map_err(FFIMobileError::FailedToVerifyDID) + .and_then(|did_chain| { + serde_json::to_string_pretty(&did_chain).map_err(FFIMobileError::FailedToSerialize) + })?) }) } -/// Verifies a verifiable credential. +/// Verifies a verifiable credential returning the serialized DIDChain as a JSON string. pub fn vc_verify_credential(credential: String, opts: String) -> Result { let mobile_opts: FFIConfig = serde_json::from_str(&opts)?; let endpoint_opts = mobile_opts.endpoint()?; @@ -67,10 +94,15 @@ pub fn vc_verify_credential(credential: String, opts: String) -> Result // https://docs.rs/ssi/0.4.0/src/ssi/vc.rs.html#1973-1975 (created time checked here) // // TODO: try setting time as now from emulator to check time used from now_ms() call - TrustchainAPI::verify_credential(&credential, ldp_opts, root_event_time, &verifier) - .await - .map_err(|err| anyhow!(err.to_string()))?; - Ok("Ok".to_string()) + Ok( + TrustchainAPI::verify_credential(&credential, ldp_opts, root_event_time, &verifier) + .await + .map_err(FFIMobileError::FailedToVerifyCredential) + .and_then(|did_chain| { + serde_json::to_string_pretty(&did_chain) + .map_err(FFIMobileError::FailedToSerialize) + })?, + ) }) } From b31d609e59af1c159130008c215906ffd0d384b7 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Mon, 7 Aug 2023 12:26:21 +0100 Subject: [PATCH 32/87] Refactor test variable --- trustchain-ffi/src/config.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/trustchain-ffi/src/config.rs b/trustchain-ffi/src/config.rs index f252af4d..0139c33c 100644 --- a/trustchain-ffi/src/config.rs +++ b/trustchain-ffi/src/config.rs @@ -160,12 +160,11 @@ mod tests { let test_mobile_options: String = format!( r#" {{ - "endpointOptions": {}, - "trustchainOptions": {}, - "linkedDataProofOptions": {} + "endpointOptions": {TEST_ENDPOINT_OPTIONS}, + "trustchainOptions": {TEST_TRUSTCHAIN_OPTIONS}, + "linkedDataProofOptions": {TEST_LINKED_DATA_PROOF_OPTIONS} }} "#, - TEST_ENDPOINT_OPTIONS, TEST_TRUSTCHAIN_OPTIONS, TEST_LINKED_DATA_PROOF_OPTIONS ); serde_json::from_str::(&test_mobile_options).unwrap(); } From 06ea345bc7c25ff09bc905e527b22d6b96c9f84c Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Mon, 7 Aug 2023 17:41:15 +0100 Subject: [PATCH 33/87] Add check for current time compared to created time in proof --- trustchain-ffi/Cargo.toml | 1 + trustchain-ffi/src/mobile.rs | 32 ++++++++++++++++++++++++++------ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/trustchain-ffi/Cargo.toml b/trustchain-ffi/Cargo.toml index 451a8f39..705a2df2 100644 --- a/trustchain-ffi/Cargo.toml +++ b/trustchain-ffi/Cargo.toml @@ -15,6 +15,7 @@ trustchain-ion = { path = "../trustchain-ion" } trustchain-api = { path = "../trustchain-api" } anyhow = "1.0" +chrono = "0.4.26" did-ion="0.1.0" flutter_rust_bridge = "1" lazy_static="1.4.0" diff --git a/trustchain-ffi/src/mobile.rs b/trustchain-ffi/src/mobile.rs index 753b33d8..0003b573 100644 --- a/trustchain-ffi/src/mobile.rs +++ b/trustchain-ffi/src/mobile.rs @@ -1,7 +1,12 @@ // TODO: add module doc comments for mobile FFI use crate::config::FFIConfig; use anyhow::Result; -use ssi::{ldp::now_ms, vc::Credential}; +use chrono::{DateTime, Utc}; +use ssi::{ + ldp::now_ms, + one_or_many::OneOrMany, + vc::{Credential, Proof}, +}; use thiserror::Error; use tokio::runtime::Runtime; use trustchain_api::{ @@ -28,6 +33,8 @@ enum FFIMobileError { FailedToVerifyDID(VerifierError), #[error("Failed to verify credential error: {0}.")] FailedToVerifyCredential(CredentialError), + #[error("Credential proof created time ({0}) is in the future relative to now ({1}).")] + FutureProofCreatedTime(DateTime, DateTime), } /// Example greet function. @@ -88,12 +95,25 @@ pub fn vc_verify_credential(credential: String, opts: String) -> Result ); let root_event_time = trustchain_opts.root_event_time; - // NB. When using android emulator, the time is less than the created time on - // the credential. This leads to a failure upon the proofs being checked: - // https://docs.rs/ssi/0.4.0/src/ssi/vc.rs.html#1243 (filtered here) - // https://docs.rs/ssi/0.4.0/src/ssi/vc.rs.html#1973-1975 (created time checked here) + // When using android emulator, the time can be less than the created time in the proof if + // the clock is not correctly synchronised. This leads to a failure upon the proofs being + // checked: + // https://docs.rs/ssi/0.4.0/src/ssi/vc.rs.html#1243 (filtered here) + // https://docs.rs/ssi/0.4.0/src/ssi/vc.rs.html#1973-1975 (created time checked here) // - // TODO: try setting time as now from emulator to check time used from now_ms() call + // To recover, check that a time later than when the created time on the credential is used. + if let Some(OneOrMany::One(Proof { + created: Some(created_time), + .. + })) = credential.proof.as_ref() + { + let now = now_ms(); + if &now < created_time { + return Err( + FFIMobileError::FutureProofCreatedTime(created_time.to_owned(), now).into(), + ); + } + } Ok( TrustchainAPI::verify_credential(&credential, ldp_opts, root_event_time, &verifier) .await From 2673d6685de753795d770a67ac6abf019c00896f Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Tue, 8 Aug 2023 09:43:59 +0100 Subject: [PATCH 34/87] Add handling of presentation request from cache --- trustchain-http/src/errors.rs | 10 ++ trustchain-http/src/server.rs | 2 +- trustchain-http/src/state.rs | 1 + trustchain-http/src/verifier.rs | 165 +++++++++++++++++++++++++------- 4 files changed, 141 insertions(+), 37 deletions(-) diff --git a/trustchain-http/src/errors.rs b/trustchain-http/src/errors.rs index 90f92639..10630fa2 100644 --- a/trustchain-http/src/errors.rs +++ b/trustchain-http/src/errors.rs @@ -28,6 +28,10 @@ pub enum TrustchainHTTPError { FailedToVerifyCredential, #[error("Invalid signature.")] InvalidSignature, + #[error("Request does not exist.")] + RequestDoesNotExist, + #[error("Could not deserialize data: {0}")] + FailedToDeserialize(serde_json::Error), } impl From for TrustchainHTTPError { @@ -90,6 +94,12 @@ impl IntoResponse for TrustchainHTTPError { err @ TrustchainHTTPError::InvalidSignature => { (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) } + err @ TrustchainHTTPError::RequestDoesNotExist => { + (StatusCode::BAD_REQUEST, err.to_string()) + } + err @ TrustchainHTTPError::FailedToDeserialize(_) => { + (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) + } }; let body = Json(json!({ "error": err_message })); (status, body).into_response() diff --git a/trustchain-http/src/server.rs b/trustchain-http/src/server.rs index 24b978dc..d99c498a 100644 --- a/trustchain-http/src/server.rs +++ b/trustchain-http/src/server.rs @@ -48,7 +48,7 @@ impl TrustchainRouter { }), ) .route( - "/vc/verifier", + "/vc/verifier/:id", get(verifier::TrustchainVerifierHTTPHandler::get_verifier).post({ let state = shared_state.clone(); move |verification_info| { diff --git a/trustchain-http/src/state.rs b/trustchain-http/src/state.rs index 38793756..62b2f744 100644 --- a/trustchain-http/src/state.rs +++ b/trustchain-http/src/state.rs @@ -23,6 +23,7 @@ impl AppState { .expect("Credential cache does not exist.") .as_slice(), ) + // TODO: change to unrwap_or_default() (empty cache) .expect("Credential cache could not be deserialized."); let presentation_requests: HashMap = serde_json::from_reader( std::fs::read(std::path::Path::new(&path).join("presentations/requests/cache.json")) diff --git a/trustchain-http/src/verifier.rs b/trustchain-http/src/verifier.rs index fd872d6c..2dfc6e10 100644 --- a/trustchain-http/src/verifier.rs +++ b/trustchain-http/src/verifier.rs @@ -4,34 +4,38 @@ use crate::resolver::RootEventTime; use crate::state::AppState; use crate::EXAMPLE_VP_REQUEST; use async_trait::async_trait; -use axum::extract::State; +use axum::extract::{Path, State}; use axum::http::StatusCode; use axum::response::{Html, IntoResponse}; use axum::Json; use log::info; use serde::{Deserialize, Serialize}; +use serde_json::Value; use ssi::did_resolve::DIDResolver; -use ssi::vc::{Credential, Issuer, Presentation, URI}; +use ssi::ldp::LinkedDataDocument; +use ssi::vc::{Credential, Presentation, URI}; use std::sync::Arc; use trustchain_core::verifier::{Timestamp, Verifier}; use trustchain_ion::verifier::IONVerifier; -// https://w3c-ccg.github.io/vp-request-spec/ -#[derive(Debug, Serialize, Deserialize)] -pub struct PresentationRequest; +/// A type for presentation requests. See [VP request spec](https://w3c-ccg.github.io/vp-request-spec/) +/// for further details. +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct PresentationRequest(Value); /// An API for a Trustchain verifier server. #[async_trait] pub trait TrustchainVerifierHTTP { - /// Constructs a presentation request (given some `presentiation_id`) to send to a credential holder from request wallet by ID - fn generate_presentation_request(presentation_id: &str) -> PresentationRequest { + /// Constructs a presentation request (given some `presentiation_id`) to send to a credential + /// holder from request wallet by ID. + fn generate_presentation_request(_presentation_id: &str) -> PresentationRequest { todo!() } - /// Verifies verifiable presentation - async fn verify_presentation(presentation: &Presentation) -> Result<(), TrustchainHTTPError> { + /// Verifies verifiable presentation. + async fn verify_presentation(_presentation: &Presentation) -> Result<(), TrustchainHTTPError> { todo!() } - /// Verifies verifiable credential + /// Verifies verifiable credential. async fn verify_credential( credential: &Credential, root_event_time: Timestamp, @@ -41,16 +45,14 @@ pub trait TrustchainVerifierHTTP { if !verify_credential_result.errors.is_empty() { return Err(TrustchainHTTPError::InvalidSignature); } - match credential.issuer { - Some(Issuer::URI(URI::String(ref issuer))) => { - Ok(verifier.verify(issuer, root_event_time).await.map(|_| ())?) - } + match credential.get_issuer() { + Some(issuer) => Ok(verifier.verify(issuer, root_event_time).await.map(|_| ())?), _ => Err(TrustchainHTTPError::NoCredentialIssuer), } } } -/// TODO +/// Handler for verification of credentials and presentations. pub struct TrustchainVerifierHTTPHandler; impl TrustchainVerifierHTTP for TrustchainVerifierHTTPHandler {} @@ -65,9 +67,15 @@ pub struct PostVerifier { impl TrustchainVerifierHTTPHandler { /// API endpoint taking the UUID of a VC. Response is the VC JSON. - // TODO: refine to allow a specific ID for the request to be passed and extracted from path - pub async fn get_verifier() -> impl IntoResponse { - (StatusCode::OK, Json(EXAMPLE_VP_REQUEST)) + pub async fn get_verifier( + Path(request_id): Path, + State(app_state): State>, + ) -> impl IntoResponse { + app_state + .presentation_requests + .get(&request_id) + .ok_or(TrustchainHTTPError::RequestDoesNotExist) + .map(|request| (StatusCode::OK, Json(request.to_owned()))) } /// Handler for credential received from POST. pub async fn post_verifier( @@ -75,8 +83,9 @@ impl TrustchainVerifierHTTPHandler { app_state: Arc, ) -> impl IntoResponse { info!( - "Received credential at presentation:\n{}", - serde_json::to_string_pretty(&verification_info).unwrap() + "Received credential:\n{}", + serde_json::to_string_pretty(&verification_info) + .map_err(TrustchainHTTPError::FailedToDeserialize)? ); TrustchainVerifierHTTPHandler::verify_credential( @@ -92,28 +101,112 @@ impl TrustchainVerifierHTTPHandler { ) }) } - - pub async fn get_verifier_qrcode(State(app_state): State>) -> Html { - // Generate a QR code for server address and combination of name and UUID - let address_str = format!( - "http://{}:{}/vc/verifier", - app_state.config.host_reference, app_state.config.port - ); - - // Respond with the QR code as a png embedded in html - Html(str_to_qr_code_html(&address_str, "Verifier")) + /// Generates a QR code for receiving requests, default to first request in cache + pub async fn get_verifier_qrcode(State(app_state): State>) -> impl IntoResponse { + app_state + .presentation_requests + .iter() + .next() + .ok_or(TrustchainHTTPError::RequestDoesNotExist) + .map(|(uid, _)| { + let address_str = format!( + "http://{}:{}/vc/verifier/{}", + app_state.config.host_reference, app_state.config.port, uid + ); + ( + StatusCode::OK, + Html(str_to_qr_code_html(&address_str, "Verifier")), + ) + }) } } #[cfg(test)] mod tests { + use super::*; + use crate::{ + config::HTTPConfig, errors::TrustchainHTTPError, server::TrustchainRouter, state::AppState, + }; + use axum_test_helper::TestClient; + use hyper::StatusCode; + use lazy_static::lazy_static; + use serde_json::json; + use ssi::{ + one_or_many::OneOrMany, + vc::{Credential, CredentialSubject, Issuer, URI}, + }; + use std::{collections::HashMap, sync::Arc}; + use trustchain_core::{utils::canonicalize, verifier::Verifier}; + use trustchain_ion::{get_ion_resolver, verifier::IONVerifier}; + + lazy_static! { + /// Lazy static reference to core configuration loaded from `trustchain_config.toml`. + pub static ref TEST_HTTP_CONFIG: HTTPConfig = HTTPConfig { + issuer_did: Some("did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q".to_string()), + ..Default::default() + }; + } + + const REQUESTS: &str = r#" + { + "b9519df2-35c1-11ee-8314-7f66e4585b4f": { + "type": "VerifiablePresentationRequest", + "query": [ + { + "type": "QueryByExample", + "credentialQuery": { + "reason": "Request credential", + "example": { + "@context": [ + "https://www.w3.org/2018/credentials/v1" + ], + "type": "VerifiableCredential" + } + } + } + ], + "challenge": "a877fb0a-11dd-11ee-9df7-9be7abdeee2d", + "domain": "https://alan-turing-institute.github.io/trustchain" + } + } + "#; // TODO: complete tests as part of verifier completion (#56) - // // Verifier integration tests - // #[tokio::test] - // #[ignore = "integration test requires ION, MongoDB, IPFS and Bitcoin RPC"] - // async fn test_get_verifier_request() { - // todo!() - // } + // Verifier integration tests + #[tokio::test] + #[ignore = "integration test requires ION, MongoDB, IPFS and Bitcoin RPC"] + async fn test_get_verifier_request() { + let state = Arc::new(AppState::new_with_cache( + TEST_HTTP_CONFIG.to_owned(), + HashMap::new(), + serde_json::from_str(REQUESTS).unwrap(), + )); + // Test response for request in cache + let app = TrustchainRouter::from(state.clone()).into_router(); + let uid = "b9519df2-35c1-11ee-8314-7f66e4585b4f"; + let uri = format!("/vc/verifier/{uid}"); + let client = TestClient::new(app); + let response = client.get(&uri).send().await; + + // Test response is OK + assert_eq!(response.status(), StatusCode::OK); + + // Test response json same as cache + let expected_request = state.presentation_requests.get(uid).unwrap(); + let actual_request = response.json::().await; + assert_eq!(&actual_request, expected_request); + + // Test response for non-existent request + let app = TrustchainRouter::from(state.clone()).into_router(); + let uid = "dd2f6d68-35c5-11ee-98c7-d317dc01648b"; + let uri = format!("/vc/verifier/{uid}"); + let client = TestClient::new(app); + let response = client.get(&uri).send().await; + assert_eq!(response.status(), StatusCode::BAD_REQUEST); + assert_eq!( + response.text().await, + json!({"error":TrustchainHTTPError::RequestDoesNotExist.to_string()}).to_string() + ); + } // #[tokio::test] // #[ignore = "integration test requires ION, MongoDB, IPFS and Bitcoin RPC"] From 131028a3f287c4049a08f3d79bb56d5767cdab74 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Tue, 8 Aug 2023 10:37:40 +0100 Subject: [PATCH 35/87] Add test for post verifier --- trustchain-http/src/lib.rs | 21 -------- trustchain-http/src/verifier.rs | 87 +++++++++++++++++++++++++-------- 2 files changed, 66 insertions(+), 42 deletions(-) diff --git a/trustchain-http/src/lib.rs b/trustchain-http/src/lib.rs index 4cfd47b5..27199478 100644 --- a/trustchain-http/src/lib.rs +++ b/trustchain-http/src/lib.rs @@ -8,24 +8,3 @@ pub mod server; pub mod state; pub mod static_handlers; pub mod verifier; - -/// Example VP request. -pub const EXAMPLE_VP_REQUEST: &str = r#"{ - "type": "VerifiablePresentationRequest", - "query": [ - { - "type": "QueryByExample", - "credentialQuery": { - "reason": "Request credential", - "example": { - "@context": [ - "https://www.w3.org/2018/credentials/v1" - ], - "type": "VerifiableCredential" - } - } - } - ], - "challenge": "a877fb0a-11dd-11ee-9df7-9be7abdeee2d", - "domain": "https://alan-turing-institute.github.io/trustchain" -}"#; diff --git a/trustchain-http/src/verifier.rs b/trustchain-http/src/verifier.rs index 2dfc6e10..a2d43668 100644 --- a/trustchain-http/src/verifier.rs +++ b/trustchain-http/src/verifier.rs @@ -1,8 +1,6 @@ use crate::errors::TrustchainHTTPError; use crate::qrcode::str_to_qr_code_html; -use crate::resolver::RootEventTime; use crate::state::AppState; -use crate::EXAMPLE_VP_REQUEST; use async_trait::async_trait; use axum::extract::{Path, State}; use axum::http::StatusCode; @@ -13,7 +11,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use ssi::did_resolve::DIDResolver; use ssi::ldp::LinkedDataDocument; -use ssi::vc::{Credential, Presentation, URI}; +use ssi::vc::{Credential, Presentation}; use std::sync::Arc; use trustchain_core::verifier::{Timestamp, Verifier}; use trustchain_ion::verifier::IONVerifier; @@ -57,9 +55,9 @@ pub struct TrustchainVerifierHTTPHandler; impl TrustchainVerifierHTTP for TrustchainVerifierHTTPHandler {} +/// Struct for deserializing credential and corresponding root event time. #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -/// Struct for deserializing credential and corresponding root event time. pub struct PostVerifier { pub credential: Credential, pub root_event_time: Timestamp, @@ -94,12 +92,7 @@ impl TrustchainVerifierHTTPHandler { &app_state.verifier, ) .await - .map(|_| { - ( - StatusCode::CREATED, - Html("Presentation successfully proved!"), - ) - }) + .map(|_| (StatusCode::OK, Html("Presentation successfully proved!"))) } /// Generates a QR code for receiving requests, default to first request in cache pub async fn get_verifier_qrcode(State(app_state): State>) -> impl IntoResponse { @@ -131,13 +124,7 @@ mod tests { use hyper::StatusCode; use lazy_static::lazy_static; use serde_json::json; - use ssi::{ - one_or_many::OneOrMany, - vc::{Credential, CredentialSubject, Issuer, URI}, - }; use std::{collections::HashMap, sync::Arc}; - use trustchain_core::{utils::canonicalize, verifier::Verifier}; - use trustchain_ion::{get_ion_resolver, verifier::IONVerifier}; lazy_static! { /// Lazy static reference to core configuration loaded from `trustchain_config.toml`. @@ -170,6 +157,40 @@ mod tests { } } "#; + + const TEST_POST_VERIFIER: &str = r#" + { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "id": "urn:uuid:46cb84e2-fa10-11ed-a0d4-bbb4e61d1556", + "type": ["VerifiableCredential"], + "credentialSubject": { + "id": "did:example:284b3f34fad911ed9aea439566dd422a", + "familyName": "Bloggs", + "degree": { + "college": "University of Oxbridge", + "name": "Bachelor of Arts", + "type": "BachelorDegree" + }, + "givenName": "Jane" + }, + "issuer": "did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q", + "issuanceDate": "2023-08-08T08:59:21.458576Z", + "proof": { + "type": "EcdsaSecp256k1Signature2019", + "proofPurpose": "assertionMethod", + "verificationMethod": "did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q#ePyXsaNza8buW6gNXaoGZ07LMTxgLC9K7cbaIjIizTI", + "created": "2023-08-08T08:59:21.461Z", + "jws": "eyJhbGciOiJFUzI1NksiLCJjcml0IjpbImI2NCJdLCJiNjQiOmZhbHNlfQ..LqLHztj2djQ9aWDGFjm3ZaOzDFIVKnOyZQVvE7CMDbYV5POYz6IejwnRkcqRf7uPYc2QbJAqCjj20PfwTOPJEw" + } + }, + "rootEventTime": 1666265405 + } + "#; + // TODO: complete tests as part of verifier completion (#56) // Verifier integration tests #[tokio::test] @@ -208,9 +229,33 @@ mod tests { ); } - // #[tokio::test] - // #[ignore = "integration test requires ION, MongoDB, IPFS and Bitcoin RPC"] - // async fn test_post_verifier_credential() { - // todo!() - // } + #[tokio::test] + #[ignore = "integration test requires ION, MongoDB, IPFS and Bitcoin RPC"] + async fn test_post_verifier_credential() { + let state = Arc::new(AppState::new_with_cache( + TEST_HTTP_CONFIG.to_owned(), + HashMap::new(), + serde_json::from_str(REQUESTS).unwrap(), + )); + // Test post of credential to verifier + let app = TrustchainRouter::from(state.clone()).into_router(); + let uid = "b9519df2-35c1-11ee-8314-7f66e4585b4f"; + let uri = format!("/vc/verifier/{uid}"); + let client = TestClient::new(app); + let post_verifier: PostVerifier = serde_json::from_str(TEST_POST_VERIFIER).unwrap(); + let response = client.post(&uri).json(&post_verifier).send().await; + assert_eq!(response.status(), StatusCode::OK); + assert_eq!("Presentation successfully proved!", response.text().await); + + // Test post of credential to verifier with bad root event time + let app = TrustchainRouter::from(state.clone()).into_router(); + let uid = "b9519df2-35c1-11ee-8314-7f66e4585b4f"; + let uri = format!("/vc/verifier/{uid}"); + let client = TestClient::new(app); + let mut post_verifier: PostVerifier = serde_json::from_str(TEST_POST_VERIFIER).unwrap(); + post_verifier.root_event_time = 1666265406; + let response = client.post(&uri).json(&post_verifier).send().await; + assert_eq!(response.status(), StatusCode::OK); + assert_eq!(response.text().await, r#"{"error":"Trustchain Verifier error: Invalid root DID: did:ion:test:EiCClfEdkTv_aM3UnBBhlOV89LlGhpQAbfeZLFdFxVFkEg."}"#.to_string()); + } } From f3421e835807b977ecc7b99f281f32bff830dcde Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Fri, 11 Aug 2023 07:46:59 +0100 Subject: [PATCH 36/87] Add info to verifier handler --- trustchain-http/src/verifier.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/trustchain-http/src/verifier.rs b/trustchain-http/src/verifier.rs index a2d43668..190f6a7c 100644 --- a/trustchain-http/src/verifier.rs +++ b/trustchain-http/src/verifier.rs @@ -80,11 +80,9 @@ impl TrustchainVerifierHTTPHandler { Json(verification_info): Json, app_state: Arc, ) -> impl IntoResponse { - info!( - "Received credential:\n{}", - serde_json::to_string_pretty(&verification_info) - .map_err(TrustchainHTTPError::FailedToDeserialize)? - ); + let verification_info_json = serde_json::to_string_pretty(&verification_info) + .map_err(TrustchainHTTPError::FailedToDeserialize)?; + info!("Received verification information:\n{verification_info_json}",); TrustchainVerifierHTTPHandler::verify_credential( &verification_info.credential, @@ -92,7 +90,14 @@ impl TrustchainVerifierHTTPHandler { &app_state.verifier, ) .await - .map(|_| (StatusCode::OK, Html("Presentation successfully proved!"))) + .map(|_| { + info!("Credential verification...ok ✅:\n{verification_info_json}"); + (StatusCode::OK, Html("Credential received and verified!")) + }) + .map_err(|err| { + info!("Credential verification...error ❌:\n{err}"); + err + }) } /// Generates a QR code for receiving requests, default to first request in cache pub async fn get_verifier_qrcode(State(app_state): State>) -> impl IntoResponse { From 421e4990338f54854096e0cc724ea2d0244aeddb Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Mon, 14 Aug 2023 08:37:12 +0100 Subject: [PATCH 37/87] Add https support --- trustchain-http/Cargo.toml | 2 ++ trustchain-http/src/bin/main.rs | 13 ++++++------- trustchain-http/src/config.rs | 7 +++++++ trustchain-http/src/server.rs | 30 ++++++++++++++++++++++++++++-- 4 files changed, 43 insertions(+), 9 deletions(-) diff --git a/trustchain-http/Cargo.toml b/trustchain-http/Cargo.toml index 28ddb727..0208cff3 100644 --- a/trustchain-http/Cargo.toml +++ b/trustchain-http/Cargo.toml @@ -15,6 +15,7 @@ trustchain-core = { path = "../trustchain-core" } trustchain-ion = { path = "../trustchain-ion" } async-trait = "0.1" axum = "0.6" +axum-server = {version="0.5.1", features = ["tls-rustls"] } base64 = "0.21.0" chrono = "^0.4" clap = { version = "^4", features=["derive", "env", "cargo"] } @@ -30,6 +31,7 @@ reqwest = "0.11.16" serde = { version = "1.0", features = ["derive"] } serde_jcs = "0.1.0" serde_json = "1.0" +shellexpand = "3.1.0" ssi = "0.4" thiserror="1.0" tokio = {version = "1.20.1", features = ["full"]} diff --git a/trustchain-http/src/bin/main.rs b/trustchain-http/src/bin/main.rs index 53def871..9a661002 100644 --- a/trustchain-http/src/bin/main.rs +++ b/trustchain-http/src/bin/main.rs @@ -1,5 +1,5 @@ use log::info; -use trustchain_http::config::HTTP_CONFIG; +use trustchain_http::config::{http_config, HTTP_CONFIG}; use trustchain_http::server; #[tokio::main] @@ -12,13 +12,12 @@ async fn main() -> std::io::Result<()> { // Print config info!("{}", config); - let addr = config.to_address(); - // Init server - server::server(config).await.unwrap(); - - // Logging - tracing::debug!("listening on {}", addr); + // Run server + match http_config().https { + false => server::http_server(config).await.unwrap(), + true => server::https_server(config).await.unwrap(), + } Ok(()) } diff --git a/trustchain-http/src/config.rs b/trustchain-http/src/config.rs index b8075c43..a5837d44 100644 --- a/trustchain-http/src/config.rs +++ b/trustchain-http/src/config.rs @@ -22,6 +22,10 @@ pub struct HTTPConfig { pub port: u16, /// Optional issuer DID pub issuer_did: Option, + /// Flag indicating whether server uses https + pub https: bool, + /// Path with certificate and key for https + pub https_path: Option, } impl std::fmt::Display for HTTPConfig { @@ -37,6 +41,8 @@ impl Default for HTTPConfig { host_reference: IpAddr::from_str(DEFAULT_HOST).unwrap(), port: DEFAULT_PORT, issuer_did: None, + https: false, + https_path: None, } } } @@ -92,6 +98,7 @@ mod tests { host_reference = "127.0.0.1" port = 8081 issuer_did = "did:ion:test:EiBcLZcELCKKtmun_CUImSlb2wcxK5eM8YXSq3MrqNe5wA" + https = false [non_http] key = "value" diff --git a/trustchain-http/src/server.rs b/trustchain-http/src/server.rs index d99c498a..48ab7f61 100644 --- a/trustchain-http/src/server.rs +++ b/trustchain-http/src/server.rs @@ -1,8 +1,11 @@ +use crate::config::http_config; use crate::middleware::validate_did; use crate::{config::HTTPConfig, issuer, resolver, state::AppState, static_handlers, verifier}; use axum::routing::IntoMakeService; use axum::{middleware, routing::get, Router}; +use axum_server::tls_rustls::RustlsConfig; use hyper::server::conn::AddrIncoming; +use std::path::PathBuf; use std::sync::Arc; use tower::ServiceBuilder; @@ -82,10 +85,33 @@ impl TrustchainRouter { } } -/// General method to spawn a Trustchain server given ServerConfig. -pub fn server(config: HTTPConfig) -> axum::Server> { +/// Spawns a Trustchain server given `HTTPConfig` with http. +pub fn http_server(config: HTTPConfig) -> axum::Server> { let addr = config.to_socket_address(); let shared_state = Arc::new(AppState::new(config)); let app = TrustchainRouter::from(shared_state).into_router(); axum::Server::bind(&addr).serve(app.into_make_service()) } + +/// Spawns a Trustchain server given `HTTPConfig` with https. +pub async fn https_server(config: HTTPConfig) -> std::io::Result<()> { + let addr = config.to_socket_address(); + let shared_state = Arc::new(AppState::new(config)); + let app = TrustchainRouter::from(shared_state).into_router(); + let tls_config = rustls_config(http_config().https_path.as_ref().unwrap()).await; + axum_server::bind_rustls(addr, tls_config) + .serve(app.into_make_service()) + .await +} + +/// Generates a `RustlsConfig` for https servers given a path with certificate and key. Based on axum [example](https://github.com/tokio-rs/axum/blob/d30375925dd22cc44aeaae2871f8ead1630fadf8/examples/tls-rustls/src/main.rs) +async fn rustls_config(path: &str) -> RustlsConfig { + // Configure certificate and private key used by https + let path = shellexpand::tilde(path); + RustlsConfig::from_pem_file( + PathBuf::from(path.as_ref()).join("cert.pem"), + PathBuf::from(path.as_ref()).join("key.pem"), + ) + .await + .expect("Failed to create Rustls config.") +} From 5b710084e052284d30b8b7549c559cc4b9513490 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Mon, 14 Aug 2023 09:02:44 +0100 Subject: [PATCH 38/87] Revise method to handle case where http specified --- trustchain-ion/src/lib.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/trustchain-ion/src/lib.rs b/trustchain-ion/src/lib.rs index 70b24b82..302d5a77 100644 --- a/trustchain-ion/src/lib.rs +++ b/trustchain-ion/src/lib.rs @@ -36,10 +36,13 @@ impl Endpoint { pub fn new(url: String, port: u16) -> Self { Self { host: url, port } } + // TODO: add more flexible address handling pub fn to_address(&self) -> String { - format!("http://{}:{}/", self.host, self.port) + match self.host.starts_with("http") { + true => format!("{}:{}/", self.host, self.port), + false => format!("http://{}:{}/", self.host, self.port), + } } - // TODO: add more flexible address methods } /// Test resolver From 9de28d0e8b5007587f0ba8fdcf0b862e0835c8a5 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Fri, 25 Aug 2023 07:24:20 +0100 Subject: [PATCH 39/87] Config http or https for QR codes displayed --- trustchain-http/src/issuer.rs | 10 ++++++++-- trustchain-http/src/verifier.rs | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/trustchain-http/src/issuer.rs b/trustchain-http/src/issuer.rs index 65dd4f2d..583fc557 100644 --- a/trustchain-http/src/issuer.rs +++ b/trustchain-http/src/issuer.rs @@ -1,3 +1,4 @@ +use crate::config::http_config; use crate::errors::TrustchainHTTPError; use crate::qrcode::str_to_qr_code_html; use crate::state::AppState; @@ -113,10 +114,15 @@ impl TrustchainIssuerHTTPHandler { pub async fn get_issuer_qrcode(State(app_state): State>) -> Html { // TODO: update to take query param entered by user. let id = "7426a2e8-f932-11ed-968a-4bb02079f142".to_string(); + let http_str = if !http_config().https { + "http" + } else { + "https" + }; // Generate a QR code for server address and combination of name and UUID let address_str = format!( - "http://{}:{}/vc/issuer/{id}", - app_state.config.host_reference, app_state.config.port + "{}://{}:{}/vc/issuer/{id}", + http_str, app_state.config.host_reference, app_state.config.port ); // Respond with the QR code as a png embedded in html Html(str_to_qr_code_html(&address_str, "Issuer")) diff --git a/trustchain-http/src/verifier.rs b/trustchain-http/src/verifier.rs index 190f6a7c..d4063371 100644 --- a/trustchain-http/src/verifier.rs +++ b/trustchain-http/src/verifier.rs @@ -1,3 +1,4 @@ +use crate::config::http_config; use crate::errors::TrustchainHTTPError; use crate::qrcode::str_to_qr_code_html; use crate::state::AppState; @@ -107,9 +108,14 @@ impl TrustchainVerifierHTTPHandler { .next() .ok_or(TrustchainHTTPError::RequestDoesNotExist) .map(|(uid, _)| { + let http_str = if !http_config().https { + "http" + } else { + "https" + }; let address_str = format!( - "http://{}:{}/vc/verifier/{}", - app_state.config.host_reference, app_state.config.port, uid + "{}://{}:{}/vc/verifier/{}", + http_str, app_state.config.host_reference, app_state.config.port, uid ); ( StatusCode::OK, From 275f55c09a99eb7e4d1ab9c3a4315e324eb9ee9c Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Thu, 7 Sep 2023 16:24:29 +0100 Subject: [PATCH 40/87] Add issue presentation FFI functionality --- trustchain-ffi/src/mobile.rs | 54 ++++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/trustchain-ffi/src/mobile.rs b/trustchain-ffi/src/mobile.rs index 0003b573..665514f3 100644 --- a/trustchain-ffi/src/mobile.rs +++ b/trustchain-ffi/src/mobile.rs @@ -3,9 +3,10 @@ use crate::config::FFIConfig; use anyhow::Result; use chrono::{DateTime, Utc}; use ssi::{ + jwk::JWK, ldp::now_ms, one_or_many::OneOrMany, - vc::{Credential, Proof}, + vc::{Credential, LinkedDataProofOptions, Presentation, Proof}, }; use thiserror::Error; use tokio::runtime::Runtime; @@ -126,11 +127,37 @@ pub fn vc_verify_credential(credential: String, opts: String) -> Result }) } +/// Issues a verifiable presentation. Analogous with [didkit](https://docs.rs/didkit/latest/didkit/c/fn.didkit_vc_issue_presentation.html). +pub fn vp_issue_presentation( + presentation: String, + opts: String, + jwk_json: String, +) -> Result { + let mobile_opts: FFIConfig = serde_json::from_str(&opts)?; + let endpoint_opts = mobile_opts.endpoint()?; + let ldp_opts = + mobile_opts + .linked_data_proof() + .cloned() + .ok() + .unwrap_or(LinkedDataProofOptions { + proof_purpose: Some(ssi::vc::ProofPurpose::Authentication), + ..Default::default() + }); + let mut presentation: Presentation = serde_json::from_str(&presentation)?; + let jwk: JWK = serde_json::from_str(&jwk_json)?; + let resolver = get_ion_resolver(&endpoint_opts.ion_endpoint().to_address()); + let rt = Runtime::new().unwrap(); + let proof = rt.block_on(async { + presentation + .generate_proof(&jwk, &ldp_opts, &resolver) + .await + })?; + presentation.add_proof(proof); + Ok(serde_json::to_string_pretty(&presentation)?) +} + // // TODO: implement once verifiable presentations are included in API -// /// Issues a verifiable presentation. Analogous with [didkit](https://docs.rs/didkit/latest/didkit/c/fn.didkit_vc_issue_presentation.html). -// pub fn vc_issue_presentation(presentation: String, opts: String, key_json: String) { -// todo!() -// } // /// Verifies a verifiable presentation. Analogous with [didkit](https://docs.rs/didkit/latest/didkit/c/fn.didkit_vc_verify_presentation.html). // pub fn vc_verify_presentation(presentation: String, opts: String) -> Result { // todo!() @@ -206,10 +233,21 @@ mod tests { vc_verify_credential(serde_json::to_string(&credential).unwrap(), ffi_opts).unwrap(); } - // // TODO: implement once verifiable presentations are included in API - // #[test] - // fn test_vc_issue_presentation() {} + #[test] + #[ignore = "integration test requires ION, MongoDB, IPFS and Bitcoin RPC"] + fn test_vp_issue_presentation() { + let ffi_opts = serde_json::to_string(&parse_toml(TEST_FFI_CONFIG)).unwrap(); + let credential: Credential = serde_json::from_str(TEST_CREDENTIAL).unwrap(); + let root_plus_1_signing_key: &str = r#"{"kty":"EC","crv":"secp256k1","x":"aApKobPO8H8wOv-oGT8K3Na-8l-B1AE3uBZrWGT6FJU","y":"dspEqltAtlTKJ7cVRP_gMMknyDPqUw-JHlpwS2mFuh0","d":"HbjLQf4tnwJR6861-91oGpERu8vmxDpW8ZroDCkmFvY"}"#; + let presentation = vp_issue_presentation( + serde_json::to_string(&credential).unwrap(), + ffi_opts, + root_plus_1_signing_key.to_string(), + ); + assert!(presentation.is_ok()); + } + // // TODO: implement once verifiable presentations are included in API // #[test] // fn test_vc_verify_presentation() {} } From 458a224f8e50808e821f5ebb414a18a5fe1c352b Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Fri, 8 Sep 2023 09:16:53 +0100 Subject: [PATCH 41/87] Add http verifier for presentations --- trustchain-http/Cargo.toml | 1 + trustchain-http/src/errors.rs | 27 +++- trustchain-http/src/verifier.rs | 226 +++++++++++++++++++++++++------- 3 files changed, 199 insertions(+), 55 deletions(-) diff --git a/trustchain-http/Cargo.toml b/trustchain-http/Cargo.toml index 0208cff3..e4d17e2f 100644 --- a/trustchain-http/Cargo.toml +++ b/trustchain-http/Cargo.toml @@ -13,6 +13,7 @@ path = "src/bin/main.rs" [dependencies] trustchain-core = { path = "../trustchain-core" } trustchain-ion = { path = "../trustchain-ion" } +trustchain-api = { path = "../trustchain-api" } async-trait = "0.1" axum = "0.6" axum-server = {version="0.5.1", features = ["tls-rustls"] } diff --git a/trustchain-http/src/errors.rs b/trustchain-http/src/errors.rs index ac8fe537..0321e053 100644 --- a/trustchain-http/src/errors.rs +++ b/trustchain-http/src/errors.rs @@ -3,8 +3,8 @@ use hyper::StatusCode; use serde_json::json; use thiserror::Error; use trustchain_core::{ - commitment::CommitmentError, issuer::IssuerError, resolver::ResolverError, - verifier::VerifierError, + commitment::CommitmentError, issuer::IssuerError, resolver::ResolverError, vc::CredentialError, + verifier::VerifierError, vp::PresentationError, }; // TODO: refine and add doc comments for error variants @@ -20,6 +20,8 @@ pub enum TrustchainHTTPError { ResolverError(ResolverError), #[error("Trustchain issuer error: {0}")] IssuerError(IssuerError), + #[error("Trustchain presentation error: {0}")] + PresentationError(PresentationError), #[error("Credential does not exist.")] CredentialDoesNotExist, #[error("No issuer available.")] @@ -57,6 +59,12 @@ impl From for TrustchainHTTPError { } } +impl From for TrustchainHTTPError { + fn from(err: PresentationError) -> Self { + TrustchainHTTPError::PresentationError(err) + } +} + // See axum IntoRespone example: // https://github.com/tokio-rs/axum/blob/main/examples/jwt/src/main.rs#L147-L160 @@ -67,10 +75,8 @@ impl IntoResponse for TrustchainHTTPError { err @ TrustchainHTTPError::InternalError => { (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) } - err @ TrustchainHTTPError::VerifierError(VerifierError::InvalidRoot(_)) => { - (StatusCode::OK, err.to_string()) - } - err @ TrustchainHTTPError::VerifierError(VerifierError::CommitmentFailure(_)) => { + err @ TrustchainHTTPError::VerifierError(VerifierError::InvalidRoot(_)) + | err @ TrustchainHTTPError::VerifierError(VerifierError::CommitmentFailure(_)) => { (StatusCode::OK, err.to_string()) } err @ TrustchainHTTPError::VerifierError(_) => { @@ -85,6 +91,15 @@ impl IntoResponse for TrustchainHTTPError { err @ TrustchainHTTPError::ResolverError(_) => { (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) } + err @ TrustchainHTTPError::PresentationError(PresentationError::CredentialError( + CredentialError::VerifierError(VerifierError::CommitmentFailure(_)), + )) + | err @ TrustchainHTTPError::PresentationError(PresentationError::CredentialError( + CredentialError::VerifierError(VerifierError::InvalidRoot(_)), + )) => (StatusCode::OK, err.to_string()), + err @ TrustchainHTTPError::PresentationError(_) => { + (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) + } err @ TrustchainHTTPError::CredentialDoesNotExist => { (StatusCode::BAD_REQUEST, err.to_string()) } diff --git a/trustchain-http/src/verifier.rs b/trustchain-http/src/verifier.rs index d4063371..bb18a47a 100644 --- a/trustchain-http/src/verifier.rs +++ b/trustchain-http/src/verifier.rs @@ -14,6 +14,8 @@ use ssi::did_resolve::DIDResolver; use ssi::ldp::LinkedDataDocument; use ssi::vc::{Credential, Presentation}; use std::sync::Arc; +use trustchain_api::api::TrustchainVPAPI; +use trustchain_api::TrustchainAPI; use trustchain_core::verifier::{Timestamp, Verifier}; use trustchain_ion::verifier::IONVerifier; @@ -31,8 +33,15 @@ pub trait TrustchainVerifierHTTP { todo!() } /// Verifies verifiable presentation. - async fn verify_presentation(_presentation: &Presentation) -> Result<(), TrustchainHTTPError> { - todo!() + async fn verify_presentation( + presentation: &Presentation, + root_event_time: Timestamp, + verifier: &IONVerifier, + ) -> Result<(), TrustchainHTTPError> { + Ok( + TrustchainAPI::verify_presentation(presentation, None, root_event_time, verifier) + .await?, + ) } /// Verifies verifiable credential. async fn verify_credential( @@ -60,12 +69,20 @@ impl TrustchainVerifierHTTP for TrustchainVerifierHTTPHandler {} #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PostVerifier { - pub credential: Credential, + pub presentation_or_credential: PresentationOrCredential, pub root_event_time: Timestamp, } +/// Enum for indicating whether verification information is a presentation or credential. +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum PresentationOrCredential { + Presentation(Presentation), + Credential(Credential), +} + impl TrustchainVerifierHTTPHandler { - /// API endpoint taking the UUID of a VC. Response is the VC JSON. + /// API endpoint taking the UUID of a presentation request. pub async fn get_verifier( Path(request_id): Path, State(app_state): State>, @@ -76,7 +93,7 @@ impl TrustchainVerifierHTTPHandler { .ok_or(TrustchainHTTPError::RequestDoesNotExist) .map(|request| (StatusCode::OK, Json(request.to_owned()))) } - /// Handler for credential received from POST. + /// Handler for presentation or credential received from POST. pub async fn post_verifier( Json(verification_info): Json, app_state: Arc, @@ -85,21 +102,42 @@ impl TrustchainVerifierHTTPHandler { .map_err(TrustchainHTTPError::FailedToDeserialize)?; info!("Received verification information:\n{verification_info_json}",); - TrustchainVerifierHTTPHandler::verify_credential( - &verification_info.credential, - verification_info.root_event_time, - &app_state.verifier, - ) - .await - .map(|_| { - info!("Credential verification...ok ✅:\n{verification_info_json}"); - (StatusCode::OK, Html("Credential received and verified!")) - }) - .map_err(|err| { - info!("Credential verification...error ❌:\n{err}"); - err - }) + match verification_info.presentation_or_credential { + PresentationOrCredential::Presentation(ref presentation) => { + TrustchainVerifierHTTPHandler::verify_presentation( + presentation, + verification_info.root_event_time, + &app_state.verifier, + ) + .await + .map(|_| { + info!("Presentation verification...ok ✅:\n{verification_info_json}"); + (StatusCode::OK, Html("Presentation received and verified!")) + }) + .map_err(|err| { + info!("Presentation verification...error ❌:\n{err}"); + err + }) + } + PresentationOrCredential::Credential(ref credential) => { + TrustchainVerifierHTTPHandler::verify_credential( + credential, + verification_info.root_event_time, + &app_state.verifier, + ) + .await + .map(|_| { + info!("Credential verification...ok ✅:\n{verification_info_json}"); + (StatusCode::OK, Html("Credential received and verified!")) + }) + .map_err(|err| { + info!("Credential verification...error ❌:\n{err}"); + err + }) + } + } } + /// Generates a QR code for receiving requests, default to first request in cache pub async fn get_verifier_qrcode(State(app_state): State>) -> impl IntoResponse { app_state @@ -169,40 +207,94 @@ mod tests { } "#; - const TEST_POST_VERIFIER: &str = r#" + const TEST_POST_VERIFIER_CREDENTIAL: &str = r#" + { + "presentationOrCredential": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "id": "urn:uuid:46cb84e2-fa10-11ed-a0d4-bbb4e61d1556", + "type": ["VerifiableCredential"], + "credentialSubject": { + "id": "did:example:284b3f34fad911ed9aea439566dd422a", + "familyName": "Bloggs", + "degree": { + "college": "University of Oxbridge", + "name": "Bachelor of Arts", + "type": "BachelorDegree" + }, + "givenName": "Jane" + }, + "issuer": "did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q", + "issuanceDate": "2023-08-08T08:59:21.458576Z", + "proof": { + "type": "EcdsaSecp256k1Signature2019", + "proofPurpose": "assertionMethod", + "verificationMethod": "did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q#ePyXsaNza8buW6gNXaoGZ07LMTxgLC9K7cbaIjIizTI", + "created": "2023-08-08T08:59:21.461Z", + "jws": "eyJhbGciOiJFUzI1NksiLCJjcml0IjpbImI2NCJdLCJiNjQiOmZhbHNlfQ..LqLHztj2djQ9aWDGFjm3ZaOzDFIVKnOyZQVvE7CMDbYV5POYz6IejwnRkcqRf7uPYc2QbJAqCjj20PfwTOPJEw" + } + } + }, + "rootEventTime": 1666265405 + } + "#; + + const TEST_POST_VERIFIER_PRESENTATION: &str = r#" { - "credential": { - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://www.w3.org/2018/credentials/examples/v1" - ], - "id": "urn:uuid:46cb84e2-fa10-11ed-a0d4-bbb4e61d1556", - "type": ["VerifiableCredential"], - "credentialSubject": { - "id": "did:example:284b3f34fad911ed9aea439566dd422a", - "familyName": "Bloggs", - "degree": { - "college": "University of Oxbridge", - "name": "Bachelor of Arts", - "type": "BachelorDegree" + "presentationOrCredential": { + "presentation": { + "@context": ["https://www.w3.org/2018/credentials/v1"], + "type": "VerifiablePresentation", + "verifiableCredential": [ + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1", + "https://w3id.org/citizenship/v1" + ], + "type": ["VerifiableCredential"], + "credentialSubject": { + "familyName": "Doe", + "givenName": "Jane", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science and Arts", + "college": "College of Engineering" + } + }, + "issuer": "did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q", + "issuanceDate": "2023-09-06T12:15:08.630033Z", + "proof": { + "type": "EcdsaSecp256k1Signature2019", + "proofPurpose": "assertionMethod", + "verificationMethod": "did:ion:test:EiBVpjUxXeSRJpvj2TewlX9zNF3GKMCKWwGmKBZqF6pk_A#kjqrr3CTkmlzJZVo0uukxNs8vrK5OEsk_OcoBO4SeMQ", + "created": "2023-09-08T07:50:31.529Z", + "jws": "eyJhbGciOiJFUzI1NksiLCJjcml0IjpbImI2NCJdLCJiNjQiOmZhbHNlfQ..AOodNoJ20UJtVK1UFsMXxr2kVpurIGjLCvTmwZKs_ahVO9GWPH05ZpM14VLanCK33K0AR6mlSna5y7DwfojDEw" + }, + "credentialSchema": { + "id": "did:example:cdf:35LB7w9ueWbagPL94T9bMLtyXDj9pX5o", + "type": "did:example:schema:22KpkXgecryx9k7N6XN1QoN3gXwBkSU8SfyyYQG" + }, + "image": "some_base64_representation" + } + ], + "proof": { + "type": "EcdsaSecp256k1Signature2019", + "proofPurpose": "authentication", + "verificationMethod": "did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q#ePyXsaNza8buW6gNXaoGZ07LMTxgLC9K7cbaIjIizTI", + "created": "2023-09-08T07:50:31.619Z", + "jws": "eyJhbGciOiJFUzI1NksiLCJjcml0IjpbImI2NCJdLCJiNjQiOmZhbHNlfQ..tXGzMYY9jdyK_fy-h99XbmUNM-V3LOtNgP_0LfhVPHBHH57TKzqAv7AWPUl4Jhqvc1L3RrvJcdwyHnZnubccvg" }, - "givenName": "Jane" - }, - "issuer": "did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q", - "issuanceDate": "2023-08-08T08:59:21.458576Z", - "proof": { - "type": "EcdsaSecp256k1Signature2019", - "proofPurpose": "assertionMethod", - "verificationMethod": "did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q#ePyXsaNza8buW6gNXaoGZ07LMTxgLC9K7cbaIjIizTI", - "created": "2023-08-08T08:59:21.461Z", - "jws": "eyJhbGciOiJFUzI1NksiLCJjcml0IjpbImI2NCJdLCJiNjQiOmZhbHNlfQ..LqLHztj2djQ9aWDGFjm3ZaOzDFIVKnOyZQVvE7CMDbYV5POYz6IejwnRkcqRf7uPYc2QbJAqCjj20PfwTOPJEw" + "holder": "did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q" } }, "rootEventTime": 1666265405 } "#; - // TODO: complete tests as part of verifier completion (#56) // Verifier integration tests #[tokio::test] #[ignore = "integration test requires ION, MongoDB, IPFS and Bitcoin RPC"] @@ -253,20 +345,56 @@ mod tests { let uid = "b9519df2-35c1-11ee-8314-7f66e4585b4f"; let uri = format!("/vc/verifier/{uid}"); let client = TestClient::new(app); - let post_verifier: PostVerifier = serde_json::from_str(TEST_POST_VERIFIER).unwrap(); + let post_verifier: PostVerifier = + serde_json::from_str(TEST_POST_VERIFIER_CREDENTIAL).unwrap(); let response = client.post(&uri).json(&post_verifier).send().await; assert_eq!(response.status(), StatusCode::OK); - assert_eq!("Presentation successfully proved!", response.text().await); + assert_eq!("Credential received and verified!", response.text().await); // Test post of credential to verifier with bad root event time let app = TrustchainRouter::from(state.clone()).into_router(); let uid = "b9519df2-35c1-11ee-8314-7f66e4585b4f"; let uri = format!("/vc/verifier/{uid}"); let client = TestClient::new(app); - let mut post_verifier: PostVerifier = serde_json::from_str(TEST_POST_VERIFIER).unwrap(); + let mut post_verifier: PostVerifier = + serde_json::from_str(TEST_POST_VERIFIER_CREDENTIAL).unwrap(); + post_verifier.root_event_time = 1666265406; + let response = client.post(&uri).json(&post_verifier).send().await; + assert_eq!(response.status(), StatusCode::OK); + // TODO: consider refining error returned + assert_eq!(response.text().await, r#"{"error":"Trustchain Verifier error: A commitment error during verification: Failed content verification. Expected data 1666265406 not found in candidate: 1666265405."}"#.to_string()); + } + + #[tokio::test] + #[ignore = "integration test requires ION, MongoDB, IPFS and Bitcoin RPC"] + async fn test_post_verifier_presentation() { + let state = Arc::new(AppState::new_with_cache( + TEST_HTTP_CONFIG.to_owned(), + HashMap::new(), + serde_json::from_str(REQUESTS).unwrap(), + )); + // Test post of presentation to verifier + let app = TrustchainRouter::from(state.clone()).into_router(); + let uid = "b9519df2-35c1-11ee-8314-7f66e4585b4f"; + let uri = format!("/vc/verifier/{uid}"); + let client = TestClient::new(app); + let post_verifier: PostVerifier = + serde_json::from_str(TEST_POST_VERIFIER_PRESENTATION).unwrap(); + let response = client.post(&uri).json(&post_verifier).send().await; + assert_eq!(response.status(), StatusCode::OK); + assert_eq!("Presentation received and verified!", response.text().await); + + // Test post of presentation to verifier with bad root event time + let app = TrustchainRouter::from(state.clone()).into_router(); + let uid = "b9519df2-35c1-11ee-8314-7f66e4585b4f"; + let uri = format!("/vc/verifier/{uid}"); + let client = TestClient::new(app); + let mut post_verifier: PostVerifier = + serde_json::from_str(TEST_POST_VERIFIER_PRESENTATION).unwrap(); post_verifier.root_event_time = 1666265406; let response = client.post(&uri).json(&post_verifier).send().await; assert_eq!(response.status(), StatusCode::OK); - assert_eq!(response.text().await, r#"{"error":"Trustchain Verifier error: Invalid root DID: did:ion:test:EiCClfEdkTv_aM3UnBBhlOV89LlGhpQAbfeZLFdFxVFkEg."}"#.to_string()); + // TODO: consider refining error returned + assert_eq!(response.text().await, r#"{"error":"Trustchain presentation error: A wrapped Credential error: A wrapped Verifier error: A commitment error during verification: Failed content verification. Expected data 1666265406 not found in candidate: 1666265405."}"#.to_string()); } } From dae595e6a24d5a367dae982d6c11ec11833da122 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Sat, 9 Sep 2023 13:59:40 +0100 Subject: [PATCH 42/87] Improve error handling for FFI presenation issuing --- trustchain-core/src/vp.rs | 9 +++++++++ trustchain-ffi/src/mobile.rs | 25 +++++++++++++++++-------- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/trustchain-core/src/vp.rs b/trustchain-core/src/vp.rs index 8b3861bf..941ffdae 100644 --- a/trustchain-core/src/vp.rs +++ b/trustchain-core/src/vp.rs @@ -21,6 +21,9 @@ pub enum PresentationError { /// Wrapped variant for Verifier Error. #[error("A wrapped Verifier error: {0}")] VerifierError(VerifierError), + /// Wrapped variant for SSI Error. + #[error("A wrapped SSI error: {0}")] + SSIError(ssi::error::Error), /// Credentials verified, but holder failed to authenticate with invalid or missing presentation /// proof. #[error("Credentials verified for an unauthenticated holder: {0:?}")] @@ -44,3 +47,9 @@ impl From for PresentationError { PresentationError::VerifierError(err) } } + +impl From for PresentationError { + fn from(err: ssi::error::Error) -> Self { + PresentationError::SSIError(err) + } +} diff --git a/trustchain-ffi/src/mobile.rs b/trustchain-ffi/src/mobile.rs index 665514f3..142ad010 100644 --- a/trustchain-ffi/src/mobile.rs +++ b/trustchain-ffi/src/mobile.rs @@ -14,7 +14,9 @@ use trustchain_api::{ api::{TrustchainDIDAPI, TrustchainVCAPI}, TrustchainAPI, }; -use trustchain_core::{resolver::ResolverError, vc::CredentialError, verifier::VerifierError}; +use trustchain_core::{ + resolver::ResolverError, vc::CredentialError, verifier::VerifierError, vp::PresentationError, +}; use trustchain_ion::{get_ion_resolver, verifier::IONVerifier}; /// A speicfic error for FFI mobile making handling easier. @@ -36,6 +38,8 @@ enum FFIMobileError { FailedToVerifyCredential(CredentialError), #[error("Credential proof created time ({0}) is in the future relative to now ({1}).")] FutureProofCreatedTime(DateTime, DateTime), + #[error("Failed to issue presentation error: {0}.")] + FailedToIssuePresentation(PresentationError), } /// Example greet function. @@ -144,17 +148,22 @@ pub fn vp_issue_presentation( proof_purpose: Some(ssi::vc::ProofPurpose::Authentication), ..Default::default() }); - let mut presentation: Presentation = serde_json::from_str(&presentation)?; + let mut presentation: Presentation = + serde_json::from_str(&presentation).map_err(FFIMobileError::FailedToDeserialize)?; let jwk: JWK = serde_json::from_str(&jwk_json)?; let resolver = get_ion_resolver(&endpoint_opts.ion_endpoint().to_address()); let rt = Runtime::new().unwrap(); - let proof = rt.block_on(async { - presentation - .generate_proof(&jwk, &ldp_opts, &resolver) - .await - })?; + let proof = rt + .block_on(async { + presentation + .generate_proof(&jwk, &ldp_opts, &resolver) + .await + }) + .map_err(|err| { + FFIMobileError::FailedToIssuePresentation(PresentationError::SSIError(err)) + })?; presentation.add_proof(proof); - Ok(serde_json::to_string_pretty(&presentation)?) + Ok(serde_json::to_string_pretty(&presentation).map_err(FFIMobileError::FailedToSerialize)?) } // // TODO: implement once verifiable presentations are included in API From 5d6728c15d4789761652ab6f9bb300409e75896f Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Mon, 11 Sep 2023 11:02:08 +0100 Subject: [PATCH 43/87] Add did:key resolution temporarily in resolver --- trustchain-core/Cargo.toml | 1 + trustchain-core/src/resolver.rs | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/trustchain-core/Cargo.toml b/trustchain-core/Cargo.toml index 8c5f6333..46aac53e 100644 --- a/trustchain-core/Cargo.toml +++ b/trustchain-core/Cargo.toml @@ -10,6 +10,7 @@ async-trait = "0.1" base64 = "0.13" canonical_json = "0.4.0" chrono = "0.4" +did-method-key = "0.1.3" futures = "0.3.21" petgraph = {version = "0.6"} serde = { version = "1.0", features = ["derive"] } diff --git a/trustchain-core/src/resolver.rs b/trustchain-core/src/resolver.rs index 8544e183..a49f55ba 100644 --- a/trustchain-core/src/resolver.rs +++ b/trustchain-core/src/resolver.rs @@ -1,5 +1,6 @@ //! DID resolution and `DIDResolver` implementation. use async_trait::async_trait; +use did_method_key::DIDKey; use serde_json::Value; use ssi::did::{DIDMethod, Document, Service, ServiceEndpoint}; use ssi::did_resolve::{ @@ -108,6 +109,13 @@ impl DIDResolver for Resolver { Option, Option, ) { + // TODO: remove upon handling with DIDMethods + if did.starts_with("did:key:") { + let did_key_resolver = DIDKey; + return did_key_resolver + .resolve(did, &ResolutionInputMetadata::default()) + .await; + } // Consider using ResolutionInputMetadata to optionally not perform transform. // Resolve with the wrapped DIDResolver and then transform to Trustchain format. self.transform(self.wrapped_resolver.resolve(did, input_metadata).await) From 5592bd8531e7038c41ca9230294ecedfc1b3a8ef Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Mon, 11 Sep 2023 11:04:58 +0100 Subject: [PATCH 44/87] Fix mobile bridge FFI --- trustchain-ffi/src/mobile_bridge.io.rs | 10 ++++++++++ trustchain-ffi/src/mobile_bridge.rs | 20 ++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/trustchain-ffi/src/mobile_bridge.io.rs b/trustchain-ffi/src/mobile_bridge.io.rs index 100321d7..0eb55178 100644 --- a/trustchain-ffi/src/mobile_bridge.io.rs +++ b/trustchain-ffi/src/mobile_bridge.io.rs @@ -33,6 +33,16 @@ pub extern "C" fn wire_vc_verify_credential( wire_vc_verify_credential_impl(port_, credential, opts) } +#[no_mangle] +pub extern "C" fn wire_vp_issue_presentation( + port_: i64, + presentation: *mut wire_uint_8_list, + opts: *mut wire_uint_8_list, + jwk_json: *mut wire_uint_8_list, +) { + wire_vp_issue_presentation_impl(port_, presentation, opts, jwk_json) +} + // Section: allocate functions #[no_mangle] diff --git a/trustchain-ffi/src/mobile_bridge.rs b/trustchain-ffi/src/mobile_bridge.rs index 418b8e27..f18a179f 100644 --- a/trustchain-ffi/src/mobile_bridge.rs +++ b/trustchain-ffi/src/mobile_bridge.rs @@ -85,6 +85,26 @@ fn wire_vc_verify_credential_impl( }, ) } +fn wire_vp_issue_presentation_impl( + port_: MessagePort, + presentation: impl Wire2Api + UnwindSafe, + opts: impl Wire2Api + UnwindSafe, + jwk_json: impl Wire2Api + UnwindSafe, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap( + WrapInfo { + debug_name: "vp_issue_presentation", + port: Some(port_), + mode: FfiCallMode::Normal, + }, + move || { + let api_presentation = presentation.wire2api(); + let api_opts = opts.wire2api(); + let api_jwk_json = jwk_json.wire2api(); + move |task_callback| vp_issue_presentation(api_presentation, api_opts, api_jwk_json) + }, + ) +} // Section: wrapper structs // Section: static checks From d1ab8415cbeed8f8449d58b72ac695c103642aaa Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Mon, 11 Sep 2023 11:05:48 +0100 Subject: [PATCH 45/87] Add additional vp_issue_presentation test with did:key --- trustchain-ffi/src/mobile.rs | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/trustchain-ffi/src/mobile.rs b/trustchain-ffi/src/mobile.rs index 142ad010..ed6fb818 100644 --- a/trustchain-ffi/src/mobile.rs +++ b/trustchain-ffi/src/mobile.rs @@ -174,6 +174,8 @@ pub fn vp_issue_presentation( #[cfg(test)] mod tests { + use ssi::vc::CredentialOrJWT; + use crate::config::parse_toml; use super::*; @@ -247,13 +249,41 @@ mod tests { fn test_vp_issue_presentation() { let ffi_opts = serde_json::to_string(&parse_toml(TEST_FFI_CONFIG)).unwrap(); let credential: Credential = serde_json::from_str(TEST_CREDENTIAL).unwrap(); + let root_plus_1_did: &str = "did:ion:test:EiBVpjUxXeSRJpvj2TewlX9zNF3GKMCKWwGmKBZqF6pk_A"; + let presentation: Presentation = Presentation { + verifiable_credential: Some(OneOrMany::One(CredentialOrJWT::Credential(credential))), + holder: Some(ssi::vc::URI::String(root_plus_1_did.to_string())), + ..Default::default() + }; let root_plus_1_signing_key: &str = r#"{"kty":"EC","crv":"secp256k1","x":"aApKobPO8H8wOv-oGT8K3Na-8l-B1AE3uBZrWGT6FJU","y":"dspEqltAtlTKJ7cVRP_gMMknyDPqUw-JHlpwS2mFuh0","d":"HbjLQf4tnwJR6861-91oGpERu8vmxDpW8ZroDCkmFvY"}"#; let presentation = vp_issue_presentation( - serde_json::to_string(&credential).unwrap(), + serde_json::to_string(&presentation).unwrap(), ffi_opts, root_plus_1_signing_key.to_string(), ); - assert!(presentation.is_ok()); + println!("{}", presentation.unwrap()); + // assert!(presentation.is_ok()); + } + + #[test] + #[ignore = "integration test requires ION, MongoDB, IPFS and Bitcoin RPC"] + fn test_vp_issue_presentation_ed25519() { + let ffi_opts = serde_json::to_string(&parse_toml(TEST_FFI_CONFIG)).unwrap(); + let credential: Credential = serde_json::from_str(TEST_CREDENTIAL).unwrap(); + let did: &str = "did:key:z6MkhG98a8j2d3jqia13vrWqzHwHAgKTv9NjYEgdV3ndbEdD"; + let key: &str = r#"{"kty":"OKP","crv":"Ed25519","x":"Kbnao1EkojaLeZ135PuIf28opnQybD0lB-_CQxuvSDg","d":"vwJwnuhHd4J0UUvjfYr8YxYwvNLU_GVkdqEbC3sUtAY"}"#; + let presentation: Presentation = Presentation { + verifiable_credential: Some(OneOrMany::One(CredentialOrJWT::Credential(credential))), + holder: Some(ssi::vc::URI::String(did.to_string())), + ..Default::default() + }; + + let presentation = vp_issue_presentation( + serde_json::to_string(&presentation).unwrap(), + ffi_opts, + key.to_string(), + ); + println!("{}", presentation.unwrap()); } // // TODO: implement once verifiable presentations are included in API From 9f123180ef689fbb87ff8c75621ba3a6346f51bc Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Mon, 11 Sep 2023 11:08:12 +0100 Subject: [PATCH 46/87] Add did:key resolution temporarily in resolver --- trustchain-core/Cargo.toml | 1 + trustchain-core/src/resolver.rs | 8 ++++++++ trustchain-http/src/middleware.rs | 10 ++++++++-- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/trustchain-core/Cargo.toml b/trustchain-core/Cargo.toml index 8c5f6333..46aac53e 100644 --- a/trustchain-core/Cargo.toml +++ b/trustchain-core/Cargo.toml @@ -10,6 +10,7 @@ async-trait = "0.1" base64 = "0.13" canonical_json = "0.4.0" chrono = "0.4" +did-method-key = "0.1.3" futures = "0.3.21" petgraph = {version = "0.6"} serde = { version = "1.0", features = ["derive"] } diff --git a/trustchain-core/src/resolver.rs b/trustchain-core/src/resolver.rs index 8544e183..a49f55ba 100644 --- a/trustchain-core/src/resolver.rs +++ b/trustchain-core/src/resolver.rs @@ -1,5 +1,6 @@ //! DID resolution and `DIDResolver` implementation. use async_trait::async_trait; +use did_method_key::DIDKey; use serde_json::Value; use ssi::did::{DIDMethod, Document, Service, ServiceEndpoint}; use ssi::did_resolve::{ @@ -108,6 +109,13 @@ impl DIDResolver for Resolver { Option, Option, ) { + // TODO: remove upon handling with DIDMethods + if did.starts_with("did:key:") { + let did_key_resolver = DIDKey; + return did_key_resolver + .resolve(did, &ResolutionInputMetadata::default()) + .await; + } // Consider using ResolutionInputMetadata to optionally not perform transform. // Resolve with the wrapped DIDResolver and then transform to Trustchain format. self.transform(self.wrapped_resolver.resolve(did, input_metadata).await) diff --git a/trustchain-http/src/middleware.rs b/trustchain-http/src/middleware.rs index 5400f36f..45828552 100644 --- a/trustchain-http/src/middleware.rs +++ b/trustchain-http/src/middleware.rs @@ -19,7 +19,10 @@ pub async fn validate_did( ) -> Result { tracing::info!(did); // Validate length is 59 (testnet) or 54 (mainnet) - if ion_config().mongo_database_ion_core.contains("testnet") && did.len() != 59 { + if ion_config().mongo_database_ion_core.contains("testnet") + && did.len() != 59 + && did.starts_with("did:ion") + { let message = json!({ "error": format!( @@ -29,7 +32,10 @@ pub async fn validate_did( ) }); return Err((StatusCode::BAD_REQUEST, Json(message))); - } else if ion_config().mongo_database_ion_core.contains("mainnet") && did.len() != 54 { + } else if ion_config().mongo_database_ion_core.contains("mainnet") + && did.len() != 54 + && did.starts_with("did:ion") + { let message = json!({ "error": format!( From 1965b326095df7edeb5cfe56ad64fa2ac405059a Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Tue, 19 Sep 2023 14:44:57 +0100 Subject: [PATCH 47/87] Add drop down menu for credential offer selection --- trustchain-http/src/issuer.rs | 14 ++++++++++---- trustchain-http/static/index.html | 5 +++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/trustchain-http/src/issuer.rs b/trustchain-http/src/issuer.rs index 583fc557..69c9f698 100644 --- a/trustchain-http/src/issuer.rs +++ b/trustchain-http/src/issuer.rs @@ -6,7 +6,7 @@ use async_trait::async_trait; use axum::extract::{Path, State}; use axum::http::StatusCode; use axum::response::{Html, IntoResponse}; -use axum::Json; +use axum::{Form, Json}; use chrono::Utc; use log::info; use serde::{Deserialize, Serialize}; @@ -14,6 +14,7 @@ use ssi::did_resolve::DIDResolver; use ssi::one_or_many::OneOrMany; use ssi::vc::Credential; use ssi::vc::VCDateTime; +use std::collections::HashMap; use std::sync::Arc; use trustchain_core::issuer::Issuer; use trustchain_core::resolver::Resolver; @@ -111,9 +112,14 @@ impl TrustchainIssuerHTTP for TrustchainIssuerHTTPHandler { impl TrustchainIssuerHTTPHandler { /// Generates QR code to display to holder to receive requested credential. - pub async fn get_issuer_qrcode(State(app_state): State>) -> Html { - // TODO: update to take query param entered by user. - let id = "7426a2e8-f932-11ed-968a-4bb02079f142".to_string(); + pub async fn get_issuer_qrcode( + State(app_state): State>, + Form(form): Form>, + ) -> Html { + let id = form + .get("uuid") + .expect("'uuid' key not in form.") + .to_owned(); let http_str = if !http_config().https { "http" } else { diff --git a/trustchain-http/static/index.html b/trustchain-http/static/index.html index 93238111..1b600a3c 100644 --- a/trustchain-http/static/index.html +++ b/trustchain-http/static/index.html @@ -7,6 +7,11 @@ Receive a credential:
+

From 5fb748e4cb79f5382356c10d16f6d5f7d87d6177 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Wed, 20 Sep 2023 16:25:34 +0100 Subject: [PATCH 48/87] Initial implementation with QR code with DID --- trustchain-http/Cargo.toml | 1 + trustchain-http/src/issuer.rs | 26 +++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/trustchain-http/Cargo.toml b/trustchain-http/Cargo.toml index e4d17e2f..8d234021 100644 --- a/trustchain-http/Cargo.toml +++ b/trustchain-http/Cargo.toml @@ -42,6 +42,7 @@ toml="0.7.2" tracing = "0.1" tracing-subscriber = "0.3" uuid = {version="1.2.2", features = ["v4", "fast-rng", "macro-diagnostics"]} +base64-url = "2.0.0" [dev-dependencies] axum-test-helper = "0.2.0" diff --git a/trustchain-http/src/issuer.rs b/trustchain-http/src/issuer.rs index 69c9f698..365a3b09 100644 --- a/trustchain-http/src/issuer.rs +++ b/trustchain-http/src/issuer.rs @@ -110,6 +110,15 @@ impl TrustchainIssuerHTTP for TrustchainIssuerHTTPHandler { } } +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct DIDQRCode { + did: String, + route: String, + uuid: String, + endpoint: String, +} + impl TrustchainIssuerHTTPHandler { /// Generates QR code to display to holder to receive requested credential. pub async fn get_issuer_qrcode( @@ -130,8 +139,23 @@ impl TrustchainIssuerHTTPHandler { "{}://{}:{}/vc/issuer/{id}", http_str, app_state.config.host_reference, app_state.config.port ); + + let did_qr_code_encoded = base64_url::encode( + &serde_json::to_string(&DIDQRCode { + did: app_state.config.issuer_did.as_ref().unwrap().to_owned(), + route: "/vc/issuer/".to_string(), + uuid: id, + endpoint: format!( + "{}://{}:{}", + http_str, app_state.config.host_reference, app_state.config.port + ), + }) + .unwrap(), + ); + // Respond with the QR code as a png embedded in html - Html(str_to_qr_code_html(&address_str, "Issuer")) + // Html(str_to_qr_code_html(&address_str, "Issuer")) + Html(str_to_qr_code_html(&did_qr_code_encoded, "Issuer")) } /// API endpoint taking the UUID of a VC. Response is the VC JSON. From 5af0850d14196915b03ae3787877a0f01dcdcc82 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Thu, 21 Sep 2023 16:24:20 +0100 Subject: [PATCH 49/87] Refactor issuer QR code --- trustchain-http/src/issuer.rs | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/trustchain-http/src/issuer.rs b/trustchain-http/src/issuer.rs index 365a3b09..53ec2912 100644 --- a/trustchain-http/src/issuer.rs +++ b/trustchain-http/src/issuer.rs @@ -134,27 +134,17 @@ impl TrustchainIssuerHTTPHandler { } else { "https" }; - // Generate a QR code for server address and combination of name and UUID - let address_str = format!( - "{}://{}:{}/vc/issuer/{id}", - http_str, app_state.config.host_reference, app_state.config.port - ); - - let did_qr_code_encoded = base64_url::encode( - &serde_json::to_string(&DIDQRCode { - did: app_state.config.issuer_did.as_ref().unwrap().to_owned(), - route: "/vc/issuer/".to_string(), - uuid: id, - endpoint: format!( - "{}://{}:{}", - http_str, app_state.config.host_reference, app_state.config.port - ), - }) - .unwrap(), - ); - + let did_qr_code_encoded = serde_json::to_string(&DIDQRCode { + did: app_state.config.issuer_did.as_ref().unwrap().to_owned(), + route: "/vc/issuer/".to_string(), + uuid: id, + endpoint: format!( + "{}://{}:{}", + http_str, app_state.config.host_reference, app_state.config.port + ), + }) + .unwrap(); // Respond with the QR code as a png embedded in html - // Html(str_to_qr_code_html(&address_str, "Issuer")) Html(str_to_qr_code_html(&did_qr_code_encoded, "Issuer")) } From d3de3db7f7e5bf5c3f37d492a427c951e018c156 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Thu, 21 Sep 2023 17:18:33 +0100 Subject: [PATCH 50/87] Add QR code with DID to verifier --- trustchain-http/src/issuer.rs | 11 +---------- trustchain-http/src/qrcode.rs | 11 +++++++++++ trustchain-http/src/verifier.rs | 18 ++++++++++++------ 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/trustchain-http/src/issuer.rs b/trustchain-http/src/issuer.rs index 53ec2912..5c29d29c 100644 --- a/trustchain-http/src/issuer.rs +++ b/trustchain-http/src/issuer.rs @@ -1,6 +1,6 @@ use crate::config::http_config; use crate::errors::TrustchainHTTPError; -use crate::qrcode::str_to_qr_code_html; +use crate::qrcode::{str_to_qr_code_html, DIDQRCode}; use crate::state::AppState; use async_trait::async_trait; use axum::extract::{Path, State}; @@ -110,15 +110,6 @@ impl TrustchainIssuerHTTP for TrustchainIssuerHTTPHandler { } } -#[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -struct DIDQRCode { - did: String, - route: String, - uuid: String, - endpoint: String, -} - impl TrustchainIssuerHTTPHandler { /// Generates QR code to display to holder to receive requested credential. pub async fn get_issuer_qrcode( diff --git a/trustchain-http/src/qrcode.rs b/trustchain-http/src/qrcode.rs index 46ca64dd..6560ed8f 100644 --- a/trustchain-http/src/qrcode.rs +++ b/trustchain-http/src/qrcode.rs @@ -3,6 +3,17 @@ use base64::write::EncoderWriter; use image::Luma; use image::{DynamicImage, ImageOutputFormat}; use qrcode::QrCode; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +/// QR code JSON type of verifiable content. +pub(crate) struct DIDQRCode { + pub did: String, + pub route: String, + pub uuid: String, + pub endpoint: String, +} pub fn image_to_base64_string(image: &DynamicImage) -> String { let mut buf = Vec::new(); diff --git a/trustchain-http/src/verifier.rs b/trustchain-http/src/verifier.rs index bb18a47a..b1bea23d 100644 --- a/trustchain-http/src/verifier.rs +++ b/trustchain-http/src/verifier.rs @@ -1,6 +1,6 @@ use crate::config::http_config; use crate::errors::TrustchainHTTPError; -use crate::qrcode::str_to_qr_code_html; +use crate::qrcode::{str_to_qr_code_html, DIDQRCode}; use crate::state::AppState; use async_trait::async_trait; use axum::extract::{Path, State}; @@ -151,13 +151,19 @@ impl TrustchainVerifierHTTPHandler { } else { "https" }; - let address_str = format!( - "{}://{}:{}/vc/verifier/{}", - http_str, app_state.config.host_reference, app_state.config.port, uid - ); + let did_qr_code_encoded = serde_json::to_string(&DIDQRCode { + did: app_state.config.issuer_did.as_ref().unwrap().to_owned(), + route: "/vc/verifier/".to_string(), + uuid: uid.to_owned(), + endpoint: format!( + "{}://{}:{}", + http_str, app_state.config.host_reference, app_state.config.port + ), + }) + .unwrap(); ( StatusCode::OK, - Html(str_to_qr_code_html(&address_str, "Verifier")), + Html(str_to_qr_code_html(&did_qr_code_encoded, "Verifier")), ) }) } From 4028688664df36b19929d9ec788b49be3bca535c Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Thu, 21 Sep 2023 17:22:22 +0100 Subject: [PATCH 51/87] Recover when requested uuid does not exist --- trustchain-http/src/issuer.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/trustchain-http/src/issuer.rs b/trustchain-http/src/issuer.rs index 5c29d29c..fc847b8d 100644 --- a/trustchain-http/src/issuer.rs +++ b/trustchain-http/src/issuer.rs @@ -115,11 +115,12 @@ impl TrustchainIssuerHTTPHandler { pub async fn get_issuer_qrcode( State(app_state): State>, Form(form): Form>, - ) -> Html { + ) -> Result, TrustchainHTTPError> { let id = form .get("uuid") - .expect("'uuid' key not in form.") + .ok_or(TrustchainHTTPError::CredentialDoesNotExist)? .to_owned(); + let http_str = if !http_config().https { "http" } else { @@ -136,7 +137,7 @@ impl TrustchainIssuerHTTPHandler { }) .unwrap(); // Respond with the QR code as a png embedded in html - Html(str_to_qr_code_html(&did_qr_code_encoded, "Issuer")) + Ok(Html(str_to_qr_code_html(&did_qr_code_encoded, "Issuer"))) } /// API endpoint taking the UUID of a VC. Response is the VC JSON. From 9fde83e0c7d4a9c6e7ae3b5b5f2cfd156225656d Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Mon, 25 Sep 2023 16:39:30 +0100 Subject: [PATCH 52/87] Add identifiers (alias of did) route for resolution --- trustchain-http/src/server.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/trustchain-http/src/server.rs b/trustchain-http/src/server.rs index 48ab7f61..23596235 100644 --- a/trustchain-http/src/server.rs +++ b/trustchain-http/src/server.rs @@ -67,6 +67,14 @@ impl TrustchainRouter { get(resolver::TrustchainHTTPHandler::get_did_resolution) .layer(ServiceBuilder::new().layer(middleware::from_fn(validate_did))), ) + // Duplicate `did` and `identifier` routes as the resolver expects a + // `SidetreeClient` that can resolve at route `/identifiers/`: + // See [here](https://docs.rs/did-ion/0.1.0/src/did_ion/sidetree.rs.html#1392-1400). + .route( + "/identifiers/:id", + get(resolver::TrustchainHTTPHandler::get_did_resolution) + .layer(ServiceBuilder::new().layer(middleware::from_fn(validate_did))), + ) .route( "/did/chain/:id", get(resolver::TrustchainHTTPHandler::get_chain_resolution), From dfd2be7a40dc5f0d6587074c00dbe118695a74a9 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Wed, 27 Sep 2023 15:23:47 +0100 Subject: [PATCH 53/87] Add conditional for endpoint host if http --- trustchain-ion/src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/trustchain-ion/src/lib.rs b/trustchain-ion/src/lib.rs index 246dc4dc..587d4aca 100644 --- a/trustchain-ion/src/lib.rs +++ b/trustchain-ion/src/lib.rs @@ -37,7 +37,11 @@ impl Endpoint { Self { host: url, port } } pub fn to_address(&self) -> String { - format!("http://{}:{}/", self.host, self.port) + if !self.host.starts_with("http") { + format!("http://{}:{}/", self.host, self.port) + } else { + format!("{}:{}/", self.host, self.port) + } } // TODO: add more flexible address methods } From 106fa0d9e052d924feaa984478e4f08bd710723d Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Wed, 27 Sep 2023 21:03:16 +0100 Subject: [PATCH 54/87] Remove unused dependency --- trustchain-http/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/trustchain-http/Cargo.toml b/trustchain-http/Cargo.toml index 8d234021..e4d17e2f 100644 --- a/trustchain-http/Cargo.toml +++ b/trustchain-http/Cargo.toml @@ -42,7 +42,6 @@ toml="0.7.2" tracing = "0.1" tracing-subscriber = "0.3" uuid = {version="1.2.2", features = ["v4", "fast-rng", "macro-diagnostics"]} -base64-url = "2.0.0" [dev-dependencies] axum-test-helper = "0.2.0" From 06ccac97a2bbfd8aa61b8d8e0ebc948509bd095a Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Wed, 27 Sep 2023 21:17:17 +0100 Subject: [PATCH 55/87] Use string for displayed host address --- trustchain-http/src/config.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/trustchain-http/src/config.rs b/trustchain-http/src/config.rs index a5837d44..c97c1b2d 100644 --- a/trustchain-http/src/config.rs +++ b/trustchain-http/src/config.rs @@ -14,10 +14,11 @@ const DEFAULT_PORT: u16 = 8081; /// Server config. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct HTTPConfig { - /// Hostname for server + /// Host address for server. For example, Android emulator `10.0.2.2` refers to `127.0.0.1` of + /// machine running emulator. pub host: IpAddr, - /// Hostname reference. For example, Android emulator 10.0.2.2 refers to 127.0.0.1 of machine running emulator. - pub host_reference: IpAddr, + /// Host address for front-end display. + pub host_reference: String, /// Port for server pub port: u16, /// Optional issuer DID @@ -38,7 +39,7 @@ impl Default for HTTPConfig { fn default() -> Self { Self { host: IpAddr::from_str(DEFAULT_HOST).unwrap(), - host_reference: IpAddr::from_str(DEFAULT_HOST).unwrap(), + host_reference: DEFAULT_HOST.into(), port: DEFAULT_PORT, issuer_did: None, https: false, From b78fbe7904c232019365b97212500aaa4efc129c Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Wed, 27 Sep 2023 21:18:48 +0100 Subject: [PATCH 56/87] Add conditional for endpoint host if http --- trustchain-ion/src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/trustchain-ion/src/lib.rs b/trustchain-ion/src/lib.rs index 246dc4dc..587d4aca 100644 --- a/trustchain-ion/src/lib.rs +++ b/trustchain-ion/src/lib.rs @@ -37,7 +37,11 @@ impl Endpoint { Self { host: url, port } } pub fn to_address(&self) -> String { - format!("http://{}:{}/", self.host, self.port) + if !self.host.starts_with("http") { + format!("http://{}:{}/", self.host, self.port) + } else { + format!("{}:{}/", self.host, self.port) + } } // TODO: add more flexible address methods } From 5012023295ad9f2a9a57cb14d1ed199a3d4e3e8e Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Thu, 28 Sep 2023 13:28:39 +0100 Subject: [PATCH 57/87] Add issue number --- trustchain-ion/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trustchain-ion/src/lib.rs b/trustchain-ion/src/lib.rs index 493d0266..65535042 100644 --- a/trustchain-ion/src/lib.rs +++ b/trustchain-ion/src/lib.rs @@ -20,7 +20,7 @@ use trustchain_core::resolver::{DIDMethodWrapper, Resolver}; pub type IONResolver = Resolver>>; /// Type alias for URL -// TODO: remove in favour of new type pattern (e.g. URL(String)) or use https://crates.io/crates/url +// TODO [#126]: remove in favour of new type pattern (e.g. URL(String)) or use https://crates.io/crates/url // for better handling of URLs. pub type URL = String; From 5468008a532190af37cc34bab605bc5e177f02a7 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Thu, 28 Sep 2023 13:56:03 +0100 Subject: [PATCH 58/87] Add FromStr implementation for FFIConfig, refactor option getters --- trustchain-ffi/src/config.rs | 30 +++++++++++++++++++++++------- trustchain-ffi/src/mobile.rs | 21 ++++++++++----------- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/trustchain-ffi/src/config.rs b/trustchain-ffi/src/config.rs index 0139c33c..c757b678 100644 --- a/trustchain-ffi/src/config.rs +++ b/trustchain-ffi/src/config.rs @@ -2,10 +2,12 @@ use anyhow::anyhow; use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; use ssi::vc::LinkedDataProofOptions; -use std::fs; +use std::{fs, str::FromStr}; use trustchain_core::TRUSTCHAIN_CONFIG; use trustchain_ion::{Endpoint, URL}; +use crate::mobile::FFIMobileError; + lazy_static! { /// Lazy static reference to ION configuration loaded from `trustchain_config.toml`. pub static ref FFI_CONFIG: FFIConfig = parse_toml( @@ -76,19 +78,33 @@ pub struct FFIConfig { impl FFIConfig { pub fn endpoint(&self) -> anyhow::Result<&EndpointOptions> { - self.endpoint_options + Ok(self + .endpoint_options .as_ref() - .ok_or_else(|| anyhow!("Expected endpoint options.")) + .ok_or(anyhow!("Expected endpoint options.")) + .map_err(FFIMobileError::NoConfig)?) } pub fn trustchain(&self) -> anyhow::Result<&TrustchainOptions> { - self.trustchain_options + Ok(self + .trustchain_options .as_ref() - .ok_or_else(|| anyhow!("Expected trustchain options.")) + .ok_or(anyhow!("Expected trustchain options.")) + .map_err(FFIMobileError::NoConfig)?) } pub fn linked_data_proof(&self) -> anyhow::Result<&LinkedDataProofOptions> { - self.linked_data_proof_options + Ok(self + .linked_data_proof_options .as_ref() - .ok_or_else(|| anyhow!("Expected linked data proof options.")) + .ok_or(anyhow!("Expected linked data proof options.")) + .map_err(FFIMobileError::NoConfig)?) + } +} + +impl FromStr for FFIConfig { + type Err = FFIMobileError; + + fn from_str(s: &str) -> Result { + serde_json::from_str(s).map_err(FFIMobileError::FailedToDeserialize) } } diff --git a/trustchain-ffi/src/mobile.rs b/trustchain-ffi/src/mobile.rs index ed6fb818..6b32defb 100644 --- a/trustchain-ffi/src/mobile.rs +++ b/trustchain-ffi/src/mobile.rs @@ -1,4 +1,4 @@ -// TODO: add module doc comments for mobile FFI +//! Mobile FFI. use crate::config::FFIConfig; use anyhow::Result; use chrono::{DateTime, Utc}; @@ -21,7 +21,7 @@ use trustchain_ion::{get_ion_resolver, verifier::IONVerifier}; /// A speicfic error for FFI mobile making handling easier. #[derive(Error, Debug)] -enum FFIMobileError { +pub enum FFIMobileError { /// Failed to deserialize JSON. #[error("JSON Deserialization Error: {0}.")] FailedToDeserialize(serde_json::Error), @@ -49,9 +49,8 @@ pub fn greet() -> String { /// Resolves a given DID document returning the serialized DID document as a JSON string. pub fn did_resolve(did: String, opts: String) -> Result { - let mobile_opts: FFIConfig = - serde_json::from_str(&opts).map_err(FFIMobileError::FailedToDeserialize)?; - let endpoint_opts = mobile_opts.endpoint().map_err(FFIMobileError::NoConfig)?; + let mobile_opts: FFIConfig = opts.parse()?; + let endpoint_opts = mobile_opts.endpoint()?; let resolver = get_ion_resolver(&endpoint_opts.ion_endpoint().to_address()); let rt = Runtime::new().unwrap(); rt.block_on(async { @@ -63,12 +62,12 @@ pub fn did_resolve(did: String, opts: String) -> Result { })?) }) } + /// Verifies a given DID returning the serialized DIDChain as a JSON string. pub fn did_verify(did: String, opts: String) -> Result { - let mobile_opts: FFIConfig = - serde_json::from_str(&opts).map_err(FFIMobileError::FailedToDeserialize)?; - let endpoint_opts = mobile_opts.endpoint().map_err(FFIMobileError::NoConfig)?; - let trustchain_opts = mobile_opts.trustchain().map_err(FFIMobileError::NoConfig)?; + let mobile_opts: FFIConfig = opts.parse()?; + let endpoint_opts = mobile_opts.endpoint()?; + let trustchain_opts = mobile_opts.trustchain()?; let root_event_time = trustchain_opts.root_event_time; let rt = Runtime::new().unwrap(); rt.block_on(async { @@ -87,7 +86,7 @@ pub fn did_verify(did: String, opts: String) -> Result { /// Verifies a verifiable credential returning the serialized DIDChain as a JSON string. pub fn vc_verify_credential(credential: String, opts: String) -> Result { - let mobile_opts: FFIConfig = serde_json::from_str(&opts)?; + let mobile_opts: FFIConfig = opts.parse()?; let endpoint_opts = mobile_opts.endpoint()?; let trustchain_opts = mobile_opts.trustchain()?; let ldp_opts = mobile_opts.linked_data_proof().cloned().ok(); @@ -137,7 +136,7 @@ pub fn vp_issue_presentation( opts: String, jwk_json: String, ) -> Result { - let mobile_opts: FFIConfig = serde_json::from_str(&opts)?; + let mobile_opts: FFIConfig = opts.parse()?; let endpoint_opts = mobile_opts.endpoint()?; let ldp_opts = mobile_opts From 524a29f697f927b13149e57c1e6b0626a9e77fc3 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Thu, 28 Sep 2023 14:17:15 +0100 Subject: [PATCH 59/87] Add README for FFI crate --- trustchain-ffi/README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 trustchain-ffi/README.md diff --git a/trustchain-ffi/README.md b/trustchain-ffi/README.md new file mode 100644 index 00000000..e69de29b From affd385acec7374f83c5e6feb18a3de1498ccf68 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Thu, 28 Sep 2023 14:29:26 +0100 Subject: [PATCH 60/87] Update README --- trustchain-ffi/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/trustchain-ffi/README.md b/trustchain-ffi/README.md index e69de29b..68592b05 100644 --- a/trustchain-ffi/README.md +++ b/trustchain-ffi/README.md @@ -0,0 +1,3 @@ +# Trustchain FFI + +FFI via [`flutter_rust_bridge`](https://cjycode.com/flutter_rust_bridge/index.html). Example usage can be found in [`trustchain-mobile`](https://github.com/alan-turing-institute/trustchain-mobile/blob/dev/install_trustchain_mobile.md#9-build-trustchain-targets). \ No newline at end of file From 4059a72fd69ce2c6549cc35a97ea409ac36da2a4 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Thu, 28 Sep 2023 14:29:38 +0100 Subject: [PATCH 61/87] clippy --- trustchain-ffi/src/config.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/trustchain-ffi/src/config.rs b/trustchain-ffi/src/config.rs index c757b678..a63bef46 100644 --- a/trustchain-ffi/src/config.rs +++ b/trustchain-ffi/src/config.rs @@ -186,9 +186,9 @@ mod tests { } #[test] fn test_ffi_options_from_toml() { - println!("{:?}", parse_toml(&TEST_FFI_OPTIONS)); + println!("{:?}", parse_toml(TEST_FFI_OPTIONS)); assert_eq!( - parse_toml(&TEST_FFI_OPTIONS) + parse_toml(TEST_FFI_OPTIONS) .endpoint() .unwrap() .trustchain_endpoint() @@ -197,7 +197,7 @@ mod tests { 8081 ); assert_eq!( - parse_toml(&TEST_FFI_OPTIONS) + parse_toml(TEST_FFI_OPTIONS) .linked_data_proof() .unwrap() .proof_purpose From ec6e4e79e9b1a679cb9d769dba2bda3a4160101f Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Thu, 28 Sep 2023 15:00:37 +0100 Subject: [PATCH 62/87] Remove obsolete bridge modules --- trustchain-ion/src/bridge_generated.io.rs | 72 ------------------- trustchain-ion/src/bridge_generated.rs | 88 ----------------------- 2 files changed, 160 deletions(-) delete mode 100644 trustchain-ion/src/bridge_generated.io.rs delete mode 100644 trustchain-ion/src/bridge_generated.rs diff --git a/trustchain-ion/src/bridge_generated.io.rs b/trustchain-ion/src/bridge_generated.io.rs deleted file mode 100644 index de86cfac..00000000 --- a/trustchain-ion/src/bridge_generated.io.rs +++ /dev/null @@ -1,72 +0,0 @@ -use super::*; -// Section: wire functions - -#[no_mangle] -pub extern "C" fn wire_greet(port_: i64) { - wire_greet_impl(port_) -} - -#[no_mangle] -pub extern "C" fn wire_resolve(port_: i64, did: *mut wire_uint_8_list) { - wire_resolve_impl(port_, did) -} - -// Section: allocate functions - -#[no_mangle] -pub extern "C" fn new_uint_8_list_0(len: i32) -> *mut wire_uint_8_list { - let ans = wire_uint_8_list { - ptr: support::new_leak_vec_ptr(Default::default(), len), - len, - }; - support::new_leak_box_ptr(ans) -} - -// Section: related functions - -// Section: impl Wire2Api - -impl Wire2Api for *mut wire_uint_8_list { - fn wire2api(self) -> String { - let vec: Vec = self.wire2api(); - String::from_utf8_lossy(&vec).into_owned() - } -} - -impl Wire2Api> for *mut wire_uint_8_list { - fn wire2api(self) -> Vec { - unsafe { - let wrap = support::box_from_leak_ptr(self); - support::vec_from_leak_ptr(wrap.ptr, wrap.len) - } - } -} -// Section: wire structs - -#[repr(C)] -#[derive(Clone)] -pub struct wire_uint_8_list { - ptr: *mut u8, - len: i32, -} - -// Section: impl NewWithNullPtr - -pub trait NewWithNullPtr { - fn new_with_null_ptr() -> Self; -} - -impl NewWithNullPtr for *mut T { - fn new_with_null_ptr() -> Self { - std::ptr::null_mut() - } -} - -// Section: sync execution mode utility - -#[no_mangle] -pub extern "C" fn free_WireSyncReturn(ptr: support::WireSyncReturn) { - unsafe { - let _ = support::box_from_leak_ptr(ptr); - }; -} diff --git a/trustchain-ion/src/bridge_generated.rs b/trustchain-ion/src/bridge_generated.rs deleted file mode 100644 index febb3c79..00000000 --- a/trustchain-ion/src/bridge_generated.rs +++ /dev/null @@ -1,88 +0,0 @@ -#![allow( - non_camel_case_types, - unused, - clippy::redundant_closure, - clippy::useless_conversion, - clippy::unit_arg, - clippy::double_parens, - non_snake_case, - clippy::too_many_arguments -)] -// AUTO GENERATED FILE, DO NOT EDIT. -// Generated by `flutter_rust_bridge`@ 1.62.1. - -use crate::api::*; -use core::panic::UnwindSafe; -use flutter_rust_bridge::*; -use std::ffi::c_void; -use std::sync::Arc; - -// Section: imports - -// Section: wire functions - -fn wire_greet_impl(port_: MessagePort) { - FLUTTER_RUST_BRIDGE_HANDLER.wrap( - WrapInfo { - debug_name: "greet", - port: Some(port_), - mode: FfiCallMode::Normal, - }, - move || move |task_callback| Ok(greet()), - ) -} -fn wire_resolve_impl(port_: MessagePort, did: impl Wire2Api + UnwindSafe) { - FLUTTER_RUST_BRIDGE_HANDLER.wrap( - WrapInfo { - debug_name: "resolve", - port: Some(port_), - mode: FfiCallMode::Normal, - }, - move || { - let api_did = did.wire2api(); - move |task_callback| Ok(resolve(api_did)) - }, - ) -} -// Section: wrapper structs - -// Section: static checks - -// Section: allocate functions - -// Section: related functions - -// Section: impl Wire2Api - -pub trait Wire2Api { - fn wire2api(self) -> T; -} - -impl Wire2Api> for *mut S -where - *mut S: Wire2Api, -{ - fn wire2api(self) -> Option { - (!self.is_null()).then(|| self.wire2api()) - } -} - -impl Wire2Api for u8 { - fn wire2api(self) -> u8 { - self - } -} - -// Section: impl IntoDart - -// Section: executor - -support::lazy_static! { - pub static ref FLUTTER_RUST_BRIDGE_HANDLER: support::DefaultHandler = Default::default(); -} - -#[cfg(not(target_family = "wasm"))] -#[path = "bridge_generated.io.rs"] -mod io; -#[cfg(not(target_family = "wasm"))] -pub use io::*; From a61af33a94e90117b54489b7e7ed943b0df62a94 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Thu, 28 Sep 2023 15:20:23 +0100 Subject: [PATCH 63/87] Pin flutter_rust_bridge to codegen version used --- trustchain-ffi/Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/trustchain-ffi/Cargo.toml b/trustchain-ffi/Cargo.toml index 705a2df2..d4e5e7b3 100644 --- a/trustchain-ffi/Cargo.toml +++ b/trustchain-ffi/Cargo.toml @@ -17,7 +17,8 @@ trustchain-api = { path = "../trustchain-api" } anyhow = "1.0" chrono = "0.4.26" did-ion="0.1.0" -flutter_rust_bridge = "1" +# Fixed to same version used to generate bridge: `flutter_rust_bridge_codegen@1.64.0` +flutter_rust_bridge = "=1.64.0" lazy_static="1.4.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" From 920b22b4c5e4eaa29ec25bd7f098ebf27a95d50c Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Fri, 29 Sep 2023 14:38:35 +0100 Subject: [PATCH 64/87] Refine comments, change host_reference to string, clippy --- trustchain-http/src/config.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/trustchain-http/src/config.rs b/trustchain-http/src/config.rs index a5837d44..6e77ec13 100644 --- a/trustchain-http/src/config.rs +++ b/trustchain-http/src/config.rs @@ -11,20 +11,20 @@ use trustchain_core::TRUSTCHAIN_CONFIG; const DEFAULT_HOST: &str = "127.0.0.1"; const DEFAULT_PORT: u16 = 8081; -/// Server config. +/// HTTP configuration. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct HTTPConfig { /// Hostname for server pub host: IpAddr, /// Hostname reference. For example, Android emulator 10.0.2.2 refers to 127.0.0.1 of machine running emulator. - pub host_reference: IpAddr, + pub host_reference: String, /// Port for server pub port: u16, /// Optional issuer DID pub issuer_did: Option, /// Flag indicating whether server uses https pub https: bool, - /// Path with certificate and key for https + /// Path containing certificate and key necessary for https pub https_path: Option, } @@ -38,7 +38,7 @@ impl Default for HTTPConfig { fn default() -> Self { Self { host: IpAddr::from_str(DEFAULT_HOST).unwrap(), - host_reference: IpAddr::from_str(DEFAULT_HOST).unwrap(), + host_reference: DEFAULT_HOST.to_string(), port: DEFAULT_PORT, issuer_did: None, https: false, @@ -79,7 +79,7 @@ pub fn http_config() -> &'static HTTP_CONFIG { &HTTP_CONFIG } -/// Wrapper struct for parsing the `http` table. +/// Wrapper struct for parsing the `http` config table. #[derive(Serialize, Deserialize, Debug, Clone)] struct Config { /// HTTP configuration data. @@ -92,7 +92,7 @@ mod tests { #[test] fn test_deserialize() { - let config_string = r##" + let config_string = r#" [http] host = "127.0.0.1" host_reference = "127.0.0.1" @@ -102,7 +102,7 @@ mod tests { [non_http] key = "value" - "##; + "#; let config: HTTPConfig = parse_toml(config_string); From f83a66b2879671fc962e348ba7312687c4744d4e Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Fri, 29 Sep 2023 14:40:16 +0100 Subject: [PATCH 65/87] Rename 'host_reference' to 'host_display' --- trustchain-http/src/config.rs | 4 ++-- trustchain-http/src/issuer.rs | 2 +- trustchain-http/src/verifier.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/trustchain-http/src/config.rs b/trustchain-http/src/config.rs index 6e77ec13..9ac39be1 100644 --- a/trustchain-http/src/config.rs +++ b/trustchain-http/src/config.rs @@ -17,7 +17,7 @@ pub struct HTTPConfig { /// Hostname for server pub host: IpAddr, /// Hostname reference. For example, Android emulator 10.0.2.2 refers to 127.0.0.1 of machine running emulator. - pub host_reference: String, + pub host_display: String, /// Port for server pub port: u16, /// Optional issuer DID @@ -38,7 +38,7 @@ impl Default for HTTPConfig { fn default() -> Self { Self { host: IpAddr::from_str(DEFAULT_HOST).unwrap(), - host_reference: DEFAULT_HOST.to_string(), + host_display: DEFAULT_HOST.to_string(), port: DEFAULT_PORT, issuer_did: None, https: false, diff --git a/trustchain-http/src/issuer.rs b/trustchain-http/src/issuer.rs index 69c9f698..c41daef6 100644 --- a/trustchain-http/src/issuer.rs +++ b/trustchain-http/src/issuer.rs @@ -128,7 +128,7 @@ impl TrustchainIssuerHTTPHandler { // Generate a QR code for server address and combination of name and UUID let address_str = format!( "{}://{}:{}/vc/issuer/{id}", - http_str, app_state.config.host_reference, app_state.config.port + http_str, app_state.config.host_display, app_state.config.port ); // Respond with the QR code as a png embedded in html Html(str_to_qr_code_html(&address_str, "Issuer")) diff --git a/trustchain-http/src/verifier.rs b/trustchain-http/src/verifier.rs index bb18a47a..ba2ddbab 100644 --- a/trustchain-http/src/verifier.rs +++ b/trustchain-http/src/verifier.rs @@ -153,7 +153,7 @@ impl TrustchainVerifierHTTPHandler { }; let address_str = format!( "{}://{}:{}/vc/verifier/{}", - http_str, app_state.config.host_reference, app_state.config.port, uid + http_str, app_state.config.host_display, app_state.config.port, uid ); ( StatusCode::OK, From 890bbac8aa533d694298a96ad52019f53d3ae2cc Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Fri, 29 Sep 2023 14:41:01 +0100 Subject: [PATCH 66/87] Rename field in test --- trustchain-http/src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trustchain-http/src/config.rs b/trustchain-http/src/config.rs index 9ac39be1..e01b7a2c 100644 --- a/trustchain-http/src/config.rs +++ b/trustchain-http/src/config.rs @@ -95,7 +95,7 @@ mod tests { let config_string = r#" [http] host = "127.0.0.1" - host_reference = "127.0.0.1" + host_display = "127.0.0.1" port = 8081 issuer_did = "did:ion:test:EiBcLZcELCKKtmun_CUImSlb2wcxK5eM8YXSq3MrqNe5wA" https = false From ad4dd0dada6f3dd6d1225f72a8c3c0c2a215d877 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Fri, 29 Sep 2023 15:05:07 +0100 Subject: [PATCH 67/87] Format comment --- trustchain-http/src/server.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/trustchain-http/src/server.rs b/trustchain-http/src/server.rs index 23596235..a9470a8c 100644 --- a/trustchain-http/src/server.rs +++ b/trustchain-http/src/server.rs @@ -112,7 +112,8 @@ pub async fn https_server(config: HTTPConfig) -> std::io::Result<()> { .await } -/// Generates a `RustlsConfig` for https servers given a path with certificate and key. Based on axum [example](https://github.com/tokio-rs/axum/blob/d30375925dd22cc44aeaae2871f8ead1630fadf8/examples/tls-rustls/src/main.rs) +/// Generates a `RustlsConfig` for https servers given a path with certificate and key. Based on +/// axum [example](https://github.com/tokio-rs/axum/blob/d30375925dd22cc44aeaae2871f8ead1630fadf8/examples/tls-rustls/src/main.rs). async fn rustls_config(path: &str) -> RustlsConfig { // Configure certificate and private key used by https let path = shellexpand::tilde(path); From 0014096c83befddd759ced046b5c7a10403ce85b Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Fri, 29 Sep 2023 15:55:26 +0100 Subject: [PATCH 68/87] Default empty cache --- trustchain-http/src/state.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/trustchain-http/src/state.rs b/trustchain-http/src/state.rs index 62b2f744..776ae1ea 100644 --- a/trustchain-http/src/state.rs +++ b/trustchain-http/src/state.rs @@ -20,17 +20,18 @@ impl AppState { let path = std::env::var(TRUSTCHAIN_DATA).expect("TRUSTCHAIN_DATA env not set."); let credentials: HashMap = serde_json::from_reader( std::fs::read(std::path::Path::new(&path).join("credentials/offers/cache.json")) - .expect("Credential cache does not exist.") + // If no cache, default to empty + .unwrap_or_default() .as_slice(), ) - // TODO: change to unrwap_or_default() (empty cache) .expect("Credential cache could not be deserialized."); let presentation_requests: HashMap = serde_json::from_reader( std::fs::read(std::path::Path::new(&path).join("presentations/requests/cache.json")) - .expect("Credential cache does not exist.") + // If no cache, default to empty + .unwrap_or_default() .as_slice(), ) - .expect("Credential cache could not be deserialized."); + .expect("Presentation cache could not be deserialized."); Self { config, verifier, From 8f049c547f5f80fff161fd3ea1db6a9cd69d27cd Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Fri, 29 Sep 2023 17:08:57 +0100 Subject: [PATCH 69/87] Fix form submission for issuing credential --- trustchain-http/src/issuer.rs | 6 +----- trustchain-http/src/server.rs | 4 ++-- trustchain-http/static/index.html | 32 +++++++++++++++++++++---------- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/trustchain-http/src/issuer.rs b/trustchain-http/src/issuer.rs index c41daef6..e7fa5aef 100644 --- a/trustchain-http/src/issuer.rs +++ b/trustchain-http/src/issuer.rs @@ -114,12 +114,8 @@ impl TrustchainIssuerHTTPHandler { /// Generates QR code to display to holder to receive requested credential. pub async fn get_issuer_qrcode( State(app_state): State>, - Form(form): Form>, + Path(id): Path, ) -> Html { - let id = form - .get("uuid") - .expect("'uuid' key not in form.") - .to_owned(); let http_str = if !http_config().https { "http" } else { diff --git a/trustchain-http/src/server.rs b/trustchain-http/src/server.rs index a9470a8c..6414f858 100644 --- a/trustchain-http/src/server.rs +++ b/trustchain-http/src/server.rs @@ -1,7 +1,7 @@ use crate::config::http_config; use crate::middleware::validate_did; use crate::{config::HTTPConfig, issuer, resolver, state::AppState, static_handlers, verifier}; -use axum::routing::IntoMakeService; +use axum::routing::{post, IntoMakeService}; use axum::{middleware, routing::get, Router}; use axum_server::tls_rustls::RustlsConfig; use hyper::server::conn::AddrIncoming; @@ -34,7 +34,7 @@ impl TrustchainRouter { router: Router::new() .route("/", get(static_handlers::index)) .route( - "/issuer", + "/issuer/:id", get(issuer::TrustchainIssuerHTTPHandler::get_issuer_qrcode), ) .route( diff --git a/trustchain-http/static/index.html b/trustchain-http/static/index.html index 1b600a3c..8ddd08da 100644 --- a/trustchain-http/static/index.html +++ b/trustchain-http/static/index.html @@ -5,20 +5,32 @@ VC server - Receive a credential: -
- - +
+
+
+
+
+

Receive a credential

+ + + +
+
+

- Present a credential: +
+ +
+
+
+

Present a credential

-
+
+
+
+
From 211ba8f022b01dce0d82f8ec2e62cec223b1c9d3 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Mon, 2 Oct 2023 11:55:44 +0100 Subject: [PATCH 70/87] Add root_event_time to http config and verifier --- trustchain-http/src/config.rs | 4 +++ trustchain-http/src/errors.rs | 5 +++ trustchain-http/src/issuer.rs | 3 +- trustchain-http/src/server.rs | 2 +- trustchain-http/src/verifier.rs | 62 +++++++++++++++++++-------------- 5 files changed, 47 insertions(+), 29 deletions(-) diff --git a/trustchain-http/src/config.rs b/trustchain-http/src/config.rs index e01b7a2c..91a973f1 100644 --- a/trustchain-http/src/config.rs +++ b/trustchain-http/src/config.rs @@ -6,6 +6,7 @@ use std::{ str::FromStr, }; use toml; +use trustchain_core::verifier::Timestamp; use trustchain_core::TRUSTCHAIN_CONFIG; const DEFAULT_HOST: &str = "127.0.0.1"; @@ -26,6 +27,8 @@ pub struct HTTPConfig { pub https: bool, /// Path containing certificate and key necessary for https pub https_path: Option, + /// Root event time for verifier. + pub root_event_time: Option, } impl std::fmt::Display for HTTPConfig { @@ -43,6 +46,7 @@ impl Default for HTTPConfig { issuer_did: None, https: false, https_path: None, + root_event_time: None, } } } diff --git a/trustchain-http/src/errors.rs b/trustchain-http/src/errors.rs index 0321e053..71758f86 100644 --- a/trustchain-http/src/errors.rs +++ b/trustchain-http/src/errors.rs @@ -34,6 +34,8 @@ pub enum TrustchainHTTPError { RequestDoesNotExist, #[error("Could not deserialize data: {0}")] FailedToDeserialize(serde_json::Error), + #[error("Root event time not configured for verification.")] + RootEventTimeNotSet, } impl From for TrustchainHTTPError { @@ -118,6 +120,9 @@ impl IntoResponse for TrustchainHTTPError { err @ TrustchainHTTPError::FailedToDeserialize(_) => { (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) } + err @ TrustchainHTTPError::RootEventTimeNotSet => { + (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) + } }; let body = Json(json!({ "error": err_message })); (status, body).into_response() diff --git a/trustchain-http/src/issuer.rs b/trustchain-http/src/issuer.rs index e7fa5aef..5f033413 100644 --- a/trustchain-http/src/issuer.rs +++ b/trustchain-http/src/issuer.rs @@ -6,7 +6,7 @@ use async_trait::async_trait; use axum::extract::{Path, State}; use axum::http::StatusCode; use axum::response::{Html, IntoResponse}; -use axum::{Form, Json}; +use axum::Json; use chrono::Utc; use log::info; use serde::{Deserialize, Serialize}; @@ -14,7 +14,6 @@ use ssi::did_resolve::DIDResolver; use ssi::one_or_many::OneOrMany; use ssi::vc::Credential; use ssi::vc::VCDateTime; -use std::collections::HashMap; use std::sync::Arc; use trustchain_core::issuer::Issuer; use trustchain_core::resolver::Resolver; diff --git a/trustchain-http/src/server.rs b/trustchain-http/src/server.rs index 6414f858..a1aa4b5a 100644 --- a/trustchain-http/src/server.rs +++ b/trustchain-http/src/server.rs @@ -1,7 +1,7 @@ use crate::config::http_config; use crate::middleware::validate_did; use crate::{config::HTTPConfig, issuer, resolver, state::AppState, static_handlers, verifier}; -use axum::routing::{post, IntoMakeService}; +use axum::routing::IntoMakeService; use axum::{middleware, routing::get, Router}; use axum_server::tls_rustls::RustlsConfig; use hyper::server::conn::AddrIncoming; diff --git a/trustchain-http/src/verifier.rs b/trustchain-http/src/verifier.rs index ba2ddbab..c14666d2 100644 --- a/trustchain-http/src/verifier.rs +++ b/trustchain-http/src/verifier.rs @@ -70,6 +70,7 @@ impl TrustchainVerifierHTTP for TrustchainVerifierHTTPHandler {} #[serde(rename_all = "camelCase")] pub struct PostVerifier { pub presentation_or_credential: PresentationOrCredential, + // TODO: remove field as obsolete pub root_event_time: Timestamp, } @@ -106,7 +107,10 @@ impl TrustchainVerifierHTTPHandler { PresentationOrCredential::Presentation(ref presentation) => { TrustchainVerifierHTTPHandler::verify_presentation( presentation, - verification_info.root_event_time, + app_state + .config + .root_event_time + .ok_or(TrustchainHTTPError::RootEventTimeNotSet)?, &app_state.verifier, ) .await @@ -122,7 +126,10 @@ impl TrustchainVerifierHTTPHandler { PresentationOrCredential::Credential(ref credential) => { TrustchainVerifierHTTPHandler::verify_credential( credential, - verification_info.root_event_time, + app_state + .config + .root_event_time + .ok_or(TrustchainHTTPError::RootEventTimeNotSet)?, &app_state.verifier, ) .await @@ -179,6 +186,7 @@ mod tests { /// Lazy static reference to core configuration loaded from `trustchain_config.toml`. pub static ref TEST_HTTP_CONFIG: HTTPConfig = HTTPConfig { issuer_did: Some("did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q".to_string()), + root_event_time: Some(1666265405), ..Default::default() }; } @@ -351,18 +359,19 @@ mod tests { assert_eq!(response.status(), StatusCode::OK); assert_eq!("Credential received and verified!", response.text().await); - // Test post of credential to verifier with bad root event time - let app = TrustchainRouter::from(state.clone()).into_router(); - let uid = "b9519df2-35c1-11ee-8314-7f66e4585b4f"; - let uri = format!("/vc/verifier/{uid}"); - let client = TestClient::new(app); - let mut post_verifier: PostVerifier = - serde_json::from_str(TEST_POST_VERIFIER_CREDENTIAL).unwrap(); - post_verifier.root_event_time = 1666265406; - let response = client.post(&uri).json(&post_verifier).send().await; - assert_eq!(response.status(), StatusCode::OK); - // TODO: consider refining error returned - assert_eq!(response.text().await, r#"{"error":"Trustchain Verifier error: A commitment error during verification: Failed content verification. Expected data 1666265406 not found in candidate: 1666265405."}"#.to_string()); + // TODO: remove as root event time now determined by verifier + // // Test post of credential to verifier with bad root event time + // let app = TrustchainRouter::from(state.clone()).into_router(); + // let uid = "b9519df2-35c1-11ee-8314-7f66e4585b4f"; + // let uri = format!("/vc/verifier/{uid}"); + // let client = TestClient::new(app); + // let mut post_verifier: PostVerifier = + // serde_json::from_str(TEST_POST_VERIFIER_CREDENTIAL).unwrap(); + // post_verifier.root_event_time = 1666265406; + // let response = client.post(&uri).json(&post_verifier).send().await; + // assert_eq!(response.status(), StatusCode::OK); + // // TODO: consider refining error returned + // assert_eq!(response.text().await, r#"{"error":"Trustchain Verifier error: A commitment error during verification: Failed content verification. Expected data 1666265406 not found in candidate: 1666265405."}"#.to_string()); } #[tokio::test] @@ -384,17 +393,18 @@ mod tests { assert_eq!(response.status(), StatusCode::OK); assert_eq!("Presentation received and verified!", response.text().await); - // Test post of presentation to verifier with bad root event time - let app = TrustchainRouter::from(state.clone()).into_router(); - let uid = "b9519df2-35c1-11ee-8314-7f66e4585b4f"; - let uri = format!("/vc/verifier/{uid}"); - let client = TestClient::new(app); - let mut post_verifier: PostVerifier = - serde_json::from_str(TEST_POST_VERIFIER_PRESENTATION).unwrap(); - post_verifier.root_event_time = 1666265406; - let response = client.post(&uri).json(&post_verifier).send().await; - assert_eq!(response.status(), StatusCode::OK); - // TODO: consider refining error returned - assert_eq!(response.text().await, r#"{"error":"Trustchain presentation error: A wrapped Credential error: A wrapped Verifier error: A commitment error during verification: Failed content verification. Expected data 1666265406 not found in candidate: 1666265405."}"#.to_string()); + // TODO: remove as root event time now determined by verifier + // // Test post of presentation to verifier with bad root event time + // let app = TrustchainRouter::from(state.clone()).into_router(); + // let uid = "b9519df2-35c1-11ee-8314-7f66e4585b4f"; + // let uri = format!("/vc/verifier/{uid}"); + // let client = TestClient::new(app); + // let mut post_verifier: PostVerifier = + // serde_json::from_str(TEST_POST_VERIFIER_PRESENTATION).unwrap(); + // post_verifier.root_event_time = 1666265406; + // let response = client.post(&uri).json(&post_verifier).send().await; + // assert_eq!(response.status(), StatusCode::OK); + // // TODO: consider refining error returned + // assert_eq!(response.text().await, r#"{"error":"Trustchain presentation error: A wrapped Credential error: A wrapped Verifier error: A commitment error during verification: Failed content verification. Expected data 1666265406 not found in candidate: 1666265405."}"#.to_string()); } } From 9076a98ca3adf314e6d6fc92b7f04893fb421993 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Wed, 4 Oct 2023 21:12:00 +0100 Subject: [PATCH 71/87] Remove obsolete tests --- trustchain-ion/tests/vc.rs | 103 ------------------------------------- 1 file changed, 103 deletions(-) delete mode 100644 trustchain-ion/tests/vc.rs diff --git a/trustchain-ion/tests/vc.rs b/trustchain-ion/tests/vc.rs deleted file mode 100644 index 6b2a45a7..00000000 --- a/trustchain-ion/tests/vc.rs +++ /dev/null @@ -1,103 +0,0 @@ -use ssi::{jsonld::ContextLoader, ldp::now_ms}; -use std::convert::TryFrom; -use trustchain_core::issuer::{Issuer, IssuerError}; -use trustchain_ion::attestor::IONAttestor; -use trustchain_ion::get_ion_resolver; - -use ssi::vc::{Credential, VCDateTime}; - -// Linked @context provides a set of allowed fields for the credential: -// "credentialSubject" key: "https://www.w3.org/2018/credentials/examples/v1" -// "image" key: "https://w3id.org/citizenship/v1" -// -// Other examples: https://www.w3.org/TR/vc-use-cases/ -const TEST_UNSIGNED_VC: &str = r##"{ - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://www.w3.org/2018/credentials/examples/v1", - "https://w3id.org/citizenship/v1" - ], - "credentialSchema": { - "id": "did:example:cdf:35LB7w9ueWbagPL94T9bMLtyXDj9pX5o", - "type": "did:example:schema:22KpkXgecryx9k7N6XN1QoN3gXwBkSU8SfyyYQG" - }, - "type": ["VerifiableCredential"], - "issuer": "did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q", - "image": "some_base64_representation", - "credentialSubject": { - "givenName": "Jane", - "familyName": "Doe", - "degree": { - "type": "BachelorDegree", - "name": "Bachelor of Science and Arts", - "college": "College of Engineering" - } - } -} -"##; - -#[ignore = "requires a running Sidetree node listening on http://localhost:3000"] -#[tokio::test] -async fn test_sign_credential() { - // 1. Set-up - let did = "did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q"; - - // Make resolver - let resolver = get_ion_resolver("http://localhost:3000/"); - - // 2. Load Attestor - let attestor = IONAttestor::new(did); - - // 3. Read credential - let vc: Credential = serde_json::from_str(TEST_UNSIGNED_VC).unwrap(); - - // 4. Generate VC and verify - - // Use attest_credential method instead of generating and adding proof - let mut vc_with_proof = attestor.sign(&vc, None, None, &resolver).await.unwrap(); - - // Verify: expect no warnings or errors - let verification_result = vc_with_proof - .verify(None, &resolver, &mut ContextLoader::default()) - .await; - assert!(verification_result.warnings.is_empty()); - assert!(verification_result.errors.is_empty()); - - // Change credential to make signature invalid - vc_with_proof.expiration_date = Some(VCDateTime::try_from(now_ms()).unwrap()); - - // Verify: expect no warnings and a signature error as VC has changed - let verification_result = vc_with_proof - .verify(None, &resolver, &mut ContextLoader::default()) - .await; - assert!(verification_result.warnings.is_empty()); - assert_eq!(verification_result.errors, vec!["signature error"]); -} - -#[ignore = "requires a running Sidetree node listening on http://localhost:3000"] -#[tokio::test] -async fn test_sign_credential_failure() { - // 1. Set-up (with a DID *not* matching the issuer field in the credential). - let did = "did:ion:test:EiDMe2SFfJ_7eXVW7RF1ZHOkeu2M-Bre0ak2cXNBH0P-TQ"; - - // Make resolver - let resolver = get_ion_resolver("http://localhost:3000/"); - - // 2. Load Attestor - let attestor = IONAttestor::new(did); - - // 3. Read credential - let vc: Credential = serde_json::from_str(TEST_UNSIGNED_VC).unwrap(); - - // 4. Generate VC and verify - - // Sign credential (expect failure). - let vc_with_proof = attestor.sign(&vc, None, None, &resolver).await; - assert!(vc_with_proof.is_err()); - assert!(matches!( - vc_with_proof, - Err(IssuerError::LDP(ssi::ldp::Error::DID( - ssi::did::Error::KeyMismatch - ))) - )); -} From e585d343790a3b660ee7fb7c51f6c71f2cbc92c2 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Wed, 4 Oct 2023 21:14:55 +0100 Subject: [PATCH 72/87] Add ContextLoader to API --- trustchain-api/src/api.rs | 114 +++++++++++++++++++++------------ trustchain-cli/src/bin/main.rs | 18 ++++-- trustchain-core/src/holder.rs | 2 + trustchain-core/src/issuer.rs | 2 + trustchain-http/src/issuer.rs | 12 +++- trustchain-ion/src/attestor.rs | 32 +++++---- 6 files changed, 121 insertions(+), 59 deletions(-) diff --git a/trustchain-api/src/api.rs b/trustchain-api/src/api.rs index 6c3457d2..80bc2412 100644 --- a/trustchain-api/src/api.rs +++ b/trustchain-api/src/api.rs @@ -93,11 +93,18 @@ pub trait TrustchainVCAPI { linked_data_proof_options: Option, key_id: Option<&str>, resolver: &T, + context_loader: &mut ContextLoader, ) -> Result { credential.issuer = Some(ssi::vc::Issuer::URI(URI::String(did.to_string()))); let attestor = IONAttestor::new(did); attestor - .sign(&credential, linked_data_proof_options, key_id, resolver) + .sign( + &credential, + linked_data_proof_options, + key_id, + resolver, + context_loader, + ) .await } @@ -107,6 +114,7 @@ pub trait TrustchainVCAPI { linked_data_proof_options: Option, root_event_time: Timestamp, verifier: &U, + context_loader: &mut ContextLoader, ) -> Result where T: DIDResolver + Send, @@ -117,7 +125,7 @@ pub trait TrustchainVCAPI { .verify( linked_data_proof_options, verifier.resolver(), - &mut ContextLoader::default(), + context_loader, ) .await; if !result.errors.is_empty() { @@ -140,11 +148,18 @@ pub trait TrustchainVPAPI { key_id: Option<&str>, endpoint: &str, linked_data_proof_options: Option, + context_loader: &mut ContextLoader, ) -> Result { let resolver = get_ion_resolver(endpoint); let attestor = IONAttestor::new(did); Ok(attestor - .sign_presentation(&presentation, linked_data_proof_options, key_id, &resolver) + .sign_presentation( + &presentation, + linked_data_proof_options, + key_id, + &resolver, + context_loader, + ) .await?) } /// Verifies a verifiable presentation. @@ -153,6 +168,7 @@ pub trait TrustchainVPAPI { ldp_options: Option, root_event_time: Timestamp, verifier: &IONVerifier, + context_loader: &mut ContextLoader, ) -> Result<(), PresentationError> { // Check credentials are present in presentation let credentials = presentation @@ -163,55 +179,59 @@ pub trait TrustchainVPAPI { // Verify signatures and issuers for each credential included in the presentation // TODO: consider concurrency limit (as rate limiting for verifier requests) let limit = Some(5); - let ldp_options_vec: Vec> = (0..credentials.len()) - .map(|_| ldp_options.clone()) + let ldp_opts_and_context_loader: Vec<(Option, ContextLoader)> = (0 + ..credentials.len()) + .map(|_| (ldp_options.clone(), context_loader.clone())) .collect(); - stream::iter(credentials.into_iter().zip(ldp_options_vec)) + stream::iter(credentials.into_iter().zip(ldp_opts_and_context_loader)) .map(Ok) - .try_for_each_concurrent(limit, |(credential_or_jwt, ldp_options)| async move { - match credential_or_jwt { - CredentialOrJWT::Credential(credential) => TrustchainAPI::verify_credential( - credential, - ldp_options, - root_event_time, - verifier, - ) - .await - .map(|_| ()), - CredentialOrJWT::JWT(jwt) => { - // decode and verify for credential jwts - match Credential::decode_verify_jwt( - jwt, - ldp_options.clone(), - verifier.resolver(), - &mut ContextLoader::default(), - ) - .await - .0 - .ok_or(CredentialError::FailedToDecodeJWT) - { - Ok(credential) => TrustchainAPI::verify_credential( - &credential, - ldp_options, + .try_for_each_concurrent( + limit, + |(credential_or_jwt, (ldp_opts, mut context_loader))| async move { + match credential_or_jwt { + CredentialOrJWT::Credential(credential) => { + TrustchainAPI::verify_credential( + credential, + ldp_opts, root_event_time, verifier, + &mut context_loader, + ) + .await + .map(|_| ()) + } + CredentialOrJWT::JWT(jwt) => { + // decode and verify for credential jwts + match Credential::decode_verify_jwt( + jwt, + ldp_opts.clone(), + verifier.resolver(), + &mut context_loader, ) .await - .map(|_| ()), - Err(e) => Err(e), + .0 + .ok_or(CredentialError::FailedToDecodeJWT) + { + Ok(credential) => TrustchainAPI::verify_credential( + &credential, + ldp_opts, + root_event_time, + verifier, + &mut context_loader, + ) + .await + .map(|_| ()), + Err(e) => Err(e), + } } } - } - }) + }, + ) .await?; // Verify signature by holder to authenticate let result = presentation - .verify( - ldp_options.clone(), - verifier.resolver(), - &mut ContextLoader::default(), - ) + .verify(ldp_options.clone(), verifier.resolver(), context_loader) .await; if !result.errors.is_empty() { return Err(PresentationError::VerifiedHolderUnauthenticated(result)); @@ -224,6 +244,7 @@ pub trait TrustchainVPAPI { mod tests { use crate::api::{TrustchainVCAPI, TrustchainVPAPI}; use crate::TrustchainAPI; + use ssi::jsonld::ContextLoader; use ssi::ldp::now_ms; use ssi::one_or_many::OneOrMany; use ssi::vc::{Credential, CredentialOrJWT, Presentation, VCDateTime}; @@ -272,11 +293,13 @@ mod tests { let issuer = IONAttestor::new(issuer_did); let mut vc_with_proof = signed_credential(issuer).await; let resolver = get_ion_resolver("http://localhost:3000/"); + let mut context_loader = ContextLoader::default(); let res = TrustchainAPI::verify_credential( &vc_with_proof, None, ROOT_EVENT_TIME_1, &IONVerifier::new(resolver), + &mut context_loader, ) .await; assert!(res.is_ok()); @@ -291,6 +314,7 @@ mod tests { None, ROOT_EVENT_TIME_1, &IONVerifier::new(resolver), + &mut context_loader, ) .await; if let CredentialError::VerificationResultError(ver_res) = res.err().unwrap() { @@ -312,6 +336,7 @@ mod tests { let vc_with_proof = signed_credential(issuer).await; let resolver = get_ion_resolver("http://localhost:3000/"); + let mut context_loader = ContextLoader::default(); // let vc: Credential = serde_json::from_str(TEST_UNSIGNED_VC).unwrap(); // let root_plus_1_signing_key: &str = r#"{"kty":"EC","crv":"secp256k1","x":"aApKobPO8H8wOv-oGT8K3Na-8l-B1AE3uBZrWGT6FJU","y":"dspEqltAtlTKJ7cVRP_gMMknyDPqUw-JHlpwS2mFuh0","d":"HbjLQf4tnwJR6861-91oGpERu8vmxDpW8ZroDCkmFvY"}"#; @@ -359,7 +384,7 @@ mod tests { }; presentation = holder - .sign_presentation(&presentation, None, None, &resolver) + .sign_presentation(&presentation, None, None, &resolver, &mut context_loader) .await .unwrap(); println!("{}", serde_json::to_string_pretty(&presentation).unwrap()); @@ -368,6 +393,7 @@ mod tests { None, ROOT_EVENT_TIME_1, &IONVerifier::new(resolver), + &mut context_loader, ) .await; println!("{:?}", res); @@ -398,6 +424,7 @@ mod tests { None, ROOT_EVENT_TIME_1, &IONVerifier::new(resolver), + &mut ContextLoader::default() ) .await, Err(PresentationError::VerifiedHolderUnauthenticated(..)) @@ -408,6 +435,9 @@ mod tests { async fn signed_credential(attestor: IONAttestor) -> Credential { let resolver = get_ion_resolver("http://localhost:3000/"); let vc: Credential = serde_json::from_str(TEST_UNSIGNED_VC).unwrap(); - attestor.sign(&vc, None, None, &resolver).await.unwrap() + attestor + .sign(&vc, None, None, &resolver, &mut ContextLoader::default()) + .await + .unwrap() } } diff --git a/trustchain-cli/src/bin/main.rs b/trustchain-cli/src/bin/main.rs index f8f8748c..09d34d17 100644 --- a/trustchain-cli/src/bin/main.rs +++ b/trustchain-cli/src/bin/main.rs @@ -1,7 +1,7 @@ //! Trustchain CLI binary use clap::{arg, ArgAction, Command}; use serde_json::to_string_pretty; -use ssi::{ldp::LinkedDataDocument, vc::Credential}; +use ssi::{jsonld::ContextLoader, ldp::LinkedDataDocument, vc::Credential}; use std::{ fs::File, io::{stdin, BufReader}, @@ -87,6 +87,7 @@ async fn main() -> Result<(), Box> { let endpoint = cli_config().ion_endpoint.to_address(); let verifier = IONVerifier::new(get_ion_resolver(&endpoint)); let resolver = verifier.resolver(); + let mut context_loader = ContextLoader::default(); match matches.subcommand() { Some(("did", sub_matches)) => { match sub_matches.subcommand() { @@ -170,10 +171,16 @@ async fn main() -> Result<(), Box> { serde_json::from_reader(buffer).unwrap() }; - let credential_with_proof = - TrustchainAPI::sign(credential, did, None, key_id, resolver) - .await - .expect("Failed to issue credential."); + let credential_with_proof = TrustchainAPI::sign( + credential, + did, + None, + key_id, + resolver, + &mut context_loader, + ) + .await + .expect("Failed to issue credential."); println!("{}", &to_string_pretty(&credential_with_proof).unwrap()); } Some(("verify", sub_matches)) => { @@ -196,6 +203,7 @@ async fn main() -> Result<(), Box> { None, root_event_time, &verifier, + &mut context_loader, ) .await; // Handle result diff --git a/trustchain-core/src/holder.rs b/trustchain-core/src/holder.rs index e77457e2..70ab9269 100644 --- a/trustchain-core/src/holder.rs +++ b/trustchain-core/src/holder.rs @@ -3,6 +3,7 @@ use crate::key_manager::KeyManagerError; use crate::subject::Subject; use async_trait::async_trait; use ssi::did_resolve::DIDResolver; +use ssi::jsonld::ContextLoader; use ssi::vc::{LinkedDataProofOptions, Presentation}; use thiserror::Error; @@ -53,5 +54,6 @@ pub trait Holder: Subject { linked_data_proof_options: Option, key_id: Option<&str>, resolver: &T, + context_loader: &mut ContextLoader, ) -> Result; } diff --git a/trustchain-core/src/issuer.rs b/trustchain-core/src/issuer.rs index 86dec4f1..21811d89 100644 --- a/trustchain-core/src/issuer.rs +++ b/trustchain-core/src/issuer.rs @@ -3,6 +3,7 @@ use crate::key_manager::KeyManagerError; use crate::subject::Subject; use async_trait::async_trait; use ssi::did_resolve::DIDResolver; +use ssi::jsonld::ContextLoader; use ssi::vc::{Credential, LinkedDataProofOptions}; use thiserror::Error; @@ -48,5 +49,6 @@ pub trait Issuer: Subject { linked_data_proof_options: Option, key_id: Option<&str>, resolver: &T, + context_loader: &mut ContextLoader, ) -> Result; } diff --git a/trustchain-http/src/issuer.rs b/trustchain-http/src/issuer.rs index f57258a8..1b235e56 100644 --- a/trustchain-http/src/issuer.rs +++ b/trustchain-http/src/issuer.rs @@ -10,6 +10,7 @@ use chrono::Utc; use log::info; use serde::{Deserialize, Serialize}; use ssi::did_resolve::DIDResolver; +use ssi::jsonld::ContextLoader; use ssi::one_or_many::OneOrMany; use ssi::vc::Credential; use ssi::vc::VCDateTime; @@ -107,7 +108,16 @@ impl TrustchainIssuerHTTP for TrustchainIssuerHTTPHandler { } } let issuer = IONAttestor::new(issuer_did); - Ok(issuer.sign(&credential, None, None, resolver).await?) + Ok(issuer + .sign( + &credential, + None, + None, + resolver, + // TODO: add context loader to app_state + &mut ContextLoader::default(), + ) + .await?) } } diff --git a/trustchain-ion/src/attestor.rs b/trustchain-ion/src/attestor.rs index 5cf81546..958a2eab 100644 --- a/trustchain-ion/src/attestor.rs +++ b/trustchain-ion/src/attestor.rs @@ -150,6 +150,7 @@ impl Issuer for IONAttestor { linked_data_proof_options: Option, key_id: Option<&str>, resolver: &T, + context_loader: &mut ContextLoader, ) -> Result { // Get the signing key. let signing_key = self.signing_key(key_id)?; @@ -160,7 +161,7 @@ impl Issuer for IONAttestor { &signing_key, &linked_data_proof_options.unwrap_or(LinkedDataProofOptions::default()), resolver, - &mut ContextLoader::default(), + context_loader, ) .await?; @@ -187,6 +188,7 @@ impl Holder for IONAttestor { linked_data_proof_options: Option, key_id: Option<&str>, resolver: &T, + context_loader: &mut ContextLoader, ) -> Result { // If no ldp options passed, use default with ProofPurpose::Authentication. let options = linked_data_proof_options.unwrap_or(LinkedDataProofOptions { @@ -210,12 +212,7 @@ impl Holder for IONAttestor { // Generate proof let proof = vp - .generate_proof( - &signing_key, - &options, - resolver, - &mut ContextLoader::default(), - ) + .generate_proof(&signing_key, &options, resolver, context_loader) .await?; // Add proof to credential vp.add_proof(proof); @@ -318,7 +315,9 @@ mod tests { let vc = serde_json::from_str(TEST_CREDENTIAL).unwrap(); // Attest to doc - let vc_with_proof = target.sign(&vc, None, None, &resolver).await; + let vc_with_proof = target + .sign(&vc, None, None, &resolver, &mut ContextLoader::default()) + .await; // Check attest was ok assert!(vc_with_proof.is_ok()); @@ -352,7 +351,9 @@ mod tests { // Sign credential (expect failure). // Note: Signing a vc with a Some() issuer field requires a running ion node - let vc_with_proof = attestor.sign(&vc, None, None, &resolver).await; + let vc_with_proof = attestor + .sign(&vc, None, None, &resolver, &mut ContextLoader::default()) + .await; assert!(vc_with_proof.is_err()); // Check error matches @@ -375,7 +376,10 @@ mod tests { let holder = IONAttestor::new(holder_did); let vc = serde_json::from_str(TEST_CREDENTIAL).unwrap(); - let vc_with_proof = issuer.sign(&vc, None, None, &resolver).await.unwrap(); + let vc_with_proof = issuer + .sign(&vc, None, None, &resolver, &mut ContextLoader::default()) + .await + .unwrap(); // Create Presentation, initially with holder field defaulting to None let presentation = Presentation { @@ -386,7 +390,13 @@ mod tests { // Holder field set to the DID of the signing holder by 'sign_presentation' // The DID is resolved during signing, which requires a running ion node. let vp = holder - .sign_presentation(&presentation, None, None, &resolver) + .sign_presentation( + &presentation, + None, + None, + &resolver, + &mut ContextLoader::default(), + ) .await; assert!(vp.is_ok()); From 37e8df0b2b04c8778740be757407af89e13567f6 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Wed, 4 Oct 2023 21:20:56 +0100 Subject: [PATCH 73/87] Use now_ns() over deprecated now_ms() --- trustchain-api/src/api.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/trustchain-api/src/api.rs b/trustchain-api/src/api.rs index 80bc2412..1a77ccb3 100644 --- a/trustchain-api/src/api.rs +++ b/trustchain-api/src/api.rs @@ -245,7 +245,7 @@ mod tests { use crate::api::{TrustchainVCAPI, TrustchainVPAPI}; use crate::TrustchainAPI; use ssi::jsonld::ContextLoader; - use ssi::ldp::now_ms; + use ssi::ldp::now_ns; use ssi::one_or_many::OneOrMany; use ssi::vc::{Credential, CredentialOrJWT, Presentation, VCDateTime}; use trustchain_core::utils::init; @@ -305,7 +305,7 @@ mod tests { assert!(res.is_ok()); // Change credential to make signature invalid - vc_with_proof.expiration_date = Some(VCDateTime::try_from(now_ms()).unwrap()); + vc_with_proof.expiration_date = Some(VCDateTime::try_from(now_ns()).unwrap()); // Verify: expect no warnings and a signature error as VC has changed let resolver = get_ion_resolver("http://localhost:3000/"); From af801c7fea90fd314da2c9c124eb42a3eccc2971 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Thu, 5 Oct 2023 15:38:03 +0100 Subject: [PATCH 74/87] Remove obsolete test code --- trustchain-http/src/verifier.rs | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/trustchain-http/src/verifier.rs b/trustchain-http/src/verifier.rs index b67f7cc2..f4558f9f 100644 --- a/trustchain-http/src/verifier.rs +++ b/trustchain-http/src/verifier.rs @@ -366,20 +366,6 @@ mod tests { let response = client.post(&uri).json(&post_verifier).send().await; assert_eq!(response.status(), StatusCode::OK); assert_eq!("Credential received and verified!", response.text().await); - - // TODO: remove as root event time now determined by verifier - // // Test post of credential to verifier with bad root event time - // let app = TrustchainRouter::from(state.clone()).into_router(); - // let uid = "b9519df2-35c1-11ee-8314-7f66e4585b4f"; - // let uri = format!("/vc/verifier/{uid}"); - // let client = TestClient::new(app); - // let mut post_verifier: PostVerifier = - // serde_json::from_str(TEST_POST_VERIFIER_CREDENTIAL).unwrap(); - // post_verifier.root_event_time = 1666265406; - // let response = client.post(&uri).json(&post_verifier).send().await; - // assert_eq!(response.status(), StatusCode::OK); - // // TODO: consider refining error returned - // assert_eq!(response.text().await, r#"{"error":"Trustchain Verifier error: A commitment error during verification: Failed content verification. Expected data 1666265406 not found in candidate: 1666265405."}"#.to_string()); } #[tokio::test] @@ -400,19 +386,5 @@ mod tests { let response = client.post(&uri).json(&post_verifier).send().await; assert_eq!(response.status(), StatusCode::OK); assert_eq!("Presentation received and verified!", response.text().await); - - // TODO: remove as root event time now determined by verifier - // // Test post of presentation to verifier with bad root event time - // let app = TrustchainRouter::from(state.clone()).into_router(); - // let uid = "b9519df2-35c1-11ee-8314-7f66e4585b4f"; - // let uri = format!("/vc/verifier/{uid}"); - // let client = TestClient::new(app); - // let mut post_verifier: PostVerifier = - // serde_json::from_str(TEST_POST_VERIFIER_PRESENTATION).unwrap(); - // post_verifier.root_event_time = 1666265406; - // let response = client.post(&uri).json(&post_verifier).send().await; - // assert_eq!(response.status(), StatusCode::OK); - // // TODO: consider refining error returned - // assert_eq!(response.text().await, r#"{"error":"Trustchain presentation error: A wrapped Credential error: A wrapped Verifier error: A commitment error during verification: Failed content verification. Expected data 1666265406 not found in candidate: 1666265405."}"#.to_string()); } } From 330ac873f03ec4495d0bee7cd5a14848ae604ec4 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Thu, 5 Oct 2023 21:26:10 +0100 Subject: [PATCH 75/87] Specify resolver = "2" in workspace --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 13190fbd..6c747ed0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,4 @@ members = [ "trustchain-cli", "trustchain-ffi" ] +resolver = "2" From ff3b192e4fa2768bc7b9fd93b6fed6ef6c2b52aa Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Fri, 6 Oct 2023 10:43:28 +0100 Subject: [PATCH 76/87] Remove unused method, extend comment --- trustchain-http/src/verifier.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/trustchain-http/src/verifier.rs b/trustchain-http/src/verifier.rs index f4558f9f..7cc46589 100644 --- a/trustchain-http/src/verifier.rs +++ b/trustchain-http/src/verifier.rs @@ -28,11 +28,6 @@ pub struct PresentationRequest(Value); /// An API for a Trustchain verifier server. #[async_trait] pub trait TrustchainVerifierHTTP { - /// Constructs a presentation request (given some `presentiation_id`) to send to a credential - /// holder from request wallet by ID. - fn generate_presentation_request(_presentation_id: &str) -> PresentationRequest { - todo!() - } /// Verifies verifiable presentation. async fn verify_presentation( presentation: &Presentation, @@ -78,7 +73,7 @@ impl TrustchainVerifierHTTP for TrustchainVerifierHTTPHandler {} #[serde(rename_all = "camelCase")] pub struct PostVerifier { pub presentation_or_credential: PresentationOrCredential, - // TODO: remove field as obsolete + // TODO [#130]: update field upon root event time changing to date and confirmation code. pub root_event_time: Timestamp, } From 4e87f206ec086ce7fe70e205b6d1839dc12affcb Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Fri, 6 Oct 2023 11:08:11 +0100 Subject: [PATCH 77/87] Add helper method for error and consts for DID lengths --- trustchain-http/src/middleware.rs | 54 ++++++++++++++++++------------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/trustchain-http/src/middleware.rs b/trustchain-http/src/middleware.rs index 45828552..9f62ccee 100644 --- a/trustchain-http/src/middleware.rs +++ b/trustchain-http/src/middleware.rs @@ -9,6 +9,23 @@ use axum::{ use serde_json::json; use trustchain_ion::config::ion_config; +// DID string lengths for validating a given DID. +const DID_LENGTH_MAINNET: usize = 54; +const DID_LENGTH_TESTNET: usize = 59; + +/// Generates an error message given DID and expected string length. +fn error_message(did: &str, expected_length: usize) -> serde_json::Value { + json!({ + "error": + format!( + "DID: {} is incorrect length {}. Should be length {}.", + did, + did.len(), + expected_length + ) + }) +} + // See example from axum: https://github.com/tokio-rs/axum/blob/v0.6.x/examples/consume-body-in-extractor-or-middleware/src/main.rs // middleware that shows how to consume the request body upfront // TODO: refactor using [did-ion method](https://docs.rs/did-ion/latest/did_ion/sidetree/trait.Sidetree.html#method.validate_did_suffix) @@ -16,35 +33,26 @@ pub async fn validate_did( Path(did): Path, request: Request, next: Next, -) -> Result { +) -> impl IntoResponse { tracing::info!(did); - // Validate length is 59 (testnet) or 54 (mainnet) + // Validate DID string lengths if ion_config().mongo_database_ion_core.contains("testnet") - && did.len() != 59 + && did.len() != DID_LENGTH_TESTNET && did.starts_with("did:ion") { - let message = json!({ - "error": - format!( - "DID: {} is incorrect length {}. Should be length 59.", - did, - did.len() - ) - }); - return Err((StatusCode::BAD_REQUEST, Json(message))); + Err(( + StatusCode::BAD_REQUEST, + Json(error_message(&did, DID_LENGTH_TESTNET)), + )) } else if ion_config().mongo_database_ion_core.contains("mainnet") - && did.len() != 54 + && did.len() != DID_LENGTH_MAINNET && did.starts_with("did:ion") { - let message = json!({ - "error": - format!( - "DID: {} is incorrect length {}. Should be length 54.", - did, - did.len() - ) - }); - return Err((StatusCode::BAD_REQUEST, Json(message))); + Err(( + StatusCode::BAD_REQUEST, + Json(error_message(&did, DID_LENGTH_MAINNET)), + )) + } else { + Ok(next.run(request).await) } - Ok(next.run(request).await) } From fda0940d18a445e388957b1af3f5e684291af17b Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Fri, 6 Oct 2023 13:17:39 +0100 Subject: [PATCH 78/87] Revise middleware for DID validation, include for chain and bundle endpoints --- trustchain-http/src/middleware.rs | 78 +++++++++++++++++++++---------- trustchain-http/src/server.rs | 6 ++- 2 files changed, 57 insertions(+), 27 deletions(-) diff --git a/trustchain-http/src/middleware.rs b/trustchain-http/src/middleware.rs index 9f62ccee..fd30441d 100644 --- a/trustchain-http/src/middleware.rs +++ b/trustchain-http/src/middleware.rs @@ -1,3 +1,4 @@ +//! Middleware for Trustchain HTTP. use axum::{ body::Body, extract::Path, @@ -6,53 +7,80 @@ use axum::{ response::IntoResponse, Json, }; +use did_ion::{ + sidetree::{DIDSuffix, Sidetree}, + ION, +}; +use lazy_static::lazy_static; use serde_json::json; use trustchain_ion::config::ion_config; +use trustchain_ion::ion::IONTest; -// DID string lengths for validating a given DID. -const DID_LENGTH_MAINNET: usize = 54; -const DID_LENGTH_TESTNET: usize = 59; +lazy_static! { + static ref ION_DID_PREFIX: String = format!("did:{}", ION::METHOD); + static ref ION_DID_TEST_PREFIX: String = + format!("{}:{}", &*ION_DID_PREFIX, IONTest::NETWORK.unwrap()); +} /// Generates an error message given DID and expected string length. -fn error_message(did: &str, expected_length: usize) -> serde_json::Value { +fn error_message(did: &str, expected_prefix: &str) -> serde_json::Value { json!({ "error": format!( - "DID: {} is incorrect length {}. Should be length {}.", + "DID: {} does not match expected prefix: {}", did, - did.len(), - expected_length + expected_prefix ) }) } -// See example from axum: https://github.com/tokio-rs/axum/blob/v0.6.x/examples/consume-body-in-extractor-or-middleware/src/main.rs -// middleware that shows how to consume the request body upfront -// TODO: refactor using [did-ion method](https://docs.rs/did-ion/latest/did_ion/sidetree/trait.Sidetree.html#method.validate_did_suffix) +// See [example](https://github.com/tokio-rs/axum/blob/v0.6.x/examples/consume-body-in-extractor-or-middleware/src/main.rs) +// from axum with middleware that shows how to consume the request body upfront pub async fn validate_did( Path(did): Path, request: Request, next: Next, ) -> impl IntoResponse { tracing::info!(did); - // Validate DID string lengths + let did_split = did.rsplit_once(':'); + if did_split.is_none() { + return Err(( + StatusCode::BAD_REQUEST, + Json(json!({"error": format!("InvalidDID: {}", did)})), + )); + } + let (did_prefix, ion_did_suffix) = did_split.unwrap(); + + // Only validate ION DIDs. Allow others to pass. + if !did_prefix.ne(&*ION_DID_PREFIX) && !did_prefix.ne(&*ION_DID_TEST_PREFIX) { + return Ok(next.run(request).await); + } + + // Validate the DID suffix given established DID method is ION. + let ion_did_suffix = DIDSuffix(ion_did_suffix.to_string()); + if let Err(err) = ION::validate_did_suffix(&ion_did_suffix) { + return Err(( + StatusCode::BAD_REQUEST, + Json(json!({"error": err.to_string()})), + )); + }; + + // Validate the ION network prefix if testnet. if ion_config().mongo_database_ion_core.contains("testnet") - && did.len() != DID_LENGTH_TESTNET - && did.starts_with("did:ion") + && did_prefix == *ION_DID_TEST_PREFIX { - Err(( + return Err(( StatusCode::BAD_REQUEST, - Json(error_message(&did, DID_LENGTH_TESTNET)), - )) - } else if ion_config().mongo_database_ion_core.contains("mainnet") - && did.len() != DID_LENGTH_MAINNET - && did.starts_with("did:ion") - { - Err(( + Json(error_message(&did, &ION_DID_TEST_PREFIX)), + )); + } + + // Validate the ION network prefix if mainnet. + if ion_config().mongo_database_ion_core.contains("mainnet") && did_prefix == *ION_DID_PREFIX { + return Err(( StatusCode::BAD_REQUEST, - Json(error_message(&did, DID_LENGTH_MAINNET)), - )) - } else { - Ok(next.run(request).await) + Json(error_message(&did, &ION_DID_PREFIX)), + )); } + Ok(next.run(request).await) } diff --git a/trustchain-http/src/server.rs b/trustchain-http/src/server.rs index a1aa4b5a..98e2082e 100644 --- a/trustchain-http/src/server.rs +++ b/trustchain-http/src/server.rs @@ -77,11 +77,13 @@ impl TrustchainRouter { ) .route( "/did/chain/:id", - get(resolver::TrustchainHTTPHandler::get_chain_resolution), + get(resolver::TrustchainHTTPHandler::get_chain_resolution) + .layer(ServiceBuilder::new().layer(middleware::from_fn(validate_did))), ) .route( "/did/bundle/:id", - get(resolver::TrustchainHTTPHandler::get_verification_bundle), + get(resolver::TrustchainHTTPHandler::get_verification_bundle) + .layer(ServiceBuilder::new().layer(middleware::from_fn(validate_did))), ) .with_state(shared_state), } From 3859462e5e132e41d4658aded60bc582a717ab04 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Fri, 6 Oct 2023 15:16:50 +0100 Subject: [PATCH 79/87] Add display for wrapped variants, unit test for middleware --- trustchain-core/src/verifier.rs | 6 ++-- trustchain-http/src/middleware.rs | 52 ++++++++++++++++++++++--------- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/trustchain-core/src/verifier.rs b/trustchain-core/src/verifier.rs index af804579..11fdfd3b 100644 --- a/trustchain-core/src/verifier.rs +++ b/trustchain-core/src/verifier.rs @@ -133,13 +133,13 @@ pub enum VerifierError { #[error("A commitment error during verification: {0}")] CommitmentFailure(CommitmentError), /// Wrapped resolver error. - #[error("A resolver error during verification.")] + #[error("A resolver error during verification: {0}")] ResolverFailure(ResolverError), /// Wrapped chain error. - #[error("A chain error during verification.")] + #[error("A chain error during verification: {0}")] ChainFailure(ChainError), /// Wrapped serde JSON deserialization error. - #[error("Failed to deserialize.")] + #[error("Failed to deserialize: {0}")] FailedToDeserialize(serde_json::Error), } diff --git a/trustchain-http/src/middleware.rs b/trustchain-http/src/middleware.rs index fd30441d..ca781f13 100644 --- a/trustchain-http/src/middleware.rs +++ b/trustchain-http/src/middleware.rs @@ -34,14 +34,7 @@ fn error_message(did: &str, expected_prefix: &str) -> serde_json::Value { }) } -// See [example](https://github.com/tokio-rs/axum/blob/v0.6.x/examples/consume-body-in-extractor-or-middleware/src/main.rs) -// from axum with middleware that shows how to consume the request body upfront -pub async fn validate_did( - Path(did): Path, - request: Request, - next: Next, -) -> impl IntoResponse { - tracing::info!(did); +fn validate_did_str(did: &str) -> Result<(), (StatusCode, Json)> { let did_split = did.rsplit_once(':'); if did_split.is_none() { return Err(( @@ -53,7 +46,7 @@ pub async fn validate_did( // Only validate ION DIDs. Allow others to pass. if !did_prefix.ne(&*ION_DID_PREFIX) && !did_prefix.ne(&*ION_DID_TEST_PREFIX) { - return Ok(next.run(request).await); + return Ok(()); } // Validate the DID suffix given established DID method is ION. @@ -67,20 +60,51 @@ pub async fn validate_did( // Validate the ION network prefix if testnet. if ion_config().mongo_database_ion_core.contains("testnet") - && did_prefix == *ION_DID_TEST_PREFIX + && did_prefix != *ION_DID_TEST_PREFIX { return Err(( StatusCode::BAD_REQUEST, - Json(error_message(&did, &ION_DID_TEST_PREFIX)), + Json(error_message(did, &ION_DID_TEST_PREFIX)), )); } // Validate the ION network prefix if mainnet. - if ion_config().mongo_database_ion_core.contains("mainnet") && did_prefix == *ION_DID_PREFIX { + if ion_config().mongo_database_ion_core.contains("mainnet") && did_prefix != *ION_DID_PREFIX { return Err(( StatusCode::BAD_REQUEST, - Json(error_message(&did, &ION_DID_PREFIX)), + Json(error_message(did, &ION_DID_PREFIX)), )); } - Ok(next.run(request).await) + Ok(()) +} +// See [example](https://github.com/tokio-rs/axum/blob/v0.6.x/examples/consume-body-in-extractor-or-middleware/src/main.rs) +// from axum with middleware that shows how to consume the request body upfront +pub async fn validate_did( + Path(did): Path, + request: Request, + next: Next, +) -> impl IntoResponse { + tracing::info!(did); + match validate_did_str(&did) { + Ok(_) => Ok(next.run(request).await), + Err(e) => Err(e), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_strings() { + assert_eq!("did:ion", *ION_DID_PREFIX); + assert_eq!("did:ion:test", *ION_DID_TEST_PREFIX); + } + + #[test] + fn test_valid_did() { + assert!( + validate_did_str("did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q").is_ok() + ) + } } From d9262ba882b6d7e464e82358532d4ac053388498 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Fri, 6 Oct 2023 15:35:46 +0100 Subject: [PATCH 80/87] Update expected error variant --- trustchain-http/src/resolver.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trustchain-http/src/resolver.rs b/trustchain-http/src/resolver.rs index 7ae39f4c..0284dc66 100644 --- a/trustchain-http/src/resolver.rs +++ b/trustchain-http/src/resolver.rs @@ -265,7 +265,7 @@ mod tests { assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR); assert_eq!( response.text().await, - r#"{"error":"Trustchain Verifier error: A resolver error during verification."}"# + r#"{"error":"Trustchain Verifier error: A resolver error during verification: DID: did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65A is not found."}"# .to_string() ); } From 197a21fe27d9edcc9862134a8efcdf5c90f05365 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Fri, 6 Oct 2023 15:44:43 +0100 Subject: [PATCH 81/87] Refine error message from middleware and fix test --- trustchain-http/src/middleware.rs | 4 +++- trustchain-http/src/resolver.rs | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/trustchain-http/src/middleware.rs b/trustchain-http/src/middleware.rs index ca781f13..dc211f9d 100644 --- a/trustchain-http/src/middleware.rs +++ b/trustchain-http/src/middleware.rs @@ -54,7 +54,9 @@ fn validate_did_str(did: &str) -> Result<(), (StatusCode, Json Date: Fri, 6 Oct 2023 15:50:01 +0100 Subject: [PATCH 82/87] Fix comment --- trustchain-http/src/middleware.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trustchain-http/src/middleware.rs b/trustchain-http/src/middleware.rs index dc211f9d..33f3a2eb 100644 --- a/trustchain-http/src/middleware.rs +++ b/trustchain-http/src/middleware.rs @@ -22,7 +22,7 @@ lazy_static! { format!("{}:{}", &*ION_DID_PREFIX, IONTest::NETWORK.unwrap()); } -/// Generates an error message given DID and expected string length. +/// Generates an error message given DID and expected prefix. fn error_message(did: &str, expected_prefix: &str) -> serde_json::Value { json!({ "error": From b3df12b66cea3e45f0c151900a7624087927198c Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Fri, 6 Oct 2023 16:24:48 +0100 Subject: [PATCH 83/87] Remove config from unit test, add error cases --- trustchain-http/src/middleware.rs | 70 ++++++++++++++++++++++++++----- 1 file changed, 60 insertions(+), 10 deletions(-) diff --git a/trustchain-http/src/middleware.rs b/trustchain-http/src/middleware.rs index 33f3a2eb..5a7a1095 100644 --- a/trustchain-http/src/middleware.rs +++ b/trustchain-http/src/middleware.rs @@ -34,7 +34,10 @@ fn error_message(did: &str, expected_prefix: &str) -> serde_json::Value { }) } -fn validate_did_str(did: &str) -> Result<(), (StatusCode, Json)> { +fn validate_did_str( + did: &str, + mongo_database_ion_core: &str, +) -> Result<(), (StatusCode, Json)> { let did_split = did.rsplit_once(':'); if did_split.is_none() { return Err(( @@ -45,7 +48,7 @@ fn validate_did_str(did: &str) -> Result<(), (StatusCode, Json Result<(), (StatusCode, Json Result<(), (StatusCode, Json, ) -> impl IntoResponse { tracing::info!(did); - match validate_did_str(&did) { + match validate_did_str(&did, &ion_config().mongo_database_ion_core) { Ok(_) => Ok(next.run(request).await), Err(e) => Err(e), } @@ -105,8 +106,57 @@ mod tests { #[test] fn test_valid_did() { - assert!( - validate_did_str("did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q").is_ok() - ) + // Ok cases + for (did, network) in [ + ( + "did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q", + "testnet", + ), + ( + "did:ion:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q", + "mainnet", + ), + ( + "did:key:z6MkhG98a8j2d3jqia13vrWqzHwHAgKTv9NjYEgdV3ndbEdD", + "testnet", + ), + ] { + assert!(validate_did_str(did, network).is_ok()); + } + // Error cases + for (did, network) in [ + // Invalid length + ( + "did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65", + "testnet", + ), + // Invalid suffix + ( + "did:ion:test:1iAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q", + "testnet", + ), + // Invalid network + ( + "did:ion:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q", + "testnet", + ), + // Invalid length + ( + "did:ion:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65", + "mainnet", + ), + // Invalid suffix + ( + "did:ion:1iAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q", + "mainnet", + ), + // Invalid network + ( + "did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q", + "mainnet", + ), + ] { + assert!(validate_did_str(did, network).is_err()); + } } } From 22ecdd4ccd9721a1f1dfadb0e56a387db8073f5c Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Wed, 11 Oct 2023 11:39:40 +0100 Subject: [PATCH 84/87] Optional flag for verifiable endpoints in QR codes, revise config --- trustchain-http/src/config.rs | 27 ++++++++++++++++++------- trustchain-http/src/issuer.rs | 36 ++++++++++++++++----------------- trustchain-http/src/qrcode.rs | 1 - trustchain-http/src/verifier.rs | 30 +++++++++++++-------------- 4 files changed, 53 insertions(+), 41 deletions(-) diff --git a/trustchain-http/src/config.rs b/trustchain-http/src/config.rs index 6bae7a03..15ae878b 100644 --- a/trustchain-http/src/config.rs +++ b/trustchain-http/src/config.rs @@ -18,16 +18,20 @@ pub struct HTTPConfig { /// Host address for server. For example, Android emulator `10.0.2.2` refers to `127.0.0.1` of /// machine running emulator. pub host: IpAddr, - /// Hostname reference. For example, Android emulator 10.0.2.2 refers to 127.0.0.1 of machine running emulator. + /// Hostname reference. For example, Android emulator 10.0.2.2 refers to 127.0.0.1 of machine + /// running emulator. pub host_display: String, /// Port for server pub port: u16, - /// Optional issuer DID - pub issuer_did: Option, + /// Optional server DID if issuing or verifying + pub server_did: Option, /// Flag indicating whether server uses https pub https: bool, /// Path containing certificate and key necessary for https pub https_path: Option, + /// Display downstream DIDs (instead of URLs) in QR codes for verifiable endpoint retrieval + /// (`None` by default and unwrapped as `true`) + pub verifiable_endpoints: Option, /// Root event time for verifier. pub root_event_time: Option, } @@ -44,9 +48,10 @@ impl Default for HTTPConfig { host: IpAddr::from_str(DEFAULT_HOST).unwrap(), host_display: DEFAULT_HOST.to_string(), port: DEFAULT_PORT, - issuer_did: None, + server_did: None, https: false, https_path: None, + verifiable_endpoints: None, root_event_time: None, } } @@ -63,6 +68,14 @@ impl HTTPConfig { .parse::() .unwrap() } + /// Provide "http" or "https" according to config. + pub fn http_scheme(&self) -> &str { + if self.https { + "https" + } else { + "http" + } + } } lazy_static! { @@ -102,7 +115,7 @@ mod tests { host = "127.0.0.1" host_display = "127.0.0.1" port = 8081 - issuer_did = "did:ion:test:EiBcLZcELCKKtmun_CUImSlb2wcxK5eM8YXSq3MrqNe5wA" + server_did = "did:ion:test:EiBcLZcELCKKtmun_CUImSlb2wcxK5eM8YXSq3MrqNe5wA" https = false [non_http] @@ -110,11 +123,11 @@ mod tests { "#; let config: HTTPConfig = parse_toml(config_string); - + assert!(config.verifiable_endpoints.is_none()); assert_eq!( config, HTTPConfig { - issuer_did: Some( + server_did: Some( "did:ion:test:EiBcLZcELCKKtmun_CUImSlb2wcxK5eM8YXSq3MrqNe5wA".to_string() ), ..HTTPConfig::default() diff --git a/trustchain-http/src/issuer.rs b/trustchain-http/src/issuer.rs index ab0c6b7b..d4b66252 100644 --- a/trustchain-http/src/issuer.rs +++ b/trustchain-http/src/issuer.rs @@ -128,23 +128,23 @@ impl TrustchainIssuerHTTPHandler { State(app_state): State>, Path(id): Path, ) -> Result, TrustchainHTTPError> { - let http_str = if !http_config().https { - "http" + let qr_code_str = if http_config().verifiable_endpoints.unwrap_or(true) { + serde_json::to_string(&DIDQRCode { + did: app_state.config.server_did.as_ref().unwrap().to_owned(), + route: "/vc/issuer/".to_string(), + uuid: id, + }) + .unwrap() } else { - "https" + format!( + "{}://{}:{}/vc/issuer/{id}", + http_config().http_scheme(), + app_state.config.host_display, + app_state.config.port + ) }; - let did_qr_code_encoded = serde_json::to_string(&DIDQRCode { - did: app_state.config.issuer_did.as_ref().unwrap().to_owned(), - route: "/vc/issuer/".to_string(), - uuid: id, - endpoint: format!( - "{}://{}:{}", - http_str, app_state.config.host_display, app_state.config.port - ), - }) - .unwrap(); // Respond with the QR code as a png embedded in html - Ok(Html(str_to_qr_code_html(&did_qr_code_encoded, "Issuer"))) + Ok(Html(str_to_qr_code_html(&qr_code_str, "Issuer"))) } /// API endpoint taking the UUID of a VC. Response is the VC JSON. @@ -154,7 +154,7 @@ impl TrustchainIssuerHTTPHandler { ) -> impl IntoResponse { let issuer_did = app_state .config - .issuer_did + .server_did .as_ref() .ok_or(TrustchainHTTPError::NoCredentialIssuer)?; @@ -181,7 +181,7 @@ impl TrustchainIssuerHTTPHandler { info!("Received VC info: {:?}", vc_info); let issuer_did = app_state .config - .issuer_did + .server_did .as_ref() .ok_or(TrustchainHTTPError::NoCredentialIssuer)?; match app_state.credentials.get(&credential_id) { @@ -222,7 +222,7 @@ mod tests { lazy_static! { /// Lazy static reference to core configuration loaded from `trustchain_config.toml`. pub static ref TEST_HTTP_CONFIG: HTTPConfig = HTTPConfig { - issuer_did: Some("did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q".to_string()), + server_did: Some("did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q".to_string()), ..Default::default() }; } @@ -269,7 +269,7 @@ mod tests { let mut actual_offer = response.json::().await; let mut credential = state.credentials.get(&uid).unwrap().clone(); credential.issuer = Some(ssi::vc::Issuer::URI(ssi::vc::URI::String( - state.config.issuer_did.as_ref().unwrap().to_string(), + state.config.server_did.as_ref().unwrap().to_string(), ))); let mut expected_offer = CredentialOffer::generate(&credential, &uid); diff --git a/trustchain-http/src/qrcode.rs b/trustchain-http/src/qrcode.rs index 6560ed8f..0fafd583 100644 --- a/trustchain-http/src/qrcode.rs +++ b/trustchain-http/src/qrcode.rs @@ -12,7 +12,6 @@ pub(crate) struct DIDQRCode { pub did: String, pub route: String, pub uuid: String, - pub endpoint: String, } pub fn image_to_base64_string(image: &DynamicImage) -> String { diff --git a/trustchain-http/src/verifier.rs b/trustchain-http/src/verifier.rs index bca4158d..1eb32a5e 100644 --- a/trustchain-http/src/verifier.rs +++ b/trustchain-http/src/verifier.rs @@ -156,24 +156,24 @@ impl TrustchainVerifierHTTPHandler { .next() .ok_or(TrustchainHTTPError::RequestDoesNotExist) .map(|(uid, _)| { - let http_str = if !http_config().https { - "http" + let qr_code_str = if http_config().verifiable_endpoints.unwrap_or(true) { + serde_json::to_string(&DIDQRCode { + did: app_state.config.server_did.as_ref().unwrap().to_owned(), + route: "/vc/verifier/".to_string(), + uuid: uid.to_owned(), + }) + .unwrap() } else { - "https" + format!( + "{}://{}:{}/vc/verifier/{uid}", + http_config().http_scheme(), + app_state.config.host_display, + app_state.config.port + ) }; - let did_qr_code_encoded = serde_json::to_string(&DIDQRCode { - did: app_state.config.issuer_did.as_ref().unwrap().to_owned(), - route: "/vc/verifier/".to_string(), - uuid: uid.to_owned(), - endpoint: format!( - "{}://{}:{}", - http_str, app_state.config.host_display, app_state.config.port - ), - }) - .unwrap(); ( StatusCode::OK, - Html(str_to_qr_code_html(&did_qr_code_encoded, "Verifier")), + Html(str_to_qr_code_html(&qr_code_str, "Verifier")), ) }) } @@ -194,7 +194,7 @@ mod tests { lazy_static! { /// Lazy static reference to core configuration loaded from `trustchain_config.toml`. pub static ref TEST_HTTP_CONFIG: HTTPConfig = HTTPConfig { - issuer_did: Some("did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q".to_string()), + server_did: Some("did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q".to_string()), root_event_time: Some(1666265405), ..Default::default() }; From d1839ce87d03b61c880c9515fd1dd0d22648b2fe Mon Sep 17 00:00:00 2001 From: Sam Greenbury <50113363+sgreenbury@users.noreply.github.com> Date: Wed, 11 Oct 2023 11:43:12 +0100 Subject: [PATCH 85/87] Revise doc comments --- trustchain-http/src/config.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/trustchain-http/src/config.rs b/trustchain-http/src/config.rs index 15ae878b..67cf238c 100644 --- a/trustchain-http/src/config.rs +++ b/trustchain-http/src/config.rs @@ -15,10 +15,11 @@ const DEFAULT_PORT: u16 = 8081; /// HTTP configuration. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct HTTPConfig { - /// Host address for server. For example, Android emulator `10.0.2.2` refers to `127.0.0.1` of + /// Host address for server. /// machine running emulator. pub host: IpAddr, - /// Hostname reference. For example, Android emulator 10.0.2.2 refers to 127.0.0.1 of machine + /// Hostname display in QR codes. For example, if using local server with an Android emulator + /// `10.0.2.2` refers to `127.0.0.1` of machine /// running emulator. pub host_display: String, /// Port for server From 9ca022c6a1a8adda2b0809300ea8a4d67d294cf8 Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Wed, 11 Oct 2023 11:52:03 +0100 Subject: [PATCH 86/87] Add doc comments --- trustchain-http/src/qrcode.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/trustchain-http/src/qrcode.rs b/trustchain-http/src/qrcode.rs index 0fafd583..1fe57ec4 100644 --- a/trustchain-http/src/qrcode.rs +++ b/trustchain-http/src/qrcode.rs @@ -9,8 +9,11 @@ use serde::{Deserialize, Serialize}; #[serde(rename_all = "camelCase")] /// QR code JSON type of verifiable content. pub(crate) struct DIDQRCode { + /// DID for issuance and/or verifier endpoint resolution. pub did: String, + /// HTTP route for making issuer or verifer requests. pub route: String, + /// UUID for identifying credential offer or presentation request. pub uuid: String, } From 7829b4165e57047f506a193b3404e2e1ef7b0dda Mon Sep 17 00:00:00 2001 From: Sam Greenbury Date: Wed, 11 Oct 2023 12:59:34 +0100 Subject: [PATCH 87/87] Use id for issuer and verifier requests consistently --- trustchain-http/src/issuer.rs | 14 +++++++------- trustchain-http/src/qrcode.rs | 4 ++-- trustchain-http/src/verifier.rs | 32 ++++++++++++++++---------------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/trustchain-http/src/issuer.rs b/trustchain-http/src/issuer.rs index d4b66252..379a937f 100644 --- a/trustchain-http/src/issuer.rs +++ b/trustchain-http/src/issuer.rs @@ -132,7 +132,7 @@ impl TrustchainIssuerHTTPHandler { serde_json::to_string(&DIDQRCode { did: app_state.config.server_did.as_ref().unwrap().to_owned(), route: "/vc/issuer/".to_string(), - uuid: id, + id, }) .unwrap() } else { @@ -284,11 +284,11 @@ mod tests { ); // Try to get an offer for non-existent credential - let uid = "46cb84e2-fa10-11ed-a0d4-bbb4e61d1555".to_string(); - let uri = format!("/vc/issuer/{uid}"); + let id = "46cb84e2-fa10-11ed-a0d4-bbb4e61d1555".to_string(); + let path = format!("/vc/issuer/{id}"); let app = TrustchainRouter::from(state.clone()).into_router(); let client = TestClient::new(app); - let response = client.get(&uri).send().await; + let response = client.get(&path).send().await; assert_eq!(response.status(), StatusCode::BAD_REQUEST); assert_eq!( response.text().await, @@ -305,12 +305,12 @@ mod tests { HashMap::new(), ))) .into_router(); - let uid = "46cb84e2-fa10-11ed-a0d4-bbb4e61d1556".to_string(); + let id = "46cb84e2-fa10-11ed-a0d4-bbb4e61d1556".to_string(); let expected_subject_id = "did:example:284b3f34fad911ed9aea439566dd422a".to_string(); - let uri = format!("/vc/issuer/{uid}"); + let path = format!("/vc/issuer/{id}"); let client = TestClient::new(app); let response = client - .post(&uri) + .post(&path) .json(&VcInfo { subject_id: expected_subject_id.to_string(), }) diff --git a/trustchain-http/src/qrcode.rs b/trustchain-http/src/qrcode.rs index 1fe57ec4..1b6908df 100644 --- a/trustchain-http/src/qrcode.rs +++ b/trustchain-http/src/qrcode.rs @@ -13,8 +13,8 @@ pub(crate) struct DIDQRCode { pub did: String, /// HTTP route for making issuer or verifer requests. pub route: String, - /// UUID for identifying credential offer or presentation request. - pub uuid: String, + /// ID for identifying credential offer or presentation request. + pub id: String, } pub fn image_to_base64_string(image: &DynamicImage) -> String { diff --git a/trustchain-http/src/verifier.rs b/trustchain-http/src/verifier.rs index 1eb32a5e..d2477987 100644 --- a/trustchain-http/src/verifier.rs +++ b/trustchain-http/src/verifier.rs @@ -88,12 +88,12 @@ pub enum PresentationOrCredential { impl TrustchainVerifierHTTPHandler { /// API endpoint taking the UUID of a presentation request. pub async fn get_verifier( - Path(request_id): Path, + Path(id): Path, State(app_state): State>, ) -> impl IntoResponse { app_state .presentation_requests - .get(&request_id) + .get(&id) .ok_or(TrustchainHTTPError::RequestDoesNotExist) .map(|request| (StatusCode::OK, Json(request.to_owned()))) } @@ -160,7 +160,7 @@ impl TrustchainVerifierHTTPHandler { serde_json::to_string(&DIDQRCode { did: app_state.config.server_did.as_ref().unwrap().to_owned(), route: "/vc/verifier/".to_string(), - uuid: uid.to_owned(), + id: uid.to_owned(), }) .unwrap() } else { @@ -323,25 +323,25 @@ mod tests { )); // Test response for request in cache let app = TrustchainRouter::from(state.clone()).into_router(); - let uid = "b9519df2-35c1-11ee-8314-7f66e4585b4f"; - let uri = format!("/vc/verifier/{uid}"); + let id = "b9519df2-35c1-11ee-8314-7f66e4585b4f"; + let path = format!("/vc/verifier/{id}"); let client = TestClient::new(app); - let response = client.get(&uri).send().await; + let response = client.get(&path).send().await; // Test response is OK assert_eq!(response.status(), StatusCode::OK); // Test response json same as cache - let expected_request = state.presentation_requests.get(uid).unwrap(); + let expected_request = state.presentation_requests.get(id).unwrap(); let actual_request = response.json::().await; assert_eq!(&actual_request, expected_request); // Test response for non-existent request let app = TrustchainRouter::from(state.clone()).into_router(); - let uid = "dd2f6d68-35c5-11ee-98c7-d317dc01648b"; - let uri = format!("/vc/verifier/{uid}"); + let id = "dd2f6d68-35c5-11ee-98c7-d317dc01648b"; + let path = format!("/vc/verifier/{id}"); let client = TestClient::new(app); - let response = client.get(&uri).send().await; + let response = client.get(&path).send().await; assert_eq!(response.status(), StatusCode::BAD_REQUEST); assert_eq!( response.text().await, @@ -359,12 +359,12 @@ mod tests { )); // Test post of credential to verifier let app = TrustchainRouter::from(state.clone()).into_router(); - let uid = "b9519df2-35c1-11ee-8314-7f66e4585b4f"; - let uri = format!("/vc/verifier/{uid}"); + let id = "b9519df2-35c1-11ee-8314-7f66e4585b4f"; + let path = format!("/vc/verifier/{id}"); let client = TestClient::new(app); let post_verifier: PostVerifier = serde_json::from_str(TEST_POST_VERIFIER_CREDENTIAL).unwrap(); - let response = client.post(&uri).json(&post_verifier).send().await; + let response = client.post(&path).json(&post_verifier).send().await; assert_eq!(response.status(), StatusCode::OK); assert_eq!("Credential received and verified!", response.text().await); } @@ -379,12 +379,12 @@ mod tests { )); // Test post of presentation to verifier let app = TrustchainRouter::from(state.clone()).into_router(); - let uid = "b9519df2-35c1-11ee-8314-7f66e4585b4f"; - let uri = format!("/vc/verifier/{uid}"); + let id = "b9519df2-35c1-11ee-8314-7f66e4585b4f"; + let path = format!("/vc/verifier/{id}"); let client = TestClient::new(app); let post_verifier: PostVerifier = serde_json::from_str(TEST_POST_VERIFIER_PRESENTATION).unwrap(); - let response = client.post(&uri).json(&post_verifier).send().await; + let response = client.post(&path).json(&post_verifier).send().await; assert_eq!(response.status(), StatusCode::OK); assert_eq!("Presentation received and verified!", response.text().await); }