Skip to content

Commit

Permalink
feat: add support for IOTA DID Method (#54)
Browse files Browse the repository at this point in the history
* WIP

* WIP

* feat: re-introduce generics

* WIP

* feat: introduce `AppState`

* style: rename `ApplicationState` and `AppState`

* feat: add `Domain` trait

* WIP

* WIP

* style: remove unused code

* fix: fix some `unwrap`s, clean code

* refactor: simplify agent_store

* feat: add `CommandHandlers` struct

* style: fix clippy warnings

* refactor: simplify Commands

* style: change name

* chore: add log messages

* fix: only update view when `CredentialOfferCreated`

* chore: add comments and logging

* refactor: separate queries

* fix: several fixes

* test: update postman collection

* fix: set `oid4vc` dependencies to specific `rev`

* chore: set `rust-version` and use `workspace.package` settings

* feat: add `GET` method for `credentials` endpoint

* test: add `GET` method for `credentials` endpoint in postman collection

This includes a test for the `POST` method for the `credentials`
endpoint that updates the `CREDENTIAL_LOCATION` environment variable

* chore: add `/v1/credentials/{credential_id}` to `openapi.yaml` file

* chore: bump `axum` dependency to `0.7`

* chore: update `axum` related code to version `0.7`

* feat: add `log_error_response` macro for cleaner logging of error responses

* chore: update POST request to a valid OBv3 `credentialSubject`

* chore: use `TraceLayer` for proper tracing in `agent_api_rest`

* `on_request` makes sure that each request is traced
* `on_response` makes sure that each response is traced
* `on_body_chunk` makes sure that each response body is traced

`TraceLayer` only accepts `Request<axum::body::Body` which is not `Serialize`,
therefore, we still need to explicitly trace the request body in each route.

* fix: remove obsolete `log_error_response` macro

The `log_error_response` macro is replaced by the `TraceLayer`.`

* refactor: use `if` + `is_err()` rather than `match` for `command_handler` useage

* feat: add `NOT_FOUND` response option

* fix: set tokio version to `1`

* feat: initialize module

* WIP

* refactor: move `ApplicationState` to `agent_shared`

* feat: add `agent_verification`

* style: rename `AuthorizationRequestTestFramework` to `ConnectionTestFramework`

* feat: add `VerificationState` to `ApplicationState`

* chore: add `Cargo.lock` file

* feat: add verification endpoints

* refactor: remove `ApplicationState` struct and replace it for a tuple

* fix: remove `ConnectionNotificationSent`

Instead of using the `VerificationServices` for sending connection notifications
we will probably need to utilize a `Query` that will function as an
outgoing adapter.

* feat: add `VerificationState` to `ApplicationState`

* feat: add `authorization_request` and `connection` tables to `init.sql`

* feat: add `EventPublisherHttp`

* feat: add `OutboundAdapter` trait

* test: add default parameters to tests  in `agent_api_rest`

* feat: make `EventPublisherHttp` support all different aggregates

* test: adjust test for `EventPublisherHttp`

* feat: add `EventPublisherHttp` to `agent_application`

* test: fix `Mutex`

* chore: remove unused environment variables from `.env.example`

* fix: rename aggregate event publishers

* test: improve tests for Verification in  `agent_api_rest`

* fix: rename siopv2 endpoints, add `path`

* feat: move `client_metadata` to `VerificationServices`

* docs: add `README.md` file for `agent_event_publisher`, remove `config.yaml`

* fix: default to empty `EventPublisherHttp` when `config.yml` does not exist

* fix: use `client_id` for the `connection_id`

* fix: return `text/plain` instead of `application/json`

* fix: add `InvalidSIOPv2AuthorizationResponse` error

* docs: add comments to Verification endpoints

* fix: fix the path to the `config.yml` file

* test: fix test

* feat: enable `agent_event_publisher_http` in docker environment

* docs: add documentation for `SecretManager` and `EventPublisherHttp`

* style: use `pub type`s for adapters

* feat: change content-type to application/json

* style: replace closure for enum variant

* fix: change `authorization_requests` endpoints response content-type to `text/plain`

* docs: add verification related endpoints to `openapi.yml`

* style: rename `OutboundAdapter` to `EventPublisher`

* docs: specify that the default Stronghold option is temporary

* refactor: move `secret_manager` function to `agent_secret_manager`

* feat: add `get_authorization_requests` endpoint

* docs: fix example link

* fix: fix `SecretManager` import

* chore: update `did-manager` and `oid4vc` dependencies

* fix: reset `format-lint-test` workflow

* test: add verification endpoints to Postman collection

* WIP

* feat: add `default_did_method`

* style: remove unnecessary `let` binding

* feat: support just-in-time external credential signing

* style: fix clippy

* WIP

* feat: add `did:iota`

* WIP

* feat: implement `Subject` responsible for singing and verifying data

* feat: add `oid4vp` support

* docs: update `/v1/authorization_request` in `openapi` file

* chore: update postman collection

* fix: set `presentation_definition` to contain `VerifiableCredential`

* fix: use slice directly

* fix: use `did:iota:rms`

* style: several cleanups

* docs: add documentation for using the IOTA DID Method

* feat: update docker-compose

* chore: update `oid4vc` dependencies

* fix: remove `test_definition.json`

* style: rename endpoint deserialization helpers

* style: rename endpoint deserialization helpers

* docs: add comment for `GenericAuthorizationResponse`

* docs: improve the IOTA DID Method related documentation

* style: add newline

---------

Co-authored-by: Daniel Mader <[email protected]>
  • Loading branch information
nanderstabel and daniel-mader authored May 14, 2024
1 parent e581d45 commit 419f768
Show file tree
Hide file tree
Showing 22 changed files with 234 additions and 156 deletions.
16 changes: 10 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ rust-version = "1.76.0"

[workspace.dependencies]
did_manager = { git = "https://[email protected]/impierce/did-manager.git", rev = "c70c0f1" }
siopv2 = { git = "https://[email protected]/impierce/openid4vc.git", rev = "ce4e3fd" }
oid4vci = { git = "https://[email protected]/impierce/openid4vc.git", rev = "ce4e3fd" }
oid4vc-core = { git = "https://[email protected]/impierce/openid4vc.git", rev = "ce4e3fd" }
oid4vc-manager = { git = "https://[email protected]/impierce/openid4vc.git", rev = "ce4e3fd" }
oid4vp = { git = "https://[email protected]/impierce/openid4vc.git", rev = "ce4e3fd" }
siopv2 = { git = "https://[email protected]/impierce/openid4vc.git", rev = "7fdd207" }
oid4vci = { git = "https://[email protected]/impierce/openid4vc.git", rev = "7fdd207" }
oid4vc-core = { git = "https://[email protected]/impierce/openid4vc.git", rev = "7fdd207" }
oid4vc-manager = { git = "https://[email protected]/impierce/openid4vc.git", rev = "7fdd207" }
oid4vp = { git = "https://[email protected]/impierce/openid4vc.git", rev = "7fdd207" }

async-trait = "0.1"
axum = { version = "0.7", features = ["tracing"] }
Expand Down
40 changes: 21 additions & 19 deletions agent_api_rest/src/issuance/credential_issuer/credential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,25 @@ mod tests {
Mock, MockServer, ResponseTemplate,
};

const CREDENTIAL_JWT: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa2dF\
ODROQ01wTWVBeDlqSzljZjVXNEc4Z2NaOXh1d0p2RzFlN3dOazhLQ2d0I3o2TWtn\
RTg0TkNNcE1lQXg5aks5Y2Y1VzRHOGdjWjl4dXdKdkcxZTd3Tms4S0NndCJ9.eyJ\
pc3MiOiJkaWQ6a2V5Ono2TWtnRTg0TkNNcE1lQXg5aks5Y2Y1VzRHOGdjWjl4dXd\
KdkcxZTd3Tms4S0NndCIsInN1YiI6ImRpZDprZXk6ejZNa2lpZXlvTE1TVnNKQVp\
2N0pqZTV3V1NrREV5bVVna3lGOGtiY3JqWnBYM3FkIiwiZXhwIjo5OTk5OTk5OTk\
5LCJpYXQiOjAsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8\
yMDE4L2NyZWRlbnRpYWxzL3YxIiwiaHR0cHM6Ly9wdXJsLmltc2dsb2JhbC5vcmc\
vc3BlYy9vYi92M3AwL2NvbnRleHQtMy4wLjIuanNvbiJdLCJpZCI6Imh0dHA6Ly9\
leGFtcGxlLmNvbS9jcmVkZW50aWFscy8zNTI3IiwidHlwZSI6WyJWZXJpZmlhYmx\
lQ3JlZGVudGlhbCIsIk9wZW5CYWRnZUNyZWRlbnRpYWwiXSwiaXNzdWVyIjoiZGl\
kOmtleTp6Nk1rZ0U4NE5DTXBNZUF4OWpLOWNmNVc0RzhnY1o5eHV3SnZHMWU3d05\
rOEtDZ3QiLCJpc3N1YW5jZURhdGUiOiIyMDEwLTAxLTAxVDAwOjAwOjAwWiIsIm5\
hbWUiOiJUZWFtd29yayBCYWRnZSIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImZpcnN\
0X25hbWUiOiJGZXJyaXMiLCJsYXN0X25hbWUiOiJSdXN0YWNlYW4iLCJpZCI6ImR\
pZDprZXk6ejZNa2lpZXlvTE1TVnNKQVp2N0pqZTV3V1NrREV5bVVna3lGOGtiY3J\
qWnBYM3FkIn19fQ.MzHsluxKNsnA01df0kUyXVBIzkBJajKhHbuG-_vNGz0QAPQ1\
6jZ4IwAtEwt6XfbV9luFalRL3qtsmDvaNBf7CA";

trait CredentialEventTrigger {
async fn prepare_credential_event_trigger(&self, app: Arc<Mutex<Option<Router>>>, is_self_signed: bool);
}
Expand Down Expand Up @@ -174,7 +193,7 @@ mod tests {
let credentials_endpoint_request = if is_self_signed {
CredentialsEndpointRequest {
offer_id: offer_id.clone(),
credential: json!("eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa2lpZXlvTE1TVnNKQVp2N0pqZTV3V1NrREV5bVVna3lGOGtiY3JqWnBYM3FkI3o2TWtpaWV5b0xNU1ZzSkFadjdKamU1d1dTa0RFeW1VZ2t5RjhrYmNyalpwWDNxZCJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtpaWV5b0xNU1ZzSkFadjdKamU1d1dTa0RFeW1VZ2t5RjhrYmNyalpwWDNxZCIsInN1YiI6ImRpZDprZXk6ejZNa2lpZXlvTE1TVnNKQVp2N0pqZTV3V1NrREV5bVVna3lGOGtiY3JqWnBYM3FkIiwiZXhwIjo5OTk5OTk5OTk5LCJpYXQiOjAsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIiwiaHR0cHM6Ly9wdXJsLmltc2dsb2JhbC5vcmcvc3BlYy9vYi92M3AwL2NvbnRleHQtMy4wLjIuanNvbiJdLCJpZCI6Imh0dHA6Ly9leGFtcGxlLmNvbS9jcmVkZW50aWFscy8zNTI3IiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIk9wZW5CYWRnZUNyZWRlbnRpYWwiXSwiaXNzdWVyIjoiZGlkOmtleTp6Nk1raWlleW9MTVNWc0pBWnY3SmplNXdXU2tERXltVWdreUY4a2JjcmpacFgzcWQiLCJpc3N1YW5jZURhdGUiOiIyMDEwLTAxLTAxVDAwOjAwOjAwWiIsIm5hbWUiOiJUZWFtd29yayBCYWRnZSIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImZpcnN0X25hbWUiOiJGZXJyaXMiLCJsYXN0X25hbWUiOiJSdXN0YWNlYW4iLCJpZCI6ImRpZDprZXk6ejZNa2lpZXlvTE1TVnNKQVp2N0pqZTV3V1NrREV5bVVna3lGOGtiY3JqWnBYM3FkIn19fQ.r7T_zOXP7E2k7eAPq5EF20shwrnPKK0mOCfNaB0phPEXVkYSG_sf6QygUDuJ8-P0yU4EEajgE0dxJuRfdMVDAQ"),
credential: json!(CREDENTIAL_JWT),
is_signed: true,
}
} else {
Expand Down Expand Up @@ -330,24 +349,7 @@ mod tests {
assert_eq!(
body,
json!({
"credential": "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa2lp\
ZXlvTE1TVnNKQVp2N0pqZTV3V1NrREV5bVVna3lGOGtiY3JqWnBYM3FkI3o2TWtp\
aWV5b0xNU1ZzSkFadjdKamU1d1dTa0RFeW1VZ2t5RjhrYmNyalpwWDNxZCJ9.eyJ\
pc3MiOiJkaWQ6a2V5Ono2TWtpaWV5b0xNU1ZzSkFadjdKamU1d1dTa0RFeW1VZ2t\
5RjhrYmNyalpwWDNxZCIsInN1YiI6ImRpZDprZXk6ejZNa2lpZXlvTE1TVnNKQVp\
2N0pqZTV3V1NrREV5bVVna3lGOGtiY3JqWnBYM3FkIiwiZXhwIjo5OTk5OTk5OTk\
5LCJpYXQiOjAsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8\
yMDE4L2NyZWRlbnRpYWxzL3YxIiwiaHR0cHM6Ly9wdXJsLmltc2dsb2JhbC5vcmc\
vc3BlYy9vYi92M3AwL2NvbnRleHQtMy4wLjIuanNvbiJdLCJpZCI6Imh0dHA6Ly9\
leGFtcGxlLmNvbS9jcmVkZW50aWFscy8zNTI3IiwidHlwZSI6WyJWZXJpZmlhYmx\
lQ3JlZGVudGlhbCIsIk9wZW5CYWRnZUNyZWRlbnRpYWwiXSwiaXNzdWVyIjoiZGl\
kOmtleTp6Nk1raWlleW9MTVNWc0pBWnY3SmplNXdXU2tERXltVWdreUY4a2Jjcmp\
acFgzcWQiLCJpc3N1YW5jZURhdGUiOiIyMDEwLTAxLTAxVDAwOjAwOjAwWiIsIm5\
hbWUiOiJUZWFtd29yayBCYWRnZSIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImZpcnN\
0X25hbWUiOiJGZXJyaXMiLCJsYXN0X25hbWUiOiJSdXN0YWNlYW4iLCJpZCI6ImR\
pZDprZXk6ejZNa2lpZXlvTE1TVnNKQVp2N0pqZTV3V1NrREV5bVVna3lGOGtiY3J\
qWnBYM3FkIn19fQ.r7T_zOXP7E2k7eAPq5EF20shwrnPKK0mOCfNaB0phPEXVkYS\
G_sf6QygUDuJ8-P0yU4EEajgE0dxJuRfdMVDAQ"
"credential": CREDENTIAL_JWT
}
)
);
Expand Down
2 changes: 1 addition & 1 deletion agent_api_rest/src/verification/authorization_requests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ pub mod tests {

let body = axum::body::to_bytes(response.into_body(), usize::MAX).await.unwrap();
let form_url_encoded_authorization_request: String = String::from_utf8(body.to_vec()).unwrap();
assert_eq!(form_url_encoded_authorization_request, format!("openid://?client_id=did%3Akey%3Az6MkiieyoLMSVsJAZv7Jje5wWSkDEymUgkyF8kbcrjZpX3qd&request_uri=https%3A%2F%2Fmy-domain.example.org%2Frequest%2F{state}"));
assert_eq!(form_url_encoded_authorization_request, format!("openid://?client_id=did%3Akey%3Az6MkgE84NCMpMeAx9jK9cf5W4G8gcZ9xuwJvG1e7wNk8KCgt&request_uri=https%3A%2F%2Fmy-domain.example.org%2Frequest%2F{state}"));

let response = app
.call(
Expand Down
1 change: 1 addition & 0 deletions agent_api_rest/src/verification/relying_party/redirect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ pub mod tests {
.unwrap();
let authorization_response = provider_manager
.generate_response(&authorization_request, Default::default())
.await
.unwrap();

let response = app
Expand Down
2 changes: 1 addition & 1 deletion agent_api_rest/src/verification/relying_party/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ pub mod tests {
let body: String = String::from_utf8(body.to_vec()).unwrap();

let header = body.split_once('.').unwrap().0;
assert_eq!(header, "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa2lpZXlvTE1TVnNKQVp2N0pqZTV3V1NrREV5bVVna3lGOGtiY3JqWnBYM3FkI3o2TWtpaWV5b0xNU1ZzSkFadjdKamU1d1dTa0RFeW1VZ2t5RjhrYmNyalpwWDNxZCJ9");
assert_eq!(header, "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa2dFODROQ01wTWVBeDlqSzljZjVXNEc4Z2NaOXh1d0p2RzFlN3dOazhLQ2d0I3o2TWtnRTg0TkNNcE1lQXg5aks5Y2Y1VzRHOGdjWjl4dXdKdkcxZTd3Tms4S0NndCJ9");
}

#[tokio::test]
Expand Down
18 changes: 18 additions & 0 deletions agent_application/docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,24 @@ docker compose up
> [!NOTE]
> If you don't have rewrite rules enabled on your reverse proxy, you can set the `AGENT_CONFIG_BASE_PATH` to a value such as `ssi-agent`.
## Utilizing the IOTA DID Method
By default, UniCore uses the JWK DID Method to generate and manage DIDs. However, UniCore also supports the IOTA DID
Method, which leverages the IOTA Tangle to store your DID document. To enable the IOTA DID Method, set these environment
variables:
```yaml
AGENT_CONFIG_ISSUER_DID: <your-pre-existing-IOTA-DID>
AGENT_CONFIG_ISSUER_FRAGMENT: <your-pre-existing-IOTA-DID-fragment>
AGENT_CONFIG_DEFAULT_DID_METHOD: "did:iota"
```
UniCore supports any of the IOTA networks (Testnet, Shimmer, Mainnet). For example, if you want to enable the development network for Shimmer, the
aforementioned environment variables would look like this:
```yaml
AGENT_CONFIG_ISSUER_DID: "did:iota:rms:0x42ad588322e58b3c07aa39e4948d021ee17ecb5747915e9e1f35f028d7ecaf90"
AGENT_CONFIG_ISSUER_FRAGMENT: "bQKQRzaop7CgEvqVq8UlgLGsdF-R-hnLFkKFZqW2VN0"
AGENT_CONFIG_DEFAULT_DID_METHOD: "did:iota:rms"
```
## Leveraging Just-in-Time Data Request Events
UniCore facilitates dynamic integration with external systems through just-in-time data request events, dispatched seamlessly via an HTTP Event Publisher. This enables real-time data retrieval and on-demand generation, enhancing flexibility and efficiency in your SSI ecosystem.
Expand Down
12 changes: 9 additions & 3 deletions agent_application/docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,16 @@ services:
AGENT_APPLICATION_URL: ${AGENT_APPLICATION_URL}
AGENT_STORE_DB_CONNECTION_STRING: postgresql://demo_user:demo_pass@cqrs-postgres-db:5432/demo
AGENT_SECRET_MANAGER_STRONGHOLD_PATH: /app/res/stronghold
AGENT_SECRET_MANAGER_STRONGHOLD_PASSWORD: "secure_password"
AGENT_SECRET_MANAGER_ISSUER_KEY_ID: "9O66nzWqYYy1LmmiOudOlh2SMIaUWoTS"

AGENT_CONFIG_STRONGHOLD_PASSWORD: "VNvRtH4tKyWwvJDpL6Vuc2aoLiKAecGQ"
AGENT_CONFIG_ISSUER_KEY_ID: "UVDxWhG2rB39FkaR7I27mHeUNrGtUgcr"

# Uncomment the following lines to use the DID method `did:iota:rms`
# AGENT_CONFIG_ISSUER_DID: "did:iota:rms:0x42ad588322e58b3c07aa39e4948d021ee17ecb5747915e9e1f35f028d7ecaf90"
# AGENT_CONFIG_ISSUER_FRAGMENT: "bQKQRzaop7CgEvqVq8UlgLGsdF-R-hnLFkKFZqW2VN0"
# AGENT_CONFIG_DEFAULT_DID_METHOD: "did:iota:rms"
volumes:
- ../../agent_secret_manager/tests/res/test.stronghold:/app/res/stronghold
- ../../agent_secret_manager/tests/res/selv.stronghold:/app/res/stronghold
- ../../agent_event_publisher_http/config.yml:/app/agent_event_publisher_http/config.yml
- ../../agent_verification/presentation_definitions:/app/agent_verification/presentation_definitions
# TODO: Remove this. This is a workaround that ensures that the `agent_verification/presentation_definitions`
Expand Down
9 changes: 3 additions & 6 deletions agent_application/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
use std::{str::FromStr, sync::Arc};

use agent_api_rest::app;
use agent_event_publisher_http::EventPublisherHttp;
use agent_issuance::{startup_commands::startup_commands, state::initialize};
use agent_secret_manager::{secret_manager, subject::Subject};
use agent_shared::config;
use agent_store::{in_memory, postgres, EventPublisher};
use agent_verification::services::VerificationServices;
use oid4vc_core::{client_metadata::ClientMetadataResource, DidMethod, SubjectSyntaxType};
use oid4vc_core::{client_metadata::ClientMetadataResource, SubjectSyntaxType};
use serde_json::json;
use std::{str::FromStr, sync::Arc};
use tracing::info;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};

Expand All @@ -35,9 +34,7 @@ async fn main() {
client_name: None,
logo_uri: None,
extension: siopv2::authorization_request::ClientMetadataParameters {
subject_syntax_types_supported: vec![SubjectSyntaxType::Did(
DidMethod::from_str(&default_did_method).unwrap(),
)],
subject_syntax_types_supported: vec![SubjectSyntaxType::from_str(&default_did_method).unwrap()],
},
},
ClientMetadataResource::ClientMetadata {
Expand Down
2 changes: 2 additions & 0 deletions agent_issuance/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,5 @@ lazy_static.workspace = true
serial_test = "3.0"
tokio.workspace = true
tracing-test.workspace = true
async-std = { version = "1.5", features = ["attributes", "tokio1"] }
rstest.workspace = true
Loading

0 comments on commit 419f768

Please sign in to comment.