From c66f58a56d9a9930c2b307dd3b63593b747fdb9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eike=20Ha=C3=9F?= Date: Wed, 17 Apr 2024 10:09:20 +0200 Subject: [PATCH] refactor TangleLabs OIDC service --- docker-compose.yml | 8 +- oid4vc/TangleLabs/package.json | 2 +- .../{index.ts => src/grpcService.ts} | 98 ++++--------------- oid4vc/TangleLabs/src/httpServer.ts | 26 +++++ oid4vc/TangleLabs/src/index.ts | 56 +++++++++++ oid4vc/TangleLabs/src/remoteSigner.ts | 43 ++++++++ proto/identity/utils.proto | 22 +++++ tooling/src/main.rs | 11 ++- 8 files changed, 179 insertions(+), 87 deletions(-) rename oid4vc/TangleLabs/{index.ts => src/grpcService.ts} (52%) create mode 100644 oid4vc/TangleLabs/src/httpServer.ts create mode 100644 oid4vc/TangleLabs/src/index.ts create mode 100644 oid4vc/TangleLabs/src/remoteSigner.ts create mode 100644 proto/identity/utils.proto diff --git a/docker-compose.yml b/docker-compose.yml index 9bcb1185..d0921175 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -47,8 +47,8 @@ services: # - redis # - identity environment: - ISSUERS_BANK_DID: did:iota:snd:0x1d78531b739a3aef0e90523213f28e869423a6d6253ea0fcbd7db2714e9606bf - ISSUERS_BANK_FRAGMENT: xwhWgiK3plOzbQESCONMneeFx4ps24rzbtC1jXEb42o + ISSUERS_BANK_DID: did:iota:snd:0x2cfb60f00089a91a96fa8fcee5e2bda15f51b0b062762ad9ca846bba536f8818 + ISSUERS_BANK_FRAGMENT: S3zEW4inNy8FYZEMVTEthdzqqSBq1WglM2k75xKHzy0 labels: - "traefik.enable=true" - "traefik.http.routers.backend.rule=Host(`backend.localhost`)" @@ -60,6 +60,8 @@ services: build: context: ./ dockerfile: ./oid4vc/TangleLabs/Dockerfile + environment: + SIGNER_KEYID: pK0JAYw5RFFKqorBn0x4w98zl2UBfRys expose: - '50051' volumes: @@ -120,7 +122,7 @@ services: image: iotaledger/identity-grpc:alpha environment: - API_ENDPOINT=http://host.docker.internal - - STRONGHOLD_PWD=nEyWjBDO3sQSDnXuwij3KIJREHnWhtIu + - STRONGHOLD_PWD=nhNc9yAjbCuh6YQxKlceLAUrWCwAYLvu - SNAPSHOT_PATH=/stronghold.hodl volumes: - "./data/stronghold.hodl:/stronghold.hodl" diff --git a/oid4vc/TangleLabs/package.json b/oid4vc/TangleLabs/package.json index 8bf7ee07..b03d8742 100644 --- a/oid4vc/TangleLabs/package.json +++ b/oid4vc/TangleLabs/package.json @@ -4,7 +4,7 @@ "description": "", "type": "module", "scripts": { - "start": "tsc --noEmit && node --loader ts-node/esm index.ts", + "start": "tsc --noEmit && node --loader ts-node/esm --experimental-specifier-resolution=node src/index.ts", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", diff --git a/oid4vc/TangleLabs/index.ts b/oid4vc/TangleLabs/src/grpcService.ts similarity index 52% rename from oid4vc/TangleLabs/index.ts rename to oid4vc/TangleLabs/src/grpcService.ts index 57ce96e1..6b44aab9 100644 --- a/oid4vc/TangleLabs/index.ts +++ b/oid4vc/TangleLabs/src/grpcService.ts @@ -4,78 +4,27 @@ import { loadSync } from "@grpc/proto-loader"; import { AuthResponse, RelyingParty, - SigningAlgs, SiopRequestResult, - bytesToString, } from "@tanglelabs/oid4vc"; -import * as KeyDIDResolver from "key-did-resolver"; -import { Resolver } from "did-resolver"; import { PresentationDefinitionV2 } from "@sphereon/pex-models"; import { fileURLToPath } from 'url'; import { dirname } from 'path'; -import express from "express"; -import asyncHandler from "express-async-handler"; - -//@ts-ignore -import { driver } from "@digitalbazaar/did-method-key"; -//@ts-ignore -import { Ed25519VerificationKey2020 } from "@digitalbazaar/ed25519-verification-key-2020"; - - -import { Signer } from 'did-jwt'; - -const remoteSigner: Signer = async (data) => { - console.log(data); - return "test"; -}; - -(async () => { - - const __filename = fileURLToPath(import.meta.url); +const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); - const protoPath = path.join( - __dirname, - "..", - "..", - "proto/oid4vc/siopv2.proto" - ); - const packageDefinition = loadSync(protoPath); - const protoPackage = grpc.loadPackageDefinition(packageDefinition); - - const didKeyDriver = driver(); +const protoPath = path.join( + __dirname, + "..", + "..", + "..", + "proto/oid4vc/siopv2.proto" +); +const packageDefinition = loadSync(protoPath); +const protoPackage = grpc.loadPackageDefinition(packageDefinition); - didKeyDriver.use({ - multibaseMultikeyHeader: "z6Mk", - fromMultibase: Ed25519VerificationKey2020.from, - }); - - const verificationKeyPair = await Ed25519VerificationKey2020.generate(); - - console.log(bytesToString(verificationKeyPair._publicKeyBuffer)); - console.log(bytesToString(verificationKeyPair._privateKeyBuffer)); - - const keyDidResolver = KeyDIDResolver.getResolver(); - let resolver = new Resolver(keyDidResolver); - - const rp = new RelyingParty({ - clientId: "did:iota:0x", - clientMetadata: { - subjectSyntaxTypesSupported: [ - "did:iota" - ], - idTokenSigningAlgValuesSupported: [ - SigningAlgs.EdDSA - ], - }, - did: "did:iota:0x", - kid: "did:iota:0x#my_key", - signer: remoteSigner, - redirectUri: "http://192.168.0.234:8080/api/auth", - resolver: resolver, - }); +export const createService = async (rp: RelyingParty) => { async function createRequest( call: grpc.ServerUnaryCall< @@ -151,26 +100,13 @@ const __dirname = dirname(__filename); gRPCServer.bindAsync( "0.0.0.0:50051", grpc.ServerCredentials.createInsecure(), - () => { - gRPCServer.start(); + (err, port) => { + if(err){ + throw err; + } + console.log(`gRPC server listening on port ${port}`); } ); - - const app = express(); - app.use(express.json()); - app.route("/api/health").get( - asyncHandler(async (req, res) => { - res.status(200).send(); - }) - ); - app.route("/api/auth").post( - asyncHandler(async (req, res) => { - console.log(req); - await rp.verifyAuthResponse(req.body); - res.status(204).send(); - }) - ); - const server = app.listen(3333, "0.0.0.0"); -})(); +}; diff --git a/oid4vc/TangleLabs/src/httpServer.ts b/oid4vc/TangleLabs/src/httpServer.ts new file mode 100644 index 00000000..7d7c4ae0 --- /dev/null +++ b/oid4vc/TangleLabs/src/httpServer.ts @@ -0,0 +1,26 @@ +import express from "express"; +import asyncHandler from "express-async-handler"; + +export const createServer = (rp) => { + + const app = express(); + app.use(express.json()); + app.route("/api/health").get( + asyncHandler(async (req, res) => { + res.status(200).send(); + }) + ); + app.route("/api/auth").post( + asyncHandler(async (req, res) => { + console.log(req); + await rp.verifyAuthResponse(req.body); + res.status(204).send(); + }) + ); + + const port = 3333; + const server = app.listen(port, "0.0.0.0", () => { + console.log(`HTTP server listening on port ${port}`); + }); + +}; diff --git a/oid4vc/TangleLabs/src/index.ts b/oid4vc/TangleLabs/src/index.ts new file mode 100644 index 00000000..228f905b --- /dev/null +++ b/oid4vc/TangleLabs/src/index.ts @@ -0,0 +1,56 @@ +import { + RelyingParty, + SigningAlgs, + bytesToString, +} from "@tanglelabs/oid4vc"; +import * as KeyDIDResolver from "key-did-resolver"; +import { Resolver } from "did-resolver"; + +//@ts-ignore +import { driver } from "@digitalbazaar/did-method-key"; +//@ts-ignore +import { Ed25519VerificationKey2020 } from "@digitalbazaar/ed25519-verification-key-2020"; + + +import { remoteSigner } from "./remoteSigner"; +import { createService } from "./grpcService"; +import { createServer } from "./httpServer"; + +(async () => { + + const didKeyDriver = driver(); + + didKeyDriver.use({ + multibaseMultikeyHeader: "z6Mk", + fromMultibase: Ed25519VerificationKey2020.from, + }); + + const verificationKeyPair = await Ed25519VerificationKey2020.generate(); + + console.log(bytesToString(verificationKeyPair._publicKeyBuffer)); + console.log(bytesToString(verificationKeyPair._privateKeyBuffer)); + + const keyDidResolver = KeyDIDResolver.getResolver(); + let resolver = new Resolver(keyDidResolver); + + const rp = new RelyingParty({ + clientId: "did:iota:0x", + clientMetadata: { + subjectSyntaxTypesSupported: [ + "did:iota" + ], + idTokenSigningAlgValuesSupported: [ + SigningAlgs.EdDSA + ], + }, + did: "did:iota:0x", + kid: "did:iota:0x#my_key", + signer: remoteSigner(process.env.SIGNER_KEYID), + redirectUri: "http://192.168.0.234:8080/api/auth", + resolver: resolver, + }); + + createService(rp); + createServer(rp); + +})(); diff --git a/oid4vc/TangleLabs/src/remoteSigner.ts b/oid4vc/TangleLabs/src/remoteSigner.ts new file mode 100644 index 00000000..8b3a7a02 --- /dev/null +++ b/oid4vc/TangleLabs/src/remoteSigner.ts @@ -0,0 +1,43 @@ +import path from "path"; +import { fileURLToPath } from 'url'; +import { dirname } from 'path'; +import * as grpc from "@grpc/grpc-js"; +import { loadSync } from "@grpc/proto-loader"; +import { Signer } from 'did-jwt'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const identityProtoPath = path.join( + __dirname, + "..", + "..", + "..", + "proto/identity/utils.proto" +); + +const identityPackageDefinition = loadSync(identityProtoPath); +const identityPackage = grpc.loadPackageDefinition(identityPackageDefinition).utils; + +export const remoteSigner: (keyId: string) => Signer = (keyId) => async (data) => { + + //@ts-ignore + const identityClient = new identityPackage.Signing( + 'identity:50051', grpc.credentials.createInsecure() + ); + + console.debug(data); + + const response = await new Promise((resolve, reject) => identityClient.sign({ + keyId, + data: Array.from(Buffer.from(data)), + }, (err, response) => { + if (err) { + console.error(err); + } + resolve(response); + })); + console.log(response) + return response as string; + +}; \ No newline at end of file diff --git a/proto/identity/utils.proto b/proto/identity/utils.proto new file mode 100644 index 00000000..e28ccf35 --- /dev/null +++ b/proto/identity/utils.proto @@ -0,0 +1,22 @@ +// 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); +} \ No newline at end of file diff --git a/tooling/src/main.rs b/tooling/src/main.rs index 74daa9e2..bf9eff35 100644 --- a/tooling/src/main.rs +++ b/tooling/src/main.rs @@ -7,6 +7,8 @@ use identity_iota::iota::IotaIdentityClientExt; use identity_iota::iota::NetworkName; use identity_iota::resolver::Resolver; use identity_iota::storage::JwkDocumentExt; +use identity_iota::storage::KeyId; +use identity_iota::storage::MethodDigest; use identity_stronghold::ED25519_KEY_TYPE; use rand::distributions::{Alphanumeric, DistString}; @@ -23,6 +25,8 @@ use iota_sdk::client::Client; use iota_sdk::client::Password; use iota_sdk::types::block::address::Address; use iota_sdk::types::block::output::AliasOutput; +use identity_iota::storage::KeyIdStorage; + // The API endpoint of an IOTA node, e.g. Hornet. const API_ENDPOINT: &str = "http://localhost"; @@ -68,7 +72,7 @@ async fn main() -> anyhow::Result<()> { Ok(()) } -async fn create_issuer(stronghold_storage: &StrongholdStorage, client: &Client) -> anyhow::Result<(String, String, Address)> { +async fn create_issuer(stronghold_storage: &StrongholdStorage, client: &Client) -> anyhow::Result<(String, KeyId, String, Address)> { // Create a DID document. let address: Address = get_address_with_funds( &client, @@ -97,6 +101,9 @@ async fn create_issuer(stronghold_storage: &StrongholdStorage, client: &Client) ) .await?; + let method = document.resolve_method(&fragment, Some(MethodScope::VerificationMethod)).ok_or(anyhow::anyhow!("no go"))?; + let key_id = storage.key_id_storage().get_key_id(&MethodDigest::new(method)?).await?; + // 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?; @@ -129,5 +136,5 @@ async fn create_issuer(stronghold_storage: &StrongholdStorage, client: &Client) String::from_utf8_lossy(decoded_jws.claims.as_ref()), "test_data" ); - Ok((document.id().to_string(), fragment, address)) + Ok((document.id().to_string(), key_id, fragment, address)) }