From f1756c0c66ba835bcfcce6bec966f00b4011faf3 Mon Sep 17 00:00:00 2001 From: Enrico Marconi Date: Thu, 25 Jul 2024 09:34:41 +0200 Subject: [PATCH] did:jwk resolution service --- bindings/grpc/proto/utils.proto | 13 ++++++ bindings/grpc/src/services/mod.rs | 2 +- bindings/grpc/src/services/utils.rs | 66 ++++++++++++++++++++++++++++- 3 files changed, 78 insertions(+), 3 deletions(-) diff --git a/bindings/grpc/proto/utils.proto b/bindings/grpc/proto/utils.proto index 87ea3f7054..2caa61244f 100644 --- a/bindings/grpc/proto/utils.proto +++ b/bindings/grpc/proto/utils.proto @@ -21,3 +21,16 @@ service Signing { rpc sign(DataSigningRequest) returns (DataSigningResponse); } +message DidJwkResolutionRequest { + // did:jwk string + string did = 1; +} + +message DidJwkResolutionResponse { + // JSON DID Document + string doc = 1; +} + +service DidJwk { + rpc resolve(DidJwkResolutionRequest) returns (DidJwkResolutionResponse); +} \ No newline at end of file diff --git a/bindings/grpc/src/services/mod.rs b/bindings/grpc/src/services/mod.rs index 8c0a533499..8a4f197792 100644 --- a/bindings/grpc/src/services/mod.rs +++ b/bindings/grpc/src/services/mod.rs @@ -23,7 +23,7 @@ pub fn routes(client: &Client, stronghold: &StrongholdStorage) -> Routes { routes.add_service(domain_linkage::service(client)); routes.add_service(document::service(client, stronghold)); routes.add_service(status_list_2021::service()); - routes.add_service(utils::service(stronghold)); + utils::init_services(&mut routes, stronghold); routes.add_service(presentation::service(client)); routes.routes() diff --git a/bindings/grpc/src/services/utils.rs b/bindings/grpc/src/services/utils.rs index 0e7d2fc570..a8214b332a 100644 --- a/bindings/grpc/src/services/utils.rs +++ b/bindings/grpc/src/services/utils.rs @@ -1,14 +1,26 @@ // Copyright 2020-2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use _utils::did_jwk_server::DidJwk as DidJwkSvc; +use _utils::did_jwk_server::DidJwkServer; use _utils::signing_server::Signing as SigningSvc; use _utils::signing_server::SigningServer; use _utils::DataSigningRequest; use _utils::DataSigningResponse; +use _utils::DidJwkResolutionRequest; +use _utils::DidJwkResolutionResponse; +use anyhow::Context; +use identity_iota::core::ToJson; +use identity_iota::did::CoreDID; +use identity_iota::document::DocumentBuilder; use identity_iota::storage::JwkStorage; use identity_iota::storage::KeyId; use identity_iota::storage::KeyStorageError; +use identity_iota::verification::jwk::Jwk; +use identity_iota::verification::jwu::decode_b64_json; +use identity_iota::verification::VerificationMethod; use identity_stronghold::StrongholdStorage; +use tonic::transport::server::RoutesBuilder; use tonic::Request; use tonic::Response; use tonic::Status; @@ -62,6 +74,56 @@ impl SigningSvc for SigningService { } } -pub fn service(stronghold: &StrongholdStorage) -> SigningServer { - SigningServer::new(SigningService::new(stronghold)) +pub fn init_services(routes: &mut RoutesBuilder, stronghold: &StrongholdStorage) { + routes.add_service(SigningServer::new(SigningService::new(stronghold))); + routes.add_service(DidJwkServer::new(DidJwkService {})); +} + +#[derive(Debug)] +pub struct DidJwkService {} + +#[tonic::async_trait] +impl DidJwkSvc for DidJwkService { + #[tracing::instrument( + name = "utils/resolve_did_jwk", + skip_all, + fields(request = ?req.get_ref()) + ret, + err, + )] + async fn resolve(&self, req: Request) -> Result, Status> { + let DidJwkResolutionRequest { did } = req.into_inner(); + let jwk = parse_did_jwk(&did).map_err(|e| Status::invalid_argument(e.to_string()))?; + let did = CoreDID::parse(did).expect("valid did:jwk"); + let verification_method = + VerificationMethod::new_from_jwk(did.clone(), jwk, Some("0")).map_err(|e| Status::internal(e.to_string()))?; + let verification_method_id = verification_method.id().clone(); + let doc = DocumentBuilder::default() + .id(did) + .verification_method(verification_method) + .assertion_method(verification_method_id.clone()) + .authentication(verification_method_id.clone()) + .capability_invocation(verification_method_id.clone()) + .capability_delegation(verification_method_id.clone()) + .key_agreement(verification_method_id) + .build() + .map_err(|e| Status::internal(e.to_string()))?; + + Ok(Response::new(DidJwkResolutionResponse { + doc: doc.to_json().map_err(|e| Status::internal(e.to_string()))?, + })) + } +} + +fn parse_did_jwk(did: &str) -> anyhow::Result { + let did_parts: [&str; 3] = did + .split(':') + .collect::>() + .try_into() + .map_err(|_| anyhow::anyhow!("invalid did:jwk \"{did}\""))?; + + match did_parts { + ["did", "jwk", data] => decode_b64_json(data).context("failed to deserialize JWK"), + _ => anyhow::bail!("invalid did:jwk string \"{did}\""), + } }