diff --git a/Cargo.lock b/Cargo.lock index d6f8efc5..7030156c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -197,6 +197,7 @@ dependencies = [ "agent_secret_manager", "agent_shared", "agent_store", + "anyhow", "async-std", "async-trait", "axum 0.7.5", @@ -212,6 +213,7 @@ dependencies = [ "identity_iota", "identity_stronghold", "iota-sdk", + "iota_stronghold", "jsonwebtoken", "lazy_static", "mime", @@ -274,6 +276,8 @@ dependencies = [ "did_manager", "futures", "identity_iota", + "identity_stronghold", + "iota-sdk", "jsonwebtoken", "lazy_static", "log", @@ -1446,7 +1450,6 @@ checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" [[package]] name = "consumer" version = "0.1.0" -source = "git+https://git@github.com/impierce/did-manager.git?rev=7c31432#7c31432bf291eb1e9788c5028fc7ceede2d03491" dependencies = [ "did_iota", "did_jwk", @@ -2012,7 +2015,6 @@ dependencies = [ [[package]] name = "did_iota" version = "0.1.0" -source = "git+https://git@github.com/impierce/did-manager.git?rev=7c31432#7c31432bf291eb1e9788c5028fc7ceede2d03491" dependencies = [ "identity_iota", "identity_stronghold", @@ -2025,7 +2027,6 @@ dependencies = [ [[package]] name = "did_jwk" version = "0.1.0" -source = "git+https://git@github.com/impierce/did-manager.git?rev=7c31432#7c31432bf291eb1e9788c5028fc7ceede2d03491" dependencies = [ "did-jwk", "identity_iota", @@ -2042,7 +2043,6 @@ dependencies = [ [[package]] name = "did_key" version = "0.1.0" -source = "git+https://git@github.com/impierce/did-manager.git?rev=7c31432#7c31432bf291eb1e9788c5028fc7ceede2d03491" dependencies = [ "did-method-key", "identity_iota", @@ -2060,9 +2060,9 @@ dependencies = [ [[package]] name = "did_manager" version = "0.1.0" -source = "git+https://git@github.com/impierce/did-manager.git?rev=7c31432#7c31432bf291eb1e9788c5028fc7ceede2d03491" dependencies = [ "consumer", + "identity_stronghold_ext", "producer", ] @@ -2098,7 +2098,6 @@ dependencies = [ [[package]] name = "did_web" version = "0.1.0" -source = "git+https://git@github.com/impierce/did-manager.git?rev=7c31432#7c31432bf291eb1e9788c5028fc7ceede2d03491" dependencies = [ "did-web", "identity_iota", @@ -2117,7 +2116,7 @@ dependencies = [ [[package]] name = "dif-presentation-exchange" version = "0.1.0" -source = "git+https://git@github.com/impierce/openid4vc.git?rev=2d89065#2d89065518c388aba3500b3c229a3f091d5bdfbd" +source = "git+https://git@github.com/impierce/openid4vc.git?rev=c2de2e7#c2de2e7d4ec1441c5961f5d35a6e0a7b0419f5db" dependencies = [ "getset", "jsonpath_lib", @@ -3344,16 +3343,13 @@ checksum = "1f8f319eeaa1825db8b2dc7a4367ea506c26c9219ac8470e59f2e5fc9432b0d7" dependencies = [ "anyhow", "async-trait", - "bls12_381_plus 0.8.17", "flate2", - "futures", "identity_core", "identity_did", "identity_document", "identity_verification", "indexmap 2.5.0", "itertools 0.11.0", - "json-proof-token", "once_cell", "roaring", "serde", @@ -3363,7 +3359,6 @@ dependencies = [ "strum 0.25.0", "thiserror", "url", - "zkryptium", ] [[package]] @@ -3480,7 +3475,6 @@ checksum = "5056f32d605f803067f38e87da01ca06f605f112ae9d8be7cc7e6590fe91c063" dependencies = [ "anyhow", "async-trait", - "bls12_381_plus 0.8.17", "futures", "identity_core", "identity_credential", @@ -3488,12 +3482,10 @@ dependencies = [ "identity_document", "identity_iota_core", "identity_verification", - "json-proof-token", "seahash", "serde", "serde_json", "thiserror", - "zkryptium", ] [[package]] @@ -3503,23 +3495,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95ff20458f3939df912af176da0409131146b0214066dc82ad028430fcda44d2" dependencies = [ "async-trait", - "bls12_381_plus 0.8.17", "identity_storage", "identity_verification", "iota-crypto", "iota-sdk", "iota_stronghold", - "json-proof-token", "rand 0.8.5", "tokio", "zeroize", - "zkryptium", ] [[package]] name = "identity_stronghold_ext" version = "0.1.0" -source = "git+https://git@github.com/impierce/did-manager.git?rev=7c31432#7c31432bf291eb1e9788c5028fc7ceede2d03491" dependencies = [ "async-trait", "elliptic-curve 0.13.8", @@ -4792,7 +4780,7 @@ dependencies = [ [[package]] name = "oid4vc-core" version = "0.1.0" -source = "git+https://git@github.com/impierce/openid4vc.git?rev=2d89065#2d89065518c388aba3500b3c229a3f091d5bdfbd" +source = "git+https://git@github.com/impierce/openid4vc.git?rev=c2de2e7#c2de2e7d4ec1441c5961f5d35a6e0a7b0419f5db" dependencies = [ "anyhow", "async-trait", @@ -4816,7 +4804,7 @@ dependencies = [ [[package]] name = "oid4vc-manager" version = "0.1.0" -source = "git+https://git@github.com/impierce/openid4vc.git?rev=2d89065#2d89065518c388aba3500b3c229a3f091d5bdfbd" +source = "git+https://git@github.com/impierce/openid4vc.git?rev=c2de2e7#c2de2e7d4ec1441c5961f5d35a6e0a7b0419f5db" dependencies = [ "anyhow", "async-trait", @@ -4848,7 +4836,7 @@ dependencies = [ [[package]] name = "oid4vci" version = "0.1.0" -source = "git+https://git@github.com/impierce/openid4vc.git?rev=2d89065#2d89065518c388aba3500b3c229a3f091d5bdfbd" +source = "git+https://git@github.com/impierce/openid4vc.git?rev=c2de2e7#c2de2e7d4ec1441c5961f5d35a6e0a7b0419f5db" dependencies = [ "anyhow", "derivative", @@ -4871,7 +4859,7 @@ dependencies = [ [[package]] name = "oid4vp" version = "0.1.0" -source = "git+https://git@github.com/impierce/openid4vc.git?rev=2d89065#2d89065518c388aba3500b3c229a3f091d5bdfbd" +source = "git+https://git@github.com/impierce/openid4vc.git?rev=c2de2e7#c2de2e7d4ec1441c5961f5d35a6e0a7b0419f5db" dependencies = [ "anyhow", "chrono", @@ -5496,7 +5484,6 @@ dependencies = [ [[package]] name = "producer" version = "0.1.0" -source = "git+https://git@github.com/impierce/did-manager.git?rev=7c31432#7c31432bf291eb1e9788c5028fc7ceede2d03491" dependencies = [ "did_iota", "did_jwk", @@ -6658,7 +6645,6 @@ dependencies = [ [[package]] name = "shared" version = "0.1.0" -source = "git+https://git@github.com/impierce/did-manager.git?rev=7c31432#7c31432bf291eb1e9788c5028fc7ceede2d03491" dependencies = [ "identity_iota", "identity_storage", @@ -6734,7 +6720,7 @@ dependencies = [ [[package]] name = "siopv2" version = "0.1.0" -source = "git+https://git@github.com/impierce/openid4vc.git?rev=2d89065#2d89065518c388aba3500b3c229a3f091d5bdfbd" +source = "git+https://git@github.com/impierce/openid4vc.git?rev=c2de2e7#c2de2e7d4ec1441c5961f5d35a6e0a7b0419f5db" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 464ff4c2..7ba18d6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,17 +19,18 @@ edition = "2021" rust-version = "1.76.0" [workspace.dependencies] -did_manager = { git = "https://git@github.com/impierce/did-manager.git", rev = "7c31432" } -siopv2 = { git = "https://git@github.com/impierce/openid4vc.git", rev = "2d89065" } -oid4vci = { git = "https://git@github.com/impierce/openid4vc.git", rev = "2d89065" } -oid4vc-core = { git = "https://git@github.com/impierce/openid4vc.git", rev = "2d89065" } -oid4vc-manager = { git = "https://git@github.com/impierce/openid4vc.git", rev = "2d89065" } -oid4vp = { git = "https://git@github.com/impierce/openid4vc.git", rev = "2d89065" } +# did_manager = { git = "https://git@github.com/impierce/did-manager.git", rev = "7c31432" } +# did_manager = { git = "https://git@github.com/impierce/did-manager.git", branch = "build/bump-iota-deps" } +did_manager = { path = "../did-manager" } +siopv2 = { git = "https://git@github.com/impierce/openid4vc.git", rev = "c2de2e7" } +oid4vci = { git = "https://git@github.com/impierce/openid4vc.git", rev = "c2de2e7" } +oid4vc-core = { git = "https://git@github.com/impierce/openid4vc.git", rev = "c2de2e7" } +oid4vc-manager = { git = "https://git@github.com/impierce/openid4vc.git", rev = "c2de2e7" } +oid4vp = { git = "https://git@github.com/impierce/openid4vc.git", rev = "c2de2e7" } async-trait = "0.1" axum = { version = "0.7", features = ["tracing"] } base64 = "0.22" -bls12_381_plus = "0.8" chrono = { version = "0.4", features = ["serde"] } cqrs-es = "0.4.2" futures = "0.3" @@ -43,7 +44,7 @@ identity_credential = { version = "1.5", default-features = false, features = [ identity_did = { version = "1.5" } identity_document = { version = "1.5" } identity_iota = { version = "1.5" } -identity_stronghold = { version = "1.5", default-features = false, features = ["bbs-plus"] } +identity_stronghold = { version = "1.5", default-features = false } identity_verification = { version = "1.5", default-features = false } iota-sdk = { version = "1.0", default-features = false, features = ["tls", "client", "stronghold"] } jsonwebtoken = "9.3" diff --git a/agent_api_rest/src/verification/relying_party/redirect.rs b/agent_api_rest/src/verification/relying_party/redirect.rs index 6a13e3ed..54bab5a0 100644 --- a/agent_api_rest/src/verification/relying_party/redirect.rs +++ b/agent_api_rest/src/verification/relying_party/redirect.rs @@ -64,7 +64,7 @@ pub mod tests { authorization_requests::tests::authorization_requests, relying_party::request::tests::request, router, }; use agent_event_publisher_http::EventPublisherHttp; - use agent_secret_manager::{secret_manager, service::Service, subject::Subject}; + use agent_secret_manager::{secret_manager, service::Service, stronghold_storage, subject::Subject}; use agent_shared::config::{set_config, Events}; use agent_store::{in_memory, EventPublisher}; use axum::{ @@ -112,6 +112,7 @@ pub mod tests { let provider_manager = ProviderManager::new( Arc::new(Subject { secret_manager: Arc::new(tokio::sync::Mutex::new(secret_manager().await)), + stronghold_storage: stronghold_storage().await, }), vec!["did:key"], vec![Algorithm::EdDSA], diff --git a/agent_application/src/main.rs b/agent_application/src/main.rs index 48e759e0..0bbd702a 100644 --- a/agent_application/src/main.rs +++ b/agent_application/src/main.rs @@ -7,7 +7,7 @@ use agent_event_publisher_http::EventPublisherHttp; use agent_holder::services::HolderServices; use agent_identity::services::IdentityServices; use agent_issuance::{services::IssuanceServices, startup_commands::startup_commands}; -use agent_secret_manager::{secret_manager, service::Service as _, subject::Subject}; +use agent_secret_manager::{secret_manager, service::Service as _, stronghold_storage, subject::Subject}; use agent_shared::config::{config, LogFormat}; use agent_store::{in_memory, postgres, EventPublisher}; use agent_verification::services::VerificationServices; @@ -28,8 +28,15 @@ async fn main() -> io::Result<()> { LogFormat::Text => tracing_subscriber.with(tracing_subscriber::fmt::layer()).init(), } + agent_identity::state::test_function().await; + + panic!(); + + let stronghold_storage = stronghold_storage().await; + let subject = Arc::new(Subject { secret_manager: Arc::new(tokio::sync::Mutex::new(secret_manager().await)), + stronghold_storage, }); let identity_services = Arc::new(IdentityServices::new(subject.clone())); diff --git a/agent_identity/Cargo.toml b/agent_identity/Cargo.toml index 8e914cdf..063cd96b 100644 --- a/agent_identity/Cargo.toml +++ b/agent_identity/Cargo.toml @@ -21,16 +21,21 @@ identity_document.workspace = true identity_iota.workspace = true identity_stronghold.workspace = true iota-sdk.workspace = true +iota_stronghold = { version = "2.1" } jsonwebtoken.workspace = true oid4vc-core.workspace = true serde.workspace = true serde_json.workspace = true thiserror.workspace = true tracing.workspace = true +rand = "0.8" +reqwest.workspace = true +anyhow = "1.0" +serial_test = "3.0" # `test_utils` dependencies rstest = { workspace = true, optional = true } -tokio = { workspace = true, optional = true } +tokio = { workspace = true } [dev-dependencies] agent_api_rest = { path = "../agent_api_rest" } @@ -57,5 +62,5 @@ async-std = { version = "1.5", features = ["attributes", "tokio1"] } [features] test_utils = [ "dep:rstest", - "dep:tokio", + # "dep:tokio", ] diff --git a/agent_identity/src/document/aggregate.rs b/agent_identity/src/document/aggregate.rs index 55572267..7be27005 100644 --- a/agent_identity/src/document/aggregate.rs +++ b/agent_identity/src/document/aggregate.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use agent_secret_manager::STRONGHOLD_PATH; use agent_shared::{ config::{config, get_preferred_did_method, get_preferred_signing_algorithm, SecretManagerConfig}, from_jsonwebtoken_algorithm_to_jwsalgorithm, @@ -99,39 +100,24 @@ impl Aggregate for Document { .await .map_err(|err| ProduceDocumentError(err.to_string()))?; - info!("Document: {:#?}", document); - Ok(vec![DocumentCreated { document_id, status, document, }]) } - SetStatus { document_id, status } => { - info!("Service ID 2: {:?}", self.document_id); + AddPublicKeyJwk { + document_id, + public_key_jwk, + } => { let mut document = self.document.clone().unwrap(); let did = document.id().clone(); - let fragment = config().secret_manager.issuer_fragment.clone().unwrap(); - let password = config().secret_manager.stronghold_password.clone(); - let stronghold_path = config().secret_manager.stronghold_path.clone(); - let issuer_eddsa_key_id = config().secret_manager.issuer_eddsa_key_id.clone().unwrap(); - - let adapter = StrongholdAdapter::builder() - .password(password) - .build(stronghold_path) - .unwrap(); - - let stronghold_storage = StrongholdStorage::new(adapter); - - let jwk: identity_iota::verification::jwk::Jwk = stronghold_storage - .get_public_key_with_type(&KeyId::new(issuer_eddsa_key_id), StrongholdKeyType::Ed25519) - .await - .unwrap(); - - info!("DID : {}", did); - info!("Fragment : {}", fragment); - info!("JWK : {:#?}", jwk); + let fragment = match self.document_id.as_str() { + "did:web" => "key-0", + "did:iota:rms" => public_key_jwk.kid().unwrap(), + _ => panic!("FIX THIS"), + }; fn method( controller: &CoreDID, @@ -147,20 +133,19 @@ impl Aggregate for Document { .unwrap() } - let verification_method = method(&did, &format!("#{fragment}"), jwk); + let verification_method = method(&did, &format!("#{fragment}"), public_key_jwk); document.remove_method(&verification_method.id()); document .insert_method(verification_method, MethodScope::VerificationMethod) .unwrap(); - info!("HELLOOO 2: {:#?}", document); + Ok(vec![PublicKeyJwkAdded { document_id, document }]) + } + SetStatus { document_id, status } => { + info!("Service ID 2: {:?}", self.document_id); - Ok(vec![StatusSet { - document_id, - status, - document, - }]) + Ok(vec![StatusSet { document_id, status }]) } AddService { service_id, @@ -222,7 +207,6 @@ impl Aggregate for Document { info!("Service ID 5: {:?}", self.document_id); let SecretManagerConfig { - stronghold_path: snapshot_path, stronghold_password: password, .. } = config().secret_manager.clone(); @@ -242,7 +226,7 @@ impl Aggregate for Document { let secret_manager: SecretManager = SecretManager::Stronghold( StrongholdSecretManager::builder() .password(Password::from(password)) - .build(snapshot_path) + .build(STRONGHOLD_PATH) .expect("FIX THIS"), ); @@ -281,9 +265,10 @@ impl Aggregate for Document { .expect("FIX THIS"); // Publish the updated Alias Output. - let updated_document: IotaDocument = client + let updated_document: CoreDocument = client .publish_did_output(&secret_manager, alias_output) .await + .map(CoreDocument::from) .expect("FIX THIS"); info!("Updated DID document: {updated_document:#}"); @@ -308,10 +293,12 @@ impl Aggregate for Document { self.status = status; self.document.replace(document); } - StatusSet { status, document, .. } => { - self.status = status; + PublicKeyJwkAdded { document, .. } => { self.document.replace(document); } + StatusSet { status, .. } => { + self.status = status; + } ServiceAdded { document } => { self.document.replace(document); } diff --git a/agent_identity/src/document/command.rs b/agent_identity/src/document/command.rs index b7f0e88e..0cc7a45a 100644 --- a/agent_identity/src/document/command.rs +++ b/agent_identity/src/document/command.rs @@ -1,5 +1,6 @@ use super::aggregate::Status; use identity_document::service::Service as DocumentService; +use identity_iota::verification::jwk::Jwk; use serde::Deserialize; #[derive(Debug, Deserialize)] @@ -9,6 +10,10 @@ pub enum DocumentCommand { document_id: String, status: Status, }, + AddPublicKeyJwk { + document_id: String, + public_key_jwk: Jwk, + }, SetStatus { document_id: String, status: Status, diff --git a/agent_identity/src/document/event.rs b/agent_identity/src/document/event.rs index 6adbb3d7..67bc1754 100644 --- a/agent_identity/src/document/event.rs +++ b/agent_identity/src/document/event.rs @@ -1,6 +1,5 @@ use cqrs_es::DomainEvent; use identity_document::document::CoreDocument; -use identity_iota::iota::IotaDocument; use serde::{Deserialize, Serialize}; use super::aggregate::Status; @@ -12,10 +11,13 @@ pub enum DocumentEvent { status: Status, document: CoreDocument, }, + PublicKeyJwkAdded { + document_id: String, + document: CoreDocument, + }, StatusSet { document_id: String, status: Status, - document: CoreDocument, }, ServiceAdded { document: CoreDocument, @@ -25,7 +27,7 @@ pub enum DocumentEvent { }, DocumentPublished { document_id: String, - updated_document: IotaDocument, + updated_document: CoreDocument, }, } @@ -35,6 +37,7 @@ impl DomainEvent for DocumentEvent { let event_type: &str = match self { DocumentCreated { .. } => "DocumentCreated", + PublicKeyJwkAdded { .. } => "PublicKeyJwkAdded", StatusSet { .. } => "StatusSet", ServiceAdded { .. } => "ServiceAdded", ServiceRemoved { .. } => "ServiceRemoved", diff --git a/agent_identity/src/document/views/mod.rs b/agent_identity/src/document/views/mod.rs index c7fc47ae..1e936a37 100644 --- a/agent_identity/src/document/views/mod.rs +++ b/agent_identity/src/document/views/mod.rs @@ -17,10 +17,12 @@ impl View for Document { self.document.replace(document.clone()); self.status.clone_from(status); } - StatusSet { status, document, .. } => { - self.status.clone_from(status); + PublicKeyJwkAdded { document, .. } => { self.document.replace(document.clone()); } + StatusSet { status, .. } => { + self.status.clone_from(status); + } ServiceAdded { document, .. } => { self.document.replace(document.clone()); } diff --git a/agent_identity/src/service/aggregate.rs b/agent_identity/src/service/aggregate.rs index 91543ea0..aecd82df 100644 --- a/agent_identity/src/service/aggregate.rs +++ b/agent_identity/src/service/aggregate.rs @@ -101,19 +101,16 @@ impl Aggregate for Service { let core_document = document.document.expect("FIX THIS"); let subject_did = core_document.id().clone(); - let fragment = if document.document_id == "did:iota:rms" { - config().secret_manager.issuer_fragment.clone().unwrap() - } else { - // TODO: can we assume that we can take the first verification method? - core_document - .verification_method() - .first() - .expect("FIX THIS") - .id() - .fragment() - .expect("FIX THIS") - .to_string() - }; + // TODO: Once we support multiple algorithms we cannot simply pick the first (and only) verification + // method in the Document. + let fragment = core_document + .verification_method() + .first() + .expect("FIX THIS") + .id() + .fragment() + .expect("FIX THIS") + .to_string(); let domain_linkage_credential = DomainLinkageCredentialBuilder::new() .issuer(subject_did.clone()) diff --git a/agent_identity/src/services.rs b/agent_identity/src/services.rs index abb9d7f2..b3dc449e 100644 --- a/agent_identity/src/services.rs +++ b/agent_identity/src/services.rs @@ -17,11 +17,12 @@ impl IdentityServices { where Self: Sized, { - use agent_secret_manager::secret_manager; + use agent_secret_manager::{secret_manager, stronghold_storage}; Arc::new(Self::new(Arc::new(futures::executor::block_on(async { Subject { secret_manager: Arc::new(tokio::sync::Mutex::new(secret_manager().await)), + stronghold_storage: stronghold_storage().await, } })))) } diff --git a/agent_identity/src/state.rs b/agent_identity/src/state.rs index b1a7ace0..06a133a0 100644 --- a/agent_identity/src/state.rs +++ b/agent_identity/src/state.rs @@ -1,10 +1,17 @@ +use agent_secret_manager::{stronghold_storage, ED25519_KEY_ID, STRONGHOLD_PATH}; use agent_shared::config::{config, SupportedDidMethod, ToggleOptions}; use agent_shared::handlers::command_handler; use agent_shared::{application_state::CommandHandler, handlers::query_handler}; use cqrs_es::persist::ViewRepository; -use did_manager::DidMethod; +use did_manager::StrongholdExtStorage; use futures::future::{join_all, try_join_all}; use identity_iota::core::Duration; +use identity_iota::credential::Jws; +use identity_iota::storage::{KeyId, KeyType}; +use identity_stronghold::{StrongholdKeyType, StrongholdStorage}; +use iota_sdk::client::secret::stronghold::StrongholdSecretManager; +use iota_sdk::types::block::output::{AliasOutputBuilder, RentStructure}; +use iota_stronghold::{SnapshotPath, Stronghold}; use jsonwebtoken::Algorithm; use oid4vc_core::Subject; use std::str::FromStr; @@ -22,6 +29,31 @@ use crate::{ service::{aggregate::Service, command::ServiceCommand, views::ServiceView}, }; +use std::path::PathBuf; + +use anyhow::Context; + +use identity_iota::iota::block::output::AliasOutput; +use identity_iota::iota::IotaClientExt; +use identity_iota::iota::IotaDocument; +use identity_iota::iota::IotaIdentityClientExt; +use identity_iota::iota::NetworkName; +use identity_iota::storage::JwkDocumentExt; +use identity_iota::storage::Storage; +use identity_iota::verification::{MethodScope, VerificationMethod}; + +use identity_iota::verification::jws::JwsAlgorithm; +use iota_sdk::client::api::GetAddressesOptions; +use iota_sdk::client::node_api::indexer::query_parameters::QueryParameter; +use iota_sdk::client::secret::SecretManager; +use iota_sdk::client::Client; +use iota_sdk::crypto::keys::bip39; +use iota_sdk::types::block::address::Address; +use iota_sdk::types::block::address::Bech32Address; +use iota_sdk::types::block::address::Hrp; +use rand::distributions::DistString; +use serde_json::Value; + #[derive(Clone)] pub struct IdentityState { pub command: CommandHandlers, @@ -74,12 +106,183 @@ impl Clone for Queries { } } +/// Initializes the [`SecretManager`] with a new mnemonic, if necessary, +/// and generates an address from the given [`SecretManager`]. +pub async fn get_address(client: &Client, secret_manager: &SecretManager) -> anyhow::Result { + let random: [u8; 32] = rand::random(); + let mnemonic = bip39::wordlist::encode(random.as_ref(), &bip39::wordlist::ENGLISH) + .map_err(|err| anyhow::anyhow!(format!("{err:?}")))?; + + if let SecretManager::Stronghold(ref stronghold) = secret_manager { + match stronghold.store_mnemonic(mnemonic).await { + Ok(()) => (), + Err(iota_sdk::client::stronghold::Error::MnemonicAlreadyStored) => (), + Err(err) => anyhow::bail!(err), + } + } else { + anyhow::bail!("expected a `StrongholdSecretManager`"); + } + + let bech32_hrp: Hrp = client.get_bech32_hrp().await?; + let address: Bech32Address = secret_manager + .generate_ed25519_addresses( + GetAddressesOptions::default() + .with_range(0..1) + .with_bech32_hrp(bech32_hrp), + ) + .await?[0]; + + Ok(address) +} +use identity_iota::storage::JwkStorage; + +pub async fn generate( + stronghold_ext_storage: &StrongholdExtStorage, + key_type: KeyType, + alg: JwsAlgorithm, +) -> Result { + let jwk_gen_output = stronghold_ext_storage.generate(key_type.clone(), alg).await.unwrap(); + info!( + "Generated new {:?} key with key ID {:?}", + &key_type.as_str(), + &jwk_gen_output.key_id.as_str() + ); + Ok(jwk_gen_output.key_id) +} + /// The unique identifier for the linked domain service. pub const DOMAIN_LINKAGE_SERVICE_ID: &str = "linked-domain-service"; /// The unique identifier for the linked verifiable presentation service. pub const VERIFIABLE_PRESENTATION_SERVICE_ID: &str = "linked-verifiable-presentation-service"; +// #[tokio::test] +// async fn test() { +// iota_stronghold::engine::snapshot::try_set_encrypt_work_factor(0).unwrap(); +// test_function().await; +// } + +pub async fn test_function() { + // The API endpoint of an IOTA node, e.g. Hornet. + let api_endpoint: &str = "https://api.testnet.shimmer.network"; + + // Create a new client to interact with the IOTA ledger. + let client: Client = Client::builder() + .with_primary_node(api_endpoint, None) + .unwrap() + .finish() + .await + .unwrap(); + + let stronghold_password = config().secret_manager.stronghold_password.clone(); + + let stronghold_adapter = StrongholdSecretManager::builder() + .password(stronghold_password.clone()) + .build(STRONGHOLD_PATH) + .unwrap(); + + // Create a `StrongholdStorage`. + // `StrongholdStorage` creates internally a `SecretManager` that can be + // referenced to avoid creating multiple instances around the same stronghold snapshot. + let stronghold_ext_storage = StrongholdExtStorage::new(stronghold_adapter); + + let ed25519_key_id = generate(&stronghold_ext_storage, KeyType::new("Ed25519"), JwsAlgorithm::EdDSA) + .await + .unwrap(); + let es256_key_id = generate(&stronghold_ext_storage, KeyType::new("ES256"), JwsAlgorithm::ES256) + .await + .unwrap(); + + let ed25519_jwk = stronghold_ext_storage + .get_ed25519_public_key(&ed25519_key_id) + .await + .unwrap(); + + let es256_jwk = stronghold_ext_storage + .get_es256_public_key(&es256_key_id) + .await + .unwrap(); + + // Create a DID document. + let address: Bech32Address = get_address(&client, stronghold_ext_storage.as_secret_manager()) + .await + .unwrap(); + println!("Address: {}", address); + + { + let ledger_sponsoring_service = config().ledger_sponsoring_service.clone().unwrap(); + let access_key = ledger_sponsoring_service.access_key; + let url = ledger_sponsoring_service.url; + let authorization = ledger_sponsoring_service.authorization; + + let client = reqwest::Client::builder().build().unwrap(); + + let json = serde_json::json!({ + "RequestSponsoring": { + "access_key": access_key, + "amount": 200000, + "address": address.to_string() + } + }); + + let mut headers = reqwest::header::HeaderMap::new(); + headers.insert("Authorization", authorization.parse().unwrap()); + + let request = client.request(reqwest::Method::POST, url).headers(headers).json(&json); + + let response = request.send().await.unwrap(); + + println!("Status: {}", response.status()); + + std::thread::sleep(std::time::Duration::from_secs(15)); + } + let address: Address = *address; + println!("Address: {}", address); + + let network_name: NetworkName = client.network_name().await.unwrap(); + let document: IotaDocument = IotaDocument::new(&network_name); + + // Construct an Alias Output containing the DID document, with the wallet address + // set as both the state controller and governor. + let alias_output: AliasOutput = client.new_did_output(address, document, None).await.unwrap(); + + // Publish the Alias Output and get the published DID document. + let mut document: IotaDocument = client + .publish_did_output(stronghold_ext_storage.as_secret_manager(), alias_output) + .await + .unwrap(); + let did = document.id().clone(); + + let ed25519_verification_method: VerificationMethod = + VerificationMethod::new_from_jwk(did.clone(), ed25519_jwk, None).unwrap(); + let es256_verification_method: VerificationMethod = VerificationMethod::new_from_jwk(did, es256_jwk, None).unwrap(); + + document + .insert_method(ed25519_verification_method, MethodScope::VerificationMethod) + .unwrap(); + document + .insert_method(es256_verification_method, MethodScope::VerificationMethod) + .unwrap(); + + // Resolve the latest output and update it with the given document. + let alias_output: AliasOutput = client.update_did_output(document.clone()).await.unwrap(); + + // Because the size of the DID document increased, we have to increase the allocated storage deposit. + // This increases the deposit amount to the new minimum. + let rent_structure: RentStructure = client.get_rent_structure().await.unwrap(); + let alias_output: AliasOutput = AliasOutputBuilder::from(&alias_output) + .with_minimum_storage_deposit(rent_structure) + .finish() + .unwrap(); + + // Publish the updated Alias Output. + let updated: IotaDocument = client + .publish_did_output(stronghold_ext_storage.as_secret_manager(), alias_output) + .await + .unwrap(); + println!("Updated DID document: {updated:#}"); +} + /// Initialize the identity state. pub async fn initialize(state: &IdentityState, subject: Arc) { info!("Initializing ..."); @@ -105,61 +308,64 @@ pub async fn initialize(state: &IdentityState, subject: Arc) { // Check whether the DID methods document already exists. let command = match query_handler(&document_id, &state.query.document).await { - Ok(Some(Document { - document: Some(document), - .. - })) => { - enabled - .then_some(DocumentCommand::SetStatus { + Ok(Some(_document_exists)) => { + if *enabled { + DocumentCommand::SetStatus { document_id: document_id.clone(), status: Status::SignAndValidate, - }) - .or_else(|| { - Some(DocumentCommand::SetStatus { - document_id: document_id.clone(), - status: Status::Disabled, - }) - }) - - // TODO: FIX THISS - // let key_id = subject.key_id(&did_method.to_string(), Algorithm::EdDSA).await.unwrap(); - // let condition = document.verification_method().iter().any(|vm| { - // info!("vm.id().to_string() == key_id: {} == {}", vm.id().to_string(), key_id); - // vm.id().to_string() == key_id}); - - // if condition { - // info!("3: DID Document for `{did_method}` already exists: {:?}", document); - // } else { - // return Err(format!("2: DID Document for `{}` already exists, but the identifier does not match the subject identifier", did_method)); - // } + } + } else { + DocumentCommand::SetStatus { + document_id: document_id.clone(), + status: Status::Disabled, + } + } } // If the DID document does not exist yet, then it needs to be created. - _document_does_not_exist => enabled - .then_some(DocumentCommand::CreateDocument { - document_id: document_id.clone(), - status: Status::SignAndValidate, - }) - .or_else(|| { - Some(DocumentCommand::CreateDocument { + _document_does_not_exist => { + if *enabled { + DocumentCommand::CreateDocument { document_id: document_id.clone(), - status: Status::Disabled, - }) - }), + status: Status::SignAndValidate, + } + } else { + return Err(format!("DID Document for `{did_method}` does not exist")); + } + } }; info!("Executing command now: {:#?}", command); - if let Some(command) = command { - if command_handler(&document_id, &state.command.document, command) - .await - .is_err() - { - warn!("5: Failed to Set status `{did_method}`"); - } + if command_handler(&document_id, &state.command.document, command) + .await + .is_err() + { + warn!("5: Failed to Set status `{did_method}`"); + } + + info!("C: here"); + + let stronghold_storage = stronghold_storage().await; + + let public_key_jwk: identity_iota::verification::jwk::Jwk = stronghold_storage + .get_ed25519_public_key(&KeyId::new(ED25519_KEY_ID)) + .await + .unwrap(); - info!("C: here"); + let command = DocumentCommand::AddPublicKeyJwk { + document_id: document_id.clone(), + public_key_jwk, + }; + + if command_handler(&document_id, &state.command.document, command) + .await + .is_err() + { + warn!("5: Failed to Set status `{did_method}`"); } + info!("D: here"); + match query_handler(&document_id, &state.query.document).await { Ok(Some(document)) => Ok(document), _ => Err(format!("DID Document for `{}` does not exist", did_method)), diff --git a/agent_secret_manager/Cargo.toml b/agent_secret_manager/Cargo.toml index 3830af2b..55408a9d 100644 --- a/agent_secret_manager/Cargo.toml +++ b/agent_secret_manager/Cargo.toml @@ -13,6 +13,8 @@ base64.workspace = true did_manager.workspace = true futures.workspace = true identity_iota.workspace = true +identity_stronghold.workspace = true +iota-sdk.workspace = true jsonwebtoken = "9.3" log = "0.4" oid4vc-core.workspace = true diff --git a/agent_secret_manager/src/lib.rs b/agent_secret_manager/src/lib.rs index f9241bb0..6039220f 100644 --- a/agent_secret_manager/src/lib.rs +++ b/agent_secret_manager/src/lib.rs @@ -1,54 +1,86 @@ use agent_shared::config::{config, get_all_enabled_did_methods, SecretManagerConfig}; -use did_manager::{InMemoryCache, SecretManager}; +use did_manager::{generate, InMemoryCache, SecretManager, StrongholdExtStorage}; +use identity_iota::{storage::KeyType, verification::jws::JwsAlgorithm}; +use iota_sdk::client::secret::stronghold::StrongholdSecretManager; use log::info; pub mod service; pub mod subject; +pub const STRONGHOLD_PATH: &str = "./agent_secret_manager/strong.hold"; +pub const ED25519_KEY_ID: &str = "ed25519-0"; +pub const ES256_KEY_ID: &str = "es256-0"; +pub const ES256K_KEY_ID: &str = "es256k-0"; + +// TODO: find better solution for this +pub async fn stronghold_storage() -> StrongholdExtStorage { + let stronghold_password = config().secret_manager.stronghold_password.clone(); + + let stronghold_adapter = StrongholdSecretManager::builder() + .password(stronghold_password.clone()) + .build(STRONGHOLD_PATH) + .unwrap(); + + let stronghold_storage = StrongholdExtStorage::new(stronghold_adapter); + + // Generate keys + // TODO: currently `generate` will generate a 'static' key-ids for each keytype. In a future improvement we need to + // make sure that the key-ids are generated dynamically and stored in some sort of key manager. + let ed25519_key_id = generate(&stronghold_storage, KeyType::new("Ed25519"), JwsAlgorithm::EdDSA) + .await + .unwrap(); + assert_eq!(ed25519_key_id.as_str(), ED25519_KEY_ID); + let es256_key_id = generate(&stronghold_storage, KeyType::new("ES256"), JwsAlgorithm::ES256) + .await + .unwrap(); + assert_eq!(es256_key_id.as_str(), ES256_KEY_ID); + let es256k_key_id = generate(&stronghold_storage, KeyType::new("ES256K"), JwsAlgorithm::ES256) + .await + .unwrap(); + assert_eq!(es256k_key_id.as_str(), ES256K_KEY_ID); + + stronghold_storage +} + // TODO: find better solution for this pub async fn secret_manager() -> SecretManager { let SecretManagerConfig { - stronghold_path: snapshot_path, stronghold_password: password, - issuer_eddsa_key_id, - issuer_es256_key_id, - issuer_did, - issuer_fragment, } = config().secret_manager.clone(); info!("{:?}", config().secret_manager); let mut builder = SecretManager::builder() - .snapshot_path(&snapshot_path) + .snapshot_path(STRONGHOLD_PATH) .password(&password); - if let Some(issuer_eddsa_key_id) = issuer_eddsa_key_id { - builder = builder.with_ed25519_key(&issuer_eddsa_key_id); - } + // if let Some(issuer_eddsa_key_id) = issuer_eddsa_key_id { + // builder = builder.with_ed25519_key(&issuer_eddsa_key_id); + // } - if let Some(issuer_es256_key_id) = issuer_es256_key_id { - builder = builder.with_es256_key(&issuer_es256_key_id); - } + // if let Some(issuer_es256_key_id) = issuer_es256_key_id { + // builder = builder.with_es256_key(&issuer_es256_key_id); + // } // If `did:iota:rms` is enabled, further values are required. - if get_all_enabled_did_methods().contains(&agent_shared::config::SupportedDidMethod::IotaRms) { - builder = - builder - .with_did( - &issuer_did - .expect("`You have enabled did:iota:rms, which requires a known DID. Please provide the value through the config or environment variable.`"), - ) - .with_fragment(&issuer_fragment.expect( - "`You have enabled did:iota:rms, which requires the fragment identifier of the key to be used. Please provide the value through the config or environment variable.`", - )); - } else { - if let Some(issuer_did) = issuer_did { - builder = builder.with_did(&issuer_did); - } - if let Some(issuer_fragment) = issuer_fragment { - builder = builder.with_fragment(&issuer_fragment); - } - } + // if get_all_enabled_did_methods().contains(&agent_shared::config::SupportedDidMethod::IotaRms) { + // builder = + // builder + // .with_did( + // &issuer_did + // .expect("`You have enabled did:iota:rms, which requires a known DID. Please provide the value through the config or environment variable.`"), + // ); + // .with_fragment(&issuer_fragment.expect( + // "`You have enabled did:iota:rms, which requires the fragment identifier of the key to be used. Please provide the value through the config or environment variable.`", + // )); + // } else { + // if let Some(issuer_did) = issuer_did { + // builder = builder.with_did(&issuer_did); + // } + // if let Some(issuer_fragment) = issuer_fragment { + // builder = builder.with_fragment(&issuer_fragment); + // } + // } if let Some(did_document_cache) = config().did_document_cache.clone() { if did_document_cache.enabled { diff --git a/agent_secret_manager/src/service.rs b/agent_secret_manager/src/service.rs index e277424f..8c6215da 100644 --- a/agent_secret_manager/src/service.rs +++ b/agent_secret_manager/src/service.rs @@ -9,11 +9,12 @@ pub trait Service { where Self: Sized, { - use crate::{secret_manager, subject::Subject}; + use crate::{secret_manager, stronghold_storage, subject::Subject}; Arc::new(Self::new(Arc::new(futures::executor::block_on(async { Subject { secret_manager: Arc::new(tokio::sync::Mutex::new(secret_manager().await)), + stronghold_storage: stronghold_storage().await, } })))) } diff --git a/agent_secret_manager/src/subject.rs b/agent_secret_manager/src/subject.rs index a96e5ae1..59bfca64 100644 --- a/agent_secret_manager/src/subject.rs +++ b/agent_secret_manager/src/subject.rs @@ -1,7 +1,7 @@ use agent_shared::{config::config, from_jsonwebtoken_algorithm_to_jwsalgorithm}; use async_trait::async_trait; use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; -use did_manager::{DidMethod, Resolver, SecretManager}; +use did_manager::{DidMethod, Resolver, SecretManager, StrongholdExtStorage}; use identity_iota::{did::DID, document::DIDUrlQuery, verification::jwk::JwkParams}; use jsonwebtoken::Algorithm; use oid4vc_core::{authentication::sign::ExternalSign, Sign, Verify}; @@ -11,6 +11,7 @@ use tokio::sync::Mutex; /// Reponsible for signing and verifying data. pub struct Subject { pub secret_manager: Arc>, + pub stronghold_storage: StrongholdExtStorage, } #[async_trait] @@ -64,27 +65,14 @@ impl Sign for Subject { let method: DidMethod = serde_json::from_str(&format!("{subject_syntax_type:?}")).ok()?; let mut secret_manager = self.secret_manager.lock().await; - if method == DidMethod::Web { - return secret_manager - .produce_document( - method, - Some(did_manager::MethodSpecificParameters::Web { origin: origin() }), - from_jsonwebtoken_algorithm_to_jwsalgorithm( - &agent_shared::config::get_preferred_signing_algorithm(), - ), - ) - .await - .ok() - .and_then(|document| document.verification_method().first().cloned()) - .map(|first| first.id().to_string()); - } - // TODO: refactor: https://github.com/impierce/ssi-agent/pull/31#discussion_r1634590990 + let method_specific_parameters = + (method == DidMethod::Web).then_some(did_manager::MethodSpecificParameters::Web { origin: origin() }); secret_manager .produce_document( method, - None, + method_specific_parameters, from_jsonwebtoken_algorithm_to_jwsalgorithm(&agent_shared::config::get_preferred_signing_algorithm()), ) .await @@ -146,6 +134,8 @@ fn origin() -> url::Origin { #[cfg(test)] mod tests { + use crate::stronghold_storage; + use super::*; use agent_shared::config::{set_config, SecretManagerConfig}; use ring::signature::{UnparsedPublicKey, ECDSA_P256_SHA256_FIXED, ED25519}; @@ -155,12 +145,7 @@ mod tests { lazy_static::lazy_static! { static ref SECRET_MANAGER_CONFIG: SecretManagerConfig = SecretManagerConfig { - stronghold_path: "../agent_secret_manager/tests/res/all_slots.stronghold".to_string(), stronghold_password: "sup3rSecr3t".to_string(), - issuer_eddsa_key_id: Some("ed25519-0".to_string()), - issuer_es256_key_id: Some("es256-0".to_string()), - issuer_did: Some("did:foo:bar".to_string()), - issuer_fragment: Some("0".to_string()), }; } @@ -170,6 +155,7 @@ mod tests { let subject = Arc::new(Subject { secret_manager: Arc::new(Mutex::new(crate::secret_manager().await)), + stronghold_storage: stronghold_storage().await, }); let mut split = ES256_SIGNED_JWT.rsplitn(2, '.'); @@ -192,6 +178,7 @@ mod tests { let subject = Arc::new(Subject { secret_manager: Arc::new(Mutex::new(crate::secret_manager().await)), + stronghold_storage: stronghold_storage().await, }); let mut split = EDDSA_SIGNED_JWT.rsplitn(2, '.'); diff --git a/agent_shared/src/config.rs b/agent_shared/src/config.rs index c579538e..202eb7c2 100644 --- a/agent_shared/src/config.rs +++ b/agent_shared/src/config.rs @@ -26,6 +26,7 @@ pub struct ApplicationConfiguration { pub external_server_response_timeout_ms: Option, pub domain_linkage_enabled: bool, pub secret_manager: SecretManagerConfig, + pub ledger_sponsoring_service: Option, pub did_document_cache: Option, pub credential_configurations: Vec, pub signing_algorithms_supported: HashMap, @@ -64,12 +65,14 @@ pub struct EventStorePostgresConfig { #[derive(Debug, Deserialize, Clone)] pub struct SecretManagerConfig { - pub stronghold_path: String, pub stronghold_password: String, - pub issuer_eddsa_key_id: Option, - pub issuer_es256_key_id: Option, - pub issuer_did: Option, - pub issuer_fragment: Option, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct LedgerSponsoringService { + pub url: Url, + pub authorization: String, + pub access_key: String, } #[derive(Debug, Deserialize, Clone)] diff --git a/agent_verification/src/authorization_request/aggregate.rs b/agent_verification/src/authorization_request/aggregate.rs index d0b4cf0b..58b1b29b 100644 --- a/agent_verification/src/authorization_request/aggregate.rs +++ b/agent_verification/src/authorization_request/aggregate.rs @@ -212,6 +212,7 @@ pub mod tests { use agent_secret_manager::secret_manager; use agent_secret_manager::service::Service as _; + use agent_secret_manager::stronghold_storage; use agent_secret_manager::subject::Subject; use agent_shared::config::set_config; use agent_shared::config::SupportedDidMethod; @@ -368,6 +369,7 @@ pub mod tests { Arc::new(futures::executor::block_on(async { Subject { secret_manager: Arc::new(tokio::sync::Mutex::new(secret_manager().await)), + stronghold_storage: stronghold_storage().await, } })), vec![did_method], @@ -547,6 +549,7 @@ pub mod tests { pub static ref VERIFIER: Subject = futures::executor::block_on(async { Subject { secret_manager: Arc::new(tokio::sync::Mutex::new(secret_manager().await)), + stronghold_storage: stronghold_storage().await, } }); pub static ref REDIRECT_URI: url::Url = "https://my-domain.example.org/redirect".parse::().unwrap();