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[]
+];