Skip to content

Commit

Permalink
Merge pull request #54 from ADORSYS-GIS/iss-36--plugin-arch
Browse files Browse the repository at this point in the history
Bring about a plugin-oriented generic server
  • Loading branch information
francis-pouatcha authored Oct 10, 2023
2 parents c48dedd + 6334519 commit e7bb167
Show file tree
Hide file tree
Showing 32 changed files with 948 additions and 334 deletions.
25 changes: 18 additions & 7 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,23 @@ jobs:
- name: Build and Test Did Utils
run: |
cd did-utils
cargo build --verbose
cargo test --verbose
cargo build
cargo test
- name: Build and Test Sample Pop Server
- name: Build and Test Did Endpoint
run: |
cd sample-pop-server
cargo build --verbose
cargo run --bin didgen
cargo test --verbose
cd did-endpoint
cargo build
cargo test
- name: Build and Test Generic Server
run: |
cd generic-server
cargo build
cargo test
- name: Build and Mediator Server
run: |
cd mediator-server
cargo build
cargo test
2 changes: 2 additions & 0 deletions did-endpoint/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
STORAGE_DIRPATH="target/storage"
SERVER_PUBLIC_DOMAIN="example.com"
28 changes: 28 additions & 0 deletions did-endpoint/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[package]
name = "did-endpoint"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
axum = { version = "0.6.20" }
chrono = { version = "0.4.26" }
did-utils = { path = "../did-utils"}
dotenv-flow = "0.15.0"
hyper = { version = "0.14.27", features = ["full"] }
multibase = { version = "0.8.0" } # earlier version due to 'did-utils'
serde_json = "1.0.104"
thiserror = "1.0.49"
tokio = { version = "1.30.0", features = ["full"] }
tracing = "0.1.37"
url = { version = "2.4.0" }
uuid = { version = "1.4.1", features = ["v4"] }
zeroize = { version = "1.6.0" }

# Plugins traits
server-plugin = { path = "../server-plugin" }

[dev-dependencies]
json-canon = "0.1.3"
tower = { version = "0.4.13", features = ["util"] }
328 changes: 328 additions & 0 deletions did-endpoint/src/didgen.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,328 @@
use crate::util::{didweb, KeyStore};
use did_utils::{
didcore::{
AssertionMethod, Authentication, Document, Jwk, KeyAgreement, KeyFormat, Service,
VerificationMethod,
},
ldmodel::Context,
};
use std::path::Path;

#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("KeyGenerationError")]
KeyGenerationError,
#[error("MissingServerPublicDomain")]
MissingServerPublicDomain,
#[error("DidAddressDerivationError")]
DidAddressDerivationError,
#[error("PersistenceError")]
PersistenceError,
#[error("Generic: {0}")]
Generic(String),
}

/// Generates keys and forward them for DID generation
///
/// All persistence is handled at `storage_dirpath`.
pub fn didgen(storage_dirpath: &str, server_public_domain: &str) -> Result<Document, Error> {
// Create a new store, which is timestamp-aware
let mut store = KeyStore::new(storage_dirpath);
tracing::info!("keystore: {}", store.path());

// Generate authentication key
tracing::debug!("generating authentication key");
let authentication_key = store
.gen_ed25519_jwk()
.map_err(|_| Error::KeyGenerationError)?;

// Generate assertion key
tracing::debug!("generating assertion key");
let assertion_key = store
.gen_ed25519_jwk()
.map_err(|_| Error::KeyGenerationError)?;

// Generate agreement key
tracing::debug!("generating agreement key");
let agreement_key = store
.gen_x25519_jwk()
.map_err(|_| Error::KeyGenerationError)?;

// Build DID document
let diddoc = gen_diddoc(
storage_dirpath,
server_public_domain,
authentication_key,
assertion_key,
agreement_key,
)?;

// Mark successful completion
tracing::debug!("successful completion");
Ok(diddoc)
}

/// Builds and persists DID document
fn gen_diddoc(
storage_dirpath: &str,
server_public_domain: &str,
authentication_key: Jwk,
assertion_key: Jwk,
agreement_key: Jwk,
) -> Result<Document, Error> {
tracing::info!("building DID document");

// Prepare DID address

let did = didweb::url_to_did_web_id(server_public_domain)
.map_err(|_| Error::DidAddressDerivationError)?;

// Prepare authentication verification method

let authentication_method = VerificationMethod {
public_key: Some(KeyFormat::Jwk(authentication_key)),
..VerificationMethod::new(
did.clone() + "#keys-1",
String::from("JsonWebKey2020"),
did.clone(),
)
};

// Prepare assertion verification method

let assertion_method = VerificationMethod {
public_key: Some(KeyFormat::Jwk(assertion_key)),
..VerificationMethod::new(
did.clone() + "#keys-2",
String::from("JsonWebKey2020"),
did.clone(),
)
};

// Prepare key agreement verification method

let agreement_method = VerificationMethod {
public_key: Some(KeyFormat::Jwk(agreement_key)),
..VerificationMethod::new(
did.clone() + "#keys-3",
String::from("JsonWebKey2020"),
did.clone(),
)
};

// Prepare service endpoint

let service = Service::new(
did.clone() + "#pop-domain",
String::from("LinkedDomains"),
format!("{server_public_domain}/.well-known/did/pop.json"),
);

// Build document

let context = Context::SetOfString(vec![
String::from("https://www.w3.org/ns/did/v1"),
String::from("https://w3id.org/security/suites/jws-2020/v1"),
]);

let diddoc = Document {
authentication: Some(vec![Authentication::Reference(
authentication_method.id.clone(), //
)]),
assertion_method: Some(vec![AssertionMethod::Reference(
assertion_method.id.clone(), //
)]),
key_agreement: Some(vec![KeyAgreement::Reference(
agreement_method.id.clone(), //
)]),
verification_method: Some(vec![
authentication_method,
assertion_method,
agreement_method,
]),
service: Some(vec![service]),
..Document::new(context, did)
};

// Serialize and persist to file

let did_json = serde_json::to_string_pretty(&diddoc).unwrap();

std::fs::create_dir_all(storage_dirpath).map_err(|_| Error::PersistenceError)?;
std::fs::write(format!("{storage_dirpath}/did.json"), did_json)
.map_err(|_| Error::PersistenceError)?;

tracing::info!("persisted DID document to disk");
Ok(diddoc)
}

/// Validates the integrity of the persisted diddoc
pub fn validate_diddoc(storage_dirpath: &str) -> Result<(), String> {
// Validate that did.json exists

let didpath = format!("{storage_dirpath}/did.json");
if !Path::new(&didpath).exists() {
return Err(String::from("Missing did.json"));
};

// Validate that keystore exists

let store = KeyStore::latest(storage_dirpath);
if store.is_none() {
return Err(String::from("Missing keystore"));
}

// Validate that did.json matches keystore

let store = store.unwrap();

let diddoc: Document = match std::fs::read_to_string(didpath) {
Err(_) => return Err(String::from("Unreadable did.json")),
Ok(content) => {
serde_json::from_str(&content).map_err(|_| String::from("Unparseable did.json"))?
}
};

for method in diddoc.verification_method.unwrap_or(vec![]) {
let pubkey = method.public_key.ok_or(String::from("Missing key"))?;
let pubkey = match pubkey {
KeyFormat::Jwk(jwk) => jwk,
_ => return Err(String::from("Unsupported key format")),
};

store
.find_keypair(&pubkey)
.ok_or(String::from("Keystore mismatch"))?;
}

Ok(())
}

#[cfg(test)]
mod tests {
use super::*;
use crate::util::dotenv_flow_read;

fn setup() -> (String, String) {
let storage_dirpath = dotenv_flow_read("STORAGE_DIRPATH")
.map(|p| format!("{}/{}", p, uuid::Uuid::new_v4()))
.unwrap();

let server_public_domain = dotenv_flow_read("SERVER_PUBLIC_DOMAIN").unwrap();

(storage_dirpath, server_public_domain)
}

fn cleanup(storage_dirpath: &str) {
std::fs::remove_dir_all(storage_dirpath).unwrap();
}

// Verifies that the didgen function returns a DID document.
// Does not validate the DID document.
#[test]
fn test_didgen() {
let (storage_dirpath, server_public_domain) = setup();

let diddoc = didgen(&storage_dirpath, &server_public_domain).unwrap();
assert_eq!(diddoc.id, "did:web:example.com");

cleanup(&storage_dirpath);
}

// Produces did doc from keys and validate that corresponding verification methods are present.
#[test]
fn test_gen_diddoc() {
let (storage_dirpath, server_public_domain) = setup();

let authentication_key = Jwk {
key_id: None,
key_type: String::from("OKP"),
curve: String::from("Ed25519"),
x: Some(String::from(
"d75a980182b10ab2463c5b1be1b4d97e06ec21ebac8552059996bd962d77f259",
)),
y: None,
d: None,
};

let assertion_key = Jwk {
key_id: None,
key_type: String::from("OKP"),
curve: String::from("Ed25519"),
x: Some(String::from(
"d75a980182b10ab2463c5b1be1b4d97e06ec21ebac8552059996bd962d77f259",
)),
y: None,
d: None,
};

let agreement_key = Jwk {
key_id: None,
key_type: String::from("OKP"),
curve: String::from("X25519"),
x: Some(String::from(
"d75a980182b10ab2463c5b1be1b4d97e06ec21ebac8552059996bd962d77f259",
)),
y: None,
d: None,
};

let diddoc = gen_diddoc(
&storage_dirpath,
&server_public_domain,
authentication_key.clone(),
assertion_key.clone(),
agreement_key.clone(),
)
.unwrap();

// Verify that the DID contains exactly the defined verification methods.
let expected_verification_methods = vec![
VerificationMethod {
id: "did:web:example.com#keys-1".to_string(),
public_key: Some(KeyFormat::Jwk(authentication_key)),
..VerificationMethod::new(
"did:web:example.com#keys-1".to_string(),
String::from("JsonWebKey2020"),
"did:web:example.com".to_string(),
)
},
VerificationMethod {
id: "did:web:example.com#keys-2".to_string(),
public_key: Some(KeyFormat::Jwk(assertion_key)),
..VerificationMethod::new(
"did:web:example.com#keys-2".to_string(),
String::from("JsonWebKey2020"),
"did:web:example.com".to_string(),
)
},
VerificationMethod {
id: "did:web:example.com#keys-3".to_string(),
public_key: Some(KeyFormat::Jwk(agreement_key)),
..VerificationMethod::new(
"did:web:example.com#keys-3".to_string(),
String::from("JsonWebKey2020"),
"did:web:example.com".to_string(),
)
},
];

let actual_verification_methods = diddoc.verification_method.unwrap();

let actual = json_canon::to_string(&actual_verification_methods).unwrap();
let expected = json_canon::to_string(&expected_verification_methods).unwrap();
assert_eq!(expected, actual);

cleanup(&storage_dirpath);
}

#[test]
fn test_validate_diddoc() {
let (storage_dirpath, server_public_domain) = setup();

didgen(&storage_dirpath, &server_public_domain).unwrap();
assert!(validate_diddoc(&storage_dirpath).is_ok());

cleanup(&storage_dirpath);
}
}
5 changes: 5 additions & 0 deletions did-endpoint/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub mod didgen;
pub mod web;
pub mod plugin;

mod util;
Loading

0 comments on commit e7bb167

Please sign in to comment.