diff --git a/bindings/grpc/proto/utils.proto b/bindings/grpc/proto/utils.proto new file mode 100644 index 0000000000..87ea3f7054 --- /dev/null +++ b/bindings/grpc/proto/utils.proto @@ -0,0 +1,23 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +syntax = "proto3"; +package utils; + +message DataSigningRequest { + // Raw data that will be signed. + bytes data = 1; + // Signing key's ID. + string key_id = 2; +} + +message DataSigningResponse { + // Raw data signature. + bytes signature = 1; +} + +// Service that handles signing operations on raw data. +service Signing { + rpc sign(DataSigningRequest) returns (DataSigningResponse); +} + diff --git a/bindings/grpc/src/services/mod.rs b/bindings/grpc/src/services/mod.rs index f632feb91a..00abe17ce1 100644 --- a/bindings/grpc/src/services/mod.rs +++ b/bindings/grpc/src/services/mod.rs @@ -7,6 +7,7 @@ pub mod domain_linkage; pub mod health_check; pub mod sd_jwt; pub mod status_list_2021; +pub mod utils; use identity_stronghold::StrongholdStorage; use iota_sdk::client::Client; @@ -21,6 +22,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)); routes.routes() } diff --git a/bindings/grpc/src/services/utils.rs b/bindings/grpc/src/services/utils.rs new file mode 100644 index 0000000000..0e7d2fc570 --- /dev/null +++ b/bindings/grpc/src/services/utils.rs @@ -0,0 +1,67 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use _utils::signing_server::Signing as SigningSvc; +use _utils::signing_server::SigningServer; +use _utils::DataSigningRequest; +use _utils::DataSigningResponse; +use identity_iota::storage::JwkStorage; +use identity_iota::storage::KeyId; +use identity_iota::storage::KeyStorageError; +use identity_stronghold::StrongholdStorage; +use tonic::Request; +use tonic::Response; +use tonic::Status; + +mod _utils { + tonic::include_proto!("utils"); +} + +#[derive(Debug, thiserror::Error)] +#[error("Key storage error: {0}")] +pub struct Error(#[from] KeyStorageError); + +impl From for Status { + fn from(value: Error) -> Self { + Status::internal(value.to_string()) + } +} + +pub struct SigningService { + storage: StrongholdStorage, +} + +impl SigningService { + pub fn new(stronghold: &StrongholdStorage) -> Self { + Self { + storage: stronghold.clone(), + } + } +} + +#[tonic::async_trait] +impl SigningSvc for SigningService { + #[tracing::instrument( + name = "utils/sign", + skip_all, + fields(request = ?req.get_ref()) + ret, + err, + )] + async fn sign(&self, req: Request) -> Result, Status> { + let DataSigningRequest { data, key_id } = req.into_inner(); + let key_id = KeyId::new(key_id); + let public_key_jwk = self.storage.get_public_key(&key_id).await.map_err(Error)?; + let signature = self + .storage + .sign(&key_id, &data, &public_key_jwk) + .await + .map_err(Error)?; + + Ok(Response::new(DataSigningResponse { signature })) + } +} + +pub fn service(stronghold: &StrongholdStorage) -> SigningServer { + SigningServer::new(SigningService::new(stronghold)) +} diff --git a/bindings/grpc/tests/api/main.rs b/bindings/grpc/tests/api/main.rs index e187cf7f1c..af4929bfae 100644 --- a/bindings/grpc/tests/api/main.rs +++ b/bindings/grpc/tests/api/main.rs @@ -10,3 +10,4 @@ mod helpers; mod jwt; mod sd_jwt_validation; mod status_list_2021; +mod utils; diff --git a/bindings/grpc/tests/api/utils.rs b/bindings/grpc/tests/api/utils.rs new file mode 100644 index 0000000000..9c863bf3de --- /dev/null +++ b/bindings/grpc/tests/api/utils.rs @@ -0,0 +1,48 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use _utils::signing_client::SigningClient; +use _utils::DataSigningRequest; +use identity_iota::verification::jws::JwsAlgorithm; +use identity_storage::JwkStorage; +use identity_storage::KeyType; +use identity_stronghold::StrongholdStorage; + +use crate::helpers::make_stronghold; +use crate::helpers::TestServer; + +mod _utils { + tonic::include_proto!("utils"); +} + +const SAMPLE_SIGNING_DATA: &'static [u8] = b"I'm just some random data to be signed :)"; + +#[tokio::test] +async fn raw_data_signing_works() -> anyhow::Result<()> { + let stronghold = StrongholdStorage::new(make_stronghold()); + let server = TestServer::new_with_stronghold(stronghold.clone()).await; + + let key_id = stronghold + .generate(KeyType::from_static_str("Ed25519"), JwsAlgorithm::EdDSA) + .await? + .key_id; + + let expected_signature = { + let public_key_jwk = stronghold.get_public_key(&key_id).await?; + stronghold.sign(&key_id, SAMPLE_SIGNING_DATA, &public_key_jwk).await? + }; + + let mut grpc_client = SigningClient::connect(server.endpoint()).await?; + let signature = grpc_client + .sign(DataSigningRequest { + data: SAMPLE_SIGNING_DATA.to_owned(), + key_id: key_id.to_string(), + }) + .await? + .into_inner() + .signature; + + assert_eq!(signature, expected_signature); + + Ok(()) +}