Skip to content

Commit

Permalink
Fix keys retrieval for VC API (#362)
Browse files Browse the repository at this point in the history
Also fix rust-cache in the CI and disable malfunctioning test suite
  • Loading branch information
sbihel committed Jun 30, 2023
1 parent e302eb0 commit a85668d
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 12 deletions.
12 changes: 11 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ jobs:

- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: |
didkit
- name: Install Rust old stable with incremental compilation
uses: actions-rs/toolchain@v1
Expand Down Expand Up @@ -128,7 +131,8 @@ jobs:
matrix:
# di-ed25519-test-suite fails one test because issuer != proof VM
# did-key-test-suite requires more checks in ssi
suite: ["vc-api-issuer-test-suite", "di-eddsa-2022-test-suite", "vc-api-verifier-test-suite"] # "di-ed25519-test-suite", "did-key-test-suite"]
# di-eddsa-2022-test-suite passes but currently it's depending on a (npm) git branch that doesn't exist anymore
suite: ["vc-api-issuer-test-suite", "vc-api-verifier-test-suite"] # "di-eddsa-2022-test-suite", "di-ed25519-test-suite", "did-key-test-suite"]
steps:
- name: Checkout DIDKit repository
uses: actions/checkout@v3
Expand Down Expand Up @@ -187,6 +191,9 @@ jobs:

- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: |
didkit
- name: Install Rust iOS targets
run: make -C lib install-rustup-ios
Expand All @@ -212,6 +219,9 @@ jobs:

- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: |
didkit
- name: Build
run: cargo build
70 changes: 69 additions & 1 deletion http/src/credentials.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,14 @@ pub async fn issue(
let proof_format = options.proof_format.unwrap_or_default();
let resolver = DID_METHODS.to_resolver();
let mut context_loader = ContextLoader::default();
let key = match pick_key(&keys, &options.ldp_options, resolver).await {
let key = match pick_key(
&keys,
&credential.issuer.clone().map(|i| i.get_id()),
&options.ldp_options,
resolver,
)
.await
{
Some(key) => key,
None => return Err((StatusCode::NOT_FOUND, "Missing key".to_string()).into()),
};
Expand Down Expand Up @@ -123,3 +130,64 @@ pub async fn verify(
}
Ok(Json(res))
}

#[cfg(test)]
mod test {
use serde_json::json;

use crate::test::default_keys;

use super::*;

#[tokio::test]
async fn issue_ed25519() {
let keys = default_keys();
let req = serde_json::from_value(json!({
"credential": {
"@context": [
"https://www.w3.org/2018/credentials/v1"
],
"id": "urn:uuid:040d4921-4756-447b-99ad-8d4978420e91",
"type": [
"VerifiableCredential"
],
"issuer": "did:key:z6MkgYAGxLBSXa6Ygk1PnUbK2F7zya8juE9nfsZhrvY7c9GD",
"credentialSubject": {
"id": "did:key:z6MktKwz7Ge1Yxzr4JHavN33wiwa8y81QdcMRLXQsrH9T53b"
}
},
"options": {
"type": "DataIntegrityProof"
}
}))
.unwrap();

let _ = issue(Extension(keys), CustomErrorJson(req)).await.unwrap();
}

#[tokio::test]
async fn issue_p256() {
let keys = default_keys();
let req = serde_json::from_value(json!({
"credential": {
"@context": [
"https://www.w3.org/2018/credentials/v1"
],
"id": "urn:uuid:040d4921-4756-447b-99ad-8d4978420e91",
"type": [
"VerifiableCredential"
],
"issuer": "did:key:zDnaej4NHTz2DtpMByubtLGzZfEjYor4ffJWLuW2eJ4KkZ3r2",
"credentialSubject": {
"id": "did:key:z6MktKwz7Ge1Yxzr4JHavN33wiwa8y81QdcMRLXQsrH9T53b"
}
},
"options": {
"type": "DataIntegrityProof"
}
}))
.unwrap();

let _ = issue(Extension(keys), CustomErrorJson(req)).await.unwrap();
}
}
102 changes: 93 additions & 9 deletions http/src/keys.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,111 @@
use std::collections::HashMap;

use didkit::{resolve_key, DIDResolver, LinkedDataProofOptions, JWK};
use didkit::{resolve_key, DIDResolver, LinkedDataProofOptions, Source, DID_METHODS, JWK};

pub type KeyMap = HashMap<JWK, JWK>;

pub async fn pick_key<'a>(
keys: &'a KeyMap,
issuer: &Option<String>,
options: &LinkedDataProofOptions,
did_resolver: &dyn DIDResolver,
) -> Option<&'a JWK> {
if keys.len() <= 1 {
return keys.values().next();
}
let vm = match options.verification_method {
Some(ref verification_method) => verification_method.to_string(),
None => return keys.values().next(),
};
let public_key = match resolve_key(&vm, did_resolver).await {
Err(_err) => {
// TODO: return error
let public_key = match (issuer, options.verification_method.clone()) {
(_, Some(vm)) => {
match resolve_key(&vm.to_string(), did_resolver).await {
Err(_err) => {
// TODO: return error
return None;
}
Ok(key) => key,
}
}
(Some(issuer), None) => {
let method = match DID_METHODS.get_method(issuer) {
Ok(m) => m,
Err(_) => {
return None;
}
};
for jwk in keys.keys() {
let did = match method.generate(&Source::Key(jwk)) {
Some(d) => d,
None => continue,
};
if &did == issuer {
return keys.get(jwk);
}
}
return None;
}
Ok(key) => key,
(None, None) => return keys.values().next(),
};
keys.get(&public_key)
}

#[cfg(test)]
mod test {
use didkit::URI;

use crate::test::default_keys;

use super::*;

#[tokio::test]
async fn pick_key_only_issuer() {
let keys = default_keys();

let p256_did = "did:key:zDnaej4NHTz2DtpMByubtLGzZfEjYor4ffJWLuW2eJ4KkZ3r2".to_string();
let ed25519_did = "did:key:z6MkgYAGxLBSXa6Ygk1PnUbK2F7zya8juE9nfsZhrvY7c9GD".to_string();

let options = LinkedDataProofOptions::default();

let key1 = pick_key(&keys, &Some(p256_did), &options, DID_METHODS.to_resolver())
.await
.unwrap();
let key2 = pick_key(
&keys,
&Some(ed25519_did),
&options,
DID_METHODS.to_resolver(),
)
.await
.unwrap();

assert_ne!(key1, key2);
}

#[tokio::test]
async fn pick_key_ldp_options() {
let keys = default_keys();

let p256_did = "did:key:zDnaej4NHTz2DtpMByubtLGzZfEjYor4ffJWLuW2eJ4KkZ3r2".to_string();

let options = LinkedDataProofOptions {
verification_method: Some(URI::String("did:key:zDnaej4NHTz2DtpMByubtLGzZfEjYor4ffJWLuW2eJ4KkZ3r2#zDnaej4NHTz2DtpMByubtLGzZfEjYor4ffJWLuW2eJ4KkZ3r2".to_string())),
..Default::default()
};

let key1 = pick_key(
&keys,
&Some(p256_did.clone()),
&options,
DID_METHODS.to_resolver(),
)
.await
.unwrap();
let key2 = pick_key(
&keys,
&Some(p256_did),
&LinkedDataProofOptions::default(),
DID_METHODS.to_resolver(),
)
.await
.unwrap();

assert_eq!(key1, key2);
}
}
7 changes: 7 additions & 0 deletions http/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ async fn main() {

#[cfg(test)]
mod test {
use didkit::JWK;
use figment::providers::Format;
use serde_json::json;

use super::*;

Expand All @@ -95,4 +97,9 @@ mod test {
fn can_generate_default_config() {
default_config();
}

pub fn default_keys() -> KeyMap {
let keys: Vec<JWK> = serde_json::from_value(json!([{"kty":"OKP","crv":"Ed25519","x":"HvjBEw94RHAh9KkiD385aYZNxGkxIkwBcrLBY5Z7Koo","d":"1onWu34oC29Y09qCRl0aD2FOp5y5obTqHZxQQRT3-bs"}, {"kty":"EC","crv":"P-256","x":"FMWMt6D0SymYPdlxXzeGMo1OrZLTrZ44aaW0_gyqCZM","y":"3DOY-ceh9ivyq9CzrmWR67ILrC7e3_FegeBxixWoiYc","d":"DjD-ngByYFcS6bfmofNeT7WNJBtWcO2GnGHJq1S9zkU"}])).unwrap();
keys.into_iter().map(|jwk| (jwk.to_public(), jwk)).collect()
}
}
9 changes: 8 additions & 1 deletion http/src/presentations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,14 @@ pub async fn issue(
let proof_format = options.proof_format.unwrap_or_default();
let resolver = DID_METHODS.to_resolver();
let mut context_loader = ContextLoader::default();
let key = match pick_key(&keys, &options.ldp_options, resolver).await {
let key = match pick_key(
&keys,
&presentation.holder.clone().map(String::from),
&options.ldp_options,
resolver,
)
.await
{
Some(key) => key,
None => return Err((StatusCode::NOT_FOUND, "Missing key".to_string()).into()),
};
Expand Down

0 comments on commit a85668d

Please sign in to comment.