From 01f2023a4112f04412df4df318c6eacf9da536a7 Mon Sep 17 00:00:00 2001 From: nklomp Date: Mon, 3 Apr 2023 15:57:12 +0200 Subject: [PATCH] feat: Add SIOPv2 Relying Party logic and REST API --- README.md | 4 +- packages/siopv2-openid4vp-common/.gitignore | 9 + packages/siopv2-openid4vp-common/package.json | 23 + .../siopv2-openid4vp-common/src/auth-model.ts | 37 ++ packages/siopv2-openid4vp-common/src/index.ts | 3 + packages/siopv2-openid4vp-common/src/utils.ts | 16 + .../siopv2-openid4vp-common/tsconfig.json | 17 + .../CHANGELOG.md | 0 .../LICENSE | 0 .../README.md | 0 .../__tests__/localAgent.test.ts | 4 +- .../__tests__/restAgent.test.ts | 4 +- .../didAuthSiopOpAuthenticatorAgentLogic.ts | 18 +- .../vc_vp_examples/pd/pd_multiple.json | 0 .../vc_vp_examples/pd/pd_single.json | 0 .../__tests__/vc_vp_examples/psc/psc.json | 0 .../vc_vp_examples/vc/vc_driverLicense.json | 0 .../vc/vc_idCardCredential.json | 0 .../vc_vp_examples/vp/vp_multiple.json | 0 .../vc_vp_examples/vp/vp_single.json | 0 .../agent.yml | 2 +- .../api-extractor.json | 0 .../package.json | 2 +- .../plugin.schema.json | 0 .../src/agent/DidAuthSiopOpAuthenticator.ts | 0 .../src/index.ts | 0 .../src/session/OID4VP.ts | 0 .../src/session/OpSession.ts | 0 .../src/session/functions.ts | 0 .../src/types/IDidAuthSiopOpAuthenticator.ts | 0 .../tsconfig.json | 0 .../siopv2-openid4vp-rp-auth/CHANGELOG.md | 71 +++ packages/siopv2-openid4vp-rp-auth/LICENSE | 201 ++++++++ packages/siopv2-openid4vp-rp-auth/README.md | 66 +++ .../__tests__/localAgent.test.ts | 51 ++ .../__tests__/restAgent.test.ts | 102 ++++ .../didAuthSiopOpAuthenticatorAgentLogic.ts | 470 ++++++++++++++++++ .../vc_vp_examples/pd/pd_multiple.json | 30 ++ .../vc_vp_examples/pd/pd_single.json | 27 + .../__tests__/vc_vp_examples/psc/psc.json | 11 + .../vc_vp_examples/vc/vc_driverLicense.json | 23 + .../vc/vc_idCardCredential.json | 23 + .../vc_vp_examples/vp/vp_multiple.json | 81 +++ .../vc_vp_examples/vp/vp_single.json | 53 ++ packages/siopv2-openid4vp-rp-auth/agent.yml | 128 +++++ .../api-extractor.json | 3 + .../siopv2-openid4vp-rp-auth/package.json | 65 +++ .../plugin.schema.json | 329 ++++++++++++ .../src/RPInstance.ts | 49 ++ .../src/agent/Siopv2RelyingParty.ts | 119 +++++ .../siopv2-openid4vp-rp-auth/src/functions.ts | 224 +++++++++ .../siopv2-openid4vp-rp-auth/src/index.ts | 7 + .../src/types/ISiopv2RelyingParty.ts | 131 +++++ .../siopv2-openid4vp-rp-auth/tsconfig.json | 10 + .../siopv2-openid4vp-rp-rest/CHANGELOG.md | 71 +++ packages/siopv2-openid4vp-rp-rest/LICENSE | 201 ++++++++ packages/siopv2-openid4vp-rp-rest/README.md | 66 +++ .../__tests__/localAgent.test.ts | 51 ++ .../__tests__/restAgent.test.ts | 102 ++++ .../didAuthSiopOpAuthenticatorAgentLogic.ts | 470 ++++++++++++++++++ .../vc_vp_examples/pd/pd_multiple.json | 30 ++ .../vc_vp_examples/pd/pd_single.json | 27 + .../__tests__/vc_vp_examples/psc/psc.json | 11 + .../vc_vp_examples/vc/vc_driverLicense.json | 23 + .../vc/vc_idCardCredential.json | 23 + .../vc_vp_examples/vp/vp_multiple.json | 81 +++ .../vc_vp_examples/vp/vp_single.json | 53 ++ packages/siopv2-openid4vp-rp-rest/agent.yml | 128 +++++ .../api-extractor.json | 3 + .../siopv2-openid4vp-rp-rest/package.json | 77 +++ .../src/agent/Siopv2RelyingPartyREST.ts | 244 +++++++++ .../siopv2-openid4vp-rp-rest/src/index.ts | 7 + .../siopv2-openid4vp-rp-rest/src/types.ts | 12 + .../siopv2-openid4vp-rp-rest/tsconfig.json | 10 + packages/tsconfig.json | 6 +- yarn.lock | 213 +++++++- 76 files changed, 4300 insertions(+), 22 deletions(-) create mode 100644 packages/siopv2-openid4vp-common/.gitignore create mode 100644 packages/siopv2-openid4vp-common/package.json create mode 100644 packages/siopv2-openid4vp-common/src/auth-model.ts create mode 100644 packages/siopv2-openid4vp-common/src/index.ts create mode 100644 packages/siopv2-openid4vp-common/src/utils.ts create mode 100644 packages/siopv2-openid4vp-common/tsconfig.json rename packages/{did-auth-siop-op-authenticator => siopv2-openid4vp-op-auth}/CHANGELOG.md (100%) rename packages/{did-auth-siop-op-authenticator => siopv2-openid4vp-op-auth}/LICENSE (100%) rename packages/{did-auth-siop-op-authenticator => siopv2-openid4vp-op-auth}/README.md (100%) rename packages/{did-auth-siop-op-authenticator => siopv2-openid4vp-op-auth}/__tests__/localAgent.test.ts (84%) rename packages/{did-auth-siop-op-authenticator => siopv2-openid4vp-op-auth}/__tests__/restAgent.test.ts (93%) rename packages/{did-auth-siop-op-authenticator => siopv2-openid4vp-op-auth}/__tests__/shared/didAuthSiopOpAuthenticatorAgentLogic.ts (94%) rename packages/{did-auth-siop-op-authenticator => siopv2-openid4vp-op-auth}/__tests__/vc_vp_examples/pd/pd_multiple.json (100%) rename packages/{did-auth-siop-op-authenticator => siopv2-openid4vp-op-auth}/__tests__/vc_vp_examples/pd/pd_single.json (100%) rename packages/{did-auth-siop-op-authenticator => siopv2-openid4vp-op-auth}/__tests__/vc_vp_examples/psc/psc.json (100%) rename packages/{did-auth-siop-op-authenticator => siopv2-openid4vp-op-auth}/__tests__/vc_vp_examples/vc/vc_driverLicense.json (100%) rename packages/{did-auth-siop-op-authenticator => siopv2-openid4vp-op-auth}/__tests__/vc_vp_examples/vc/vc_idCardCredential.json (100%) rename packages/{did-auth-siop-op-authenticator => siopv2-openid4vp-op-auth}/__tests__/vc_vp_examples/vp/vp_multiple.json (100%) rename packages/{did-auth-siop-op-authenticator => siopv2-openid4vp-op-auth}/__tests__/vc_vp_examples/vp/vp_single.json (100%) rename packages/{did-auth-siop-op-authenticator => siopv2-openid4vp-op-auth}/agent.yml (97%) rename packages/{did-auth-siop-op-authenticator => siopv2-openid4vp-op-auth}/api-extractor.json (100%) rename packages/{did-auth-siop-op-authenticator => siopv2-openid4vp-op-auth}/package.json (96%) rename packages/{did-auth-siop-op-authenticator => siopv2-openid4vp-op-auth}/plugin.schema.json (100%) rename packages/{did-auth-siop-op-authenticator => siopv2-openid4vp-op-auth}/src/agent/DidAuthSiopOpAuthenticator.ts (100%) rename packages/{did-auth-siop-op-authenticator => siopv2-openid4vp-op-auth}/src/index.ts (100%) rename packages/{did-auth-siop-op-authenticator => siopv2-openid4vp-op-auth}/src/session/OID4VP.ts (100%) rename packages/{did-auth-siop-op-authenticator => siopv2-openid4vp-op-auth}/src/session/OpSession.ts (100%) rename packages/{did-auth-siop-op-authenticator => siopv2-openid4vp-op-auth}/src/session/functions.ts (100%) rename packages/{did-auth-siop-op-authenticator => siopv2-openid4vp-op-auth}/src/types/IDidAuthSiopOpAuthenticator.ts (100%) rename packages/{did-auth-siop-op-authenticator => siopv2-openid4vp-op-auth}/tsconfig.json (100%) create mode 100644 packages/siopv2-openid4vp-rp-auth/CHANGELOG.md create mode 100644 packages/siopv2-openid4vp-rp-auth/LICENSE create mode 100644 packages/siopv2-openid4vp-rp-auth/README.md create mode 100644 packages/siopv2-openid4vp-rp-auth/__tests__/localAgent.test.ts create mode 100644 packages/siopv2-openid4vp-rp-auth/__tests__/restAgent.test.ts create mode 100644 packages/siopv2-openid4vp-rp-auth/__tests__/shared/didAuthSiopOpAuthenticatorAgentLogic.ts create mode 100644 packages/siopv2-openid4vp-rp-auth/__tests__/vc_vp_examples/pd/pd_multiple.json create mode 100644 packages/siopv2-openid4vp-rp-auth/__tests__/vc_vp_examples/pd/pd_single.json create mode 100644 packages/siopv2-openid4vp-rp-auth/__tests__/vc_vp_examples/psc/psc.json create mode 100644 packages/siopv2-openid4vp-rp-auth/__tests__/vc_vp_examples/vc/vc_driverLicense.json create mode 100644 packages/siopv2-openid4vp-rp-auth/__tests__/vc_vp_examples/vc/vc_idCardCredential.json create mode 100644 packages/siopv2-openid4vp-rp-auth/__tests__/vc_vp_examples/vp/vp_multiple.json create mode 100644 packages/siopv2-openid4vp-rp-auth/__tests__/vc_vp_examples/vp/vp_single.json create mode 100644 packages/siopv2-openid4vp-rp-auth/agent.yml create mode 100644 packages/siopv2-openid4vp-rp-auth/api-extractor.json create mode 100644 packages/siopv2-openid4vp-rp-auth/package.json create mode 100644 packages/siopv2-openid4vp-rp-auth/plugin.schema.json create mode 100644 packages/siopv2-openid4vp-rp-auth/src/RPInstance.ts create mode 100644 packages/siopv2-openid4vp-rp-auth/src/agent/Siopv2RelyingParty.ts create mode 100644 packages/siopv2-openid4vp-rp-auth/src/functions.ts create mode 100644 packages/siopv2-openid4vp-rp-auth/src/index.ts create mode 100644 packages/siopv2-openid4vp-rp-auth/src/types/ISiopv2RelyingParty.ts create mode 100644 packages/siopv2-openid4vp-rp-auth/tsconfig.json create mode 100644 packages/siopv2-openid4vp-rp-rest/CHANGELOG.md create mode 100644 packages/siopv2-openid4vp-rp-rest/LICENSE create mode 100644 packages/siopv2-openid4vp-rp-rest/README.md create mode 100644 packages/siopv2-openid4vp-rp-rest/__tests__/localAgent.test.ts create mode 100644 packages/siopv2-openid4vp-rp-rest/__tests__/restAgent.test.ts create mode 100644 packages/siopv2-openid4vp-rp-rest/__tests__/shared/didAuthSiopOpAuthenticatorAgentLogic.ts create mode 100644 packages/siopv2-openid4vp-rp-rest/__tests__/vc_vp_examples/pd/pd_multiple.json create mode 100644 packages/siopv2-openid4vp-rp-rest/__tests__/vc_vp_examples/pd/pd_single.json create mode 100644 packages/siopv2-openid4vp-rp-rest/__tests__/vc_vp_examples/psc/psc.json create mode 100644 packages/siopv2-openid4vp-rp-rest/__tests__/vc_vp_examples/vc/vc_driverLicense.json create mode 100644 packages/siopv2-openid4vp-rp-rest/__tests__/vc_vp_examples/vc/vc_idCardCredential.json create mode 100644 packages/siopv2-openid4vp-rp-rest/__tests__/vc_vp_examples/vp/vp_multiple.json create mode 100644 packages/siopv2-openid4vp-rp-rest/__tests__/vc_vp_examples/vp/vp_single.json create mode 100644 packages/siopv2-openid4vp-rp-rest/agent.yml create mode 100644 packages/siopv2-openid4vp-rp-rest/api-extractor.json create mode 100644 packages/siopv2-openid4vp-rp-rest/package.json create mode 100644 packages/siopv2-openid4vp-rp-rest/src/agent/Siopv2RelyingPartyREST.ts create mode 100644 packages/siopv2-openid4vp-rp-rest/src/index.ts create mode 100644 packages/siopv2-openid4vp-rp-rest/src/types.ts create mode 100644 packages/siopv2-openid4vp-rp-rest/tsconfig.json diff --git a/README.md b/README.md index d682cc334..802e1e803 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ multiple packages (see lerna below). It contains plugins that extend the Veramo - Json-LD VC handler: [Issues and verifies JSON-LD based VCs and VPs](./packages/vc-handler-ld-local/README.md) - OpenID Connect and Presentation Exchange: - SIOPv2 and - OIDC4VP: [Self-Issued OpenID Connect and OpenID Connect for Verifiable Presentations](./packages/did-auth-siop-op-authenticator/README.md) + OIDC4VP: [Self-Issued OpenID Connect and OpenID Connect for Verifiable Presentations](packages/siopv2-openid4vp-op-auth/README.md) - WACI PEx QR code: [Create QR codes for use with WACI PEx for React and React-Native](./packages/qr-code-generator/README.md) ## SSI SDK Core @@ -74,7 +74,7 @@ Verifiable Credentials and Verifiable Presentations ## OpenID Connect -The [Self-Issued OpenID Connect and OpenID Connect for Verifiable Presentations](./packages/did-auth-siop-op-authenticator/README.md) +The [Self-Issued OpenID Connect and OpenID Connect for Verifiable Presentations](packages/siopv2-openid4vp-op-auth/README.md) plugin allows an OP to authenticate against a Relying Party using Self-Issued OpenID Connect and optionally OpenID Connect for Verifiable Presentations, with the help of our [Presentation-Exchange library](https://github.com/Sphereon-Opensource/pe-js). diff --git a/packages/siopv2-openid4vp-common/.gitignore b/packages/siopv2-openid4vp-common/.gitignore new file mode 100644 index 000000000..bc413e9a2 --- /dev/null +++ b/packages/siopv2-openid4vp-common/.gitignore @@ -0,0 +1,9 @@ +.vscode/* +.idea/* +*.iml +.nyc_output +build +dist +node_modules +coverage +*.log diff --git a/packages/siopv2-openid4vp-common/package.json b/packages/siopv2-openid4vp-common/package.json new file mode 100644 index 000000000..d3498310e --- /dev/null +++ b/packages/siopv2-openid4vp-common/package.json @@ -0,0 +1,23 @@ +{ + "name": "@sphereon/ssi-sdk-siopv2-openid4vp-common", + "version": "0.9.0", + "description": "Common SIOPv2 and OpenID4VP types between modules", + "source": "src/index.ts", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "author": "Sphereon ", + "license": "Apache-2.0", + "private": false, + "dependencies": { + "@sphereon/did-auth-siop": "^0.3.0-unstable.30" + }, + "scripts": { + "build": "tsc" + }, + "devDependencies": { + "@types/node": "^16.18.0" + }, + "files": [ + "dist/**/*" + ] +} diff --git a/packages/siopv2-openid4vp-common/src/auth-model.ts b/packages/siopv2-openid4vp-common/src/auth-model.ts new file mode 100644 index 000000000..5b7007c51 --- /dev/null +++ b/packages/siopv2-openid4vp-common/src/auth-model.ts @@ -0,0 +1,37 @@ +// noinspection JSUnusedGlobalSymbols +import {AuthorizationResponsePayload} from "@sphereon/did-auth-siop"; + +export interface ClaimPayloadCommonOpts { + [x: string]: any; +} +export declare enum AuthorizationRequestStateStatus { + CREATED = "created", + SENT = "sent", + RECEIVED = "received", + VERIFIED = "verified", + ERROR = "error" +} +export declare enum AuthorizationResponseStateStatus { + CREATED = "created", + SENT = "sent", + RECEIVED = "received", + VERIFIED = "verified", + ERROR = "error" +} + +export interface GenerateAuthRequestURIResponse { + correlationId: string; + definitionId: string; + authRequestURI: string; + authStatusURI: string; +} + + +export interface AuthStatusResponse { + status: AuthorizationRequestStateStatus | AuthorizationResponseStateStatus; + correlationId: string; + error?: string + definitionId: string; + lastUpdated: number; + payload?: AuthorizationResponsePayload; // Only put in here once the status reaches Verified on the RP side +} diff --git a/packages/siopv2-openid4vp-common/src/index.ts b/packages/siopv2-openid4vp-common/src/index.ts new file mode 100644 index 000000000..d08da2951 --- /dev/null +++ b/packages/siopv2-openid4vp-common/src/index.ts @@ -0,0 +1,3 @@ +export * from './auth-model'; +export * from './utils'; + diff --git a/packages/siopv2-openid4vp-common/src/utils.ts b/packages/siopv2-openid4vp-common/src/utils.ts new file mode 100644 index 000000000..97128977f --- /dev/null +++ b/packages/siopv2-openid4vp-common/src/utils.ts @@ -0,0 +1,16 @@ +import * as u8a from 'uint8arrays' + +export function base64ToBytes(s: string): Uint8Array { + const inputBase64Url = s.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '') + return u8a.fromString(inputBase64Url, 'base64url') +} + +export function decodeBase64url(s: string): string { + return u8a.toString(base64ToBytes(s)) +} + +// noinspection JSUnusedLocalSymbols + +export function uriWithBase(path: string) { + return `${process.env.BACKEND_BASE_URL}${path.startsWith('/') ? path : '/' + path}`; +} diff --git a/packages/siopv2-openid4vp-common/tsconfig.json b/packages/siopv2-openid4vp-common/tsconfig.json new file mode 100644 index 000000000..f6211d63d --- /dev/null +++ b/packages/siopv2-openid4vp-common/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../tsconfig-base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "declarationDir": "dist", + "strictPropertyInitialization": false, + "noUnusedLocals": false + }, + "references": [{ "path": "../ssi-types" }], + "include": [ + "src/**/*", + ], + "exclude": [ + "node_modules/**/*" + ] +} diff --git a/packages/did-auth-siop-op-authenticator/CHANGELOG.md b/packages/siopv2-openid4vp-op-auth/CHANGELOG.md similarity index 100% rename from packages/did-auth-siop-op-authenticator/CHANGELOG.md rename to packages/siopv2-openid4vp-op-auth/CHANGELOG.md diff --git a/packages/did-auth-siop-op-authenticator/LICENSE b/packages/siopv2-openid4vp-op-auth/LICENSE similarity index 100% rename from packages/did-auth-siop-op-authenticator/LICENSE rename to packages/siopv2-openid4vp-op-auth/LICENSE diff --git a/packages/did-auth-siop-op-authenticator/README.md b/packages/siopv2-openid4vp-op-auth/README.md similarity index 100% rename from packages/did-auth-siop-op-authenticator/README.md rename to packages/siopv2-openid4vp-op-auth/README.md diff --git a/packages/did-auth-siop-op-authenticator/__tests__/localAgent.test.ts b/packages/siopv2-openid4vp-op-auth/__tests__/localAgent.test.ts similarity index 84% rename from packages/did-auth-siop-op-authenticator/__tests__/localAgent.test.ts rename to packages/siopv2-openid4vp-op-auth/__tests__/localAgent.test.ts index 6ee98712c..65f1f9aa3 100644 --- a/packages/did-auth-siop-op-authenticator/__tests__/localAgent.test.ts +++ b/packages/siopv2-openid4vp-op-auth/__tests__/localAgent.test.ts @@ -17,7 +17,7 @@ function getFileAsJson(path: string) { let agent: any const presentationSignCallback: PresentationSignCallback = async (args) => { - const presentationSignProof = getFileAsJson('./packages/did-auth-siop-op-authenticator/__tests__/vc_vp_examples/psc/psc.json') + const presentationSignProof = getFileAsJson('./packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/psc/psc.json') return { ...args.presentation, @@ -26,7 +26,7 @@ const presentationSignCallback: PresentationSignCallback = async (args) => { } const setup = async (): Promise => { - const config = getConfig('packages/did-auth-siop-op-authenticator/agent.yml') + const config = getConfig('packages/siopv2-openid4vp-op-auth/agent.yml') config.agent.$args[0].plugins[1].$args[0] = presentationSignCallback const { localAgent } = createObjects(config, { localAgent: '/agent' }) agent = localAgent diff --git a/packages/did-auth-siop-op-authenticator/__tests__/restAgent.test.ts b/packages/siopv2-openid4vp-op-auth/__tests__/restAgent.test.ts similarity index 93% rename from packages/did-auth-siop-op-authenticator/__tests__/restAgent.test.ts rename to packages/siopv2-openid4vp-op-auth/__tests__/restAgent.test.ts index 81083ab8c..847c1eb8b 100644 --- a/packages/did-auth-siop-op-authenticator/__tests__/restAgent.test.ts +++ b/packages/siopv2-openid4vp-op-auth/__tests__/restAgent.test.ts @@ -32,7 +32,7 @@ let serverAgent: IAgent let restServer: Server const presentationSignCallback: PresentationSignCallback = async (args) => { - const presentationSignProof = getFileAsJson('./packages/did-auth-siop-op-authenticator/__tests__/vc_vp_examples/psc/psc.json') + const presentationSignProof = getFileAsJson('./packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/psc/psc.json') return { ...args.presentation, @@ -61,7 +61,7 @@ const getAgent = (options?: IAgentOptions) => }) const setup = async (): Promise => { - const config = getConfig('packages/did-auth-siop-op-authenticator/agent.yml') + const config = getConfig('packages/siopv2-openid4vp-op-auth/agent.yml') config.agent.$args[0].plugins[1].$args[0] = presentationSignCallback const { agent } = createObjects(config, { agent: '/agent' }) agent.registerCustomApprovalForSiop({ key: 'success', customApproval: () => Promise.resolve() }) diff --git a/packages/did-auth-siop-op-authenticator/__tests__/shared/didAuthSiopOpAuthenticatorAgentLogic.ts b/packages/siopv2-openid4vp-op-auth/__tests__/shared/didAuthSiopOpAuthenticatorAgentLogic.ts similarity index 94% rename from packages/did-auth-siop-op-authenticator/__tests__/shared/didAuthSiopOpAuthenticatorAgentLogic.ts rename to packages/siopv2-openid4vp-op-auth/__tests__/shared/didAuthSiopOpAuthenticatorAgentLogic.ts index ed69ab5d1..440346c01 100644 --- a/packages/did-auth-siop-op-authenticator/__tests__/shared/didAuthSiopOpAuthenticatorAgentLogic.ts +++ b/packages/siopv2-openid4vp-op-auth/__tests__/shared/didAuthSiopOpAuthenticatorAgentLogic.ts @@ -175,12 +175,12 @@ export default (testContext: { agent = testContext.getAgent() const idCardCredential: VerifiableCredential = getFileAsJson( - './packages/did-auth-siop-op-authenticator/__tests__/vc_vp_examples/vc/vc_idCardCredential.json' + './packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/vc/vc_idCardCredential.json' ) await agent.dataStoreSaveVerifiableCredential({ verifiableCredential: idCardCredential }) const driverLicenseCredential: VerifiableCredential = getFileAsJson( - './packages/did-auth-siop-op-authenticator/__tests__/vc_vp_examples/vc/vc_driverLicense.json' + './packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/vc/vc_driverLicense.json' ) await agent.dataStoreSaveVerifiableCredential({ verifiableCredential: driverLicenseCredential }) @@ -330,10 +330,10 @@ export default (testContext: { it('should get authentication details with single credential', async () => { const pd_single: PresentationDefinitionWithLocation = getFileAsJson( - './packages/did-auth-siop-op-authenticator/__tests__/vc_vp_examples/pd/pd_single.json' + './packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/pd/pd_single.json' ) const vp_single: IPresentationWithDefinition = getFileAsJson( - './packages/did-auth-siop-op-authenticator/__tests__/vc_vp_examples/vp/vp_single.json' + './packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/vp/vp_single.json' ) const presentation = CredentialMapper.toWrappedVerifiablePresentation(vp_single.presentation) presentation.presentation.presentation_submission!.id = expect.any(String) @@ -361,10 +361,10 @@ export default (testContext: { it('should get authentication details with getting specific credentials', async () => { const pdSingle: PresentationDefinitionWithLocation = getFileAsJson( - './packages/did-auth-siop-op-authenticator/__tests__/vc_vp_examples/pd/pd_single.json' + './packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/pd/pd_single.json' ) const vpSingle: IPresentationWithDefinition = getFileAsJson( - './packages/did-auth-siop-op-authenticator/__tests__/vc_vp_examples/vp/vp_single.json' + './packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/vp/vp_single.json' ) const presentation = CredentialMapper.toWrappedVerifiablePresentation(vpSingle.presentation) presentation.presentation.presentation_submission!.id = expect.any(String) @@ -396,10 +396,10 @@ export default (testContext: { it('should get authentication details with multiple credentials', async () => { const pdMultiple: PresentationDefinitionWithLocation = getFileAsJson( - './packages/did-auth-siop-op-authenticator/__tests__/vc_vp_examples/pd/pd_multiple.json' + './packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/pd/pd_multiple.json' ) const vpMultiple: IPresentationWithDefinition = getFileAsJson( - './packages/did-auth-siop-op-authenticator/__tests__/vc_vp_examples/vp/vp_multiple.json' + './packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/vp/vp_multiple.json' ) const presentation = CredentialMapper.toWrappedVerifiablePresentation(vpMultiple.presentation) presentation.presentation.presentation_submission!.id = expect.any(String) @@ -449,7 +449,7 @@ export default (testContext: { it('should send authentication response', async () => { const pdMultiple: PresentationDefinitionWithLocation = getFileAsJson( - './packages/did-auth-siop-op-authenticator/__tests__/vc_vp_examples/pd/pd_multiple.json' + './packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/pd/pd_multiple.json' ) const result = await agent.sendSiopAuthorizationResponse({ diff --git a/packages/did-auth-siop-op-authenticator/__tests__/vc_vp_examples/pd/pd_multiple.json b/packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/pd/pd_multiple.json similarity index 100% rename from packages/did-auth-siop-op-authenticator/__tests__/vc_vp_examples/pd/pd_multiple.json rename to packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/pd/pd_multiple.json diff --git a/packages/did-auth-siop-op-authenticator/__tests__/vc_vp_examples/pd/pd_single.json b/packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/pd/pd_single.json similarity index 100% rename from packages/did-auth-siop-op-authenticator/__tests__/vc_vp_examples/pd/pd_single.json rename to packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/pd/pd_single.json diff --git a/packages/did-auth-siop-op-authenticator/__tests__/vc_vp_examples/psc/psc.json b/packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/psc/psc.json similarity index 100% rename from packages/did-auth-siop-op-authenticator/__tests__/vc_vp_examples/psc/psc.json rename to packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/psc/psc.json diff --git a/packages/did-auth-siop-op-authenticator/__tests__/vc_vp_examples/vc/vc_driverLicense.json b/packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/vc/vc_driverLicense.json similarity index 100% rename from packages/did-auth-siop-op-authenticator/__tests__/vc_vp_examples/vc/vc_driverLicense.json rename to packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/vc/vc_driverLicense.json diff --git a/packages/did-auth-siop-op-authenticator/__tests__/vc_vp_examples/vc/vc_idCardCredential.json b/packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/vc/vc_idCardCredential.json similarity index 100% rename from packages/did-auth-siop-op-authenticator/__tests__/vc_vp_examples/vc/vc_idCardCredential.json rename to packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/vc/vc_idCardCredential.json diff --git a/packages/did-auth-siop-op-authenticator/__tests__/vc_vp_examples/vp/vp_multiple.json b/packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/vp/vp_multiple.json similarity index 100% rename from packages/did-auth-siop-op-authenticator/__tests__/vc_vp_examples/vp/vp_multiple.json rename to packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/vp/vp_multiple.json diff --git a/packages/did-auth-siop-op-authenticator/__tests__/vc_vp_examples/vp/vp_single.json b/packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/vp/vp_single.json similarity index 100% rename from packages/did-auth-siop-op-authenticator/__tests__/vc_vp_examples/vp/vp_single.json rename to packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/vp/vp_single.json diff --git a/packages/did-auth-siop-op-authenticator/agent.yml b/packages/siopv2-openid4vp-op-auth/agent.yml similarity index 97% rename from packages/did-auth-siop-op-authenticator/agent.yml rename to packages/siopv2-openid4vp-op-auth/agent.yml index 98f5bd4e4..f78f74ed5 100644 --- a/packages/did-auth-siop-op-authenticator/agent.yml +++ b/packages/siopv2-openid4vp-op-auth/agent.yml @@ -117,7 +117,7 @@ agent: - schemaValidation: false plugins: - $ref: /didResolver - - $require: ./packages/did-auth-siop-op-authenticator/dist#DidAuthSiopOpAuthenticator + - $require: ./packages/siopv2-openid4vp-op-auth/dist#DidAuthSiopOpAuthenticator $args: - presentationSignCallback: {} - $require: '@veramo/data-store#DataStore' diff --git a/packages/did-auth-siop-op-authenticator/api-extractor.json b/packages/siopv2-openid4vp-op-auth/api-extractor.json similarity index 100% rename from packages/did-auth-siop-op-authenticator/api-extractor.json rename to packages/siopv2-openid4vp-op-auth/api-extractor.json diff --git a/packages/did-auth-siop-op-authenticator/package.json b/packages/siopv2-openid4vp-op-auth/package.json similarity index 96% rename from packages/did-auth-siop-op-authenticator/package.json rename to packages/siopv2-openid4vp-op-auth/package.json index 0edd880d9..8d9c2bc9f 100644 --- a/packages/did-auth-siop-op-authenticator/package.json +++ b/packages/siopv2-openid4vp-op-auth/package.json @@ -1,5 +1,5 @@ { - "name": "@sphereon/ssi-sdk-did-auth-siop-authenticator", + "name": "@sphereon/ssi-sdk-siopv2-openid4vp-op-auth", "version": "0.9.0", "source": "src/index.ts", "main": "dist/index.js", diff --git a/packages/did-auth-siop-op-authenticator/plugin.schema.json b/packages/siopv2-openid4vp-op-auth/plugin.schema.json similarity index 100% rename from packages/did-auth-siop-op-authenticator/plugin.schema.json rename to packages/siopv2-openid4vp-op-auth/plugin.schema.json diff --git a/packages/did-auth-siop-op-authenticator/src/agent/DidAuthSiopOpAuthenticator.ts b/packages/siopv2-openid4vp-op-auth/src/agent/DidAuthSiopOpAuthenticator.ts similarity index 100% rename from packages/did-auth-siop-op-authenticator/src/agent/DidAuthSiopOpAuthenticator.ts rename to packages/siopv2-openid4vp-op-auth/src/agent/DidAuthSiopOpAuthenticator.ts diff --git a/packages/did-auth-siop-op-authenticator/src/index.ts b/packages/siopv2-openid4vp-op-auth/src/index.ts similarity index 100% rename from packages/did-auth-siop-op-authenticator/src/index.ts rename to packages/siopv2-openid4vp-op-auth/src/index.ts diff --git a/packages/did-auth-siop-op-authenticator/src/session/OID4VP.ts b/packages/siopv2-openid4vp-op-auth/src/session/OID4VP.ts similarity index 100% rename from packages/did-auth-siop-op-authenticator/src/session/OID4VP.ts rename to packages/siopv2-openid4vp-op-auth/src/session/OID4VP.ts diff --git a/packages/did-auth-siop-op-authenticator/src/session/OpSession.ts b/packages/siopv2-openid4vp-op-auth/src/session/OpSession.ts similarity index 100% rename from packages/did-auth-siop-op-authenticator/src/session/OpSession.ts rename to packages/siopv2-openid4vp-op-auth/src/session/OpSession.ts diff --git a/packages/did-auth-siop-op-authenticator/src/session/functions.ts b/packages/siopv2-openid4vp-op-auth/src/session/functions.ts similarity index 100% rename from packages/did-auth-siop-op-authenticator/src/session/functions.ts rename to packages/siopv2-openid4vp-op-auth/src/session/functions.ts diff --git a/packages/did-auth-siop-op-authenticator/src/types/IDidAuthSiopOpAuthenticator.ts b/packages/siopv2-openid4vp-op-auth/src/types/IDidAuthSiopOpAuthenticator.ts similarity index 100% rename from packages/did-auth-siop-op-authenticator/src/types/IDidAuthSiopOpAuthenticator.ts rename to packages/siopv2-openid4vp-op-auth/src/types/IDidAuthSiopOpAuthenticator.ts diff --git a/packages/did-auth-siop-op-authenticator/tsconfig.json b/packages/siopv2-openid4vp-op-auth/tsconfig.json similarity index 100% rename from packages/did-auth-siop-op-authenticator/tsconfig.json rename to packages/siopv2-openid4vp-op-auth/tsconfig.json diff --git a/packages/siopv2-openid4vp-rp-auth/CHANGELOG.md b/packages/siopv2-openid4vp-rp-auth/CHANGELOG.md new file mode 100644 index 000000000..fe7bc128c --- /dev/null +++ b/packages/siopv2-openid4vp-rp-auth/CHANGELOG.md @@ -0,0 +1,71 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [0.9.0](https://github.com/Sphereon-Opensource/SSI-SDK/compare/v0.8.0...v0.9.0) (2023-03-09) + +### Bug Fixes + +- credential mapper for jtw ([f04345b](https://github.com/Sphereon-Opensource/SSI-SDK/commit/f04345b97ff9a78a3dff096599f0b675b3239a3e)) +- Fix DID handling in OP session ([926e358](https://github.com/Sphereon-Opensource/SSI-SDK/commit/926e358ef3eadf19fc3c8f7c9940fe6322c5ff85)) +- Incorrect verification method id returned when signing credentials in some cases ([c508507](https://github.com/Sphereon-Opensource/SSI-SDK/commit/c508507ddd2e35fcb377a79bad3c82d695b3d93d)) +- Move parseDid method to ssi-types ([0b28de3](https://github.com/Sphereon-Opensource/SSI-SDK/commit/0b28de3de21afd0a224d3d174103e072162231ed)) + +### Features + +- Add jwt as signature when decoding JWT VCs/VPs ([f089ac1](https://github.com/Sphereon-Opensource/SSI-SDK/commit/f089ac18dc470f0b8c581b49e70e7eba64d72bc3)) +- Allow to relax JWT timing checks, where the JWT claim is slightly different from the VC claim. Used for issuance and expiration dates ([85bff6d](https://github.com/Sphereon-Opensource/SSI-SDK/commit/85bff6da21dea5d8f636ea1f55b41be00b18b002)) +- Create VP in OP Authenticator and allow for callbacks ([0ed86d8](https://github.com/Sphereon-Opensource/SSI-SDK/commit/0ed86d8d2b655a718d7c8cf1a946e0150bf877ce)) +- Make sure VP type corresponds with PEX definition ([129b663](https://github.com/Sphereon-Opensource/SSI-SDK/commit/129b66383752e05ab3067e459bff591a07aac690)) +- Make sure VP type corresponds with PEX definition ([3dafa3f](https://github.com/Sphereon-Opensource/SSI-SDK/commit/3dafa3ff4c794d13eff3e2e0b6a85675667db089)) +- Update SIOP OP to be in line wiht latest SIOP and also supporting late binding of identifiers ([2beea04](https://github.com/Sphereon-Opensource/SSI-SDK/commit/2beea04a6604d82b12ecbc11e68a9f41775c22ed)) + +# [0.8.0](https://github.com/Sphereon-Opensource/SSI-SDK/compare/v0.7.0...v0.8.0) (2022-09-03) + +**Note:** Version bump only for package @sphereon/ssi-sdk-did-auth-siop-authenticator + +# [0.7.0](https://github.com/Sphereon-Opensource/SSI-SDK/compare/v0.6.0...v0.7.0) (2022-08-05) + +**Note:** Version bump only for package @sphereon/ssi-sdk-did-auth-siop-authenticator + +# [0.6.0](https://github.com/Sphereon-Opensource/SSI-SDK/compare/v0.5.1...v0.6.0) (2022-07-01) + +### Features + +- Add custom DID resolver support ([45cea11](https://github.com/Sphereon-Opensource/SSI-SDK/commit/45cea1182693b698611b062a9d664ad92e8dcd6a)) +- Add default DID resolver support ([eebce18](https://github.com/Sphereon-Opensource/SSI-SDK/commit/eebce18bf9cc9d28a8bcdd6886100b7a8921bb2f)) +- Add did resolver and method support per OpSession ([9378b45](https://github.com/Sphereon-Opensource/SSI-SDK/commit/9378b451d4907c8d5385f464b27f858547409bb4)) +- Add did resolver and method support per OpSession ([a9f7afc](https://github.com/Sphereon-Opensource/SSI-SDK/commit/a9f7afc386189ca4851ce967f5abf7db812d1003)) +- Add supported DID methods ([df74ccd](https://github.com/Sphereon-Opensource/SSI-SDK/commit/df74ccddcab06a032ca47a033a46bd0268826f72)) +- Add supported DID methods ([7322265](https://github.com/Sphereon-Opensource/SSI-SDK/commit/732226544503c2bcc32bf4400da82e9154361abb)) + +## [0.5.1](https://github.com/Sphereon-Opensource/SSI-SDK/compare/v0.5.0...v0.5.1) (2022-02-23) + +**Note:** Version bump only for package @sphereon/ssi-sdk-did-auth-siop-authenticator + +# [0.5.0](https://github.com/Sphereon-Opensource/SSI-SDK/compare/v0.4.0...v0.5.0) (2022-02-23) + +**Note:** Version bump only for package @sphereon/ssi-sdk-did-auth-siop-authenticator + +# [0.4.0](https://github.com/Sphereon-Opensource/SSI-SDK/compare/v0.3.4...v0.4.0) (2022-02-11) + +**Note:** Version bump only for package @sphereon/ssi-sdk-did-auth-siop-authenticator + +## [0.3.4](https://github.com/Sphereon-Opensource/SSI-SDK/compare/v0.3.3...v0.3.4) (2022-02-11) + +### Bug Fixes + +- fix imports ([738f4ca](https://github.com/Sphereon-Opensource/SSI-SDK/commit/738f4cafdf75c9d4831a3c31de1c0d5aff1d7285)) + +## [0.3.1](https://github.com/Sphereon-Opensource/SSI-SDK/compare/v0.3.0...v0.3.1) (2022-01-28) + +**Note:** Version bump only for package @sphereon/ssi-sdk-did-auth-siop-authenticator + +# [0.3.0](https://github.com/Sphereon-Opensource/SSI-SDK/compare/v0.2.0...v0.3.0) (2022-01-16) + +**Note:** Version bump only for package @sphereon/ssi-sdk-did-auth-siop-authenticator + +# [0.2.0](https://github.com/Sphereon-Opensource/SSI-SDK/compare/v0.1.0...v0.2.0) (2021-12-16) + +**Note:** Version bump only for package @sphereon/ssi-sdk-did-auth-siop-authenticator diff --git a/packages/siopv2-openid4vp-rp-auth/LICENSE b/packages/siopv2-openid4vp-rp-auth/LICENSE new file mode 100644 index 000000000..8aee96b74 --- /dev/null +++ b/packages/siopv2-openid4vp-rp-auth/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2021] [Sphereon BV] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/siopv2-openid4vp-rp-auth/README.md b/packages/siopv2-openid4vp-rp-auth/README.md new file mode 100644 index 000000000..595235c4a --- /dev/null +++ b/packages/siopv2-openid4vp-rp-auth/README.md @@ -0,0 +1,66 @@ + +

+
+ Sphereon +
SIOPv2 and OpenID4VP Relying Party +
+

+ +--- + +**Warning: This package still is in very early development. Breaking changes without notice will happen at this point!** + +--- + +An authentication plugin using the [Self Issued OpenID Provider v2 (SIOP)](https://github.com/Sphereon-Opensource/did-auth-siop) authentication library for having Relying Parties conforming to +the [Self Issued OpenID Provider v2 (SIOPv2)](https://openid.net/specs/openid-connect-self-issued-v2-1_0.html) +and [OpenID Connect for Verifiable Presentations (OIDC4VP)](https://openid.net/specs/openid-connect-4-verifiable-presentations-1_0.html) +as specified in the OpenID Connect working group. + +## Self Issued OpenID Provider v2 (SIOPv2) + +For more information about [Self Issued OpenID Provider v2 (SIOP)](https://github.com/Sphereon-Opensource/did-auth-siop#introduction), see the documentation in the readme. + +## Requirements + +For this plugin a DID resolver is also required. A DID resolver can be added to the agent as plugin as seen in the example below. + +## Available functions + + +## Usage + +### Adding the plugin to an agent: + +```typescript +import { IDidAuthSiopOpAuthenticator } from '@sphereon/ssi-sdk-did-auth-siop-authenticator' +import { Resolver } from 'did-resolver' +import { getDidKeyResolver } from '@veramo/did-provider-key' +import { DIDResolverPlugin } from '@veramo/did-resolver' +import { getUniResolver } from '@sphereon/did-uni-client' + +const agent = createAgent({ + plugins: [ + new DidAuthSiopOpAuthenticator(), + new DIDResolverPlugin({ + resolver: new Resolver({ + ...getDidKeyResolver(), + ...getUniResolver('web'), + ...getUniResolver('jwk'), + }), + }), + ], +}) +``` + +## Installation + +```shell +yarn add @sphereon/ssi-sdk-siopv2-openid4vp-rp +``` + +## Build + +```shell +yarn build +``` diff --git a/packages/siopv2-openid4vp-rp-auth/__tests__/localAgent.test.ts b/packages/siopv2-openid4vp-rp-auth/__tests__/localAgent.test.ts new file mode 100644 index 000000000..65f1f9aa3 --- /dev/null +++ b/packages/siopv2-openid4vp-rp-auth/__tests__/localAgent.test.ts @@ -0,0 +1,51 @@ +import * as fs from 'fs' +import { getConfig } from '@veramo/cli/build/setup' +import { createObjects } from '@veramo/cli/build/lib/objectCreator' +import didAuthSiopOpAuthenticatorAgentLogic from './shared/didAuthSiopOpAuthenticatorAgentLogic' +import { PresentationSignCallback } from '@sphereon/did-auth-siop' + +jest.setTimeout(30000) + +function getFile(path: string) { + return fs.readFileSync(path, 'utf-8') +} + +function getFileAsJson(path: string) { + return JSON.parse(getFile(path)) +} + +let agent: any + +const presentationSignCallback: PresentationSignCallback = async (args) => { + const presentationSignProof = getFileAsJson('./packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/psc/psc.json') + + return { + ...args.presentation, + ...presentationSignProof, + } +} + +const setup = async (): Promise => { + const config = getConfig('packages/siopv2-openid4vp-op-auth/agent.yml') + config.agent.$args[0].plugins[1].$args[0] = presentationSignCallback + const { localAgent } = createObjects(config, { localAgent: '/agent' }) + agent = localAgent + + return true +} + +const tearDown = async (): Promise => { + return true +} + +const getAgent = () => agent +const testContext = { + getAgent, + setup, + tearDown, + isRestTest: false, +} + +xdescribe('Local integration tests', () => { + didAuthSiopOpAuthenticatorAgentLogic(testContext) +}) diff --git a/packages/siopv2-openid4vp-rp-auth/__tests__/restAgent.test.ts b/packages/siopv2-openid4vp-rp-auth/__tests__/restAgent.test.ts new file mode 100644 index 000000000..24f9678d3 --- /dev/null +++ b/packages/siopv2-openid4vp-rp-auth/__tests__/restAgent.test.ts @@ -0,0 +1,102 @@ +import * as fs from 'fs' +import 'cross-fetch/polyfill' +// @ts-ignore +import express from 'express' +import { IAgent, createAgent, IAgentOptions, IDataStore } from '@veramo/core' +import { AgentRestClient } from '@veramo/remote-client' +import { Server } from 'http' +import { AgentRouter, RequestWithAgentRouter } from '@veramo/remote-server' +import { getConfig } from '@veramo/cli/build/setup' +import { createObjects } from '@veramo/cli/build/lib/objectCreator' +import { Siopv2RelyingParty, ISiopv2RelyingParty } from '../src' +import { Resolver } from 'did-resolver' +import { getDidKeyResolver } from '@veramo/did-provider-key' +import { DIDResolverPlugin } from '@veramo/did-resolver' +import { getUniResolver } from '@sphereon/did-uni-client' +import didAuthSiopOpAuthenticatorAgentLogic from './shared/didAuthSiopOpAuthenticatorAgentLogic' +import { PresentationSignCallback } from '@sphereon/did-auth-siop' + +jest.setTimeout(30000) + +function getFile(path: string) { + return fs.readFileSync(path, 'utf-8') +} + +function getFileAsJson(path: string) { + return JSON.parse(getFile(path)) +} + +const port = 3002 +const basePath = '/agent' +let serverAgent: IAgent +let restServer: Server + +const presentationSignCallback: PresentationSignCallback = async (args) => { + const presentationSignProof = getFileAsJson('./packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/psc/psc.json') + + return { + ...args.presentation, + ...presentationSignProof, + } +} + +const getAgent = (options?: IAgentOptions) => + createAgent({ + ...options, + plugins: [ + new Siopv2RelyingParty(presentationSignCallback), + new DIDResolverPlugin({ + resolver: new Resolver({ + ...getDidKeyResolver(), + ...getUniResolver('lto', { resolveUrl: 'https://uniresolver.test.sphereon.io/1.0/identifiers' }), + ...getUniResolver('factom', { resolveUrl: 'https://uniresolver.test.sphereon.io/1.0/identifiers' }), + }), + }), + new AgentRestClient({ + url: 'http://localhost:' + port + basePath, + enabledMethods: serverAgent.availableMethods(), + schema: serverAgent.getSchema(), + }), + ], + }) + +const setup = async (): Promise => { + const config = getConfig('packages/siopv2-openid4vp-op-auth/agent.yml') + config.agent.$args[0].plugins[1].$args[0] = presentationSignCallback + const { agent } = createObjects(config, { agent: '/agent' }) + agent.registerCustomApprovalForSiop({ key: 'success', customApproval: () => Promise.resolve() }) + agent.registerCustomApprovalForSiop({ key: 'failure', customApproval: () => Promise.reject(new Error('denied')) }) + serverAgent = agent + + const agentRouter = AgentRouter({ + exposedMethods: serverAgent.availableMethods(), + }) + + const requestWithAgent = RequestWithAgentRouter({ + agent: serverAgent, + }) + + return new Promise((resolve) => { + const app = express() + app.use(basePath, requestWithAgent, agentRouter) + restServer = app.listen(port, () => { + resolve(true) + }) + }) +} + +const tearDown = async (): Promise => { + restServer.close() + return true +} + +const testContext = { + getAgent, + setup, + tearDown, + isRestTest: true, +} + +xdescribe('REST integration tests', () => { + didAuthSiopOpAuthenticatorAgentLogic(testContext) +}) diff --git a/packages/siopv2-openid4vp-rp-auth/__tests__/shared/didAuthSiopOpAuthenticatorAgentLogic.ts b/packages/siopv2-openid4vp-rp-auth/__tests__/shared/didAuthSiopOpAuthenticatorAgentLogic.ts new file mode 100644 index 000000000..809bfbc8e --- /dev/null +++ b/packages/siopv2-openid4vp-rp-auth/__tests__/shared/didAuthSiopOpAuthenticatorAgentLogic.ts @@ -0,0 +1,470 @@ +import * as fs from 'fs' +import { IDataStore, TAgent, VerifiableCredential } from '@veramo/core' +import { IAuthRequestDetails, ISiopv2RelyingParty, IPresentationWithDefinition } from '../../src' +import { + AuthorizationRequest, + OP, + ParsedAuthorizationRequestURI, + PresentationDefinitionWithLocation, + ResponseContext, + ResponseMode, + ResponseType, + SubjectIdentifierType, + UrlEncodingFormat, + VerificationMode, + VerifiedAuthorizationRequest, +} from '@sphereon/did-auth-siop' +import { mapIdentifierKeysToDoc } from '@veramo/utils' +import { CredentialMapper } from '@sphereon/ssi-types' +import { mapIdentifierKeysToDocWithJwkSupport } from '@sphereon/ssi-sdk-did-utils' + +function getFile(path: string) { + return fs.readFileSync(path, 'utf-8') +} + +function getFileAsJson(path: string) { + return JSON.parse(getFile(path)) +} + +const nock = require('nock') +jest.mock('@veramo/utils', () => ({ + ...jest.requireActual('@veramo/utils'), + mapIdentifierKeysToDoc: jest.fn(), +})) + +jest.mock('@sphereon/ssi-sdk-did-utils', () => ({ + ...jest.requireActual('@sphereon/ssi-sdk-did-utils'), + mapIdentifierKeysToDocWithJwkSupport: jest.fn(), +})) + +type ConfiguredAgent = TAgent + +const didMethod = 'ethr' +const did = 'did:ethr:0xb9c5714089478a327f09197987f16f9e5d936e8a' +const identifier = { + did, + provider: '', + controllerKeyId: `${did}#controller`, + keys: [ + { + kid: `${did}#controller`, + kms: '', + type: 'Secp256k1' as const, + publicKeyHex: '1e21e21e...', + privateKeyHex: 'eqfcvnqwdnwqn...', + }, + ], + services: [], +} +const authKeys = [ + { + kid: `${did}#controller`, + kms: '', + type: 'Secp256k1', + publicKeyHex: '1e21e21e...', + privateKeyHex: 'eqfcvnqwdnwqn...', + meta: { + verificationMethod: { + id: `${did}#controller`, + type: 'EcdsaSecp256k1RecoveryMethod2020', + controller: did, + blockchainAccountId: '0xB9C5714089478a327F09197987f16f9E5d936E8a@eip155:1', + publicKeyHex: '1e21e21e...', + }, + }, + }, +] + +console.log(identifier) +const sessionId = 'sessionId' +const otherSessionId = 'other_sessionId' +const redirectUrl = 'http://example/ext/get-auth-request-url' +const stateId = '2hAyTM7PB3SGJaeGU7QeTJ' +const nonce = 'o5qwML7DnrcLMs9Vdizyz9' +const scope = 'openid' +const openIDURI = + 'openid://?response_type=id_token' + + '&scope=openid' + + '&client_id=' + + did + + '&redirect_uri=' + + redirectUrl + + '&iss=' + + did + + '&response_mode=post' + + '&response_context=rp' + + '&nonce=' + + nonce + + '&state=' + + stateId + + '®istration=registration_value' + + '&request=ey...' +const registration = { + did_methods_supported: [`did:${didMethod}:`], + subject_identifiers_supported: SubjectIdentifierType.DID, + credential_formats_supported: [], +} +const authorizationRequest: ParsedAuthorizationRequestURI = { + encodedUri: 'uri_example', + encodingFormat: UrlEncodingFormat.FORM_URL_ENCODED, + scheme: 'scheme2022122200', + requestObjectJwt: 'ey...', + authorizationRequestPayload: { + response_type: ResponseType.ID_TOKEN, + scope, + client_id: did, + redirect_uri: redirectUrl, + iss: did, + response_mode: ResponseMode.POST, + response_context: ResponseContext.RP, + nonce, + stateId, + registration, + request: 'ey...', + }, + registration, +} +const authorizationVerificationMockedResult = { + payload: {}, + verifyOpts: {}, +} + +const createAuthorizationResponseMockedResult = { + didResolutionResult: { + didResolutionMetadata: {}, + didDocument: { + id: did, + }, + didDocumentMetadata: {}, + }, + issuer: did, + signer: { + id: did, + type: 'authentication', + controller: did, + }, + jwt: 'ey...', + authorizationRequestPayload: { + scope, + response_type: ResponseType.ID_TOKEN, + client_id: did, + redirect_uri: redirectUrl, + response_mode: ResponseMode.POST, + response_context: ResponseContext.RP, + nonce: nonce, + }, + verifyOpts: { + verification: { + mode: VerificationMode.INTERNAL, + resolveOpts: {}, + }, + }, +} + +export default (testContext: { + getAgent: () => ConfiguredAgent + setup: () => Promise + tearDown: () => Promise + isRestTest: boolean +}) => { + describe('DID Auth SIOP OP Authenticator Agent Plugin', () => { + let agent: ConfiguredAgent + + beforeAll(async () => { + await testContext.setup() + agent = testContext.getAgent() + + const idCardCredential: VerifiableCredential = getFileAsJson( + './packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/vc/vc_idCardCredential.json' + ) + await agent.dataStoreSaveVerifiableCredential({ verifiableCredential: idCardCredential }) + + const driverLicenseCredential: VerifiableCredential = getFileAsJson( + './packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/vc/vc_driverLicense.json' + ) + await agent.dataStoreSaveVerifiableCredential({ verifiableCredential: driverLicenseCredential }) + + nock(redirectUrl).get(`?stateId=${stateId}`).times(5).reply(200, openIDURI) + + const mockedMapIdentifierKeysToDocMethod = mapIdentifierKeysToDoc as jest.Mock + mockedMapIdentifierKeysToDocMethod.mockReturnValue(Promise.resolve(authKeys)) + + const mockedMapIdentifierKeysToDocMethodWithJwkSupport = mapIdentifierKeysToDocWithJwkSupport as jest.Mock + mockedMapIdentifierKeysToDocMethodWithJwkSupport.mockReturnValue(Promise.resolve(authKeys)) + + const mockedparseAuthorizationRequestURIMethod = jest.fn() + OP.prototype.parseAuthorizationRequestURI = mockedparseAuthorizationRequestURIMethod + mockedparseAuthorizationRequestURIMethod.mockReturnValue(Promise.resolve(authorizationRequest)) + + const mockedverifyAuthorizationRequestMethod = jest.fn() + OP.prototype.verifyAuthorizationRequest = mockedverifyAuthorizationRequestMethod + mockedverifyAuthorizationRequestMethod.mockReturnValue(Promise.resolve(authorizationVerificationMockedResult)) + + const mockedcreateAuthorizationResponse = jest.fn() + OP.prototype.createAuthorizationResponse = mockedcreateAuthorizationResponse + mockedcreateAuthorizationResponse.mockReturnValue(Promise.resolve(createAuthorizationResponseMockedResult)) + + const mocksubmitAuthorizationResponseMethod = jest.fn() + OP.prototype.submitAuthorizationResponse = mocksubmitAuthorizationResponseMethod + mocksubmitAuthorizationResponseMethod.mockReturnValue(Promise.resolve({ status: 200, statusText: 'example_value' })) + + await agent.siopRegisterOPSession({ + sessionId, + requestJwtOrUri: openIDURI, + }) + }) + + afterAll(testContext.tearDown) + + it('should register OP session', async () => { + const sessionId = 'new_session_id' + const result = await agent.siopRegisterOPSession({ + sessionId, + requestJwtOrUri: openIDURI, + }) + + expect(result.id).toEqual(sessionId) + }) + + it('should remove OP session', async () => { + await agent.siopRegisterOPSession({ + sessionId: otherSessionId, + requestJwtOrUri: openIDURI, + }) + await agent.siopRemoveOPSession({ + sessionId: otherSessionId, + }) + + await expect( + agent.siopGetOPSession({ + sessionId: otherSessionId, + }) + ).rejects.toThrow(`No session found for id: ${otherSessionId}`) + }) + + if (!testContext.isRestTest) { + it('should register custom approval function', async () => { + await expect( + agent.siopRegisterOPCustomApproval({ + key: 'test_register', + customApproval: (verifiedAuthenticationRequest: VerifiedAuthorizationRequest) => Promise.resolve(), + }) + ).resolves.not.toThrow() + }) + + it('should remove custom approval function', async () => { + await agent.siopRegisterOPCustomApproval({ + key: 'test_delete', + customApproval: (verifiedAuthenticationRequest: VerifiedAuthorizationRequest) => Promise.resolve(), + }) + const result = await agent.siopRemoveOPCustomApproval({ + key: 'test_delete', + }) + + expect(result).toEqual(true) + }) + } + + it('should authenticate with DID SIOP without custom approval', async () => { + const result = await agent.authenticateWithSiop({ + sessionId, + stateId, + redirectUrl, + }) + + expect(result.status).toEqual(200) + }) + + it('should authenticate with DID SIOP with custom approval', async () => { + const result = await agent.authenticateWithSiop({ + sessionId, + stateId, + redirectUrl, + customApproval: testContext.isRestTest + ? 'success' + : (verifiedAuthenticationRequest: VerifiedAuthorizationRequest) => { + return Promise.resolve() + }, + }) + + expect(result.status).toEqual(200) + }) + + it('should not authenticate with DID SIOP with unknown custom approval key', async () => { + const customApprovalKey = 'some_random_key' + await expect( + agent.authenticateWithSiop({ + sessionId, + stateId, + redirectUrl, + customApproval: customApprovalKey, + }) + ).rejects.toThrow(`Custom approval not found for key: ${customApprovalKey}`) + }) + + it('should not authenticate with DID SIOP when custom approval fails', async () => { + const denied = 'denied' + await expect( + agent.authenticateWithSiop({ + sessionId, + stateId, + redirectUrl, + customApproval: testContext.isRestTest + ? 'failure' + : (verifiedAuthenticationRequest: VerifiedAuthorizationRequest) => { + return Promise.reject(new Error(denied)) + }, + }) + ).rejects.toThrow(denied) + }) + + it('should get authenticate request from RP', async () => { + const result = await agent.getSiopAuthorizationRequestFromRP({ + sessionId, + stateId, + redirectUrl, + }) + + expect(result).toEqual(authorizationRequest) + }) + + it('should get authentication details with single credential', async () => { + const pd_single: PresentationDefinitionWithLocation = getFileAsJson( + './packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/pd/pd_single.json' + ) + const vp_single: IPresentationWithDefinition = getFileAsJson( + './packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/vp/vp_single.json' + ) + const presentation = CredentialMapper.toWrappedVerifiablePresentation(vp_single.presentation) + presentation.presentation.presentation_submission!.id = expect.any(String) + + const result: IAuthRequestDetails = await agent.getSiopAuthorizationRequestDetails({ + sessionId, + verifiedAuthorizationRequest: { + ...createAuthorizationResponseMockedResult, + presentationDefinitions: [pd_single], + authorizationRequest: {} as AuthorizationRequest, + versions: [], + payload: {}, + }, + signingOptions: { + nonce: 'nonce202212272050', + domain: 'domain202212272051', + }, + }) + + expect(result).toMatchObject({ + id: 'did:ethr:0xb9c5714089478a327f09197987f16f9e5d936e8a', + verifiablePresentationMatches: [vp_single], + }) + }) + + it('should get authentication details with getting specific credentials', async () => { + const pdSingle: PresentationDefinitionWithLocation = getFileAsJson( + './packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/pd/pd_single.json' + ) + const vpSingle: IPresentationWithDefinition = getFileAsJson( + './packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/vp/vp_single.json' + ) + const presentation = CredentialMapper.toWrappedVerifiablePresentation(vpSingle.presentation) + presentation.presentation.presentation_submission!.id = expect.any(String) + + const result: IAuthRequestDetails = await agent.getSiopAuthorizationRequestDetails({ + sessionId, + verifiedAuthorizationRequest: { + ...createAuthorizationResponseMockedResult, + presentationDefinitions: [pdSingle], + authorizationRequest: {} as AuthorizationRequest, + versions: [], + payload: {}, + }, + credentialFilter: { + where: [ + { + column: 'id', + value: ['https://example.com/credentials/1872'], + }, + ], + }, + }) + + expect(result).toMatchObject({ + id: 'did:ethr:0xb9c5714089478a327f09197987f16f9e5d936e8a', + verifiablePresentationMatches: [vpSingle], + }) + }) + + it('should get authentication details with multiple credentials', async () => { + const pdMultiple: PresentationDefinitionWithLocation = getFileAsJson( + './packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/pd/pd_multiple.json' + ) + const vpMultiple: IPresentationWithDefinition = getFileAsJson( + './packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/vp/vp_multiple.json' + ) + const presentation = CredentialMapper.toWrappedVerifiablePresentation(vpMultiple.presentation) + presentation.presentation.presentation_submission!.id = expect.any(String) + + const result: IAuthRequestDetails = await agent.getSiopAuthorizationRequestDetails({ + sessionId, + verifiedAuthorizationRequest: { + ...createAuthorizationResponseMockedResult, + presentationDefinitions: [pdMultiple], + authorizationRequest: {} as AuthorizationRequest, + versions: [], + payload: {}, + }, + signingOptions: { + nonce: 'nonce202212272050', + domain: 'domain202212272051', + }, + }) + + expect(result).toMatchObject({ + id: 'did:ethr:0xb9c5714089478a327f09197987f16f9e5d936e8a', + verifiablePresentationMatches: [vpMultiple], + }) + }) + + it('should verify authentication request URI with did methods supported provided', async () => { + authorizationRequest.registration.did_methods_supported = [`did:${didMethod}:`] + + const result = await agent.verifySiopAuthorizationRequestURI({ + sessionId, + requestURI: authorizationRequest, + }) + + expect(result).toEqual(authorizationVerificationMockedResult) + }) + + it('should verify authentication request URI without did methods supported provided', async () => { + authorizationRequest.registration.did_methods_supported = [] + + const result = await agent.verifySiopAuthorizationRequestURI({ + sessionId, + requestURI: authorizationRequest, + }) + + expect(result).toEqual(authorizationVerificationMockedResult) + }) + + it('should send authentication response', async () => { + const pdMultiple: PresentationDefinitionWithLocation = getFileAsJson( + './packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/pd/pd_multiple.json' + ) + + const result = await agent.sendSiopAuthorizationResponse({ + sessionId, + verifiedAuthorizationRequest: { + ...createAuthorizationResponseMockedResult, + presentationDefinitions: [pdMultiple], + authorizationRequest: {} as AuthorizationRequest, + versions: [], + authorizationRequestPayload: {}, + payload: {}, + }, + }) + + expect(result.status).toEqual(200) + }) + }) +} diff --git a/packages/siopv2-openid4vp-rp-auth/__tests__/vc_vp_examples/pd/pd_multiple.json b/packages/siopv2-openid4vp-rp-auth/__tests__/vc_vp_examples/pd/pd_multiple.json new file mode 100644 index 000000000..8c86517e8 --- /dev/null +++ b/packages/siopv2-openid4vp-rp-auth/__tests__/vc_vp_examples/pd/pd_multiple.json @@ -0,0 +1,30 @@ +{ + "definition": { + "id": "Credentials", + "input_descriptors": [ + { + "id": "ID Card Credential and Driver's License", + "schema": [ + { + "uri": "https://www.w3.org/2018/credentials/examples/v1/IDCardCredential" + }, + { + "uri": "https://www.w3.org/2018/credentials/examples/v1/DriversLicense" + } + ], + "constraints": { + "fields": [ + { + "path": ["$.issuer.id"], + "filter": { + "type": "string", + "pattern": "did:example:[issuer|ebfeb1f712ebc6f1c276e12ec21]" + } + } + ] + } + } + ] + }, + "location": "presentation_definition" +} diff --git a/packages/siopv2-openid4vp-rp-auth/__tests__/vc_vp_examples/pd/pd_single.json b/packages/siopv2-openid4vp-rp-auth/__tests__/vc_vp_examples/pd/pd_single.json new file mode 100644 index 000000000..81a68f376 --- /dev/null +++ b/packages/siopv2-openid4vp-rp-auth/__tests__/vc_vp_examples/pd/pd_single.json @@ -0,0 +1,27 @@ +{ + "definition": { + "id": "Credentials", + "input_descriptors": [ + { + "id": "ID Card Credential", + "schema": [ + { + "uri": "https://www.w3.org/2018/credentials/examples/v1/IDCardCredential" + } + ], + "constraints": { + "fields": [ + { + "path": ["$.issuer.id"], + "filter": { + "type": "string", + "pattern": "did:example:issuer" + } + } + ] + } + } + ] + }, + "location": "presentation_definition" +} diff --git a/packages/siopv2-openid4vp-rp-auth/__tests__/vc_vp_examples/psc/psc.json b/packages/siopv2-openid4vp-rp-auth/__tests__/vc_vp_examples/psc/psc.json new file mode 100644 index 000000000..9755bd33f --- /dev/null +++ b/packages/siopv2-openid4vp-rp-auth/__tests__/vc_vp_examples/psc/psc.json @@ -0,0 +1,11 @@ +{ + "proof": { + "type": "RsaSignature2018", + "created": "2018-09-14T21:19:10Z", + "proofPurpose": "authentication", + "verificationMethod": "did:example:ebfeb1f712ebc6f1c276e12ec21#keys-1", + "challenge": "1f44d55f-f161-4938-a659-f8026467f126", + "domain": "4jt78h47fh47", + "jws": "eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..kTCYt5XsITJX1CxPCT8yAV-TVIw5WEuts01mq-pQy7UJiN5mgREEMGlv50aqzpqh4Qq_PbChOMqsLfRoPsnsgxD-WUcX16dUOqV0G_zS245-kronKb78cPktb3rk-BuQy72IFLN25DYuNzVBAh4vGHSrQyHUGlcTwLtjPAnKb78" + } +} diff --git a/packages/siopv2-openid4vp-rp-auth/__tests__/vc_vp_examples/vc/vc_driverLicense.json b/packages/siopv2-openid4vp-rp-auth/__tests__/vc_vp_examples/vc/vc_driverLicense.json new file mode 100644 index 000000000..daa0f0f2b --- /dev/null +++ b/packages/siopv2-openid4vp-rp-auth/__tests__/vc_vp_examples/vc/vc_driverLicense.json @@ -0,0 +1,23 @@ +{ + "id": "https://example.com/credentials/1873", + "type": ["VerifiableCredential", "DriversLicense"], + "@context": ["https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1/DriversLicense"], + "issuer": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21" + }, + "issuanceDate": "2010-01-01T19:23:24Z", + "credentialSubject": { + "given_name": "John", + "family_name": "Doe", + "birthdate": "1975-01-05" + }, + "proof": { + "type": "RsaSignature2018", + "created": "2018-09-14T21:19:10Z", + "proofPurpose": "authentication", + "verificationMethod": "did:example:ebfeb1f712ebc6f1c276e12ec21#keys-1", + "challenge": "1f44d55f-f161-4938-a659-f8026467f126", + "domain": "4jt78h47fh47", + "jws": "eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..kTCYt5XsITJX1CxPCT8yAV-TVIw5WEuts01mq-pQy7UJiN5mgREEMGlv50aqzpqh4Qq_PbChOMqsLfRoPsnsgxD-WUcX16dUOqV0G_zS245-kronKb78cPktb3rk-BuQy72IFLN25DYuNzVBAh4vGHSrQyHUGlcTwLtjPAnKb78" + } +} diff --git a/packages/siopv2-openid4vp-rp-auth/__tests__/vc_vp_examples/vc/vc_idCardCredential.json b/packages/siopv2-openid4vp-rp-auth/__tests__/vc_vp_examples/vc/vc_idCardCredential.json new file mode 100644 index 000000000..01abcea0e --- /dev/null +++ b/packages/siopv2-openid4vp-rp-auth/__tests__/vc_vp_examples/vc/vc_idCardCredential.json @@ -0,0 +1,23 @@ +{ + "id": "https://example.com/credentials/1872", + "type": ["VerifiableCredential", "IDCardCredential"], + "@context": ["https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1/IDCardCredential"], + "issuer": { + "id": "did:example:issuer" + }, + "issuanceDate": "2010-01-01T19:23:24Z", + "credentialSubject": { + "given_name": "Fredrik", + "family_name": "Stremberg", + "birthdate": "1949-01-22" + }, + "proof": { + "type": "RsaSignature2018", + "created": "2018-09-14T21:19:10Z", + "proofPurpose": "authentication", + "verificationMethod": "did:example:ebfeb1f712ebc6f1c276e12ec21#keys-1", + "challenge": "1f44d55f-f161-4938-a659-f8026467f126", + "domain": "4jt78h47fh47", + "jws": "eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..kTCYt5XsITJX1CxPCT8yAV-TVIw5WEuts01mq-pQy7UJiN5mgREEMGlv50aqzpqh4Qq_PbChOMqsLfRoPsnsgxD-WUcX16dUOqV0G_zS245-kronKb78cPktb3rk-BuQy72IFLN25DYuNzVBAh4vGHSrQyHUGlcTwLtjPAnKb78" + } +} diff --git a/packages/siopv2-openid4vp-rp-auth/__tests__/vc_vp_examples/vp/vp_multiple.json b/packages/siopv2-openid4vp-rp-auth/__tests__/vc_vp_examples/vp/vp_multiple.json new file mode 100644 index 000000000..fdb562154 --- /dev/null +++ b/packages/siopv2-openid4vp-rp-auth/__tests__/vc_vp_examples/vp/vp_multiple.json @@ -0,0 +1,81 @@ +{ + "format": "ldp_vp", + "location": "authorization_response", + "presentation": { + "@context": ["https://www.w3.org/2018/credentials/v1", "https://identity.foundation/presentation-exchange/submission/v1"], + "presentation_submission": { + "definition_id": "Credentials", + "descriptor_map": [ + { + "format": "ldp_vc", + "id": "ID Card Credential and Driver's License", + "path": "$.verifiableCredential[0]" + }, + { + "format": "ldp_vc", + "id": "ID Card Credential and Driver's License", + "path": "$.verifiableCredential[1]" + } + ], + "id": "8oBenRGlNXd0Sp770bCb3" + }, + "proof": { + "type": "RsaSignature2018", + "created": "2018-09-14T21:19:10Z", + "proofPurpose": "authentication", + "verificationMethod": "did:example:ebfeb1f712ebc6f1c276e12ec21#keys-1", + "challenge": "1f44d55f-f161-4938-a659-f8026467f126", + "domain": "4jt78h47fh47", + "jws": "eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..kTCYt5XsITJX1CxPCT8yAV-TVIw5WEuts01mq-pQy7UJiN5mgREEMGlv50aqzpqh4Qq_PbChOMqsLfRoPsnsgxD-WUcX16dUOqV0G_zS245-kronKb78cPktb3rk-BuQy72IFLN25DYuNzVBAh4vGHSrQyHUGlcTwLtjPAnKb78" + }, + "type": ["VerifiablePresentation", "PresentationSubmission"], + "verifiableCredential": [ + { + "@context": ["https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1/IDCardCredential"], + "credentialSubject": { + "birthdate": "1949-01-22", + "family_name": "Stremberg", + "given_name": "Fredrik" + }, + "id": "https://example.com/credentials/1872", + "issuanceDate": "2010-01-01T19:23:24Z", + "issuer": { + "id": "did:example:issuer" + }, + "proof": { + "challenge": "1f44d55f-f161-4938-a659-f8026467f126", + "created": "2018-09-14T21:19:10Z", + "domain": "4jt78h47fh47", + "jws": "eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..kTCYt5XsITJX1CxPCT8yAV-TVIw5WEuts01mq-pQy7UJiN5mgREEMGlv50aqzpqh4Qq_PbChOMqsLfRoPsnsgxD-WUcX16dUOqV0G_zS245-kronKb78cPktb3rk-BuQy72IFLN25DYuNzVBAh4vGHSrQyHUGlcTwLtjPAnKb78", + "proofPurpose": "authentication", + "type": "RsaSignature2018", + "verificationMethod": "did:example:ebfeb1f712ebc6f1c276e12ec21#keys-1" + }, + "type": ["VerifiableCredential", "IDCardCredential"] + }, + { + "id": "https://example.com/credentials/1873", + "type": ["VerifiableCredential", "DriversLicense"], + "@context": ["https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1/DriversLicense"], + "issuer": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21" + }, + "issuanceDate": "2010-01-01T19:23:24Z", + "credentialSubject": { + "given_name": "John", + "family_name": "Doe", + "birthdate": "1975-01-05" + }, + "proof": { + "type": "RsaSignature2018", + "created": "2018-09-14T21:19:10Z", + "proofPurpose": "authentication", + "verificationMethod": "did:example:ebfeb1f712ebc6f1c276e12ec21#keys-1", + "challenge": "1f44d55f-f161-4938-a659-f8026467f126", + "domain": "4jt78h47fh47", + "jws": "eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..kTCYt5XsITJX1CxPCT8yAV-TVIw5WEuts01mq-pQy7UJiN5mgREEMGlv50aqzpqh4Qq_PbChOMqsLfRoPsnsgxD-WUcX16dUOqV0G_zS245-kronKb78cPktb3rk-BuQy72IFLN25DYuNzVBAh4vGHSrQyHUGlcTwLtjPAnKb78" + } + } + ] + } +} diff --git a/packages/siopv2-openid4vp-rp-auth/__tests__/vc_vp_examples/vp/vp_single.json b/packages/siopv2-openid4vp-rp-auth/__tests__/vc_vp_examples/vp/vp_single.json new file mode 100644 index 000000000..3ca91b205 --- /dev/null +++ b/packages/siopv2-openid4vp-rp-auth/__tests__/vc_vp_examples/vp/vp_single.json @@ -0,0 +1,53 @@ +{ + "format": "ldp_vp", + "location": "authorization_response", + "presentation": { + "@context": ["https://www.w3.org/2018/credentials/v1", "https://identity.foundation/presentation-exchange/submission/v1"], + "presentation_submission": { + "definition_id": "Credentials", + "descriptor_map": [ + { + "format": "ldp_vc", + "id": "ID Card Credential", + "path": "$.verifiableCredential[0]" + } + ], + "id": "8oBenRGlNXd0Sp770bCb3" + }, + "proof": { + "type": "RsaSignature2018", + "created": "2018-09-14T21:19:10Z", + "proofPurpose": "authentication", + "verificationMethod": "did:example:ebfeb1f712ebc6f1c276e12ec21#keys-1", + "challenge": "1f44d55f-f161-4938-a659-f8026467f126", + "domain": "4jt78h47fh47", + "jws": "eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..kTCYt5XsITJX1CxPCT8yAV-TVIw5WEuts01mq-pQy7UJiN5mgREEMGlv50aqzpqh4Qq_PbChOMqsLfRoPsnsgxD-WUcX16dUOqV0G_zS245-kronKb78cPktb3rk-BuQy72IFLN25DYuNzVBAh4vGHSrQyHUGlcTwLtjPAnKb78" + }, + "type": ["VerifiablePresentation", "PresentationSubmission"], + "verifiableCredential": [ + { + "@context": ["https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1/IDCardCredential"], + "credentialSubject": { + "birthdate": "1949-01-22", + "family_name": "Stremberg", + "given_name": "Fredrik" + }, + "id": "https://example.com/credentials/1872", + "issuanceDate": "2010-01-01T19:23:24Z", + "issuer": { + "id": "did:example:issuer" + }, + "proof": { + "challenge": "1f44d55f-f161-4938-a659-f8026467f126", + "created": "2018-09-14T21:19:10Z", + "domain": "4jt78h47fh47", + "jws": "eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..kTCYt5XsITJX1CxPCT8yAV-TVIw5WEuts01mq-pQy7UJiN5mgREEMGlv50aqzpqh4Qq_PbChOMqsLfRoPsnsgxD-WUcX16dUOqV0G_zS245-kronKb78cPktb3rk-BuQy72IFLN25DYuNzVBAh4vGHSrQyHUGlcTwLtjPAnKb78", + "proofPurpose": "authentication", + "type": "RsaSignature2018", + "verificationMethod": "did:example:ebfeb1f712ebc6f1c276e12ec21#keys-1" + }, + "type": ["VerifiableCredential", "IDCardCredential"] + } + ] + } +} diff --git a/packages/siopv2-openid4vp-rp-auth/agent.yml b/packages/siopv2-openid4vp-rp-auth/agent.yml new file mode 100644 index 000000000..f78f74ed5 --- /dev/null +++ b/packages/siopv2-openid4vp-rp-auth/agent.yml @@ -0,0 +1,128 @@ +version: 3.0 + +constants: + baseUrl: http://localhost:3335 + port: 3335 + # please use your own X25519 key, this is only an example + secretKey: 29739248cad1bd1a0fc4d9b75cd4d2990de535baf5caadfdf8d8f86664aa830c + methods: + - authenticateWithSiop + - getSiopAuthorizationRequestFromRP + - getSiopAuthorizationRequestDetails + - verifySiopAuthorizationRequestURI + - sendSiopAuthorizationResponse + +# Database +dbConnection: + $require: typeorm#DataSource + $args: + - type: sqlite + database: ':memory:' + synchronize: false + migrationsRun: true + migrations: + $require: '@veramo/data-store?t=object#migrations' + logging: false + entities: + $require: '@veramo/data-store?t=object#Entities' + +server: + baseUrl: + $ref: /constants/baseUrl + port: + $ref: /constants/port + use: + # CORS + - - $require: 'cors' + + # Add agent to the request object + - - $require: '@veramo/remote-server?t=function#RequestWithAgentRouter' + $args: + - agent: + $ref: /agent + + # API base path + - - /agent + - $require: '@veramo/remote-server?t=function#apiKeyAuth' + $args: + # Please configure your own API key. This is used when executing agent methods through ${baseUrl}/agent or ${baseUrl}/api-docs + - apiKey: test123 + - $require: '@veramo/remote-server?t=function#AgentRouter' + $args: + - exposedMethods: + $ref: /constants/methods + + # Open API schema + - - /open-api.json + - $require: '@veramo/remote-server?t=function#ApiSchemaRouter' + $args: + - basePath: :3335/agent + securityScheme: bearer + apiName: Agent + apiVersion: '1.0.0' + exposedMethods: + $ref: /constants/methods + + # Swagger docs + - - /api-docs + - $require: swagger-ui-express?t=object#serve + - $require: swagger-ui-express?t=function#setup + $args: + - null + - swaggerOptions: + url: '/open-api.json' + + # Execute during server initialization + init: + - $require: '@veramo/remote-server?t=function#createDefaultDid' + $args: + - agent: + $ref: /agent + baseUrl: + $ref: /constants/baseUrl + messagingServiceEndpoint: /messaging + +# DID resolvers +didResolver: + $require: '@veramo/did-resolver#DIDResolverPlugin' + $args: + - resolver: + $require: did-resolver#Resolver + $args: + - elem: + $ref: /universal-resolver + io: + $ref: /universal-resolver + ion: + $ref: /universal-resolver + sov: + $ref: /universal-resolver + ethr: + $ref: /ethr-did-resolver + +ethr-did-resolver: + $require: ethr-did-resolver?t=function&p=/ethr#getResolver + $args: + - infuraProjectId: 5ffc47f65c4042ce847ef66a3fa70d4c + +universal-resolver: + $require: '@veramo/did-resolver#UniversalResolver' + $args: + - url: https://dev.uniresolver.io/1.0/identifiers/ + +# Agent +agent: + $require: '@veramo/core#Agent' + $args: + - schemaValidation: false + plugins: + - $ref: /didResolver + - $require: ./packages/siopv2-openid4vp-op-auth/dist#DidAuthSiopOpAuthenticator + $args: + - presentationSignCallback: {} + - $require: '@veramo/data-store#DataStore' + $args: + - $ref: /dbConnection + - $require: '@veramo/data-store#DataStoreORM' + $args: + - $ref: /dbConnection diff --git a/packages/siopv2-openid4vp-rp-auth/api-extractor.json b/packages/siopv2-openid4vp-rp-auth/api-extractor.json new file mode 100644 index 000000000..94c2c6a9f --- /dev/null +++ b/packages/siopv2-openid4vp-rp-auth/api-extractor.json @@ -0,0 +1,3 @@ +{ + "extends": "../include/api-extractor-base.json" +} diff --git a/packages/siopv2-openid4vp-rp-auth/package.json b/packages/siopv2-openid4vp-rp-auth/package.json new file mode 100644 index 000000000..908e32586 --- /dev/null +++ b/packages/siopv2-openid4vp-rp-auth/package.json @@ -0,0 +1,65 @@ +{ + "name": "@sphereon/ssi-sdk-siopv2-openid4vp-rp-auth", + "version": "0.9.0", + "source": "src/index.ts", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "veramo": { + "pluginInterfaces": { + "ISiopv2RelyingParty": "./src/types/ISiopv2RelyingParty.ts" + } + }, + "scripts": { + "build": "tsc --build", + "build:clean": "tsc --build --clean && tsc --build" + }, + "dependencies": { + "@sphereon/did-auth-siop": "^0.3.0-unstable.30", + "@sphereon/pex": "2.0.0-unstable.13", + "@sphereon/ssi-sdk-core": "^0.9.0", + "@sphereon/ssi-sdk-did-utils": "^0.9.0", + "@sphereon/ssi-sdk-siopv2-openid4vp-common": "^0.9.0", + "@sphereon/ssi-types": "^0.9.0", + "@sphereon/wellknown-dids-client": "^0.1.3", + "@types/uuid": "^9.0.1", + "@veramo/core": "4.2.0", + "@veramo/credential-w3c": "4.2.0", + "@veramo/kv-store": "file:.yalc/@veramo/kv-store", + "cross-fetch": "^3.1.5", + "uuid": "^8.3.2" + }, + "devDependencies": { + "@sphereon/did-uni-client": "^0.6.0", + "@veramo/cli": "4.2.0", + "@veramo/did-provider-key": "4.2.0", + "@veramo/did-resolver": "4.2.0", + "@veramo/utils": "4.2.0", + "did-resolver": "^4.1.0", + "nock": "^13.2.1" + }, + "files": [ + "dist/**/*", + "src/**/*", + "README.md", + "plugin.schema.json", + "LICENSE" + ], + "publishConfig": { + "access": "public" + }, + "repository": "git@github.com:Sphereon-Opensource/SSI-SDK.git", + "author": "Sphereon ", + "license": "Apache-2.0", + "keywords": [ + "Sphereon", + "SSI", + "Veramo", + "DID", + "SIOP", + "SIOPv2", + "OIDC4VP", + "Presentation Exchange", + "OpenID Connect", + "Authenticator" + ] +} diff --git a/packages/siopv2-openid4vp-rp-auth/plugin.schema.json b/packages/siopv2-openid4vp-rp-auth/plugin.schema.json new file mode 100644 index 000000000..03a1513eb --- /dev/null +++ b/packages/siopv2-openid4vp-rp-auth/plugin.schema.json @@ -0,0 +1,329 @@ +{ + "IDidAuthSiopOpAuthenticator": { + "components": { + "schemas": { + "IGetSiopSessionArgs": { + "type": "object", + "properties": { + "sessionId": { + "type": "string" + }, + "additionalProperties": false + }, + "required": ["sessionId"], + "description": "Arguments needed for {@link DidAuthSiopOpAuthenticator.getSessionForSiop } " + }, + "IRegisterSiopSessionArgs": { + "type": "object", + "properties": { + "identifier": { + "type": "object", + "properties": { + "did": { + "type": "string" + }, + "alias": { + "type": "string" + }, + "provider": { + "type": "string" + }, + "controllerKeyId": { + "type": "string" + }, + "keys": { + "type": "array", + "items": { + "type": "object", + "properties": { + "additionalProperties": true + } + } + }, + "services": { + "type": "array", + "items": { + "type": "object", + "properties": { + "additionalProperties": true + } + } + } + }, + "additionalProperties": false, + "required": ["did", "provider", "keys", "services"] + }, + "sessionId": { + "type": "string" + }, + "expiresIn": { + "type": "number" + }, + "additionalProperties": false + }, + "required": ["identifier"], + "description": "Arguments needed for {@link DidAuthSiopOpAuthenticator.registerSessionForSiop } " + }, + "IRemoveSiopSessionArgs": { + "type": "object", + "properties": { + "sessionId": { + "type": "string" + }, + "additionalProperties": false + }, + "required": ["sessionId"], + "description": "Arguments needed for {@link DidAuthSiopOpAuthenticator.removeSessionForSiop } " + }, + "IAuthenticateWithSiopArgs": { + "type": "object", + "properties": { + "sessionId": { + "type": "string" + }, + "stateId": { + "type": "string" + }, + "redirectUrl": { + "type": "string" + }, + "additionalProperties": false + }, + "required": ["sessionId", "stateId", "redirectUrl"], + "description": "Arguments needed for {@link DidAuthSiopOpAuthenticator.authenticateWithSiop } " + }, + "IResponse": { + "type": "object", + "properties": { + "status": { + "type": "number" + }, + "additionalProperties": true + }, + "required": ["status"], + "description": "Result of {@link DidAuthSiopOpAuthenticator.authenticateWithSiop & DidAuthSiopOpAuthenticator.sendSiopAuthenticationResponse } " + }, + "IGetSiopAuthenticationRequestFromRpArgs": { + "type": "object", + "properties": { + "sessionId": { + "type": "string" + }, + "stateId": { + "type": "string" + }, + "redirectUrl": { + "type": "string" + }, + "additionalProperties": false + }, + "required": ["sessionId", "stateId", "redirectUrl"], + "description": "Arguments needed for {@link DidAuthSiopOpAuthenticator.getSiopAuthenticationRequestFromRP } " + }, + "ParsedAuthenticationRequestURI": { + "type": "object", + "properties": { + "jwt": { + "type": "string" + }, + "requestPayload": { + "type": "object", + "properties": { + "additionalProperties": true + } + }, + "registration": { + "type": "object", + "properties": { + "additionalProperties": true + } + }, + "additionalProperties": false + }, + "required": ["jwt", "requestPayload", "registration"], + "description": "Result of {@link DidAuthSiopOpAuthenticator.getSiopAuthenticationRequestFromRP } " + }, + "IGetSiopAuthenticationRequestDetailsArgs": { + "type": "object", + "properties": { + "sessionId": { + "type": "string" + }, + "verifiedAuthenticationRequest": { + "type": "object", + "properties": { + "additionalProperties": true + } + }, + "credentialFilter": { + "type": "object", + "properties": { + "additionalProperties": true + } + }, + "additionalProperties": false + }, + "required": ["sessionId", "verifiedAuthenticationRequest"], + "description": "Arguments needed for {@link DidAuthSiopOpAuthenticator.getSiopAuthenticationRequestDetails } " + }, + "IAuthRequestDetails": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "alsoKnownAs": { + "type": "array", + "items": { + "type": "string" + } + }, + "vpResponseOpts": { + "type": "object", + "properties": { + "additionalProperties": true + } + }, + "additionalProperties": false + }, + "required": ["id", "vpResponseOpts"], + "description": "Result of {@link DidAuthSiopOpAuthenticator.getSiopAuthenticationRequestDetails } " + }, + "IVerifySiopAuthenticationRequestUriArgs": { + "type": "object", + "properties": { + "sessionId": { + "type": "string" + }, + "ParsedAuthenticationRequestURI": { + "type": "object", + "properties": { + "additionalProperties": true + } + }, + "additionalProperties": false + }, + "required": ["sessionId", "ParsedAuthenticationRequestURI"], + "description": "Arguments needed for {@link DidAuthSiopOpAuthenticator.verifySiopAuthenticationRequestURI } " + }, + "VerifiedAuthorizationRequest": { + "type": "object", + "properties": { + "payload": { + "type": "object", + "properties": { + "additionalProperties": true + } + }, + "presentationDefinitions": { + "type": "object", + "properties": { + "additionalProperties": true + } + }, + "verifyOpts": { + "type": "object", + "properties": { + "additionalProperties": true + } + }, + "additionalProperties": false + }, + "required": ["payload", "verifyOpts"], + "description": "Result of {@link DidAuthSiopOpAuthenticator.verifySiopAuthenticationRequestURI } " + }, + "ISendSiopAuthenticationResponseArgs": { + "type": "object", + "properties": { + "sessionId": { + "type": "string" + }, + "verifiedAuthenticationRequest": { + "type": "object", + "properties": { + "additionalProperties": true + } + }, + "verifiablePresentationResponse": { + "type": "object", + "properties": { + "additionalProperties": true + } + }, + "additionalProperties": false + }, + "required": ["sessionId", "verifiedAuthenticationRequest"], + "description": "Arguments needed for {@link DidAuthSiopOpAuthenticator.sendSiopAuthenticationResponse } " + } + }, + "methods": { + "getSessionForSiop": { + "description": "Get SIOP session", + "arguments": { + "$ref": "#/components/schemas/IGetSiopSessionArgs" + }, + "returnType": "object" + }, + "registerSessionForSiop": { + "description": "Register SIOP session", + "arguments": { + "$ref": "#/components/schemas/IRegisterSiopSessionArgs" + }, + "returnType": "object" + }, + "removeSessionForSiop": { + "description": "Remove SIOP session", + "arguments": { + "$ref": "#/components/schemas/IRemoveSiopSessionArgs" + }, + "returnType": "boolean" + }, + "authenticateWithSiop": { + "description": "Authenticate using DID Auth SIOP", + "arguments": { + "$ref": "#/components/schemas/IAuthenticateWithSiopArgs" + }, + "returnType": { + "$ref": "#/components/schemas/Response" + } + }, + "getSiopAuthenticationRequestFromRP": { + "description": "Get authentication request from RP", + "arguments": { + "$ref": "#/components/schemas/IGetSiopAuthenticationRequestFromRpArgs" + }, + "returnType": { + "$ref": "#/components/schemas/ParsedAuthenticationRequestURI" + } + }, + "getSiopAuthenticationRequestDetails": { + "description": "Get authentication request details", + "arguments": { + "$ref": "#/components/schemas/IGetSiopAuthenticationRequestDetailsArgs" + }, + "returnType": { + "$ref": "#/components/schemas/IAuthRequestDetails" + } + }, + "verifySiopAuthenticationRequestURI": { + "description": "Verify authentication request URI", + "arguments": { + "$ref": "#/components/schemas/IVerifySiopAuthenticationRequestUriArgs" + }, + "returnType": { + "$ref": "#/components/schemas/VerifiedAuthorizationRequest" + } + }, + "sendSiopAuthenticationResponse": { + "description": "Send authentication response", + "arguments": { + "$ref": "#/components/schemas/ISendSiopAuthenticationResponseArgs" + }, + "returnType": { + "$ref": "#/components/schemas/IRequiredContext" + } + } + } + } + } +} diff --git a/packages/siopv2-openid4vp-rp-auth/src/RPInstance.ts b/packages/siopv2-openid4vp-rp-auth/src/RPInstance.ts new file mode 100644 index 000000000..c52e7f646 --- /dev/null +++ b/packages/siopv2-openid4vp-rp-auth/src/RPInstance.ts @@ -0,0 +1,49 @@ +import { RP } from '@sphereon/did-auth-siop' +import { IPEXOptions, IRequiredContext, IRPOptions } from './types/ISiopv2RelyingParty' +import { IPresentationDefinition } from '@sphereon/pex' +import { createRPBuilder } from './functions' + +export class RPInstance { + private readonly context: IRequiredContext + private _rp: RP | undefined + private readonly _pexOptions: IPEXOptions | undefined + private readonly _rpOptions: IRPOptions + + public constructor({ rpOpts, pexOpts }: { + rpOpts: IRPOptions, + pexOpts?: IPEXOptions + }, context: IRequiredContext) { + this._rpOptions = rpOpts + this._pexOptions = pexOpts + this.context = context + } + + + public async get(): Promise { + if (!this._rp) { + const builder = await createRPBuilder({ rpOpts: this._rpOptions, pexOpts: this._pexOptions, context: this.context }) + this._rp = builder.build() + } + return this._rp! + } + + + get rpOptions() { + return this._rpOptions + } + + + get pexOptions() { + return this._pexOptions + } + + get definitionId(): string | undefined { + return this.pexOptions?.definitionId + } + + public getPresentationDefinition(): IPresentationDefinition | undefined { + return this.pexOptions?.definition + } + +} + diff --git a/packages/siopv2-openid4vp-rp-auth/src/agent/Siopv2RelyingParty.ts b/packages/siopv2-openid4vp-rp-auth/src/agent/Siopv2RelyingParty.ts new file mode 100644 index 000000000..6d2e5b98c --- /dev/null +++ b/packages/siopv2-openid4vp-rp-auth/src/agent/Siopv2RelyingParty.ts @@ -0,0 +1,119 @@ +import { + IPEXDefinitionPersistArgs, + IPEXInstanceOptions, + IRequiredContext, + IRPOptions, ISiopRPInstanceArgs, + ISiopv2RPOpts, + schema, VersionDiscoveryResult, +} from '../index' +import { IAgentPlugin } from '@veramo/core' + +import { ISiopv2RelyingParty } from '../types/ISiopv2RelyingParty' +import { IKeyValueStore, IValueData, KeyValueStore } from '@veramo/kv-store' +import { IPresentationDefinition, PEX } from '@sphereon/pex' +import { RPInstance } from '../RPInstance' + +export class Siopv2RelyingParty implements IAgentPlugin { + private static PEX = new PEX() + private readonly _definitionStore: IKeyValueStore + private readonly opts: ISiopv2RPOpts + private readonly _optionsStore: IKeyValueStore + private static readonly _DEFAULT_OPTS_KEY = '_default' + readonly schema = schema.IDidAuthSiopOpAuthenticator + + readonly methods: ISiopv2RelyingParty = { + pexDefinitionGet: this.pexDefinitionGet.bind(this), + pexDefinitionPersist: this.pexDefinitionPersist.bind(this), + pexDefinitionExists: this.pexDefinitionExists.bind(this), + pexDefinitionVersion: this.pexDefinitionVersion.bind(this), + siopRPInstance: this.siopRPInstance.bind(this), + } + + constructor( + opts: ISiopv2RPOpts, + ) { + this.opts = opts + this._optionsStore = opts.optionsStore ?? new KeyValueStore({ + namespace: 'siop-rp-opts', + store: new Map, + }) + this.opts.optionsStore = this._optionsStore + this._definitionStore = opts.definitionStore ?? new KeyValueStore({ + namespace: 'siop-rp-defs', + store: new Map(), + }) + this.opts.definitionStore = this._definitionStore + if (opts.defaultOpts) { + this.optionsStore.set(Siopv2RelyingParty._DEFAULT_OPTS_KEY, opts.defaultOpts) + } + if (Array.isArray(opts.instanceOpts)) { + for (const instance of opts.instanceOpts) { + const instanceOpts = JSON.parse(JSON.stringify(instance)) as IPEXInstanceOptions + const definition = instanceOpts.definition + if (definition) { + this.definitionStore.set(instanceOpts.definitionId, definition) + delete instanceOpts.definition + } + if (instanceOpts.rpOpts) { + this.optionsStore.set(instanceOpts.definitionId, instanceOpts.rpOpts) + } + } + } + } + + + private async pexDefinitionGet(definitionId: string): Promise { + return this.definitionStore.get(definitionId) + } + + + private async pexDefinitionExists(definitionId: string): Promise { + return this.definitionStore.has(definitionId) + } + + private async pexDefinitionPersist(args: IPEXDefinitionPersistArgs): Promise> { + const { definition, definitionId, ttl } = args + Siopv2RelyingParty.PEX.validateDefinition(definition) // throws an error in case the def is not valid + return await this.definitionStore.set(definitionId || definition.id, definition, ttl).then(valueData => { + if (args.rpOpts) { + this.optionsStore.set(definitionId, args.rpOpts) + } + return valueData + }, + ) + } + + async siopRPOptionsGet(definitionId?: string): Promise { + const options = await this.optionsStore.get(definitionId ?? Siopv2RelyingParty._DEFAULT_OPTS_KEY) ?? await this.optionsStore.get(Siopv2RelyingParty._DEFAULT_OPTS_KEY) + if (!options) { + throw Error(`Could not get specific nor default options for definition ${definitionId}`) + } + return options + } + + async siopRPInstance(args: ISiopRPInstanceArgs, context: IRequiredContext): Promise { + const { definitionId } = args + const rpOpts = await this.siopRPOptionsGet(definitionId ?? Siopv2RelyingParty._DEFAULT_OPTS_KEY) + const definition = definitionId ? await this.pexDefinitionGet(definitionId) : undefined + return new RPInstance({ rpOpts, ...(definitionId ? { pexOpts: { definitionId, definition } } : {}) }, context) + } + + async pexDefinitionVersion(presentationDefinition: IPresentationDefinition): Promise { + return Siopv2RelyingParty.PEX.definitionVersionDiscovery(presentationDefinition) + } + + + private get definitionStore(): IKeyValueStore { + return this._definitionStore + } + + private get optionsStore(): IKeyValueStore { + return this._optionsStore + } + + + public getDefinitionRP(definitionId: string) { + + } + +} diff --git a/packages/siopv2-openid4vp-rp-auth/src/functions.ts b/packages/siopv2-openid4vp-rp-auth/src/functions.ts new file mode 100644 index 000000000..c1b14c727 --- /dev/null +++ b/packages/siopv2-openid4vp-rp-auth/src/functions.ts @@ -0,0 +1,224 @@ +import { IDIDOptions, IIdentifierOpts, IPEXOptions, IRequiredContext, IRPOptions } from './types/ISiopv2RelyingParty' +import { EventEmitter } from 'events' +import { AgentDIDResolver, getAgentDIDMethods, mapIdentifierKeysToDocWithJwkSupport } from '@sphereon/ssi-sdk-did-utils' +import { KeyAlgo, SuppliedSigner } from '@sphereon/ssi-sdk-core' +import { + CheckLinkedDomain, + InMemoryRPSessionManager, + PassBy, + PropertyTarget, + ResponseMode, + ResponseType, + RevocationVerification, + RP, + Scope, + SigningAlgo, + SubjectType, + SupportedVersion, +} from '@sphereon/did-auth-siop' +import { IPresentationDefinition } from '@sphereon/pex' +import { DIDDocumentSection, IIdentifier, IKey, TKeyType } from '@veramo/core' +import { _ExtendedIKey } from '@veramo/utils' +import { IVerifyCallbackArgs, IVerifyCredentialResult } from '@sphereon/wellknown-dids-client' +import { PresentationVerificationResult } from '@sphereon/did-auth-siop/dist/main/authorization-response/types' + + +/* +export async function getPresentationDefinitionStore(pexOptions?: IPEXOptions): Promise | undefined> { + if (pexOptions && pexOptions.definitionId) { + if (!pexOptions.definitionStore) { + // yes the assignment is ugly, but we want an in-memory fallback and it cannot be re-instantiated every time + pexOptions.definitionStore = new KeyValueStore({ + namespace: 'definitions', + store: new Map(), + }) + } + return pexOptions.definitionStore + } + return undefined +} +*/ + +export async function getPresentationDefinition(pexOptions?: IPEXOptions): Promise { + return pexOptions?.definition + /*const store = await getPresentationDefinitionStore(pexOptions) + return store && pexOptions?.definitionId ? store.get(pexOptions?.definitionId) : undefined*/ +} + + +export function getRequestVersion(rpOptions: IRPOptions): SupportedVersion { + if (Array.isArray(rpOptions.supportedVersions) && rpOptions.supportedVersions.length > 0) { + return rpOptions.supportedVersions[0] + } + return SupportedVersion.JWT_VC_PRESENTATION_PROFILE_v1 +} + + +export function getDID(didOptions: IDIDOptions): string { + if (typeof didOptions.identifierOpts.identifier === 'string') { + return didOptions.identifierOpts.identifier + } else if (typeof didOptions.identifierOpts.identifier === 'object') { + return didOptions.identifierOpts.identifier.did + } + throw Error(`Cannot get DID from identifier value`) +} + + +export async function getIdentifier(didOptions: IDIDOptions, context: IRequiredContext): Promise { + if (typeof didOptions.identifierOpts.identifier === 'string') { + return context.agent.didManagerGet({ did: didOptions.identifierOpts.identifier }) + } else if (typeof didOptions.identifierOpts.identifier === 'object') { + return didOptions.identifierOpts.identifier + } + throw Error(`Cannot get agent identifier value from options`) +} + + +export async function getSupportedDIDMethods(didOpts: IDIDOptions, context: IRequiredContext) { + return didOpts.supportedDIDMethods ?? (await getAgentDIDMethods(context)) +} + +function getWellKnownDIDVerifyCallback(didOpts: IDIDOptions, context: IRequiredContext) { + return didOpts.wellknownDIDVerifyCallback + ? didOpts.wellknownDIDVerifyCallback + : async (args: IVerifyCallbackArgs): Promise => { + const result = await context.agent.verifyCredential({ credential: args.credential, fetchRemoteContexts: true }) + return { verified: result.verified } + } +} + +export function getPresentationVerificationCallback(didOpts: IDIDOptions, context: IRequiredContext) { + async function presentationVerificationCallback(args: any): Promise { + const result = await context.agent.verifyPresentation({ + presentation: args, + fetchRemoteContexts: true, + domain: getDID(didOpts), + }) + console.log(`VP verification result: ${JSON.stringify(result, null, 2)}`) + return { verified: result.verified } + } + + return presentationVerificationCallback +} + +export async function createRPBuilder({ + rpOpts, + pexOpts, + context, + }: { + rpOpts: IRPOptions + pexOpts?: IPEXOptions | undefined + context: IRequiredContext +}) { + const { didOpts } = rpOpts + const definition = !!pexOpts ? await getPresentationDefinition(pexOpts) : undefined + const did = getDID(didOpts) + const didMethods = await getSupportedDIDMethods(didOpts, context) + const identifier = await getIdentifier(didOpts, context) + const key = await getKey(identifier, didOpts.identifierOpts.verificationMethodSection, context, didOpts.identifierOpts.kid) + const kid = determineKid(key, didOpts.identifierOpts) + + const eventEmitter = rpOpts.eventEmitter ?? new EventEmitter() + + const builder = RP.builder({ requestVersion: getRequestVersion(rpOpts) }) + .withScope('openid', PropertyTarget.REQUEST_OBJECT) + .withResponseMode(rpOpts.responseMode ?? ResponseMode.POST) + .withResponseType(ResponseType.ID_TOKEN, PropertyTarget.REQUEST_OBJECT) + .withCustomResolver(rpOpts.didOpts.resolveOpts?.resolver ?? new AgentDIDResolver(context, rpOpts.didOpts.resolveOpts?.noUniversalResolverFallback !== false)) + .withClientId(did, PropertyTarget.REQUEST_OBJECT) + // todo: move to options fill/correct method + .withSupportedVersions(rpOpts.supportedVersions ?? [SupportedVersion.JWT_VC_PRESENTATION_PROFILE_v1, SupportedVersion.SIOPv2_ID1, SupportedVersion.SIOPv2_D11]) + + .withEventEmitter(eventEmitter) + .withSessionManager(rpOpts.sessionManager ?? new InMemoryRPSessionManager(eventEmitter)) + .withClientMetadata( + { + + //FIXME: All of the below should be configurable. Some should come from builder, some should be determined by the agent + idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256, SigningAlgo.ES256K], // added newly + requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256, SigningAlgo.ES256K], // added newly + responseTypesSupported: [ResponseType.ID_TOKEN], // added newly + client_name: 'Sphereon', + vpFormatsSupported: { + jwt_vc: { alg: ['EdDSA', 'ES256K'] }, + jwt_vp: { alg: ['ES256K', 'EdDSA'] }, + }, + scopesSupported: [Scope.OPENID_DIDAUTHN], + subjectTypesSupported: [SubjectType.PAIRWISE], + subject_syntax_types_supported: didMethods.map(method => `did:${method}`), + passBy: PassBy.VALUE, + }, PropertyTarget.REQUEST_OBJECT, + ) + + .withCheckLinkedDomain(didOpts.checkLinkedDomains ?? CheckLinkedDomain.IF_PRESENT) + .withRevocationVerification(RevocationVerification.NEVER) + .withPresentationVerification(getPresentationVerificationCallback(didOpts, context)) + + + didMethods.forEach((method) => builder.addDidMethod(method)) + builder.withWellknownDIDVerifyCallback(getWellKnownDIDVerifyCallback(didOpts, context)) + + if (definition) { + builder.withPresentationDefinition({ definition }, PropertyTarget.REQUEST_OBJECT) + } + + + builder.withSuppliedSignature( + SuppliedSigner(key, context, getSigningAlgo(key.type) as unknown as KeyAlgo), + did, + kid, + getSigningAlgo(key.type), + ) + + + return builder +} + +export async function createRP({ + rpOptions, + context, + }: { + rpOptions: IRPOptions + context: IRequiredContext +}): Promise { + return (await createRPBuilder({ rpOpts: rpOptions, context })).build() +} + +export async function getKey( + identifier: IIdentifier, + verificationMethodSection: DIDDocumentSection = 'authentication', + context: IRequiredContext, + keyId?: string, +): Promise { + const keys = await mapIdentifierKeysToDocWithJwkSupport(identifier, verificationMethodSection, context) + if (!keys || keys.length === 0) { + throw new Error(`No keys found for verificationMethodSection: ${verificationMethodSection} and did ${identifier.did}`) + } + + const identifierKey = keyId ? keys.find((key: _ExtendedIKey) => key.kid === keyId || key.meta.verificationMethod.id === keyId) : keys[0] + if (!identifierKey) { + throw new Error(`No matching verificationMethodSection key found for keyId: ${keyId}`) + } + + return identifierKey +} + +export function determineKid(key: IKey, idOpts: IIdentifierOpts): string { + return key.meta?.verificationMethod.id ?? idOpts.kid ?? key.kid +} + +export function getSigningAlgo(type: TKeyType): SigningAlgo { + switch (type) { + case 'Ed25519': + return SigningAlgo.EDDSA + case 'Secp256k1': + return SigningAlgo.ES256K + case 'Secp256r1': + return SigningAlgo.ES256 + // @ts-ignore + case 'RSA': + return SigningAlgo.RS256 + default: + throw Error('Key type not yet supported') + } +} diff --git a/packages/siopv2-openid4vp-rp-auth/src/index.ts b/packages/siopv2-openid4vp-rp-auth/src/index.ts new file mode 100644 index 000000000..a7212a7ce --- /dev/null +++ b/packages/siopv2-openid4vp-rp-auth/src/index.ts @@ -0,0 +1,7 @@ +/** + * @public + */ +const schema = require('../plugin.schema.json') +export { schema } +export { Siopv2RelyingParty } from './agent/Siopv2RelyingParty' +export * from './types/ISiopv2RelyingParty' diff --git a/packages/siopv2-openid4vp-rp-auth/src/types/ISiopv2RelyingParty.ts b/packages/siopv2-openid4vp-rp-auth/src/types/ISiopv2RelyingParty.ts new file mode 100644 index 000000000..f57ecf530 --- /dev/null +++ b/packages/siopv2-openid4vp-rp-auth/src/types/ISiopv2RelyingParty.ts @@ -0,0 +1,131 @@ +import { + DIDDocumentSection, + IAgentContext, + ICredentialIssuer, + ICredentialVerifier, + IDataStoreORM, + IDIDManager, + IIdentifier, + IKeyManager, + IPluginMethodMap, + IResolver, +} from '@veramo/core' +import { W3CVerifiablePresentation } from '@sphereon/ssi-types' +import { + CheckLinkedDomain, + IRPSessionManager, + PresentationDefinitionWithLocation, + PresentationVerificationCallback, + ResolveOpts, + ResponseMode, + SupportedVersion, + VerifiablePresentationTypeFormat, + VPTokenLocation, +} from '@sphereon/did-auth-siop' +import { VerifyCallback } from '@sphereon/wellknown-dids-client' + +import { Resolvable } from 'did-resolver' +import { DIDDocument } from '@sphereon/did-uni-client' +import { EventEmitter } from 'events' +import { IKeyValueStore, IValueData } from '@veramo/kv-store' +import { IPresentationDefinition } from '@sphereon/pex' +import { RPInstance } from '../RPInstance' +import { PEVersion } from '@sphereon/pex/dist/main/lib/types' + + +export interface ISiopv2RelyingParty extends IPluginMethodMap { + pexDefinitionGet(definitionId: string): Promise + + pexDefinitionExists(definitionId: string): Promise + + pexDefinitionPersist(args: IPEXDefinitionPersistArgs): Promise> + + pexDefinitionVersion(presentationDefinition: IPresentationDefinition): Promise + + siopRPInstance(args: ISiopRPInstanceArgs, context: IRequiredContext): Promise +} + + +export interface ISiopv2RPOpts { + optionsStore?: IKeyValueStore + definitionStore?: IKeyValueStore + defaultOpts?: IRPDefaultOpts + instanceOpts?: IPEXInstanceOptions[] +} + +export interface IRPDefaultOpts extends IRPOptions { +} + + +export interface IPEXDefinitionPersistArgs extends IPEXInstanceOptions { + definition: IPresentationDefinition + ttl?: number +} + +export interface ISiopRPInstanceArgs { + definitionId?: string +} + +export interface IPEXInstanceOptions extends IPEXOptions { + rpOpts?: IRPOptions +} + +export interface IPEXOptions { + presentationVerifyCallback?: PresentationVerificationCallback + definition?: IPresentationDefinition + definitionId: string +} + +export interface PerDidResolver { + didMethod: string + resolver: Resolvable +} + + +export interface IAuthRequestDetails { + rpDIDDocument?: DIDDocument + id: string + verifiablePresentationMatches: IPresentationWithDefinition[] + alsoKnownAs?: string[] +} + + +export interface IPresentationWithDefinition { + location: VPTokenLocation + definition: PresentationDefinitionWithLocation + format: VerifiablePresentationTypeFormat + presentation: W3CVerifiablePresentation +} + + +export type IRequiredContext = IAgentContext + +export interface IRPOptions { + responseMode?: ResponseMode + supportedVersions?: SupportedVersion[] + sessionManager?: IRPSessionManager + expiresIn?: number + eventEmitter?: EventEmitter + didOpts: IDIDOptions +} + +export interface IDIDOptions { + resolveOpts?: ResolveOpts + identifierOpts: IIdentifierOpts + supportedDIDMethods?: string[] + checkLinkedDomains?: CheckLinkedDomain + wellknownDIDVerifyCallback?: VerifyCallback +} + + +export interface IIdentifierOpts { + identifier: IIdentifier | string + verificationMethodSection?: DIDDocumentSection + kid?: string +} + + +export interface VersionDiscoveryResult { + version?: PEVersion + error?: string +} diff --git a/packages/siopv2-openid4vp-rp-auth/tsconfig.json b/packages/siopv2-openid4vp-rp-auth/tsconfig.json new file mode 100644 index 000000000..319245f3d --- /dev/null +++ b/packages/siopv2-openid4vp-rp-auth/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig-base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "declarationDir": "dist", + "esModuleInterop": true + }, + "references": [{ "path": "../siopv2-openid4vp-common" }, { "path": "../ssi-types" }, { "path": "../ssi-sdk-core" }, { "path": "../did-utils" }] +} diff --git a/packages/siopv2-openid4vp-rp-rest/CHANGELOG.md b/packages/siopv2-openid4vp-rp-rest/CHANGELOG.md new file mode 100644 index 000000000..fe7bc128c --- /dev/null +++ b/packages/siopv2-openid4vp-rp-rest/CHANGELOG.md @@ -0,0 +1,71 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [0.9.0](https://github.com/Sphereon-Opensource/SSI-SDK/compare/v0.8.0...v0.9.0) (2023-03-09) + +### Bug Fixes + +- credential mapper for jtw ([f04345b](https://github.com/Sphereon-Opensource/SSI-SDK/commit/f04345b97ff9a78a3dff096599f0b675b3239a3e)) +- Fix DID handling in OP session ([926e358](https://github.com/Sphereon-Opensource/SSI-SDK/commit/926e358ef3eadf19fc3c8f7c9940fe6322c5ff85)) +- Incorrect verification method id returned when signing credentials in some cases ([c508507](https://github.com/Sphereon-Opensource/SSI-SDK/commit/c508507ddd2e35fcb377a79bad3c82d695b3d93d)) +- Move parseDid method to ssi-types ([0b28de3](https://github.com/Sphereon-Opensource/SSI-SDK/commit/0b28de3de21afd0a224d3d174103e072162231ed)) + +### Features + +- Add jwt as signature when decoding JWT VCs/VPs ([f089ac1](https://github.com/Sphereon-Opensource/SSI-SDK/commit/f089ac18dc470f0b8c581b49e70e7eba64d72bc3)) +- Allow to relax JWT timing checks, where the JWT claim is slightly different from the VC claim. Used for issuance and expiration dates ([85bff6d](https://github.com/Sphereon-Opensource/SSI-SDK/commit/85bff6da21dea5d8f636ea1f55b41be00b18b002)) +- Create VP in OP Authenticator and allow for callbacks ([0ed86d8](https://github.com/Sphereon-Opensource/SSI-SDK/commit/0ed86d8d2b655a718d7c8cf1a946e0150bf877ce)) +- Make sure VP type corresponds with PEX definition ([129b663](https://github.com/Sphereon-Opensource/SSI-SDK/commit/129b66383752e05ab3067e459bff591a07aac690)) +- Make sure VP type corresponds with PEX definition ([3dafa3f](https://github.com/Sphereon-Opensource/SSI-SDK/commit/3dafa3ff4c794d13eff3e2e0b6a85675667db089)) +- Update SIOP OP to be in line wiht latest SIOP and also supporting late binding of identifiers ([2beea04](https://github.com/Sphereon-Opensource/SSI-SDK/commit/2beea04a6604d82b12ecbc11e68a9f41775c22ed)) + +# [0.8.0](https://github.com/Sphereon-Opensource/SSI-SDK/compare/v0.7.0...v0.8.0) (2022-09-03) + +**Note:** Version bump only for package @sphereon/ssi-sdk-did-auth-siop-authenticator + +# [0.7.0](https://github.com/Sphereon-Opensource/SSI-SDK/compare/v0.6.0...v0.7.0) (2022-08-05) + +**Note:** Version bump only for package @sphereon/ssi-sdk-did-auth-siop-authenticator + +# [0.6.0](https://github.com/Sphereon-Opensource/SSI-SDK/compare/v0.5.1...v0.6.0) (2022-07-01) + +### Features + +- Add custom DID resolver support ([45cea11](https://github.com/Sphereon-Opensource/SSI-SDK/commit/45cea1182693b698611b062a9d664ad92e8dcd6a)) +- Add default DID resolver support ([eebce18](https://github.com/Sphereon-Opensource/SSI-SDK/commit/eebce18bf9cc9d28a8bcdd6886100b7a8921bb2f)) +- Add did resolver and method support per OpSession ([9378b45](https://github.com/Sphereon-Opensource/SSI-SDK/commit/9378b451d4907c8d5385f464b27f858547409bb4)) +- Add did resolver and method support per OpSession ([a9f7afc](https://github.com/Sphereon-Opensource/SSI-SDK/commit/a9f7afc386189ca4851ce967f5abf7db812d1003)) +- Add supported DID methods ([df74ccd](https://github.com/Sphereon-Opensource/SSI-SDK/commit/df74ccddcab06a032ca47a033a46bd0268826f72)) +- Add supported DID methods ([7322265](https://github.com/Sphereon-Opensource/SSI-SDK/commit/732226544503c2bcc32bf4400da82e9154361abb)) + +## [0.5.1](https://github.com/Sphereon-Opensource/SSI-SDK/compare/v0.5.0...v0.5.1) (2022-02-23) + +**Note:** Version bump only for package @sphereon/ssi-sdk-did-auth-siop-authenticator + +# [0.5.0](https://github.com/Sphereon-Opensource/SSI-SDK/compare/v0.4.0...v0.5.0) (2022-02-23) + +**Note:** Version bump only for package @sphereon/ssi-sdk-did-auth-siop-authenticator + +# [0.4.0](https://github.com/Sphereon-Opensource/SSI-SDK/compare/v0.3.4...v0.4.0) (2022-02-11) + +**Note:** Version bump only for package @sphereon/ssi-sdk-did-auth-siop-authenticator + +## [0.3.4](https://github.com/Sphereon-Opensource/SSI-SDK/compare/v0.3.3...v0.3.4) (2022-02-11) + +### Bug Fixes + +- fix imports ([738f4ca](https://github.com/Sphereon-Opensource/SSI-SDK/commit/738f4cafdf75c9d4831a3c31de1c0d5aff1d7285)) + +## [0.3.1](https://github.com/Sphereon-Opensource/SSI-SDK/compare/v0.3.0...v0.3.1) (2022-01-28) + +**Note:** Version bump only for package @sphereon/ssi-sdk-did-auth-siop-authenticator + +# [0.3.0](https://github.com/Sphereon-Opensource/SSI-SDK/compare/v0.2.0...v0.3.0) (2022-01-16) + +**Note:** Version bump only for package @sphereon/ssi-sdk-did-auth-siop-authenticator + +# [0.2.0](https://github.com/Sphereon-Opensource/SSI-SDK/compare/v0.1.0...v0.2.0) (2021-12-16) + +**Note:** Version bump only for package @sphereon/ssi-sdk-did-auth-siop-authenticator diff --git a/packages/siopv2-openid4vp-rp-rest/LICENSE b/packages/siopv2-openid4vp-rp-rest/LICENSE new file mode 100644 index 000000000..8aee96b74 --- /dev/null +++ b/packages/siopv2-openid4vp-rp-rest/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2021] [Sphereon BV] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/siopv2-openid4vp-rp-rest/README.md b/packages/siopv2-openid4vp-rp-rest/README.md new file mode 100644 index 000000000..ba5ca2eaf --- /dev/null +++ b/packages/siopv2-openid4vp-rp-rest/README.md @@ -0,0 +1,66 @@ + +

+
+ Sphereon +
SIOPv2 and OpenID4VP Relying Party REST endpoints +
+

+ +--- + +**Warning: This package still is in very early development. Breaking changes without notice will happen at this point!** + +--- + +An authentication plugin using the [Self Issued OpenID Provider v2 (SIOP)](https://github.com/Sphereon-Opensource/did-auth-siop) authentication library for having Relying Parties conforming to +the [Self Issued OpenID Provider v2 (SIOPv2)](https://openid.net/specs/openid-connect-self-issued-v2-1_0.html) +and [OpenID Connect for Verifiable Presentations (OIDC4VP)](https://openid.net/specs/openid-connect-4-verifiable-presentations-1_0.html) +as specified in the OpenID Connect working group. + +## Self Issued OpenID Provider v2 (SIOPv2) + +For more information about [Self Issued OpenID Provider v2 (SIOP)](https://github.com/Sphereon-Opensource/did-auth-siop#introduction), see the documentation in the readme. + +## Requirements + +For this plugin a DID resolver is also required. A DID resolver can be added to the agent as plugin as seen in the example below. + +## Available functions + + +## Usage + +### Adding the plugin to an agent: + +```typescript +import { IDidAuthSiopOpAuthenticator } from '@sphereon/ssi-sdk-did-auth-siop-authenticator' +import { Resolver } from 'did-resolver' +import { getDidKeyResolver } from '@veramo/did-provider-key' +import { DIDResolverPlugin } from '@veramo/did-resolver' +import { getUniResolver } from '@sphereon/did-uni-client' + +const agent = createAgent({ + plugins: [ + new DidAuthSiopOpAuthenticator(), + new DIDResolverPlugin({ + resolver: new Resolver({ + ...getDidKeyResolver(), + ...getUniResolver('web'), + ...getUniResolver('jwk'), + }), + }), + ], +}) +``` + +## Installation + +```shell +yarn add @sphereon/ssi-sdk-siopv2-openid4vp-rp +``` + +## Build + +```shell +yarn build +``` diff --git a/packages/siopv2-openid4vp-rp-rest/__tests__/localAgent.test.ts b/packages/siopv2-openid4vp-rp-rest/__tests__/localAgent.test.ts new file mode 100644 index 000000000..65f1f9aa3 --- /dev/null +++ b/packages/siopv2-openid4vp-rp-rest/__tests__/localAgent.test.ts @@ -0,0 +1,51 @@ +import * as fs from 'fs' +import { getConfig } from '@veramo/cli/build/setup' +import { createObjects } from '@veramo/cli/build/lib/objectCreator' +import didAuthSiopOpAuthenticatorAgentLogic from './shared/didAuthSiopOpAuthenticatorAgentLogic' +import { PresentationSignCallback } from '@sphereon/did-auth-siop' + +jest.setTimeout(30000) + +function getFile(path: string) { + return fs.readFileSync(path, 'utf-8') +} + +function getFileAsJson(path: string) { + return JSON.parse(getFile(path)) +} + +let agent: any + +const presentationSignCallback: PresentationSignCallback = async (args) => { + const presentationSignProof = getFileAsJson('./packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/psc/psc.json') + + return { + ...args.presentation, + ...presentationSignProof, + } +} + +const setup = async (): Promise => { + const config = getConfig('packages/siopv2-openid4vp-op-auth/agent.yml') + config.agent.$args[0].plugins[1].$args[0] = presentationSignCallback + const { localAgent } = createObjects(config, { localAgent: '/agent' }) + agent = localAgent + + return true +} + +const tearDown = async (): Promise => { + return true +} + +const getAgent = () => agent +const testContext = { + getAgent, + setup, + tearDown, + isRestTest: false, +} + +xdescribe('Local integration tests', () => { + didAuthSiopOpAuthenticatorAgentLogic(testContext) +}) diff --git a/packages/siopv2-openid4vp-rp-rest/__tests__/restAgent.test.ts b/packages/siopv2-openid4vp-rp-rest/__tests__/restAgent.test.ts new file mode 100644 index 000000000..24f9678d3 --- /dev/null +++ b/packages/siopv2-openid4vp-rp-rest/__tests__/restAgent.test.ts @@ -0,0 +1,102 @@ +import * as fs from 'fs' +import 'cross-fetch/polyfill' +// @ts-ignore +import express from 'express' +import { IAgent, createAgent, IAgentOptions, IDataStore } from '@veramo/core' +import { AgentRestClient } from '@veramo/remote-client' +import { Server } from 'http' +import { AgentRouter, RequestWithAgentRouter } from '@veramo/remote-server' +import { getConfig } from '@veramo/cli/build/setup' +import { createObjects } from '@veramo/cli/build/lib/objectCreator' +import { Siopv2RelyingParty, ISiopv2RelyingParty } from '../src' +import { Resolver } from 'did-resolver' +import { getDidKeyResolver } from '@veramo/did-provider-key' +import { DIDResolverPlugin } from '@veramo/did-resolver' +import { getUniResolver } from '@sphereon/did-uni-client' +import didAuthSiopOpAuthenticatorAgentLogic from './shared/didAuthSiopOpAuthenticatorAgentLogic' +import { PresentationSignCallback } from '@sphereon/did-auth-siop' + +jest.setTimeout(30000) + +function getFile(path: string) { + return fs.readFileSync(path, 'utf-8') +} + +function getFileAsJson(path: string) { + return JSON.parse(getFile(path)) +} + +const port = 3002 +const basePath = '/agent' +let serverAgent: IAgent +let restServer: Server + +const presentationSignCallback: PresentationSignCallback = async (args) => { + const presentationSignProof = getFileAsJson('./packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/psc/psc.json') + + return { + ...args.presentation, + ...presentationSignProof, + } +} + +const getAgent = (options?: IAgentOptions) => + createAgent({ + ...options, + plugins: [ + new Siopv2RelyingParty(presentationSignCallback), + new DIDResolverPlugin({ + resolver: new Resolver({ + ...getDidKeyResolver(), + ...getUniResolver('lto', { resolveUrl: 'https://uniresolver.test.sphereon.io/1.0/identifiers' }), + ...getUniResolver('factom', { resolveUrl: 'https://uniresolver.test.sphereon.io/1.0/identifiers' }), + }), + }), + new AgentRestClient({ + url: 'http://localhost:' + port + basePath, + enabledMethods: serverAgent.availableMethods(), + schema: serverAgent.getSchema(), + }), + ], + }) + +const setup = async (): Promise => { + const config = getConfig('packages/siopv2-openid4vp-op-auth/agent.yml') + config.agent.$args[0].plugins[1].$args[0] = presentationSignCallback + const { agent } = createObjects(config, { agent: '/agent' }) + agent.registerCustomApprovalForSiop({ key: 'success', customApproval: () => Promise.resolve() }) + agent.registerCustomApprovalForSiop({ key: 'failure', customApproval: () => Promise.reject(new Error('denied')) }) + serverAgent = agent + + const agentRouter = AgentRouter({ + exposedMethods: serverAgent.availableMethods(), + }) + + const requestWithAgent = RequestWithAgentRouter({ + agent: serverAgent, + }) + + return new Promise((resolve) => { + const app = express() + app.use(basePath, requestWithAgent, agentRouter) + restServer = app.listen(port, () => { + resolve(true) + }) + }) +} + +const tearDown = async (): Promise => { + restServer.close() + return true +} + +const testContext = { + getAgent, + setup, + tearDown, + isRestTest: true, +} + +xdescribe('REST integration tests', () => { + didAuthSiopOpAuthenticatorAgentLogic(testContext) +}) diff --git a/packages/siopv2-openid4vp-rp-rest/__tests__/shared/didAuthSiopOpAuthenticatorAgentLogic.ts b/packages/siopv2-openid4vp-rp-rest/__tests__/shared/didAuthSiopOpAuthenticatorAgentLogic.ts new file mode 100644 index 000000000..809bfbc8e --- /dev/null +++ b/packages/siopv2-openid4vp-rp-rest/__tests__/shared/didAuthSiopOpAuthenticatorAgentLogic.ts @@ -0,0 +1,470 @@ +import * as fs from 'fs' +import { IDataStore, TAgent, VerifiableCredential } from '@veramo/core' +import { IAuthRequestDetails, ISiopv2RelyingParty, IPresentationWithDefinition } from '../../src' +import { + AuthorizationRequest, + OP, + ParsedAuthorizationRequestURI, + PresentationDefinitionWithLocation, + ResponseContext, + ResponseMode, + ResponseType, + SubjectIdentifierType, + UrlEncodingFormat, + VerificationMode, + VerifiedAuthorizationRequest, +} from '@sphereon/did-auth-siop' +import { mapIdentifierKeysToDoc } from '@veramo/utils' +import { CredentialMapper } from '@sphereon/ssi-types' +import { mapIdentifierKeysToDocWithJwkSupport } from '@sphereon/ssi-sdk-did-utils' + +function getFile(path: string) { + return fs.readFileSync(path, 'utf-8') +} + +function getFileAsJson(path: string) { + return JSON.parse(getFile(path)) +} + +const nock = require('nock') +jest.mock('@veramo/utils', () => ({ + ...jest.requireActual('@veramo/utils'), + mapIdentifierKeysToDoc: jest.fn(), +})) + +jest.mock('@sphereon/ssi-sdk-did-utils', () => ({ + ...jest.requireActual('@sphereon/ssi-sdk-did-utils'), + mapIdentifierKeysToDocWithJwkSupport: jest.fn(), +})) + +type ConfiguredAgent = TAgent + +const didMethod = 'ethr' +const did = 'did:ethr:0xb9c5714089478a327f09197987f16f9e5d936e8a' +const identifier = { + did, + provider: '', + controllerKeyId: `${did}#controller`, + keys: [ + { + kid: `${did}#controller`, + kms: '', + type: 'Secp256k1' as const, + publicKeyHex: '1e21e21e...', + privateKeyHex: 'eqfcvnqwdnwqn...', + }, + ], + services: [], +} +const authKeys = [ + { + kid: `${did}#controller`, + kms: '', + type: 'Secp256k1', + publicKeyHex: '1e21e21e...', + privateKeyHex: 'eqfcvnqwdnwqn...', + meta: { + verificationMethod: { + id: `${did}#controller`, + type: 'EcdsaSecp256k1RecoveryMethod2020', + controller: did, + blockchainAccountId: '0xB9C5714089478a327F09197987f16f9E5d936E8a@eip155:1', + publicKeyHex: '1e21e21e...', + }, + }, + }, +] + +console.log(identifier) +const sessionId = 'sessionId' +const otherSessionId = 'other_sessionId' +const redirectUrl = 'http://example/ext/get-auth-request-url' +const stateId = '2hAyTM7PB3SGJaeGU7QeTJ' +const nonce = 'o5qwML7DnrcLMs9Vdizyz9' +const scope = 'openid' +const openIDURI = + 'openid://?response_type=id_token' + + '&scope=openid' + + '&client_id=' + + did + + '&redirect_uri=' + + redirectUrl + + '&iss=' + + did + + '&response_mode=post' + + '&response_context=rp' + + '&nonce=' + + nonce + + '&state=' + + stateId + + '®istration=registration_value' + + '&request=ey...' +const registration = { + did_methods_supported: [`did:${didMethod}:`], + subject_identifiers_supported: SubjectIdentifierType.DID, + credential_formats_supported: [], +} +const authorizationRequest: ParsedAuthorizationRequestURI = { + encodedUri: 'uri_example', + encodingFormat: UrlEncodingFormat.FORM_URL_ENCODED, + scheme: 'scheme2022122200', + requestObjectJwt: 'ey...', + authorizationRequestPayload: { + response_type: ResponseType.ID_TOKEN, + scope, + client_id: did, + redirect_uri: redirectUrl, + iss: did, + response_mode: ResponseMode.POST, + response_context: ResponseContext.RP, + nonce, + stateId, + registration, + request: 'ey...', + }, + registration, +} +const authorizationVerificationMockedResult = { + payload: {}, + verifyOpts: {}, +} + +const createAuthorizationResponseMockedResult = { + didResolutionResult: { + didResolutionMetadata: {}, + didDocument: { + id: did, + }, + didDocumentMetadata: {}, + }, + issuer: did, + signer: { + id: did, + type: 'authentication', + controller: did, + }, + jwt: 'ey...', + authorizationRequestPayload: { + scope, + response_type: ResponseType.ID_TOKEN, + client_id: did, + redirect_uri: redirectUrl, + response_mode: ResponseMode.POST, + response_context: ResponseContext.RP, + nonce: nonce, + }, + verifyOpts: { + verification: { + mode: VerificationMode.INTERNAL, + resolveOpts: {}, + }, + }, +} + +export default (testContext: { + getAgent: () => ConfiguredAgent + setup: () => Promise + tearDown: () => Promise + isRestTest: boolean +}) => { + describe('DID Auth SIOP OP Authenticator Agent Plugin', () => { + let agent: ConfiguredAgent + + beforeAll(async () => { + await testContext.setup() + agent = testContext.getAgent() + + const idCardCredential: VerifiableCredential = getFileAsJson( + './packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/vc/vc_idCardCredential.json' + ) + await agent.dataStoreSaveVerifiableCredential({ verifiableCredential: idCardCredential }) + + const driverLicenseCredential: VerifiableCredential = getFileAsJson( + './packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/vc/vc_driverLicense.json' + ) + await agent.dataStoreSaveVerifiableCredential({ verifiableCredential: driverLicenseCredential }) + + nock(redirectUrl).get(`?stateId=${stateId}`).times(5).reply(200, openIDURI) + + const mockedMapIdentifierKeysToDocMethod = mapIdentifierKeysToDoc as jest.Mock + mockedMapIdentifierKeysToDocMethod.mockReturnValue(Promise.resolve(authKeys)) + + const mockedMapIdentifierKeysToDocMethodWithJwkSupport = mapIdentifierKeysToDocWithJwkSupport as jest.Mock + mockedMapIdentifierKeysToDocMethodWithJwkSupport.mockReturnValue(Promise.resolve(authKeys)) + + const mockedparseAuthorizationRequestURIMethod = jest.fn() + OP.prototype.parseAuthorizationRequestURI = mockedparseAuthorizationRequestURIMethod + mockedparseAuthorizationRequestURIMethod.mockReturnValue(Promise.resolve(authorizationRequest)) + + const mockedverifyAuthorizationRequestMethod = jest.fn() + OP.prototype.verifyAuthorizationRequest = mockedverifyAuthorizationRequestMethod + mockedverifyAuthorizationRequestMethod.mockReturnValue(Promise.resolve(authorizationVerificationMockedResult)) + + const mockedcreateAuthorizationResponse = jest.fn() + OP.prototype.createAuthorizationResponse = mockedcreateAuthorizationResponse + mockedcreateAuthorizationResponse.mockReturnValue(Promise.resolve(createAuthorizationResponseMockedResult)) + + const mocksubmitAuthorizationResponseMethod = jest.fn() + OP.prototype.submitAuthorizationResponse = mocksubmitAuthorizationResponseMethod + mocksubmitAuthorizationResponseMethod.mockReturnValue(Promise.resolve({ status: 200, statusText: 'example_value' })) + + await agent.siopRegisterOPSession({ + sessionId, + requestJwtOrUri: openIDURI, + }) + }) + + afterAll(testContext.tearDown) + + it('should register OP session', async () => { + const sessionId = 'new_session_id' + const result = await agent.siopRegisterOPSession({ + sessionId, + requestJwtOrUri: openIDURI, + }) + + expect(result.id).toEqual(sessionId) + }) + + it('should remove OP session', async () => { + await agent.siopRegisterOPSession({ + sessionId: otherSessionId, + requestJwtOrUri: openIDURI, + }) + await agent.siopRemoveOPSession({ + sessionId: otherSessionId, + }) + + await expect( + agent.siopGetOPSession({ + sessionId: otherSessionId, + }) + ).rejects.toThrow(`No session found for id: ${otherSessionId}`) + }) + + if (!testContext.isRestTest) { + it('should register custom approval function', async () => { + await expect( + agent.siopRegisterOPCustomApproval({ + key: 'test_register', + customApproval: (verifiedAuthenticationRequest: VerifiedAuthorizationRequest) => Promise.resolve(), + }) + ).resolves.not.toThrow() + }) + + it('should remove custom approval function', async () => { + await agent.siopRegisterOPCustomApproval({ + key: 'test_delete', + customApproval: (verifiedAuthenticationRequest: VerifiedAuthorizationRequest) => Promise.resolve(), + }) + const result = await agent.siopRemoveOPCustomApproval({ + key: 'test_delete', + }) + + expect(result).toEqual(true) + }) + } + + it('should authenticate with DID SIOP without custom approval', async () => { + const result = await agent.authenticateWithSiop({ + sessionId, + stateId, + redirectUrl, + }) + + expect(result.status).toEqual(200) + }) + + it('should authenticate with DID SIOP with custom approval', async () => { + const result = await agent.authenticateWithSiop({ + sessionId, + stateId, + redirectUrl, + customApproval: testContext.isRestTest + ? 'success' + : (verifiedAuthenticationRequest: VerifiedAuthorizationRequest) => { + return Promise.resolve() + }, + }) + + expect(result.status).toEqual(200) + }) + + it('should not authenticate with DID SIOP with unknown custom approval key', async () => { + const customApprovalKey = 'some_random_key' + await expect( + agent.authenticateWithSiop({ + sessionId, + stateId, + redirectUrl, + customApproval: customApprovalKey, + }) + ).rejects.toThrow(`Custom approval not found for key: ${customApprovalKey}`) + }) + + it('should not authenticate with DID SIOP when custom approval fails', async () => { + const denied = 'denied' + await expect( + agent.authenticateWithSiop({ + sessionId, + stateId, + redirectUrl, + customApproval: testContext.isRestTest + ? 'failure' + : (verifiedAuthenticationRequest: VerifiedAuthorizationRequest) => { + return Promise.reject(new Error(denied)) + }, + }) + ).rejects.toThrow(denied) + }) + + it('should get authenticate request from RP', async () => { + const result = await agent.getSiopAuthorizationRequestFromRP({ + sessionId, + stateId, + redirectUrl, + }) + + expect(result).toEqual(authorizationRequest) + }) + + it('should get authentication details with single credential', async () => { + const pd_single: PresentationDefinitionWithLocation = getFileAsJson( + './packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/pd/pd_single.json' + ) + const vp_single: IPresentationWithDefinition = getFileAsJson( + './packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/vp/vp_single.json' + ) + const presentation = CredentialMapper.toWrappedVerifiablePresentation(vp_single.presentation) + presentation.presentation.presentation_submission!.id = expect.any(String) + + const result: IAuthRequestDetails = await agent.getSiopAuthorizationRequestDetails({ + sessionId, + verifiedAuthorizationRequest: { + ...createAuthorizationResponseMockedResult, + presentationDefinitions: [pd_single], + authorizationRequest: {} as AuthorizationRequest, + versions: [], + payload: {}, + }, + signingOptions: { + nonce: 'nonce202212272050', + domain: 'domain202212272051', + }, + }) + + expect(result).toMatchObject({ + id: 'did:ethr:0xb9c5714089478a327f09197987f16f9e5d936e8a', + verifiablePresentationMatches: [vp_single], + }) + }) + + it('should get authentication details with getting specific credentials', async () => { + const pdSingle: PresentationDefinitionWithLocation = getFileAsJson( + './packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/pd/pd_single.json' + ) + const vpSingle: IPresentationWithDefinition = getFileAsJson( + './packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/vp/vp_single.json' + ) + const presentation = CredentialMapper.toWrappedVerifiablePresentation(vpSingle.presentation) + presentation.presentation.presentation_submission!.id = expect.any(String) + + const result: IAuthRequestDetails = await agent.getSiopAuthorizationRequestDetails({ + sessionId, + verifiedAuthorizationRequest: { + ...createAuthorizationResponseMockedResult, + presentationDefinitions: [pdSingle], + authorizationRequest: {} as AuthorizationRequest, + versions: [], + payload: {}, + }, + credentialFilter: { + where: [ + { + column: 'id', + value: ['https://example.com/credentials/1872'], + }, + ], + }, + }) + + expect(result).toMatchObject({ + id: 'did:ethr:0xb9c5714089478a327f09197987f16f9e5d936e8a', + verifiablePresentationMatches: [vpSingle], + }) + }) + + it('should get authentication details with multiple credentials', async () => { + const pdMultiple: PresentationDefinitionWithLocation = getFileAsJson( + './packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/pd/pd_multiple.json' + ) + const vpMultiple: IPresentationWithDefinition = getFileAsJson( + './packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/vp/vp_multiple.json' + ) + const presentation = CredentialMapper.toWrappedVerifiablePresentation(vpMultiple.presentation) + presentation.presentation.presentation_submission!.id = expect.any(String) + + const result: IAuthRequestDetails = await agent.getSiopAuthorizationRequestDetails({ + sessionId, + verifiedAuthorizationRequest: { + ...createAuthorizationResponseMockedResult, + presentationDefinitions: [pdMultiple], + authorizationRequest: {} as AuthorizationRequest, + versions: [], + payload: {}, + }, + signingOptions: { + nonce: 'nonce202212272050', + domain: 'domain202212272051', + }, + }) + + expect(result).toMatchObject({ + id: 'did:ethr:0xb9c5714089478a327f09197987f16f9e5d936e8a', + verifiablePresentationMatches: [vpMultiple], + }) + }) + + it('should verify authentication request URI with did methods supported provided', async () => { + authorizationRequest.registration.did_methods_supported = [`did:${didMethod}:`] + + const result = await agent.verifySiopAuthorizationRequestURI({ + sessionId, + requestURI: authorizationRequest, + }) + + expect(result).toEqual(authorizationVerificationMockedResult) + }) + + it('should verify authentication request URI without did methods supported provided', async () => { + authorizationRequest.registration.did_methods_supported = [] + + const result = await agent.verifySiopAuthorizationRequestURI({ + sessionId, + requestURI: authorizationRequest, + }) + + expect(result).toEqual(authorizationVerificationMockedResult) + }) + + it('should send authentication response', async () => { + const pdMultiple: PresentationDefinitionWithLocation = getFileAsJson( + './packages/siopv2-openid4vp-op-auth/__tests__/vc_vp_examples/pd/pd_multiple.json' + ) + + const result = await agent.sendSiopAuthorizationResponse({ + sessionId, + verifiedAuthorizationRequest: { + ...createAuthorizationResponseMockedResult, + presentationDefinitions: [pdMultiple], + authorizationRequest: {} as AuthorizationRequest, + versions: [], + authorizationRequestPayload: {}, + payload: {}, + }, + }) + + expect(result.status).toEqual(200) + }) + }) +} diff --git a/packages/siopv2-openid4vp-rp-rest/__tests__/vc_vp_examples/pd/pd_multiple.json b/packages/siopv2-openid4vp-rp-rest/__tests__/vc_vp_examples/pd/pd_multiple.json new file mode 100644 index 000000000..8c86517e8 --- /dev/null +++ b/packages/siopv2-openid4vp-rp-rest/__tests__/vc_vp_examples/pd/pd_multiple.json @@ -0,0 +1,30 @@ +{ + "definition": { + "id": "Credentials", + "input_descriptors": [ + { + "id": "ID Card Credential and Driver's License", + "schema": [ + { + "uri": "https://www.w3.org/2018/credentials/examples/v1/IDCardCredential" + }, + { + "uri": "https://www.w3.org/2018/credentials/examples/v1/DriversLicense" + } + ], + "constraints": { + "fields": [ + { + "path": ["$.issuer.id"], + "filter": { + "type": "string", + "pattern": "did:example:[issuer|ebfeb1f712ebc6f1c276e12ec21]" + } + } + ] + } + } + ] + }, + "location": "presentation_definition" +} diff --git a/packages/siopv2-openid4vp-rp-rest/__tests__/vc_vp_examples/pd/pd_single.json b/packages/siopv2-openid4vp-rp-rest/__tests__/vc_vp_examples/pd/pd_single.json new file mode 100644 index 000000000..81a68f376 --- /dev/null +++ b/packages/siopv2-openid4vp-rp-rest/__tests__/vc_vp_examples/pd/pd_single.json @@ -0,0 +1,27 @@ +{ + "definition": { + "id": "Credentials", + "input_descriptors": [ + { + "id": "ID Card Credential", + "schema": [ + { + "uri": "https://www.w3.org/2018/credentials/examples/v1/IDCardCredential" + } + ], + "constraints": { + "fields": [ + { + "path": ["$.issuer.id"], + "filter": { + "type": "string", + "pattern": "did:example:issuer" + } + } + ] + } + } + ] + }, + "location": "presentation_definition" +} diff --git a/packages/siopv2-openid4vp-rp-rest/__tests__/vc_vp_examples/psc/psc.json b/packages/siopv2-openid4vp-rp-rest/__tests__/vc_vp_examples/psc/psc.json new file mode 100644 index 000000000..9755bd33f --- /dev/null +++ b/packages/siopv2-openid4vp-rp-rest/__tests__/vc_vp_examples/psc/psc.json @@ -0,0 +1,11 @@ +{ + "proof": { + "type": "RsaSignature2018", + "created": "2018-09-14T21:19:10Z", + "proofPurpose": "authentication", + "verificationMethod": "did:example:ebfeb1f712ebc6f1c276e12ec21#keys-1", + "challenge": "1f44d55f-f161-4938-a659-f8026467f126", + "domain": "4jt78h47fh47", + "jws": "eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..kTCYt5XsITJX1CxPCT8yAV-TVIw5WEuts01mq-pQy7UJiN5mgREEMGlv50aqzpqh4Qq_PbChOMqsLfRoPsnsgxD-WUcX16dUOqV0G_zS245-kronKb78cPktb3rk-BuQy72IFLN25DYuNzVBAh4vGHSrQyHUGlcTwLtjPAnKb78" + } +} diff --git a/packages/siopv2-openid4vp-rp-rest/__tests__/vc_vp_examples/vc/vc_driverLicense.json b/packages/siopv2-openid4vp-rp-rest/__tests__/vc_vp_examples/vc/vc_driverLicense.json new file mode 100644 index 000000000..daa0f0f2b --- /dev/null +++ b/packages/siopv2-openid4vp-rp-rest/__tests__/vc_vp_examples/vc/vc_driverLicense.json @@ -0,0 +1,23 @@ +{ + "id": "https://example.com/credentials/1873", + "type": ["VerifiableCredential", "DriversLicense"], + "@context": ["https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1/DriversLicense"], + "issuer": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21" + }, + "issuanceDate": "2010-01-01T19:23:24Z", + "credentialSubject": { + "given_name": "John", + "family_name": "Doe", + "birthdate": "1975-01-05" + }, + "proof": { + "type": "RsaSignature2018", + "created": "2018-09-14T21:19:10Z", + "proofPurpose": "authentication", + "verificationMethod": "did:example:ebfeb1f712ebc6f1c276e12ec21#keys-1", + "challenge": "1f44d55f-f161-4938-a659-f8026467f126", + "domain": "4jt78h47fh47", + "jws": "eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..kTCYt5XsITJX1CxPCT8yAV-TVIw5WEuts01mq-pQy7UJiN5mgREEMGlv50aqzpqh4Qq_PbChOMqsLfRoPsnsgxD-WUcX16dUOqV0G_zS245-kronKb78cPktb3rk-BuQy72IFLN25DYuNzVBAh4vGHSrQyHUGlcTwLtjPAnKb78" + } +} diff --git a/packages/siopv2-openid4vp-rp-rest/__tests__/vc_vp_examples/vc/vc_idCardCredential.json b/packages/siopv2-openid4vp-rp-rest/__tests__/vc_vp_examples/vc/vc_idCardCredential.json new file mode 100644 index 000000000..01abcea0e --- /dev/null +++ b/packages/siopv2-openid4vp-rp-rest/__tests__/vc_vp_examples/vc/vc_idCardCredential.json @@ -0,0 +1,23 @@ +{ + "id": "https://example.com/credentials/1872", + "type": ["VerifiableCredential", "IDCardCredential"], + "@context": ["https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1/IDCardCredential"], + "issuer": { + "id": "did:example:issuer" + }, + "issuanceDate": "2010-01-01T19:23:24Z", + "credentialSubject": { + "given_name": "Fredrik", + "family_name": "Stremberg", + "birthdate": "1949-01-22" + }, + "proof": { + "type": "RsaSignature2018", + "created": "2018-09-14T21:19:10Z", + "proofPurpose": "authentication", + "verificationMethod": "did:example:ebfeb1f712ebc6f1c276e12ec21#keys-1", + "challenge": "1f44d55f-f161-4938-a659-f8026467f126", + "domain": "4jt78h47fh47", + "jws": "eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..kTCYt5XsITJX1CxPCT8yAV-TVIw5WEuts01mq-pQy7UJiN5mgREEMGlv50aqzpqh4Qq_PbChOMqsLfRoPsnsgxD-WUcX16dUOqV0G_zS245-kronKb78cPktb3rk-BuQy72IFLN25DYuNzVBAh4vGHSrQyHUGlcTwLtjPAnKb78" + } +} diff --git a/packages/siopv2-openid4vp-rp-rest/__tests__/vc_vp_examples/vp/vp_multiple.json b/packages/siopv2-openid4vp-rp-rest/__tests__/vc_vp_examples/vp/vp_multiple.json new file mode 100644 index 000000000..fdb562154 --- /dev/null +++ b/packages/siopv2-openid4vp-rp-rest/__tests__/vc_vp_examples/vp/vp_multiple.json @@ -0,0 +1,81 @@ +{ + "format": "ldp_vp", + "location": "authorization_response", + "presentation": { + "@context": ["https://www.w3.org/2018/credentials/v1", "https://identity.foundation/presentation-exchange/submission/v1"], + "presentation_submission": { + "definition_id": "Credentials", + "descriptor_map": [ + { + "format": "ldp_vc", + "id": "ID Card Credential and Driver's License", + "path": "$.verifiableCredential[0]" + }, + { + "format": "ldp_vc", + "id": "ID Card Credential and Driver's License", + "path": "$.verifiableCredential[1]" + } + ], + "id": "8oBenRGlNXd0Sp770bCb3" + }, + "proof": { + "type": "RsaSignature2018", + "created": "2018-09-14T21:19:10Z", + "proofPurpose": "authentication", + "verificationMethod": "did:example:ebfeb1f712ebc6f1c276e12ec21#keys-1", + "challenge": "1f44d55f-f161-4938-a659-f8026467f126", + "domain": "4jt78h47fh47", + "jws": "eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..kTCYt5XsITJX1CxPCT8yAV-TVIw5WEuts01mq-pQy7UJiN5mgREEMGlv50aqzpqh4Qq_PbChOMqsLfRoPsnsgxD-WUcX16dUOqV0G_zS245-kronKb78cPktb3rk-BuQy72IFLN25DYuNzVBAh4vGHSrQyHUGlcTwLtjPAnKb78" + }, + "type": ["VerifiablePresentation", "PresentationSubmission"], + "verifiableCredential": [ + { + "@context": ["https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1/IDCardCredential"], + "credentialSubject": { + "birthdate": "1949-01-22", + "family_name": "Stremberg", + "given_name": "Fredrik" + }, + "id": "https://example.com/credentials/1872", + "issuanceDate": "2010-01-01T19:23:24Z", + "issuer": { + "id": "did:example:issuer" + }, + "proof": { + "challenge": "1f44d55f-f161-4938-a659-f8026467f126", + "created": "2018-09-14T21:19:10Z", + "domain": "4jt78h47fh47", + "jws": "eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..kTCYt5XsITJX1CxPCT8yAV-TVIw5WEuts01mq-pQy7UJiN5mgREEMGlv50aqzpqh4Qq_PbChOMqsLfRoPsnsgxD-WUcX16dUOqV0G_zS245-kronKb78cPktb3rk-BuQy72IFLN25DYuNzVBAh4vGHSrQyHUGlcTwLtjPAnKb78", + "proofPurpose": "authentication", + "type": "RsaSignature2018", + "verificationMethod": "did:example:ebfeb1f712ebc6f1c276e12ec21#keys-1" + }, + "type": ["VerifiableCredential", "IDCardCredential"] + }, + { + "id": "https://example.com/credentials/1873", + "type": ["VerifiableCredential", "DriversLicense"], + "@context": ["https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1/DriversLicense"], + "issuer": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21" + }, + "issuanceDate": "2010-01-01T19:23:24Z", + "credentialSubject": { + "given_name": "John", + "family_name": "Doe", + "birthdate": "1975-01-05" + }, + "proof": { + "type": "RsaSignature2018", + "created": "2018-09-14T21:19:10Z", + "proofPurpose": "authentication", + "verificationMethod": "did:example:ebfeb1f712ebc6f1c276e12ec21#keys-1", + "challenge": "1f44d55f-f161-4938-a659-f8026467f126", + "domain": "4jt78h47fh47", + "jws": "eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..kTCYt5XsITJX1CxPCT8yAV-TVIw5WEuts01mq-pQy7UJiN5mgREEMGlv50aqzpqh4Qq_PbChOMqsLfRoPsnsgxD-WUcX16dUOqV0G_zS245-kronKb78cPktb3rk-BuQy72IFLN25DYuNzVBAh4vGHSrQyHUGlcTwLtjPAnKb78" + } + } + ] + } +} diff --git a/packages/siopv2-openid4vp-rp-rest/__tests__/vc_vp_examples/vp/vp_single.json b/packages/siopv2-openid4vp-rp-rest/__tests__/vc_vp_examples/vp/vp_single.json new file mode 100644 index 000000000..3ca91b205 --- /dev/null +++ b/packages/siopv2-openid4vp-rp-rest/__tests__/vc_vp_examples/vp/vp_single.json @@ -0,0 +1,53 @@ +{ + "format": "ldp_vp", + "location": "authorization_response", + "presentation": { + "@context": ["https://www.w3.org/2018/credentials/v1", "https://identity.foundation/presentation-exchange/submission/v1"], + "presentation_submission": { + "definition_id": "Credentials", + "descriptor_map": [ + { + "format": "ldp_vc", + "id": "ID Card Credential", + "path": "$.verifiableCredential[0]" + } + ], + "id": "8oBenRGlNXd0Sp770bCb3" + }, + "proof": { + "type": "RsaSignature2018", + "created": "2018-09-14T21:19:10Z", + "proofPurpose": "authentication", + "verificationMethod": "did:example:ebfeb1f712ebc6f1c276e12ec21#keys-1", + "challenge": "1f44d55f-f161-4938-a659-f8026467f126", + "domain": "4jt78h47fh47", + "jws": "eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..kTCYt5XsITJX1CxPCT8yAV-TVIw5WEuts01mq-pQy7UJiN5mgREEMGlv50aqzpqh4Qq_PbChOMqsLfRoPsnsgxD-WUcX16dUOqV0G_zS245-kronKb78cPktb3rk-BuQy72IFLN25DYuNzVBAh4vGHSrQyHUGlcTwLtjPAnKb78" + }, + "type": ["VerifiablePresentation", "PresentationSubmission"], + "verifiableCredential": [ + { + "@context": ["https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1/IDCardCredential"], + "credentialSubject": { + "birthdate": "1949-01-22", + "family_name": "Stremberg", + "given_name": "Fredrik" + }, + "id": "https://example.com/credentials/1872", + "issuanceDate": "2010-01-01T19:23:24Z", + "issuer": { + "id": "did:example:issuer" + }, + "proof": { + "challenge": "1f44d55f-f161-4938-a659-f8026467f126", + "created": "2018-09-14T21:19:10Z", + "domain": "4jt78h47fh47", + "jws": "eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..kTCYt5XsITJX1CxPCT8yAV-TVIw5WEuts01mq-pQy7UJiN5mgREEMGlv50aqzpqh4Qq_PbChOMqsLfRoPsnsgxD-WUcX16dUOqV0G_zS245-kronKb78cPktb3rk-BuQy72IFLN25DYuNzVBAh4vGHSrQyHUGlcTwLtjPAnKb78", + "proofPurpose": "authentication", + "type": "RsaSignature2018", + "verificationMethod": "did:example:ebfeb1f712ebc6f1c276e12ec21#keys-1" + }, + "type": ["VerifiableCredential", "IDCardCredential"] + } + ] + } +} diff --git a/packages/siopv2-openid4vp-rp-rest/agent.yml b/packages/siopv2-openid4vp-rp-rest/agent.yml new file mode 100644 index 000000000..f78f74ed5 --- /dev/null +++ b/packages/siopv2-openid4vp-rp-rest/agent.yml @@ -0,0 +1,128 @@ +version: 3.0 + +constants: + baseUrl: http://localhost:3335 + port: 3335 + # please use your own X25519 key, this is only an example + secretKey: 29739248cad1bd1a0fc4d9b75cd4d2990de535baf5caadfdf8d8f86664aa830c + methods: + - authenticateWithSiop + - getSiopAuthorizationRequestFromRP + - getSiopAuthorizationRequestDetails + - verifySiopAuthorizationRequestURI + - sendSiopAuthorizationResponse + +# Database +dbConnection: + $require: typeorm#DataSource + $args: + - type: sqlite + database: ':memory:' + synchronize: false + migrationsRun: true + migrations: + $require: '@veramo/data-store?t=object#migrations' + logging: false + entities: + $require: '@veramo/data-store?t=object#Entities' + +server: + baseUrl: + $ref: /constants/baseUrl + port: + $ref: /constants/port + use: + # CORS + - - $require: 'cors' + + # Add agent to the request object + - - $require: '@veramo/remote-server?t=function#RequestWithAgentRouter' + $args: + - agent: + $ref: /agent + + # API base path + - - /agent + - $require: '@veramo/remote-server?t=function#apiKeyAuth' + $args: + # Please configure your own API key. This is used when executing agent methods through ${baseUrl}/agent or ${baseUrl}/api-docs + - apiKey: test123 + - $require: '@veramo/remote-server?t=function#AgentRouter' + $args: + - exposedMethods: + $ref: /constants/methods + + # Open API schema + - - /open-api.json + - $require: '@veramo/remote-server?t=function#ApiSchemaRouter' + $args: + - basePath: :3335/agent + securityScheme: bearer + apiName: Agent + apiVersion: '1.0.0' + exposedMethods: + $ref: /constants/methods + + # Swagger docs + - - /api-docs + - $require: swagger-ui-express?t=object#serve + - $require: swagger-ui-express?t=function#setup + $args: + - null + - swaggerOptions: + url: '/open-api.json' + + # Execute during server initialization + init: + - $require: '@veramo/remote-server?t=function#createDefaultDid' + $args: + - agent: + $ref: /agent + baseUrl: + $ref: /constants/baseUrl + messagingServiceEndpoint: /messaging + +# DID resolvers +didResolver: + $require: '@veramo/did-resolver#DIDResolverPlugin' + $args: + - resolver: + $require: did-resolver#Resolver + $args: + - elem: + $ref: /universal-resolver + io: + $ref: /universal-resolver + ion: + $ref: /universal-resolver + sov: + $ref: /universal-resolver + ethr: + $ref: /ethr-did-resolver + +ethr-did-resolver: + $require: ethr-did-resolver?t=function&p=/ethr#getResolver + $args: + - infuraProjectId: 5ffc47f65c4042ce847ef66a3fa70d4c + +universal-resolver: + $require: '@veramo/did-resolver#UniversalResolver' + $args: + - url: https://dev.uniresolver.io/1.0/identifiers/ + +# Agent +agent: + $require: '@veramo/core#Agent' + $args: + - schemaValidation: false + plugins: + - $ref: /didResolver + - $require: ./packages/siopv2-openid4vp-op-auth/dist#DidAuthSiopOpAuthenticator + $args: + - presentationSignCallback: {} + - $require: '@veramo/data-store#DataStore' + $args: + - $ref: /dbConnection + - $require: '@veramo/data-store#DataStoreORM' + $args: + - $ref: /dbConnection diff --git a/packages/siopv2-openid4vp-rp-rest/api-extractor.json b/packages/siopv2-openid4vp-rp-rest/api-extractor.json new file mode 100644 index 000000000..94c2c6a9f --- /dev/null +++ b/packages/siopv2-openid4vp-rp-rest/api-extractor.json @@ -0,0 +1,3 @@ +{ + "extends": "../include/api-extractor-base.json" +} diff --git a/packages/siopv2-openid4vp-rp-rest/package.json b/packages/siopv2-openid4vp-rp-rest/package.json new file mode 100644 index 000000000..7cfef5bd3 --- /dev/null +++ b/packages/siopv2-openid4vp-rp-rest/package.json @@ -0,0 +1,77 @@ +{ + "name": "@sphereon/ssi-sdk-siopv2-openid4vp-rp-rest", + "version": "0.9.0", + "source": "src/index.ts", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc --build", + "build:clean": "tsc --build --clean && tsc --build" + }, + "dependencies": { + "@sphereon/did-auth-siop": "^0.3.0-unstable.30", + "@sphereon/pex": "2.0.0-unstable.13", + "@sphereon/ssi-sdk-core": "^0.9.0", + "@sphereon/ssi-sdk-did-utils": "^0.9.0", + "@sphereon/ssi-sdk-siopv2-openid4vp-common": "^0.9.0", + "@sphereon/ssi-sdk-siopv2-openid4vp-rp-auth": "^0.9.0", + "@sphereon/ssi-types": "^0.9.0", + "@sphereon/wellknown-dids-client": "^0.1.3", + "@types/uuid": "^9.0.1", + "@veramo/core": "4.2.0", + "@veramo/credential-w3c": "4.2.0", + "@veramo/kv-store": "file:.yalc/@veramo/kv-store", + "cross-fetch": "^3.1.5", + "dotenv-flow": "^3.2.0", + "body-parser": "^1.19.0", + "cookie-parser": "^1.4.5", + "cors": "^2.8.5", + "expiry-map": "^1.1.0", + "express": "^4.18.2", + "short-uuid": "^4.2.2", + + "uuid": "^8.3.2" + }, + "devDependencies": { + "@sphereon/did-uni-client": "^0.6.0", + "@veramo/cli": "4.2.0", + "@veramo/did-provider-key": "4.2.0", + "@veramo/did-resolver": "4.2.0", + "@veramo/utils": "4.2.0", + "@types/body-parser": "^1.19.2", + "@types/cookie-parser": "^1.4.3", + "@types/cors": "^2.8.13", + "@types/debug": "^4.1.7", + "@types/dotenv-flow": "^3.2.0", + "@types/express": "^4.17.13", + "@types/express-http-proxy": "^1.6.3", + "@types/node": "^18.15.0", + "did-resolver": "^4.1.0", + "nock": "^13.2.1" + }, + "files": [ + "dist/**/*", + "src/**/*", + "README.md", + "plugin.schema.json", + "LICENSE" + ], + "publishConfig": { + "access": "public" + }, + "repository": "git@github.com:Sphereon-Opensource/SSI-SDK.git", + "author": "Sphereon ", + "license": "Apache-2.0", + "keywords": [ + "Sphereon", + "SSI", + "Veramo", + "DID", + "SIOP", + "SIOPv2", + "OIDC4VP", + "Presentation Exchange", + "OpenID Connect", + "Authenticator" + ] +} diff --git a/packages/siopv2-openid4vp-rp-rest/src/agent/Siopv2RelyingPartyREST.ts b/packages/siopv2-openid4vp-rp-rest/src/agent/Siopv2RelyingPartyREST.ts new file mode 100644 index 000000000..87fa4d5c6 --- /dev/null +++ b/packages/siopv2-openid4vp-rp-rest/src/agent/Siopv2RelyingPartyREST.ts @@ -0,0 +1,244 @@ +// noinspection JSUnusedGlobalSymbols + +import * as dotenv from 'dotenv-flow' +import express, { Express, Response } from 'express' +import cookieParser from 'cookie-parser' +import uuid from 'short-uuid' +// import * as core from "express-serve-static-core"; +import { + AuthorizationRequestState, + AuthorizationRequestStateStatus, + AuthorizationResponseState, + AuthorizationResponseStateStatus, + PresentationDefinitionLocation, + RP, + VerifiedAuthorizationResponse, +} from '@sphereon/did-auth-siop' +import bodyParser from 'body-parser' +import { + AuthStatusResponse, + GenerateAuthRequestURIResponse, + uriWithBase, +} from '@sphereon/ssi-sdk-siopv2-openid4vp-common' +import { IRequiredContext } from '../types' + + +export class RestAPI { + public express: Express + private context: IRequiredContext + + + constructor(context: IRequiredContext) { + this.context = context + dotenv.config() + + this.express = express() + const port = process.env.PORT || 5000 + const secret = process.env.COOKIE_SIGNING_KEY + + this.express.use((req, res, next) => { + res.header('Access-Control-Allow-Origin', '*') + // Request methods you wish to allow + res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE') + + // Request headers you wish to allow + res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type') + + // Set to true if you need the website to include cookies in the requests sent + // to the API (e.g. in case you use sessions) + res.setHeader('Access-Control-Allow-Credentials', 'true') + next() + }) + // this.express.use(cors({ credentials: true })); + // this.express.use('/proxy', proxy('www.gssoogle.com')); + this.express.use(bodyParser.urlencoded({ extended: true })) + this.express.use(bodyParser.json()) + this.express.use(cookieParser(secret)) + this.express.listen(port as number, '0.0.0.0', () => console.log(`Listening on port ${port}`)) + this.registerWebAppEndpoints() + this.registerOpenID4VPEndpoints() + } + + private static sendErrorResponse(response: Response, statusCode: number, message: string) { + response.statusCode = statusCode + response.status(statusCode).send(message) + } + + + private agent() { + return this.context.agent + } + + private async rp(definitionId?: string): Promise { + if (definitionId && !this.agent().pexDefinitionExists(definitionId)) { + throw Error(`Definition ${definitionId} not found`) + } + return this.agent().siopRPInstance({ definitionId }).then(ins => { + return ins.get() + }) + } + + private registerWebAppEndpoints() { + this.express.get('/webapp/definitions/:definitionId/auth-request-uri', (request, response) => { + const definitionId = request.params.definitionId + const state: string = uuid.uuid() + const nonce: string = uuid.uuid() + const correlationId = state + const requestByReferenceURI = uriWithBase(`/ext/definitions/${definitionId}/auth-requests/${correlationId}`) + const redirectURI = uriWithBase(`/ext/definitions/${definitionId}/auth-responses/${correlationId}`) + + this.rp(definitionId).then(rp => rp.createAuthorizationRequestURI({ + correlationId, + nonce, + state, + requestByReferenceURI, + redirectURI, + // definitionId? + }).then(authRequest => { + const authRequestBody: GenerateAuthRequestURIResponse = { + correlationId, + definitionId, + authRequestURI: authRequest.encodedUri, + authStatusURI: `${uriWithBase('/webapp/auth-status')}`, + } + console.log(`Auth Request URI data: ${authRequestBody}`) + return response.send(authRequestBody) + }), + ).catch((e: Error) => { + console.error(e, e.stack) + return RestAPI.sendErrorResponse(response, 500, 'Could not create an authorization request URI: ' + e.message) + }) + }) + + this.express.post('/webapp/auth-status', async (request, response) => { + console.log('Received auth-status request...') + const correlationId: string = request.body.correlationId as string + const definitionId: string = request.body.definitionId as string + const rp = await this.rp(definitionId) + const requestState = await rp.sessionManager.getRequestStateByCorrelationId(correlationId, false) + if (!requestState || !definitionId) { + console.log(`No authentication request mapping could be found for the given URL. correlation: ${correlationId}, definitionId: ${definitionId}`) + response.statusCode = 404 + + const statusBody: AuthStatusResponse = { + status: requestState ? requestState.status : AuthorizationRequestStateStatus.ERROR, + error: 'No authentication request mapping could be found for the given URL.', + correlationId, + definitionId, + lastUpdated: requestState ? requestState.lastUpdated : Date.now(), + } + return response.send(statusBody) + } + + let responseState + if (requestState.status === 'sent') { + responseState = await rp.sessionManager.getResponseStateByCorrelationId(correlationId, false) + } + const overallState: AuthorizationRequestState | AuthorizationResponseState = responseState ?? requestState + + const statusBody: AuthStatusResponse = { + status: overallState.status, + ...(overallState.error ? { error: overallState.error?.message } : {}), + correlationId, + definitionId, + lastUpdated: overallState.lastUpdated, + ...(responseState && responseState.status === 'verified' ? { payload: await responseState.response.mergedPayloads() } : {}), + } + console.log(`Will send auth status: ${JSON.stringify(statusBody)}`) + if (overallState.status === AuthorizationRequestStateStatus.ERROR || overallState.status === AuthorizationResponseStateStatus.ERROR) { + response.statusCode = 500 + return response.send(statusBody) + } + response.statusCode = 200 + return response.send(statusBody) + }) + + this.express.delete('/webapp/definitions/:definitionId/auth-requests/:correlationId', async (request, response) => { + const correlationId: string = request.params.correlationId + const definitionId: string = request.params.definitionId + const rp = await this.rp(definitionId) + rp.sessionManager.deleteStateForCorrelationId(correlationId) + }, + ) + } + + private registerOpenID4VPEndpoints() { + this.express.get('/ext/definitions/:definitionId/auth-requests/:correlationId', async (request, response) => { + const correlationId = request.params.correlationId + const definitionId = request.params.definitionId + if (!correlationId || !definitionId) { + console.log('No authorization request could be found for the given url.') + return RestAPI.sendErrorResponse(response, 404, 'No authorization request could be found') + } + const rp = await this.rp(definitionId) + const requestState = await rp.sessionManager.getRequestStateByCorrelationId(correlationId, false) + if (!requestState) { + console.log('No authentication request could be found in registry. CorrelationID: ' + correlationId) + return RestAPI.sendErrorResponse(response, 404, `No authorization request could be found for ${correlationId}`) + } + const requestObject = await requestState.request?.requestObject?.toJwt() + console.log('JWT Request object:') + console.log(requestObject) + + let error = undefined + try { + response.statusCode = 200 + return response.send(requestObject) + } catch (e) { + error = e + } finally { + if (rp) { + rp.signalAuthRequestRetrieved({ correlationId, error: error ? error as Error : undefined }) + } + } + }) + + this.express.post('/ext/definitions/:definitionId/auth-responses/:correlationId', async (request, response) => { + // const correlationId = request.params.correlationId + const definitionId = request.params.definitionId + console.log('Authorization Response (siop-sessions') + console.log(JSON.stringify(request.body, null, 2)) + const jwt = request.body + const definition = await this.agent().pexDefinitionGet(definitionId) + if (!definition) { + response.statusCode = 404 + response.statusMessage = `No definition ${definitionId}` + return response.send() + } + + const rp = await this.rp(definitionId) + + rp.verifyAuthorizationResponse(jwt, { + presentationDefinitions: [{ + location: PresentationDefinitionLocation.CLAIMS_VP_TOKEN, + definition, + }], + }) + .then((verifiedResponse: VerifiedAuthorizationResponse) => { + console.log('verifiedResponse: ', JSON.stringify(verifiedResponse, null, 2)) + + const wrappedPresentation = verifiedResponse?.oid4vpSubmission?.presentations[0] + if (wrappedPresentation) { + const credentialSubject = wrappedPresentation.presentation.verifiableCredential[0].credential.credentialSubject + console.log('AND WE ARE DONE!') + console.log(JSON.stringify(credentialSubject, null, 2)) + console.log(JSON.stringify(wrappedPresentation.presentation, null, 2)) + response.statusCode = 200 + // todo: delete session + } else { + response.statusCode = 500 + response.statusMessage = 'Missing Credentials' + } + return response.send() + }) + .catch(reason => { + console.error('verifyAuthenticationResponseJwt failed:', reason) + + }) + response.statusCode = 500 + response.statusMessage = 'Missing Credentials' + return response.send() + }, + ) + } +} diff --git a/packages/siopv2-openid4vp-rp-rest/src/index.ts b/packages/siopv2-openid4vp-rp-rest/src/index.ts new file mode 100644 index 000000000..a8abc5072 --- /dev/null +++ b/packages/siopv2-openid4vp-rp-rest/src/index.ts @@ -0,0 +1,7 @@ +/** + * @public + */ +const schema = require('../plugin.schema.json') +export { schema } +export * from './agent/Siopv2RelyingPartyREST' +export * from './types' diff --git a/packages/siopv2-openid4vp-rp-rest/src/types.ts b/packages/siopv2-openid4vp-rp-rest/src/types.ts new file mode 100644 index 000000000..5808f33af --- /dev/null +++ b/packages/siopv2-openid4vp-rp-rest/src/types.ts @@ -0,0 +1,12 @@ +import { + IAgentContext, + ICredentialIssuer, + ICredentialVerifier, + IDataStoreORM, + IDIDManager, + IKeyManager, + IResolver, +} from '@veramo/core' +import { ISiopv2RelyingParty } from '@sphereon/ssi-sdk-siopv2-openid4vp-rp-auth' + +export type IRequiredContext = IAgentContext diff --git a/packages/siopv2-openid4vp-rp-rest/tsconfig.json b/packages/siopv2-openid4vp-rp-rest/tsconfig.json new file mode 100644 index 000000000..b01aacd72 --- /dev/null +++ b/packages/siopv2-openid4vp-rp-rest/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig-base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "declarationDir": "dist", + "esModuleInterop": true + }, + "references": [{ "path": "../siopv2-openid4vp-common" }, { "path": "../siopv2-openid4vp-rp-auth" }, { "path": "../ssi-types" }, { "path": "../ssi-sdk-core" }, { "path": "../did-utils" }] +} diff --git a/packages/tsconfig.json b/packages/tsconfig.json index 4f7d49e49..618780e16 100644 --- a/packages/tsconfig.json +++ b/packages/tsconfig.json @@ -10,10 +10,14 @@ { "path": "ms-authenticator" }, { "path": "ms-request-api" }, { "path": "vc-handler-ld-local" }, - { "path": "did-auth-siop-op-authenticator" }, + { "path": "siopv2-openid4vp-common"}, + { "path": "siopv2-openid4vp-op-auth"}, + { "path": "siopv2-openid4vp-rp-auth"}, + { "path": "siopv2-openid4vp-rp-rest"}, { "path": "qr-code-generator" }, { "path": "contact-manager" }, { "path": "data-store" }, + { "path": "wellknown-did-issuer" }, { "path": "wellknown-did-verifier" }, { "path": "jwk-did-provider" } ] diff --git a/yarn.lock b/yarn.lock index 0670adbb5..a0a4d3181 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2389,6 +2389,39 @@ uint8arrays "^3.1.1" uuid "^9.0.0" +"@sphereon/did-auth-siop@^0.3.0-unstable.30": + version "0.3.0-unstable.30" + resolved "https://registry.yarnpkg.com/@sphereon/did-auth-siop/-/did-auth-siop-0.3.0-unstable.30.tgz#06bc51c919d4970aa4ba19ce6fb8f1ee0149c380" + integrity sha512-zD5vWtTBbiJt4Nb2HCgTYztZT6LU+fSIaDDOXbhVAkLtHhkOKYaadg1QqfuZ1Iwa0y7vA6iZf4giSVOARB1U/w== + dependencies: + "@sphereon/did-uni-client" "^0.6.0" + "@sphereon/pex" "^2.0.0-unstable.12" + "@sphereon/pex-models" "^1.2.2" + "@sphereon/ssi-types" "^0.9.0" + "@sphereon/wellknown-dids-client" "^0.1.3" + "@stablelib/ed25519" "^1.0.3" + "@stablelib/random" "^1.0.2" + "@stablelib/sha256" "^1.0.1" + "@stablelib/x25519" "^1.0.3" + "@stablelib/xchacha20poly1305" "^1.0.1" + bech32 "^2.0.0" + bs58 "^5.0.0" + canonicalize "^1.0.8" + cross-fetch "^3.1.5" + did-jwt "^6.11.2" + did-resolver "^4.1.0" + elliptic "^6.5.4" + eth-crypto "^2.5.0" + events "^3.3.0" + jose "^4.12.0" + js-sha3 "^0.8.0" + language-tags "^1.0.8" + multiformats "^11.0.1" + querystring "^0.2.1" + ts-interface-checker "^1.0.2" + uint8arrays "^3.1.1" + uuid "^9.0.0" + "@sphereon/did-uni-client@^0.4.0": version "0.4.0" resolved "https://registry.yarnpkg.com/@sphereon/did-uni-client/-/did-uni-client-0.4.0.tgz#1a4c4302c3ad6714e23aa07adf8154a81c1968dd" @@ -2472,6 +2505,20 @@ nanoid "^3.3.4" string.prototype.matchall "^4.0.8" +"@sphereon/pex@2.0.0-unstable.13": + version "2.0.0-unstable.13" + resolved "https://registry.yarnpkg.com/@sphereon/pex/-/pex-2.0.0-unstable.13.tgz#27e6315b4758ff0bf8c198ba45d5c95f2d6495b1" + integrity sha512-ZJPQK4xqL9urfYRUSZMHp/jb0j4VmrR89WG7gbKJb9St6KLL1Ew/jxARP6+vrMeo5lvLH5KkX/Wq4POK15vDyw== + dependencies: + "@sphereon/pex-models" "^1.2.2" + "@sphereon/ssi-types" "^0.9.0" + ajv "^8.12.0" + ajv-formats "^2.1.1" + jsonpath "^1.1.1" + jwt-decode "^3.1.2" + nanoid "^3.3.4" + string.prototype.matchall "^4.0.8" + "@sphereon/wellknown-dids-client@^0.1.3": version "0.1.3" resolved "https://registry.yarnpkg.com/@sphereon/wellknown-dids-client/-/wellknown-dids-client-0.1.3.tgz#4711599ed732903e9f45fe051660f925c9b508a4" @@ -3197,7 +3244,7 @@ dependencies: "@types/node" "*" -"@types/body-parser@*": +"@types/body-parser@*", "@types/body-parser@^1.19.2": version "1.19.2" resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== @@ -3212,6 +3259,20 @@ dependencies: "@types/node" "*" +"@types/cookie-parser@^1.4.3": + version "1.4.3" + resolved "https://registry.yarnpkg.com/@types/cookie-parser/-/cookie-parser-1.4.3.tgz#3a01df117c5705cf89a84c876b50c5a1fd427a21" + integrity sha512-CqSKwFwefj4PzZ5n/iwad/bow2hTCh0FlNAeWLtQM3JA/NX/iYagIpWG2cf1bQKQ2c9gU2log5VUCrn7LDOs0w== + dependencies: + "@types/express" "*" + +"@types/cors@^2.8.13": + version "2.8.13" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.13.tgz#b8ade22ba455a1b8cb3b5d3f35910fd204f84f94" + integrity sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA== + dependencies: + "@types/node" "*" + "@types/crypto-js@^3.1.43": version "3.1.47" resolved "https://registry.yarnpkg.com/@types/crypto-js/-/crypto-js-3.1.47.tgz#36e549dd3f1322742a3a738e7c113ebe48221860" @@ -3224,6 +3285,18 @@ dependencies: "@types/ms" "*" +"@types/dotenv-flow@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@types/dotenv-flow/-/dotenv-flow-3.2.0.tgz#3a632bcb72d5a6fa770da644a1f63d69a6c7075e" + integrity sha512-A79hbPwocbYkcTwGcDOFbKDuqyVo5mLAz/6Iq465YZ7R7Go5bT1PIM8I2jlPQkaD9u9fbotGVLkUPhX+9XUHfw== + +"@types/express-http-proxy@^1.6.3": + version "1.6.3" + resolved "https://registry.yarnpkg.com/@types/express-http-proxy/-/express-http-proxy-1.6.3.tgz#35fc0fb32e7741bc50619869de381ef759621fd0" + integrity sha512-dX3+Cb0HNPtqhC5JUWzzuODHRlgJRZx7KvwKVVwkOvm+8vOtpsh3qy8+qLv5X1hs4vdVHWKyXf86DwJot5H8pg== + dependencies: + "@types/express" "*" + "@types/express-serve-static-core@^4.17.33": version "4.17.33" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz#de35d30a9d637dc1450ad18dd583d75d5733d543" @@ -3240,7 +3313,7 @@ dependencies: "@types/express" "*" -"@types/express@*": +"@types/express@*", "@types/express@^4.17.13": version "4.17.17" resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.17.tgz#01d5437f6ef9cfa8668e616e13c2f2ac9a491ae4" integrity sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q== @@ -3343,11 +3416,21 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.0.tgz#286a65e3fdffd691e170541e6ecb0410b16a38be" integrity sha512-z6nr0TTEOBGkzLGmbypWOGnpSpSIBorEhC4L+4HeQ2iezKCi4f77kyslRwvHeNitymGQ+oFyIWGP96l/DPSV9w== +"@types/node@^16.18.0": + version "16.18.23" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.23.tgz#b6e934fe427eb7081d0015aad070acb3373c3c90" + integrity sha512-XAMpaw1s1+6zM+jn2tmw8MyaRDIJfXxqmIQIS0HfoGYPuf7dUWeiUKopwq13KFX9lEp1+THGtlaaYx39Nxr58g== + "@types/node@^16.18.14": version "16.18.14" resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.14.tgz#5465ce598486a703caddbefe8603f8a2cffa3461" integrity sha512-wvzClDGQXOCVNU4APPopC2KtMYukaF1MN/W3xAmslx22Z4/IF1/izDMekuyoUlwfnDHYCIZGaj7jMwnJKBTxKw== +"@types/node@^18.15.0": + version "18.15.11" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.11.tgz#b3b790f09cb1696cffcec605de025b088fa4225f" + integrity sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q== + "@types/normalize-package-data@^2.4.0": version "2.4.1" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" @@ -3720,6 +3803,16 @@ ws "^8.11.0" yaml "^2.1.3" +"@veramo/core-types@5.1.2": + version "5.1.2" + resolved "https://registry.yarnpkg.com/@veramo/core-types/-/core-types-5.1.2.tgz#c8e6771780ba8b32fc1a582ed012def807a3117c" + integrity sha512-IVmbcn9yBE8v/x29nGvScCT9sIKu/iYJ8+dqN3jIVL2t//HRvNhrHsxw83Wb6DGqr46r0ebM+kEnPfZAAxdk/w== + dependencies: + credential-status "^2.0.5" + debug "^4.3.3" + did-jwt-vc "^3.1.0" + did-resolver "^4.0.1" + "@veramo/core@4.2.0", "@veramo/core@^4.2.0": version "4.2.0" resolved "https://registry.yarnpkg.com/@veramo/core/-/core-4.2.0.tgz#68e9db2ccb25fd00695e61f3e7de728736127f7d" @@ -3926,6 +4019,28 @@ elliptic "^6.5.4" uint8arrays "^3.0.0" +"@veramo/kv-store@file:packages/siopv2-openid4vp-rp-auth/.yalc/@veramo/kv-store": + version "5.1.2" + dependencies: + "@veramo/core-types" "5.1.2" + "@veramo/utils" "5.1.2" + debug "^4.3.4" + events "^3.3.0" + json-buffer "^3.0.1" + typeorm "^0.3.10" + uint8arrays "^3.1.1" + +"@veramo/kv-store@file:packages/siopv2-openid4vp-rp-rest/.yalc/@veramo/kv-store": + version "5.1.2" + dependencies: + "@veramo/core-types" "5.1.2" + "@veramo/utils" "5.1.2" + debug "^4.3.4" + events "^3.3.0" + json-buffer "^3.0.1" + typeorm "^0.3.10" + uint8arrays "^3.1.1" + "@veramo/message-handler@4.2.0", "@veramo/message-handler@^4.2.0": version "4.2.0" resolved "https://registry.yarnpkg.com/@veramo/message-handler/-/message-handler-4.2.0.tgz#959273a61d00c05131fcecfd8d899cda72b966ce" @@ -3980,7 +4095,7 @@ debug "^4.3.3" url-parse "^1.5.4" -"@veramo/utils@4.2.0", "@veramo/utils@^4.2.0": +"@veramo/utils@4.2.0", "@veramo/utils@5.1.2", "@veramo/utils@^4.2.0": version "4.2.0" resolved "https://registry.yarnpkg.com/@veramo/utils/-/utils-4.2.0.tgz#43f5b90306aef7a3bb9dcee58eb03a6f4d573d5c" integrity sha512-jHkli0Qz9rFsWzPAdfJP3P2MFxvVMZPDXZvtVBm8x1fjAGrw/Htz/c5drhDAeBXnqPd9011/7cyvp6AOvdbc8Q== @@ -4246,6 +4361,11 @@ ansicolors@~0.3.2: resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" integrity sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg== +any-base@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/any-base/-/any-base-1.1.0.tgz#ae101a62bc08a597b4c9ab5b7089d456630549fe" + integrity sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg== + any-promise@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" @@ -4787,6 +4907,24 @@ body-parser@1.20.1: type-is "~1.6.18" unpipe "1.0.0" +body-parser@^1.19.0: + version "1.20.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" + integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== + dependencies: + bytes "3.1.2" + content-type "~1.0.5" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.11.0" + raw-body "2.5.2" + type-is "~1.6.18" + unpipe "1.0.0" + borc@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/borc/-/borc-2.1.2.tgz#6ce75e7da5ce711b963755117dd1b187f6f8cf19" @@ -5458,7 +5596,7 @@ content-disposition@0.5.4: dependencies: safe-buffer "5.2.1" -content-type@~1.0.4: +content-type@~1.0.4, content-type@~1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== @@ -5563,11 +5701,24 @@ convert-source-map@^2.0.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== +cookie-parser@^1.4.5: + version "1.4.6" + resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.6.tgz#3ac3a7d35a7a03bbc7e365073a26074824214594" + integrity sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA== + dependencies: + cookie "0.4.1" + cookie-signature "1.0.6" + cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== +cookie@0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" + integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== + cookie@0.4.2: version "0.4.2" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" @@ -6127,11 +6278,23 @@ dot-prop@^5.1.0: dependencies: is-obj "^2.0.0" +dotenv-flow@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/dotenv-flow/-/dotenv-flow-3.2.0.tgz#a5d79dd60ddb6843d457a4874aaf122cf659a8b7" + integrity sha512-GEB6RrR4AbqDJvNSFrYHqZ33IKKbzkvLYiD5eo4+9aFXr4Y4G+QaFrB/fNp0y6McWBmvaPn3ZNjIufnj8irCtg== + dependencies: + dotenv "^8.0.0" + dotenv@^16.0.0, dotenv@^16.0.3: version "16.0.3" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.3.tgz#115aec42bac5053db3c456db30cc243a5a836a07" integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ== +dotenv@^8.0.0: + version "8.6.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b" + integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g== + dotenv@~10.0.0: version "10.0.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" @@ -6904,6 +7067,13 @@ expect@^29.0.0, expect@^29.5.0: jest-message-util "^29.5.0" jest-util "^29.5.0" +expiry-map@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/expiry-map/-/expiry-map-1.1.0.tgz#a625630c32370e60b5866d5315b1ca28f3da2562" + integrity sha512-EviBh1pKXf8TuPIf88HS27Ti2biOZA1Ci4FeWhXzgY/tCBv05FA+7gO0jXt7AF5JgaiReAld1ag3jd5vzvKupw== + dependencies: + map-age-cleaner "^0.1.0" + express-handlebars@^6.0.2: version "6.0.7" resolved "https://registry.yarnpkg.com/express-handlebars/-/express-handlebars-6.0.7.tgz#f779254664eff0e250362ef1c2b30587059c212a" @@ -9442,6 +9612,11 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +json-buffer@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + json-canonicalize@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/json-canonicalize/-/json-canonicalize-1.0.4.tgz#efb2d0b07df12365e39028aa70f879237ec102ea" @@ -10252,6 +10427,13 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" +map-age-cleaner@^0.1.0: + version "0.1.3" + resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" + integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== + dependencies: + p-defer "^1.0.0" + map-obj@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" @@ -11469,6 +11651,11 @@ osenv@^0.1.4: os-homedir "^1.0.0" os-tmpdir "^1.0.0" +p-defer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" + integrity sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw== + p-each-series@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.2.0.tgz#105ab0357ce72b202a8a8b94933672657b5e2a9a" @@ -12265,6 +12452,16 @@ raw-body@2.5.1: iconv-lite "0.4.24" unpipe "1.0.0" +raw-body@2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" + integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + rc@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" @@ -12968,6 +13165,14 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +short-uuid@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/short-uuid/-/short-uuid-4.2.2.tgz#4bb3d926da04a4a5f34420d17b5551fd6d9d535c" + integrity sha512-IE7hDSGV2U/VZoCsjctKX6l5t5ak2jE0+aeGJi3KtvjIUNuZVmHVYUjNBhmo369FIWGDtaieRaO8A83Lvwfpqw== + dependencies: + any-base "^1.1.0" + uuid "^8.3.2" + side-channel@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"