From 6a5c1c80b92fe69ee62c1e1cfa5c07a2bca43aa2 Mon Sep 17 00:00:00 2001 From: Emmanouil Koukoularis Date: Tue, 5 Sep 2023 16:16:59 +0300 Subject: [PATCH] test --- config/config.template.ts | 3 +- samples/wallet-mock/app.js | 15 +++---- src/app.ts | 2 + src/routers/presentation.router.ts | 13 +++++++ src/routers/storage.router.ts | 1 - src/routers/verifiers.router.ts | 15 +++++++ src/services/DatabaseKeystoreService.ts | 28 +++++-------- ...enidForCredentialIssuanceMattrV2Service.ts | 1 - .../OpenidForCredentialIssuanceService.ts | 4 +- src/services/OpenidForPresentationService.ts | 30 +++++++++++++- src/services/VerifierRegistryService.ts | 39 +++++++++++++++++++ src/services/W3CDidKeyUtilityService.ts | 32 +++++++++++++++ src/services/interfaces.ts | 9 +++++ src/services/inversify.config.ts | 18 ++++++++- src/services/types.ts | 3 ++ 15 files changed, 177 insertions(+), 36 deletions(-) create mode 100644 src/routers/verifiers.router.ts create mode 100644 src/services/VerifierRegistryService.ts create mode 100644 src/services/W3CDidKeyUtilityService.ts diff --git a/config/config.template.ts b/config/config.template.ts index f4c40bd..3100307 100644 --- a/config/config.template.ts +++ b/config/config.template.ts @@ -16,6 +16,7 @@ export = { walletClientUrl: "WALLET_CLIENT_URL", alg: "ES256", servicesConfiguration: { - issuanceService: "OpenidForCredentialIssuanceService" + issuanceService: "OpenidForCredentialIssuanceService", // OpenidForCredentialIssuanceService or OpenidForCredentialIssuanceMattrV2Service + didKeyService: "W3C", // W3C or EBSI } } \ No newline at end of file diff --git a/samples/wallet-mock/app.js b/samples/wallet-mock/app.js index efc5037..92fff73 100644 --- a/samples/wallet-mock/app.js +++ b/samples/wallet-mock/app.js @@ -48,9 +48,6 @@ app.get('/', async (req, res) => { { headers: { "Authorization": `Bearer ${global.user.appToken}` }} ).then(response => { let { vc_list } = response.data; - console.log("VC list = "); - - console.dir(vc_list); vc_list = vc_list.map((vc) => { const vcjwt = vc.credential; const payload = JSON.parse(base64url.decode(vcjwt.split('.')[1])); @@ -74,9 +71,7 @@ app.get('/vp', async (req, res) => { { headers: { "Authorization": `Bearer ${global.user.appToken}` }} ).then(response => { let { vp_list } = response.data; - console.log("VP list = "); - console.dir(vp_list); vp_list = vp_list.map((vp) => { const vpjwt = vp.presentation; const payload = JSON.parse(base64url.decode(vpjwt.split('.')[1])); @@ -101,7 +96,6 @@ app.get('/vc/:vc_id', async (req, res) => { { headers: { "Authorization": `Bearer ${global.user.appToken}` }} ).then(response => { let vc = response.data; - console.log("VC list = "); const vcjwt = vc.credential; const payload = base64url.decode(vcjwt.split('.')[1]); @@ -165,11 +159,13 @@ app.get('/init/issuance/:iss', async (req, res) => { app.get('/init/verification/vid', async (req, res) => { - const url = new URL("http://127.0.0.1:8003/verification/authorize"); - url.searchParams.append("scope", "openid vid") - url.searchParams.append("redirect_uri", "http://127.0.0.1:7777"); + const url = new URL("http://wallet-enterprise-vid-issuer:8003/verification/authorize"); + url.searchParams.append("scope", "openid ver_test:vp_token vid") + url.searchParams.append("redirect_uri", "http://wallet-mock:7777"); url.searchParams.append("client_id", global.user.did) url.searchParams.append("response_type", "code") + url.searchParams.append("state", "123xxx") + res.redirect(url.toString()) }); @@ -188,7 +184,6 @@ async function handleCredentialOffer(req, res, next) { { credential_offer_url: url }, { headers: { "Authorization": `Bearer ${global.user.appToken}` }} ).then(success => { - console.log("SUccess = ", success.data) const { redirect_to } = issuanceInitiation.data; return res.redirect(redirect_to); }).catch(e => { diff --git a/src/app.ts b/src/app.ts index 194e311..01e25b5 100644 --- a/src/app.ts +++ b/src/app.ts @@ -10,6 +10,7 @@ import { issuanceRouter } from './routers/issuance.router'; import { storageRouter } from './routers/storage.router'; import { presentationRouter } from './routers/presentation.router'; import { legalPersonRouter } from './routers/legal_person.router'; +import verifiersRouter from './routers/verifiers.router'; const app: Express = express(); // __dirname is "/path/to/dist/src" @@ -52,6 +53,7 @@ app.use('/issuance', issuanceRouter); app.use('/storage', storageRouter); app.use('/presentation', presentationRouter); app.use('/legal_person', legalPersonRouter); +app.use('/verifiers', verifiersRouter); app.listen(config.port, () => { console.log(`eDiplomas Register app listening at ${config.url}`) diff --git a/src/routers/presentation.router.ts b/src/routers/presentation.router.ts index 052a171..ac8de29 100644 --- a/src/routers/presentation.router.ts +++ b/src/routers/presentation.router.ts @@ -16,7 +16,20 @@ const openidForPresentationService = appContainer.get(TYP const presentationRouter: Router = express.Router(); presentationRouter.use(AuthMiddleware); +presentationRouter.post('/initiate', async (req, res) => { + const { + verifier_id, + scope_name + } = req.body; + try { + const { redirect_to } = await openidForPresentationService.initiateVerificationFlow(req.user.username, verifier_id, scope_name); + return res.send({ redirect_to }); + } + catch(e) { + return res.status(500).send({ error: "Cannot initiate verification flow" }); + } +}) presentationRouter.post('/handle/authorization/request', async (req, res) => { const { diff --git a/src/routers/storage.router.ts b/src/routers/storage.router.ts index 7c36822..16a8abe 100644 --- a/src/routers/storage.router.ts +++ b/src/routers/storage.router.ts @@ -29,7 +29,6 @@ async function getAllVerifiableCredentialsController(req, res) { } }); - console.log("VC list = ", vc_list) res.status(200).send({ vc_list: vc_list }) } diff --git a/src/routers/verifiers.router.ts b/src/routers/verifiers.router.ts new file mode 100644 index 0000000..26e624a --- /dev/null +++ b/src/routers/verifiers.router.ts @@ -0,0 +1,15 @@ +import { Router } from "express"; +import { appContainer } from "../services/inversify.config"; +import { VerifierRegistryService } from "../services/VerifierRegistryService"; + + + +const verifiersRouter = Router(); +const verifiersRegistryService = appContainer.resolve(VerifierRegistryService) + + +verifiersRouter.get('/all', async (req, res) => { + res.send({ verifiers: await verifiersRegistryService.getAllVerifiers() }); +}); + +export default verifiersRouter; \ No newline at end of file diff --git a/src/services/DatabaseKeystoreService.ts b/src/services/DatabaseKeystoreService.ts index be20d01..0397e64 100644 --- a/src/services/DatabaseKeystoreService.ts +++ b/src/services/DatabaseKeystoreService.ts @@ -1,36 +1,28 @@ import { SignJWT, importJWK, jwtVerify } from "jose"; -import { AdditionalKeystoreParameters, WalletKeystore } from "./interfaces"; +import { AdditionalKeystoreParameters, DidKeyUtilityService, WalletKeystore } from "./interfaces"; import { getUserByUsername, storeKeypair } from "../entities/user.entity"; import { SignVerifiablePresentationJWT, WalletKey } from "@gunet/ssi-sdk"; import { randomUUID } from "crypto"; import { verifiablePresentationSchemaURL } from "../util/util"; -import { injectable } from "inversify"; -import * as ed25519 from "@transmute/did-key-ed25519"; -import * as crypto from "node:crypto"; +import { inject, injectable } from "inversify"; + import "reflect-metadata"; +import { TYPES } from "./types"; @injectable() export class DatabaseKeystoreService implements WalletKeystore { - public static readonly identifier = "DatabaseKeystoreService" private readonly algorithm = "EdDSA"; - constructor() { } + constructor( + @inject(TYPES.DidKeyUtilityService) private didKeyService: DidKeyUtilityService, + ) { } async generateKeyPair(username: string): Promise<{ did: string }> { - const { didDocument, keys } = await ed25519.generate( - { - secureRandom: () => { - return crypto.randomBytes(32); - }, - }, - { accept: 'application/did+json' } - ); - console.log("DID document = ", didDocument) - console.log("Keys = ", keys); - storeKeypair(username, didDocument.id, Buffer.from(JSON.stringify(keys[0]))); - return { did: didDocument.id } + const { did, key } = await this.didKeyService.generateKeyPair(); + storeKeypair(username, did, Buffer.from(JSON.stringify(key))); + return { did: did } } async createIdToken(username: string, nonce: string, audience: string, additionalParameters: AdditionalKeystoreParameters): Promise<{ id_token: string; }> { diff --git a/src/services/OpenidForCredentialIssuanceMattrV2Service.ts b/src/services/OpenidForCredentialIssuanceMattrV2Service.ts index f4a5e40..0ad11e5 100644 --- a/src/services/OpenidForCredentialIssuanceMattrV2Service.ts +++ b/src/services/OpenidForCredentialIssuanceMattrV2Service.ts @@ -35,7 +35,6 @@ type IssuanceState = { @injectable() export class OpenidForCredentialIssuanceMattrV2Service implements OpenidCredentialReceiving { - public static readonly identifier = "OpenidForCredentialIssuanceService" // identifierService: IdentifierService = new IdentifierService(); // legalPersonService: LegalPersonService = new LegalPersonService(); diff --git a/src/services/OpenidForCredentialIssuanceService.ts b/src/services/OpenidForCredentialIssuanceService.ts index 57da6df..7d002ac 100644 --- a/src/services/OpenidForCredentialIssuanceService.ts +++ b/src/services/OpenidForCredentialIssuanceService.ts @@ -34,7 +34,6 @@ type IssuanceState = { @injectable() export class OpenidForCredentialIssuanceService implements OpenidCredentialReceiving { - public static readonly identifier = "OpenidForCredentialIssuanceService" // identifierService: IdentifierService = new IdentifierService(); // legalPersonService: LegalPersonService = new LegalPersonService(); @@ -359,7 +358,8 @@ export class OpenidForCredentialIssuanceService implements OpenidCredentialRecei for (const cr of credentialResponses) { - this.checkConstantlyForPendingCredential(state, cr.acceptance_token); + if (cr.acceptance_token) + this.checkConstantlyForPendingCredential(state, cr.acceptance_token); } // remove the ones that are for deferred endpoint diff --git a/src/services/OpenidForPresentationService.ts b/src/services/OpenidForPresentationService.ts index 2b53ca0..500174a 100644 --- a/src/services/OpenidForPresentationService.ts +++ b/src/services/OpenidForPresentationService.ts @@ -12,6 +12,9 @@ import "reflect-metadata"; import { OutboundRequest } from "./types/OutboundRequest"; import { getUserByUsername } from "../entities/user.entity"; import { z } from 'zod'; +import config from "../../config"; +import { randomUUID } from "crypto"; +import { VerifierRegistryService } from "./VerifierRegistryService"; type PresentationDefinition = { id: string, @@ -51,6 +54,7 @@ type Field = { } type VerificationState = { + holder_state?: string; presentation_definition?: PresentationDefinition; audience?: string; nonce?: string; @@ -61,17 +65,39 @@ type VerificationState = { @injectable() export class OpenidForPresentationService implements OutboundCommunication { - public static readonly identifier = "OpenidForPresentationService" states = new Map(); + constructor( @inject(TYPES.WalletKeystore) private walletKeystore: WalletKeystore, + @inject(TYPES.VerifierRegistryService) private verifierRegistryService: VerifierRegistryService, @inject(TYPES.OpenidForCredentialIssuanceService) private OpenidCredentialReceivingService: OpenidCredentialReceiving ) { } + async initiateVerificationFlow(username: string, verifierId: number, scopeName: string): Promise<{ redirect_to?: string }> { + const verifier = (await this.verifierRegistryService.getAllVerifiers()).filter(ver => ver.id == verifierId)[0]; + + const userFetchRes = await getUserByUsername(username); + if (userFetchRes.err) { + return {}; + } + + const holder_state = randomUUID(); + this.states.set(username, { holder_state }); + + const user = userFetchRes.unwrap(); + const url = new URL(verifier.url); + url.searchParams.append("scope", "openid " + scopeName); + url.searchParams.append("redirect_uri", config.walletClientUrl); + url.searchParams.append("client_id", user.did); + url.searchParams.append("response_type", "code"); + url.searchParams.append("state", holder_state); + return { redirect_to: url.toString() }; + } + async handleRequest(username: string, requestURL: string): Promise { try { const { redirect_to } = await this.parseIdTokenRequest(username, requestURL); @@ -371,7 +397,7 @@ export class OpenidForPresentationService implements OutboundCommunication { const directPostPayload = { vp_token: vp_token, - presentation_submission: JSON.stringify(presentationSubmission), + presentation_submission: presentationSubmission, state: state }; const { newLocation } = await axios.post(redirect_uri, qs.stringify(directPostPayload), { diff --git a/src/services/VerifierRegistryService.ts b/src/services/VerifierRegistryService.ts new file mode 100644 index 0000000..632ae91 --- /dev/null +++ b/src/services/VerifierRegistryService.ts @@ -0,0 +1,39 @@ +import { injectable } from "inversify"; +import 'reflect-metadata'; + +type Verifier = { + id: number; + name: string; + url: string; + scopes: { + name: string; + description: string; + }[]; +} + +@injectable() +export class VerifierRegistryService { + private readonly verifierRegistry: Verifier[] = [ + { + id: 1, + name: "National Authority", + url: "http://wallet-enterprise-vid-issuer:8003/verification/authorize", + scopes: [ + { + name: "vid", + description: "Present your Verifiable ID" + }, + { + name: "ver:test", + description: "Test" + } + ] + } + ]; + + + + async getAllVerifiers() { + return this.verifierRegistry; + } +} \ No newline at end of file diff --git a/src/services/W3CDidKeyUtilityService.ts b/src/services/W3CDidKeyUtilityService.ts new file mode 100644 index 0000000..0d756e0 --- /dev/null +++ b/src/services/W3CDidKeyUtilityService.ts @@ -0,0 +1,32 @@ +import { injectable } from 'inversify'; +import 'reflect-metadata'; +import { DidKeyUtilityService } from './interfaces'; +import { JWK } from 'jose'; +import * as ed25519 from "@transmute/did-key-ed25519"; +import * as crypto from "node:crypto"; + + +@injectable() +export class W3CDidKeyUtilityService implements DidKeyUtilityService { + + + async getPublicKeyJwk(did: string): Promise { + const result = await ed25519.resolve(did, { accept: 'application/did+json' }); + const verificationMethod = result.didDocument.verificationMethod[0] as any; + return verificationMethod.publicKeyJwk as JWK; + } + + async generateKeyPair(): Promise<{ did: string, key: any }> { + const { didDocument, keys } = await ed25519.generate( + { + secureRandom: () => { + return crypto.randomBytes(32); + }, + }, + { accept: 'application/did+json' } + ); + console.log("DID document = ", didDocument) + console.log("Keys = ", keys); + return { did: didDocument.id, key: keys[0] }; + } +} \ No newline at end of file diff --git a/src/services/interfaces.ts b/src/services/interfaces.ts index 29ffedf..e5b23d0 100644 --- a/src/services/interfaces.ts +++ b/src/services/interfaces.ts @@ -1,3 +1,4 @@ +import { JWK } from "jose"; import { LegalPersonEntity } from "../entities/LegalPerson.entity"; import { OutboundRequest } from "./types/OutboundRequest"; @@ -30,6 +31,9 @@ export interface WalletKeystore { export interface OutboundCommunication { + + initiateVerificationFlow(username: string, verifierId: number, scopeName: string): Promise<{ redirect_to?: string }>; + handleRequest(username: string, requestURL: string): Promise; /** @@ -44,4 +48,9 @@ export interface OutboundCommunication { export interface LegalPersonsRegistry { getByIdentifier(did: string): Promise; +} + +export interface DidKeyUtilityService { + getPublicKeyJwk(did: string): Promise; + generateKeyPair(): Promise<{ did: string, key: any }> } \ No newline at end of file diff --git a/src/services/inversify.config.ts b/src/services/inversify.config.ts index 031a626..5446bbd 100644 --- a/src/services/inversify.config.ts +++ b/src/services/inversify.config.ts @@ -1,12 +1,14 @@ import { Container } from "inversify"; import { TYPES } from "./types"; -import { OpenidCredentialReceiving, LegalPersonsRegistry, OutboundCommunication, WalletKeystore } from "./interfaces"; +import { OpenidCredentialReceiving, OutboundCommunication, WalletKeystore, DidKeyUtilityService } from "./interfaces"; import { OpenidForCredentialIssuanceService } from "./OpenidForCredentialIssuanceService"; import { OpenidForPresentationService } from "./OpenidForPresentationService"; import "reflect-metadata"; import { DatabaseKeystoreService } from "./DatabaseKeystoreService"; import { OpenidForCredentialIssuanceMattrV2Service } from "./OpenidForCredentialIssuanceMattrV2Service"; import config from "../../config"; +import { W3CDidKeyUtilityService } from "./W3CDidKeyUtilityService"; +import { VerifierRegistryService } from "./VerifierRegistryService"; const appContainer = new Container(); @@ -36,6 +38,20 @@ case "OpenidForCredentialIssuanceMattrV2Service": appContainer.bind(TYPES.OpenidForPresentationService) .to(OpenidForPresentationService) +switch (config.servicesConfiguration.didKeyService) { +case "W3C": + appContainer.bind(TYPES.DidKeyUtilityService) + .to(W3CDidKeyUtilityService) + break; +default: + appContainer.bind(TYPES.DidKeyUtilityService) + .to(W3CDidKeyUtilityService) + break; +} + +appContainer.bind(TYPES.VerifierRegistryService) + .to(VerifierRegistryService) + export { appContainer } diff --git a/src/services/types.ts b/src/services/types.ts index 4559d39..519f8cb 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -14,6 +14,9 @@ const TYPES = { OpenidForPresentationService: Symbol.for("OpenidForPresentationService"), + DidKeyUtilityService: Symbol.for("DidKeyUtilityService"), + VerifierRegistryService: Symbol.for("VerifierRegistryService"), + }; export { TYPES }; \ No newline at end of file