Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
f61e9b5
WIP
nanderstabel Jun 26, 2024
4ffa6d1
feat init agetn_holder
nanderstabel Aug 22, 2024
ebfc36d
feat: add `HolderState`
nanderstabel Aug 26, 2024
bca826e
feat: add Holder functionality to `agent_store` and `agent_api_rest`
nanderstabel Aug 26, 2024
75d7a31
feat: add Holder functionality to Event Publisher
nanderstabel Aug 26, 2024
c2b2d62
feat: add `SendCredentialOffer` to `agent_verification`
nanderstabel Aug 26, 2024
87d061f
feat: add `/offers/send` issuance endpoint to `agent_api_rest`
nanderstabel Aug 26, 2024
34ff280
fix: remove incorrect Content Type
nanderstabel Aug 27, 2024
7f1ab4f
feat: add `Status` enum
nanderstabel Aug 27, 2024
302e63f
feat: add REST API for Holder
nanderstabel Aug 27, 2024
2673bac
feat: add `AllOffersView`
nanderstabel Aug 27, 2024
5ee0ae4
feat: add Holder views to `init.sql`
nanderstabel Aug 27, 2024
1babdd4
fix: fix `OfferView` update
nanderstabel Aug 27, 2024
7bcc730
feat: add credentials endpoint for Holder
nanderstabel Aug 27, 2024
2000769
refactor: refactor Router
nanderstabel Aug 27, 2024
7599bee
test: refactor test framework
nanderstabel Aug 30, 2024
805591e
refactor: deprecate `path` closure
nanderstabel Aug 30, 2024
dc8c25d
refactor: remove unused dependencies
nanderstabel Aug 30, 2024
9f20fb6
style: add clippy exception
nanderstabel Aug 30, 2024
7c13929
build: bump oid4vc dependencies
nanderstabel Aug 30, 2024
384244c
refactor: move all `CustomQuery` logic to `agent_shared`
nanderstabel Aug 30, 2024
4c39ac7
fix: add Into<SubjectSyntaxType> for SupportedDidMethod
nanderstabel Aug 30, 2024
29f25da
fix: return 200 OK when list is empty
nanderstabel Aug 30, 2024
e08a045
refactor: clean up code
nanderstabel Aug 30, 2024
f307da3
fix: Fix error handling for the Offer aggregate
nanderstabel Aug 30, 2024
29e90d1
fix: add error handling for to Offer aggregate
nanderstabel Aug 30, 2024
cda0b76
refactor: apply clippy suggestion
nanderstabel Aug 30, 2024
bb43a33
test: update Postman Collection
nanderstabel Aug 30, 2024
5aae168
feat: add Events to `config.rs`
nanderstabel Aug 30, 2024
3056a09
docs: add new Holder events to `agent_event_publisher_http` document…
nanderstabel Aug 30, 2024
fa0e631
Merge branch 'dev' into feat/holder-init
nanderstabel Aug 30, 2024
c3fc105
Merge branch 'dev' into feat/linked-vp
nanderstabel Sep 5, 2024
005cf5d
Merge branch 'feat/holder-init' into feat/linked-vp
nanderstabel Sep 12, 2024
ecf05c4
feat: init `agent_identity`
nanderstabel Sep 18, 2024
1bf0c72
style: use consistent nameing for `View` variables
nanderstabel Sep 18, 2024
ceed053
Merge branch 'feat/holder-init' into feat/linked-vp
nanderstabel Sep 18, 2024
961fb2f
style: rename variables
nanderstabel Sep 18, 2024
e4783b3
refactor: use `type` for `View`s to reduce code duplication
nanderstabel Sep 18, 2024
a20cffe
refactor: use `Jwt` instead of `Value`
nanderstabel Sep 18, 2024
504c3ea
feat: add error handling
nanderstabel Sep 18, 2024
24507cb
fix: remove `presentation_id` from route
nanderstabel Sep 18, 2024
0cabb68
refactor: add error handling and comments
nanderstabel Sep 18, 2024
3f24ce4
refactor: remove unused dependencies
nanderstabel Sep 18, 2024
1647121
build: remove unused dependencies
nanderstabel Sep 18, 2024
5acfef1
feat: add `UnsupportedCredentialFormatError` error
nanderstabel Sep 18, 2024
afefa88
test: update Postman Collection
nanderstabel Sep 19, 2024
3649e63
feat: update `init.sql` file
nanderstabel Sep 19, 2024
d936087
feat: add tests and error handling
nanderstabel Sep 19, 2024
9170c6f
Merge branch 'dev' into feat/linked-vp
nanderstabel Oct 3, 2024
5ef35b6
feat: add error handling
nanderstabel Oct 3, 2024
ffd1754
test: add unit tests for `Service`, `Presentation` and received `Offer`
nanderstabel Oct 4, 2024
0068581
feat: add `GET` method for `/v0/services` endpoint
nanderstabel Oct 4, 2024
610af21
test: update Postman collection
nanderstabel Oct 4, 2024
2ff7d34
docs: add document, service and presentation events
nanderstabel Oct 4, 2024
3b4987f
fix: remove unused import
nanderstabel Oct 4, 2024
a8b5dc4
ci: add DS_Store to .gitignore file
nanderstabel Oct 5, 2024
ca3a9ed
feat: add Document and Service to config.rs
nanderstabel Oct 5, 2024
ae83256
fix: update .env.example variables
nanderstabel Oct 7, 2024
6f614e8
feat: make `/accept` endpoint respond with the Offer
nanderstabel Oct 7, 2024
b25c5f9
feat: add individual aggregate instance endpoints
nanderstabel Oct 4, 2024
64cd02c
test: update Postman collection
nanderstabel Oct 4, 2024
18bd045
fix: add `all_authorization_requests` table
nanderstabel Oct 4, 2024
98d7709
test: update test
nanderstabel Oct 5, 2024
42cf68c
feat: init identity `Connection` aggregate
nanderstabel Oct 5, 2024
f8ce95d
refactor: merge verification Connection into AuthorizationRequest agg…
nanderstabel Oct 5, 2024
594864e
feat: add `Connection` aggregate and corresponding endpoints
nanderstabel Oct 6, 2024
06bc359
feat: add `Connection` related endpoints to Postman collection
nanderstabel Oct 9, 2024
d2df459
fix: add `all_connections` table
nanderstabel Oct 9, 2024
129b6b2
fix: undo openbadgesv3_credentials change
nanderstabel Oct 9, 2024
f62388e
feat: add Status field to Issuance Offer and Credential
nanderstabel Oct 9, 2024
b4ebf82
WIP
nanderstabel Oct 9, 2024
b02e13f
feat: add `services/:service_id`endpoint
nanderstabel Oct 15, 2024
18536f0
feat: change `openid4vci/offers` from GET to POST
nanderstabel Oct 15, 2024
79c791e
test: add `services/:service_id` endpoints to POstman collection
nanderstabel Oct 15, 2024
c5a72e0
fix: change method from `get` to `post`
nanderstabel Oct 15, 2024
007e986
feat: add public `/linked-verifiable-presentations` endpoint
nanderstabel Oct 15, 2024
b34a93d
feat: add `offers_params` endpoint handler
nanderstabel Oct 17, 2024
bd2ca9c
WIP
nanderstabel Oct 18, 2024
8785ec3
feat: persist DID Web Document
nanderstabel Oct 22, 2024
39d9056
feat: validate SD-JWTs
nanderstabel Oct 22, 2024
abcc6b9
fix: always recreate document
nanderstabel Oct 23, 2024
1cc8d4c
Merge branch 'beta' into feat/sd-jwt
nanderstabel Dec 12, 2024
a50a02e
Merge branch 'beta' into feat/sd-jwt
daniel-mader Dec 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
568 changes: 449 additions & 119 deletions Cargo.lock

Large diffs are not rendered by default.

27 changes: 16 additions & 11 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,35 +19,40 @@ edition = "2021"
rust-version = "1.76.0"

[workspace.dependencies]
did_manager = { git = "https://[email protected]/impierce/did-manager.git", tag = "v1.0.0-beta.3" }
siopv2 = { git = "https://[email protected]/impierce/openid4vc.git", rev = "7be5b72" }
oid4vci = { git = "https://[email protected]/impierce/openid4vc.git", rev = "7be5b72" }
oid4vc-core = { git = "https://[email protected]/impierce/openid4vc.git", rev = "7be5b72" }
oid4vc-manager = { git = "https://[email protected]/impierce/openid4vc.git", rev = "7be5b72" }
oid4vp = { git = "https://[email protected]/impierce/openid4vc.git", rev = "7be5b72" }
# did_manager = { git = "https://[email protected]/impierce/did-manager.git", tag = "v1.0.0-beta.3" }
did_manager = { git = "https://[email protected]/impierce/did-manager.git", rev = "c1cfda0" }
siopv2 = { git = "https://[email protected]/impierce/openid4vc.git", rev = "0f77733" }
oid4vci = { git = "https://[email protected]/impierce/openid4vc.git", rev = "0f77733" }
oid4vc-core = { git = "https://[email protected]/impierce/openid4vc.git", rev = "0f77733" }
oid4vc-manager = { git = "https://[email protected]/impierce/openid4vc.git", rev = "0f77733" }
oid4vp = { git = "https://[email protected]/impierce/openid4vc.git", rev = "0f77733" }

async-trait = "0.1"
axum = { version = "0.7", features = ["tracing"] }
base64 = "0.22"
chrono = { version = "0.4", features = ["serde"] }
cqrs-es = "0.4.2"
futures = "0.3"
identity_core = "1.3"
identity_credential = { version = "1.3", default-features = false, features = [
identity_core = { git = "https://github.com/impierce/identity.rs", branch = "fix/compile-fixes" }
identity_credential = { git = "https://github.com/impierce/identity.rs", branch = "fix/compile-fixes", default-features = false, features = [
"validator",
"credential",
"presentation",
"domain-linkage",
"sd-jwt-vc"
] }
identity_did = { version = "1.3" }
identity_iota = { version = "1.3" }
identity_verification = { version = "1.3", default-features = false }

identity_did = { git = "https://github.com/impierce/identity.rs", branch = "fix/compile-fixes" }
identity_document = { git = "https://github.com/impierce/identity.rs", branch = "fix/compile-fixes" }
identity_iota = { git = "https://github.com/impierce/identity.rs", branch = "fix/compile-fixes" }
identity_verification = { git = "https://github.com/impierce/identity.rs", branch = "fix/compile-fixes", default-features = false }
jsonwebtoken = "9.3"
lazy_static = "1.4"
mime = { version = "0.3" }
once_cell = { version = "1.19" }
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
rstest = "0.22"
sd-jwt-payload-rework = { package = "sd-jwt-payload", git = "https://github.com/iotaledger/sd-jwt-payload.git", rev = "0300fc5", default-features = false, features = ["sha"] }
serde = { version = "1.0", default-features = false, features = ["derive"] }
serde_json = { version = "1.0" }
serde_with = "3.7"
Expand Down
2 changes: 1 addition & 1 deletion agent_identity/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ did_manager.workspace = true
identity_credential.workspace = true
identity_core.workspace = true
identity_did.workspace = true
identity_document = { version = "1.3" }
identity_document.workspace = true
jsonwebtoken.workspace = true
oid4vc-core.workspace = true
serde.workspace = true
Expand Down
1 change: 1 addition & 0 deletions agent_shared/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ rust-version.workspace = true

[dependencies]
async-trait.workspace = true
base64.workspace = true
chrono.workspace = true
config = { version = "0.14" }
cqrs-es.workspace = true
Expand Down
1 change: 1 addition & 0 deletions agent_shared/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub mod error;
pub mod generic_query;
pub mod handlers;
pub mod url_utils;
pub mod verifier;

pub use ::config::ConfigError;
use identity_iota::verification::jws::JwsAlgorithm;
Expand Down
50 changes: 50 additions & 0 deletions agent_shared/src/verifier.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use std::str::FromStr as _;

use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
use identity_iota::core::{FromJson as _, ToJson as _};
use identity_iota::verification;
use identity_iota::verification::jws::{
JwsVerifier, SignatureVerificationError, SignatureVerificationErrorKind, VerificationInput,
};
use jsonwebtoken::crypto::verify;
use jsonwebtoken::{Algorithm, DecodingKey, Validation};

/// This `Verifier` uses `jsonwebtoken` under the hood to verify verification input.
pub struct Verifier;
impl JwsVerifier for Verifier {
fn verify(
&self,
input: VerificationInput,
public_key: &verification::jwk::Jwk,
) -> Result<(), SignatureVerificationError> {
use SignatureVerificationErrorKind::*;

let algorithm =
Algorithm::from_str(&input.alg.to_string()).map_err(|_| SignatureVerificationError::new(UnsupportedAlg))?;

// Convert the `IotaIdentityJwk` first into a `jsonwebtoken::Jwk` and then into a `DecodingKey`.
let decoding_key = public_key
.to_json()
.ok()
.and_then(|public_key| jsonwebtoken::jwk::Jwk::from_json(&public_key).ok())
.and_then(|jwk| DecodingKey::from_jwk(&jwk).ok())
.ok_or(SignatureVerificationError::new(KeyDecodingFailure))?;

let mut validation = Validation::new(algorithm);
validation.validate_aud = false;
validation.required_spec_claims.clear();

match verify(
&URL_SAFE_NO_PAD.encode(input.decoded_signature),
&input.signing_input,
&decoding_key,
algorithm,
) {
Ok(true) => Ok(()),
Err(_) | Ok(false) => Err(SignatureVerificationError::new(
// TODO: more fine-grained error handling?
InvalidSignature,
)),
}
}
}
6 changes: 6 additions & 0 deletions agent_verification/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ anyhow = "1.0"
async-trait.workspace = true
chrono.workspace = true
cqrs-es.workspace = true
did_manager.workspace = true
futures.workspace = true
identity_credential.workspace = true
jsonwebtoken.workspace = true
oid4vc-core.workspace = true
oid4vc-manager.workspace = true
Expand All @@ -26,6 +28,10 @@ tracing.workspace = true
url.workspace = true
tokio.workspace = true

identity_iota.workspace = true
sd-jwt-payload-rework.workspace = true
base64.workspace = true

[dev-dependencies]
agent_shared = { path = "../agent_shared", features = ["test_utils"] }
agent_verification = { path = ".", features = ["test_utils"] }
Expand Down
104 changes: 90 additions & 14 deletions agent_verification/src/authorization_request/aggregate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,25 @@ use crate::{
},
services::VerificationServices,
};
use agent_shared::config::{config, get_preferred_signing_algorithm};
use agent_shared::{
config::{config, get_preferred_signing_algorithm},
verifier::Verifier,
};
use async_trait::async_trait;
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
use cqrs_es::Aggregate;
use did_manager::Resolver;
use identity_credential::sd_jwt_vc::SdJwtVc;
use identity_iota::{
core::ToJson as _,
credential::KeyBindingJWTValidationOptions,
did::DID as _,
document::DIDUrlQuery,
verification::jwk::{Jwk, JwkParams},
};
use oid4vc_core::{authorization_request::ByReference, scope::Scope};
use oid4vp::{authorization_request::ClientIdScheme, Oid4vpParams};
use sd_jwt_payload_rework::{RequiredKeyBinding, Sha256Hasher};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tracing::info;
Expand All @@ -21,7 +35,7 @@ pub struct AuthorizationRequest {
pub form_url_encoded_authorization_request: Option<String>,
pub signed_authorization_request_object: Option<String>,
pub id_token: Option<String>,
pub vp_token: Option<String>,
pub vp_tokens: Option<Vec<String>>,
pub state: Option<String>,
}

Expand Down Expand Up @@ -155,18 +169,77 @@ impl Aggregate for AuthorizationRequest {
}])
}
GenericAuthorizationResponse::OID4VP(oid4vp_authorization_response) => {
let _ = relying_party
.validate_response(&oid4vp_authorization_response)
.await
.map_err(InvalidOID4VPAuthorizationResponse)?;

let vp_token = match oid4vp_authorization_response.extension.oid4vp_parameters {
Oid4vpParams::Params { vp_token, .. } => vp_token,
let mut vp_tokens = match &oid4vp_authorization_response.extension.oid4vp_parameters {
Oid4vpParams::Params { vp_token, .. } => vec![vp_token.clone()],
Oid4vpParams::Jwt { .. } => return Err(UnsupportedJwtParameterError),
};

for vp_token in &mut vp_tokens {
if let Ok(sd_jwt_vc) = vp_token.parse::<SdJwtVc>() {
info!("VC SD-JWT: {}", sd_jwt_vc);

if let Some(cnf) = &sd_jwt_vc.claims().cnf {
let jwk = match cnf {
RequiredKeyBinding::Jwk(jwk) => Jwk::from_params(
serde_json::from_value::<JwkParams>(serde_json::json!(jwk))
.map_err(|e| InvalidCnfParameterError(e.to_string()))?,
),
RequiredKeyBinding::Kid(kid) => {
info!("Cnf `kid` value: {kid}");

let did_url = identity_iota::did::DIDUrl::parse(kid)
.map_err(|e| InvalidDidUrlError(format!("Invalid DID URL: {}", e)))?;

let resolver = Resolver::new().await;

let document = resolver
.resolve(did_url.did().as_str())
.await
.map_err(|e| UnsupportedDidMethodError(e.to_string()))?;

let verification_method = document
.resolve_method(
DIDUrlQuery::from(&did_url),
Some(identity_iota::verification::MethodScope::VerificationMethod),
)
.ok_or(MissingVerificationMethodError)?;

verification_method
.data()
.public_key_jwk()
.ok_or(MissingVerificationMethodKeyError)?
.clone()
}
_ => return Err(UnsupportedCnfParameterError),
};

sd_jwt_vc
.validate_key_binding(
&Verifier,
&jwk,
&Sha256Hasher::new(),
&KeyBindingJWTValidationOptions::default(),
)
.map_err(|_| InvalidKeyBindingError)?;
}
let disclosed_object = sd_jwt_vc.into_disclosed_object(&Sha256Hasher::new()).unwrap();

info!("Disclosed object: {:?}", disclosed_object);

*vp_token = URL_SAFE_NO_PAD.encode(
disclosed_object
.to_json_vec()
.map_err(|e| InvalidDisclosedObjectError(e.to_string()))?,
);
} else {
let _ = relying_party
.validate_response(&oid4vp_authorization_response)
.await
.map_err(InvalidOID4VPAuthorizationResponse)?;
}
}
Ok(vec![OID4VPAuthorizationResponseVerified {
vp_token,
vp_tokens,
state: oid4vp_authorization_response.state,
}])
}
Expand Down Expand Up @@ -200,8 +273,8 @@ impl Aggregate for AuthorizationRequest {
self.id_token.replace(id_token);
self.state = state;
}
OID4VPAuthorizationResponseVerified { vp_token, state } => {
self.vp_token.replace(vp_token);
OID4VPAuthorizationResponseVerified { vp_tokens, state } => {
self.vp_tokens.replace(vp_tokens);
self.state = state;
}
}
Expand All @@ -228,6 +301,7 @@ pub mod tests {
use oid4vc_manager::ProviderManager;
use oid4vci::VerifiableCredentialJwt;
use oid4vp::oid4vp::AuthorizationResponseInput;
use oid4vp::oid4vp::PresentationInputType;
use oid4vp::PresentationDefinition;
use rstest::rstest;
use serde_json::json;
Expand Down Expand Up @@ -355,7 +429,7 @@ pub mod tests {
state: Some("state".to_string()),
},
"vp_token" => AuthorizationRequestEvent::OID4VPAuthorizationResponseVerified {
vp_token: token,
vp_tokens: vec![token],
state: Some("state".to_string()),
},
_ => unreachable!("Invalid response type."),
Expand Down Expand Up @@ -444,7 +518,9 @@ pub mod tests {
.generate_response(
oid4vp_authorization_request,
AuthorizationResponseInput {
verifiable_presentation,
verifiable_presentation_input: vec![PresentationInputType::Unsigned(
verifiable_presentation,
)],
presentation_submission,
},
)
Expand Down
16 changes: 16 additions & 0 deletions agent_verification/src/authorization_request/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,20 @@ pub enum AuthorizationRequestError {
InvalidOID4VPAuthorizationResponse(#[source] anyhow::Error),
#[error("`jwt` parameter is not supported yet")]
UnsupportedJwtParameterError,
#[error("`cnf` parameter must be a JWK or a `kid` string")]
UnsupportedCnfParameterError,
#[error("Invalid `cnf` parameter: {0}")]
InvalidCnfParameterError(String),
#[error("Invalid key binding")]
InvalidKeyBindingError,
#[error("Invalid DID URL: {0}")]
InvalidDidUrlError(String),
#[error("Unsupported DID method: {0}")]
UnsupportedDidMethodError(String),
#[error("Unable to find verification method")]
MissingVerificationMethodError,
#[error("No verification method key found")]
MissingVerificationMethodKeyError,
#[error("Invalid disclosed object: {0}")]
InvalidDisclosedObjectError(String),
}
2 changes: 1 addition & 1 deletion agent_verification/src/authorization_request/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub enum AuthorizationRequestEvent {
state: Option<String>,
},
OID4VPAuthorizationResponseVerified {
vp_token: String,
vp_tokens: Vec<String>,
state: Option<String>,
},
}
Expand Down
4 changes: 2 additions & 2 deletions agent_verification/src/authorization_request/views/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ impl View<AuthorizationRequest> for AuthorizationRequest {
self.id_token.replace(id_token.clone());
self.state.clone_from(state);
}
OID4VPAuthorizationResponseVerified { vp_token, state } => {
self.vp_token.replace(vp_token.clone());
OID4VPAuthorizationResponseVerified { vp_tokens, state } => {
self.vp_tokens.replace(vp_tokens.clone());
self.state.clone_from(state);
}
}
Expand Down
10 changes: 8 additions & 2 deletions agent_verification/src/services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use agent_shared::config::{config, get_all_enabled_did_methods, get_preferred_di
use jsonwebtoken::Algorithm;
use oid4vc_core::{client_metadata::ClientMetadataResource, Subject};
use oid4vc_manager::RelyingPartyManager;
use oid4vp::ClaimFormatProperty;
use oid4vp::{ClaimFormatDesignation, ClaimFormatProperty};
use serde_json::json;
use std::{collections::HashMap, str::FromStr, sync::Arc};

Expand Down Expand Up @@ -58,7 +58,13 @@ impl Service for VerificationServices {
.map(|(c, _)| {
(
c.clone(),
ClaimFormatProperty::Alg(signing_algorithms_supported.clone()),
match c {
ClaimFormatDesignation::VcSdJwt => ClaimFormatProperty::SdJwt {
sd_jwt_alg_values: signing_algorithms_supported.clone(),
kb_jwt_alg_values: vec![],
},
_ => ClaimFormatProperty::Alg(signing_algorithms_supported.clone()),
},
)
})
.collect(),
Expand Down
Loading