From b8cd1960a66a1c35bdcb63fd4d8debc81ab03d05 Mon Sep 17 00:00:00 2001 From: Timo Glastra <timo@animo.id> Date: Tue, 29 Oct 2024 12:04:18 +0530 Subject: [PATCH] feat: add custom eaas Signed-off-by: Timo Glastra <timo@animo.id> --- agent/src/endpoints.ts | 40 +++++++------- agent/src/issuer.ts | 60 +++++++++++++++++++-- agent/src/issuerMetadata.ts | 101 +++++++++++++++++++++++++----------- 3 files changed, 148 insertions(+), 53 deletions(-) diff --git a/agent/src/endpoints.ts b/agent/src/endpoints.ts index f1e8580..eb59c97 100644 --- a/agent/src/endpoints.ts +++ b/agent/src/endpoints.ts @@ -2,16 +2,15 @@ import { DifPresentationExchangeService, JsonTransformer, KeyType, + MdocVerifiablePresentation, // Mdoc, // MdocVerifiablePresentation, RecordNotFoundError, - TypedArrayEncoder, W3cJsonLdVerifiablePresentation, W3cJwtVerifiablePresentation, getJwkFromKey, } from '@credo-ts/core' import { OpenId4VcVerificationSessionState } from '@credo-ts/openid4vc' -import { Key as AskarKey, KeyAlgs } from '@hyperledger/aries-askar-nodejs' import express, { type NextFunction, type Request, type Response } from 'express' import z from 'zod' import { agent } from './agent' @@ -60,15 +59,16 @@ apiRouter.get('/issuer', async (_, response: Response) => { const issuer = await getIssuer() return response.json({ - credentialsSupported: issuer.credentialsSupported.map((c) => ({ - display: - c.format === 'vc+sd-jwt' - ? `${c.vct} (vc+sd-jwt)` - : c.format === 'mso_mdoc' - ? `${c.doctype} (mso_mdoc)` - : 'Unregistered format', - id: c.id, - })), + credentialsSupported: issuer.credentialsSupported.map((c) => { + const displayName = + c.display?.[0]?.name ?? + (c.format === 'vc+sd-jwt' ? c.vct : c.format === 'mso_mdoc' ? c.doctype : 'Unregistered format') + + return { + display: `${displayName} (${c.format})`, + id: c.id, + } + }), display: issuer.display, availableX509Certificates: [AGENT_HOST], }) @@ -208,16 +208,16 @@ apiRouter.get('/requests/:verificationSessionId', async (request, response) => { } } - // if (presentation instanceof MdocVerifiablePresentation) { - // const deviceSigned = JSON.parse(presentation.deviceSignedBase64Url).deviceSigned - // const disclosedClaims = await Mdoc.getDisclosedClaims(deviceSigned) - // console.log('disclosedClaims', JSON.stringify(disclosedClaims, null, 2)) + if (presentation instanceof MdocVerifiablePresentation) { + const deviceSigned = JSON.parse(presentation.deviceSignedBase64Url).deviceSigned + // const disclosedClaims = await Mdoc.getDisclosedClaims(deviceSigned) + // console.log('disclosedClaims', JSON.stringify(disclosedClaims, null, 2)) - // return { - // pretty: JsonTransformer.toJSON(disclosedClaims), - // encoded: deviceSigned, - // } - // } + return { + pretty: JsonTransformer.toJSON({}), + encoded: deviceSigned, + } + } return { pretty: { diff --git a/agent/src/issuer.ts b/agent/src/issuer.ts index 196d497..c783d2b 100644 --- a/agent/src/issuer.ts +++ b/agent/src/issuer.ts @@ -1,10 +1,16 @@ import type { KeyType } from '@credo-ts/core' -import type { OpenId4VciCredentialRequestToCredentialMapper, OpenId4VciSignMdocCredential } from '@credo-ts/openid4vc' +import type { + OpenId4VciCredentialRequestToCredentialMapper, + OpenId4VciSignMdocCredential, + OpenId4VciSignSdJwtCredential, +} from '@credo-ts/openid4vc' import { agent } from './agent' import { AGENT_HOST } from './constants' import { credentialsSupported, issuerDisplay, + mockEmployeeBadgeMdoc, + mockIdenticonAttendeeSdJwt, mockPidOpenId4VcPlaygroundCredentialMsoMdocJwk, mockPidOpenId4VcPlaygroundCredentialSdJwtVcJwk, } from './issuerMetadata' @@ -46,7 +52,7 @@ export const credentialRequestToCredentialMapper: OpenId4VciCredentialRequestToC // for the key binding holderBinding, credentialConfigurationIds, -}) => { +}): Promise<OpenId4VciSignMdocCredential | OpenId4VciSignSdJwtCredential> => { const credentialConfigurationId = credentialConfigurationIds[0] const x509Certificate = getX509Certificate() @@ -112,7 +118,29 @@ export const credentialRequestToCredentialMapper: OpenId4VciCredentialRequestToC ], age_equal_or_over: { _sd: ['12', '14', '16', '18', '21', '65'] }, }, - } + } as const satisfies OpenId4VciSignSdJwtCredential + } + + if (credentialConfigurationId === mockIdenticonAttendeeSdJwt.id) { + return { + credentialSupportedId: mockIdenticonAttendeeSdJwt.id, + format: 'vc+sd-jwt', + holder: holderBinding, + payload: { + vct: mockIdenticonAttendeeSdJwt.vct, + first_name: 'Erika', + last_name: 'Mustermann', + sponsorship_tier: 'Platinum', + }, + issuer: { + method: 'x5c', + x5c: [x509Certificate], + issuer: AGENT_HOST, + }, + disclosureFrame: { + _sd: ['first_name', 'last_name'], + }, + } as const } if (credentialConfigurationId === mockPidOpenId4VcPlaygroundCredentialMsoMdocJwk.id) { @@ -156,7 +184,31 @@ export const credentialRequestToCredentialMapper: OpenId4VciCredentialRequestToC validFrom: new Date('2024-08-12T14:49:42.124Z'), signed: new Date(), }, - } satisfies OpenId4VciSignMdocCredential + } as const satisfies OpenId4VciSignMdocCredential + } + + if (credentialConfigurationId === mockEmployeeBadgeMdoc.id) { + return { + credentialSupportedId: mockEmployeeBadgeMdoc.id, + format: 'mso_mdoc', + holderKey: holderBinding.key, + docType: mockEmployeeBadgeMdoc.doctype, + issuerCertificate: x509Certificate, + namespaces: { + [mockEmployeeBadgeMdoc.doctype]: { + is_admin: true, + last_name: 'Mustermann', + first_name: 'Erika', + department: 'Sales', + employee_id: '181888100', + }, + }, + validityInfo: { + validUntil: new Date('2025-08-26T14:49:42.124Z'), + validFrom: new Date('2024-08-12T14:49:42.124Z'), + signed: new Date(), + }, + } as const satisfies OpenId4VciSignMdocCredential } throw new Error(`Unsupported credential ${credentialConfigurationId}`) diff --git a/agent/src/issuerMetadata.ts b/agent/src/issuerMetadata.ts index a14cd6d..ed15cc3 100644 --- a/agent/src/issuerMetadata.ts +++ b/agent/src/issuerMetadata.ts @@ -1,67 +1,110 @@ -import { JwaSignatureAlgorithm } from '@credo-ts/core' +import { JwaSignatureAlgorithm } from "@credo-ts/core"; import { OpenId4VciCredentialFormatProfile, type OpenId4VciCredentialSupportedWithId, type OpenId4VciIssuerMetadataDisplay, -} from '@credo-ts/openid4vc' +} from "@credo-ts/openid4vc"; -const ANIMO_DARK_BACKGROUND = '#202223' -const WHITE = '#FFFFFF' +const ANIMO_DARK_BACKGROUND = "#202223"; +const WHITE = "#FFFFFF"; export const issuerDisplay = [ { background_color: ANIMO_DARK_BACKGROUND, - name: 'Animo + Funke OpenID4VC Playground', - locale: 'en', + name: "Animo + Funke OpenID4VC Playground", + locale: "en", logo: { - alt_text: 'Animo logo', - url: 'https://i.imgur.com/PUAIUed.jpeg', + alt_text: "Animo logo", + url: "https://i.imgur.com/PUAIUed.jpeg", }, text_color: WHITE, }, -] satisfies OpenId4VciIssuerMetadataDisplay[] +] satisfies OpenId4VciIssuerMetadataDisplay[]; + +export const mockIdenticonAttendeeSdJwt = { + format: "vc+sd-jwt", + id: "mockIdenticonAttendeeSdJwt", + vct: "https://funke.animo.id/IdenticonAttendee", + cryptographic_binding_methods_supported: ["jwk"], + cryptographic_suites_supported: [JwaSignatureAlgorithm.ES256], + proof_types_supported: { + jwt: { + proof_signing_alg_values_supported: [JwaSignatureAlgorithm.ES256], + }, + }, + display: [ + { + name: "Identicon Attendee", + background_image: { + url: "https://i.imgur.com/uiczYkL.png", + uri: "https://i.imgur.com/uiczYkL.png", + }, + text_color: WHITE, + }, + ], +} as const satisfies OpenId4VciCredentialSupportedWithId; + +export const mockEmployeeBadgeMdoc = { + format: "mso_mdoc", + doctype: "id.animo.employeebadge", + id: "mockEmployeeBadgeMdoc", + cryptographic_binding_methods_supported: ["cose_key"], + cryptographic_suites_supported: [JwaSignatureAlgorithm.ES256], + proof_types_supported: { + jwt: { + proof_signing_alg_values_supported: [JwaSignatureAlgorithm.ES256], + }, + }, + display: [ + { + name: "Animo Employee Badge", + background_color: ANIMO_DARK_BACKGROUND, + text_color: WHITE, + }, + ], +} as const satisfies OpenId4VciCredentialSupportedWithId; export const mockPidOpenId4VcPlaygroundCredentialSdJwtVcJwk = { - id: 'mockPidOpenId4VcPlaygroundSdJwtVcJwk', + id: "mockPidOpenId4VcPlaygroundSdJwtVcJwk", format: OpenId4VciCredentialFormatProfile.SdJwtVc, - vct: 'https://example.bmi.bund.de/credential/pid/1.0', - cryptographic_binding_methods_supported: ['jwk'], + vct: "https://example.bmi.bund.de/credential/pid/1.0", + cryptographic_binding_methods_supported: ["jwk"], cryptographic_suites_supported: [JwaSignatureAlgorithm.ES256], display: [ { - name: 'PID', - description: 'Mock PID issued by Animo', + name: "PID", + description: "Mock PID issued by Animo", background_color: ANIMO_DARK_BACKGROUND, - locale: 'en', + locale: "en", text_color: WHITE, }, ], -} as const satisfies OpenId4VciCredentialSupportedWithId +} as const satisfies OpenId4VciCredentialSupportedWithId; export const mockPidOpenId4VcPlaygroundCredentialMsoMdocJwk = { - id: 'mockPidOpenId4VcPlaygroundMsoMdocJwk', + id: "mockPidOpenId4VcPlaygroundMsoMdocJwk", format: OpenId4VciCredentialFormatProfile.MsoMdoc, - doctype: 'eu.europa.ec.eudi.pid.1', - cryptographic_binding_methods_supported: ['jwk'], + doctype: "eu.europa.ec.eudi.pid.1", + cryptographic_binding_methods_supported: ["jwk"], cryptographic_suites_supported: [JwaSignatureAlgorithm.ES256], display: [ { - name: 'PID', - description: 'Mock PID issued by Animo', + name: "PID", + description: "Mock PID issued by Animo", background_color: ANIMO_DARK_BACKGROUND, - locale: 'en', + locale: "en", text_color: WHITE, }, ], -} as const satisfies OpenId4VciCredentialSupportedWithId +} as const satisfies OpenId4VciCredentialSupportedWithId; export const credentialsSupported = [ - mockPidOpenId4VcPlaygroundCredentialSdJwtVcJwk, - mockPidOpenId4VcPlaygroundCredentialMsoMdocJwk, -] as const satisfies OpenId4VciCredentialSupportedWithId[] + mockEmployeeBadgeMdoc, + mockIdenticonAttendeeSdJwt +] as const satisfies OpenId4VciCredentialSupportedWithId[]; -type CredentialSupportedId = (typeof credentialsSupported)[number]['id'] +type CredentialSupportedId = (typeof credentialsSupported)[number]["id"]; export const credentialSupportedIds = credentialsSupported.map((s) => s.id) as [ CredentialSupportedId, - ...CredentialSupportedId[], -] + ...CredentialSupportedId[] +];