Skip to content

Commit

Permalink
Merge pull request #127 from alan-turing-institute/85-ssi-0.6.0
Browse files Browse the repository at this point in the history
Update to recent SSI (#85)
  • Loading branch information
sgreenbury authored Oct 5, 2023
2 parents bcac76b + 924b23d commit d523744
Show file tree
Hide file tree
Showing 21 changed files with 267 additions and 115 deletions.
4 changes: 2 additions & 2 deletions trustchain-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ trustchain-ion = { path = "../trustchain-ion" }

async-trait = "0.1"
serde_json = "1.0"
ssi = "0.4"
did-ion = "0.1.0"
ssi = {git="https://github.com/alan-turing-institute/ssi.git", branch="modify-encode-sign-jwt", features = ["http-did", "secp256k1"]}
did-ion = {git="https://github.com/alan-turing-institute/ssi.git", branch="modify-encode-sign-jwt"}
futures = "0.3.28"

[dev-dependencies]
Expand Down
118 changes: 79 additions & 39 deletions trustchain-api/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use did_ion::sidetree::DocumentState;
use futures::{stream, StreamExt, TryStreamExt};
use ssi::{
did_resolve::DIDResolver,
jsonld::ContextLoader,
ldp::LinkedDataDocument,
vc::{Credential, CredentialOrJWT, URI},
vc::{LinkedDataProofOptions, Presentation},
Expand Down Expand Up @@ -92,11 +93,18 @@ pub trait TrustchainVCAPI {
linked_data_proof_options: Option<LinkedDataProofOptions>,
key_id: Option<&str>,
resolver: &T,
context_loader: &mut ContextLoader,
) -> Result<Credential, IssuerError> {
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
}

Expand All @@ -106,14 +114,19 @@ pub trait TrustchainVCAPI {
linked_data_proof_options: Option<LinkedDataProofOptions>,
root_event_time: Timestamp,
verifier: &U,
context_loader: &mut ContextLoader,
) -> Result<DIDChain, CredentialError>
where
T: DIDResolver + Send,
U: Verifier<T> + Send + Sync,
{
// Verify signature
let result = credential
.verify(linked_data_proof_options, verifier.resolver())
.verify(
linked_data_proof_options,
verifier.resolver(),
context_loader,
)
.await;
if !result.errors.is_empty() {
return Err(CredentialError::VerificationResultError(result));
Expand All @@ -135,11 +148,18 @@ pub trait TrustchainVPAPI {
key_id: Option<&str>,
endpoint: &str,
linked_data_proof_options: Option<LinkedDataProofOptions>,
context_loader: &mut ContextLoader,
) -> Result<Presentation, PresentationError> {
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.
Expand All @@ -148,6 +168,7 @@ pub trait TrustchainVPAPI {
ldp_options: Option<LinkedDataProofOptions>,
root_event_time: Timestamp,
verifier: &IONVerifier<T>,
context_loader: &mut ContextLoader,
) -> Result<(), PresentationError> {
// Check credentials are present in presentation
let credentials = presentation
Expand All @@ -158,50 +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<Option<LinkedDataProofOptions>> = (0..credentials.len())
.map(|_| ldp_options.clone())
let ldp_opts_and_context_loader: Vec<(Option<LinkedDataProofOptions>, 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(),
)
.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())
.verify(ldp_options.clone(), verifier.resolver(), context_loader)
.await;
if !result.errors.is_empty() {
return Err(PresentationError::VerifiedHolderUnauthenticated(result));
Expand All @@ -214,7 +244,8 @@ pub trait TrustchainVPAPI {
mod tests {
use crate::api::{TrustchainVCAPI, TrustchainVPAPI};
use crate::TrustchainAPI;
use ssi::ldp::now_ms;
use ssi::jsonld::ContextLoader;
use ssi::ldp::now_ns;
use ssi::one_or_many::OneOrMany;
use ssi::vc::{Credential, CredentialOrJWT, Presentation, VCDateTime};
use trustchain_core::utils::init;
Expand Down Expand Up @@ -262,17 +293,19 @@ 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());

// 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/");
Expand All @@ -281,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() {
Expand All @@ -302,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"}"#;
Expand Down Expand Up @@ -349,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());
Expand All @@ -358,6 +393,7 @@ mod tests {
None,
ROOT_EVENT_TIME_1,
&IONVerifier::new(resolver),
&mut context_loader,
)
.await;
println!("{:?}", res);
Expand Down Expand Up @@ -388,6 +424,7 @@ mod tests {
None,
ROOT_EVENT_TIME_1,
&IONVerifier::new(resolver),
&mut ContextLoader::default()
)
.await,
Err(PresentationError::VerifiedHolderUnauthenticated(..))
Expand All @@ -398,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()
}
}
4 changes: 2 additions & 2 deletions trustchain-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ trustchain-ion = { path = "../trustchain-ion" }
trustchain-api = { path = "../trustchain-api" }

clap = { version = "4.0.32", features=["derive", "cargo"] }
did-ion = "0.1.0"
did-ion = {git="https://github.com/alan-turing-institute/ssi.git", branch="modify-encode-sign-jwt"}
lazy_static="1.4.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
ssi = "0.4"
ssi = {git="https://github.com/alan-turing-institute/ssi.git", branch="modify-encode-sign-jwt", features = ["http-did", "secp256k1"]}
tokio = {version = "1.20.1", features = ["full"]}
toml="0.7.2"
18 changes: 13 additions & 5 deletions trustchain-cli/src/bin/main.rs
Original file line number Diff line number Diff line change
@@ -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},
Expand Down Expand Up @@ -87,6 +87,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
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() {
Expand Down Expand Up @@ -170,10 +171,16 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
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)) => {
Expand All @@ -196,6 +203,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
None,
root_event_time,
&verifier,
&mut context_loader,
)
.await;
// Handle result
Expand Down
4 changes: 2 additions & 2 deletions trustchain-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ async-trait = "0.1"
base64 = "0.13"
canonical_json = "0.4.0"
chrono = "0.4"
did-method-key = "0.1.3"
did-method-key = {git="https://github.com/alan-turing-institute/ssi.git", branch="modify-encode-sign-jwt"}
futures = "0.3.21"
petgraph = {version = "0.6"}
serde = { version = "1.0", features = ["derive"] }
serde_jcs = "0.1.0"
serde_json = "1.0"
sha2 = "0.10.7"
ssi = { version = "0.4", 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"]}
Expand Down
23 changes: 17 additions & 6 deletions trustchain-core/src/holder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@ 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;

/// An error relating to a Trustchain holder.
#[derive(Error, Debug)]
pub enum HolderError {
/// Wrapped error for SSI error.
#[error("A wrapped variant for an SSI error: {0}")]
SSI(ssi::error::Error),
/// Wrapped error for ssi-vc error.
#[error("A wrapped variant for an SSI VC error: {0}")]
VC(ssi::vc::Error),
/// Wrapped error for ssi-ldp error.
#[error("A wrapped variant for an SSI LDP error: {0}")]
LDP(ssi::ldp::Error),
/// Wrapped error for key manager error.
#[error("A wrapped variant for a key manager error: {0}")]
KeyManager(KeyManagerError),
Expand All @@ -20,9 +24,15 @@ pub enum HolderError {
MismatchedHolder,
}

impl From<ssi::error::Error> for HolderError {
fn from(err: ssi::error::Error) -> Self {
HolderError::SSI(err)
impl From<ssi::vc::Error> for HolderError {
fn from(err: ssi::vc::Error) -> Self {
HolderError::VC(err)
}
}

impl From<ssi::ldp::Error> for HolderError {
fn from(err: ssi::ldp::Error) -> Self {
HolderError::LDP(err)
}
}

Expand All @@ -44,5 +54,6 @@ pub trait Holder: Subject {
linked_data_proof_options: Option<LinkedDataProofOptions>,
key_id: Option<&str>,
resolver: &T,
context_loader: &mut ContextLoader,
) -> Result<Presentation, HolderError>;
}
Loading

0 comments on commit d523744

Please sign in to comment.