Skip to content

Commit

Permalink
mdoc verification
Browse files Browse the repository at this point in the history
Signed-off-by: Timo Glastra <[email protected]>
  • Loading branch information
TimoGlastra committed Nov 1, 2024
1 parent 06baa03 commit 279abe4
Show file tree
Hide file tree
Showing 11 changed files with 330 additions and 309 deletions.
2 changes: 1 addition & 1 deletion agent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"@credo-ts/node": "https://gitpkg.vercel.app/animo/aries-framework-javascript/packages/node?funke",
"@credo-ts/openid4vc": "https://gitpkg.vercel.app/animo/aries-framework-javascript/packages/openid4vc?funke",
"@hyperledger/aries-askar-nodejs": "^0.2.3",
"@protokoll/mdoc-client": "^0.2.35",
"@protokoll/mdoc-client": "^0.2.36",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.18.2",
Expand Down
50 changes: 36 additions & 14 deletions agent/src/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import {
DifPresentationExchangeService,
JsonTransformer,
KeyType,
MdocVerifiablePresentation,
// Mdoc,
// MdocVerifiablePresentation,
MdocDeviceResponse,
RecordNotFoundError,
W3cJsonLdVerifiablePresentation,
W3cJwtVerifiablePresentation,
Expand All @@ -19,6 +17,7 @@ import { getIssuerIdForCredentialConfigurationId } from './issuer'
import { issuers } from './issuers'
import { getX509Certificate } from './keyMethods'
import { getVerifier } from './verifier'
import { allDefinitions, verifiers } from './verifiers'

const zCreateOfferRequest = z.object({
// FIXME: rename offeredCredentials to credentialSupportedIds in AFJ
Expand Down Expand Up @@ -132,8 +131,21 @@ apiRouter.post('/offers/receive', async (request: Request, response: Response) =
})
})

apiRouter.get('/verifier', async (_, response: Response) => {
return response.json({
presentationRequests: verifiers.flatMap((i) =>
i.presentationRequests.map((c) => {
return {
display: c.name,
id: c.id,
}
})
),
})
})

const zCreatePresentationRequestBody = z.object({
presentationDefinition: z.record(z.string(), z.any()),
presentationDefinitionId: z.string(),
requestScheme: z.string(),
responseMode: z.enum(['direct_post.jwt', 'direct_post']),
})
Expand All @@ -148,7 +160,13 @@ apiRouter.post('/requests/create', async (request: Request, response: Response)

const x509Certificate = getX509Certificate()

const definition = createPresentationRequestBody.presentationDefinition
const definitionId = createPresentationRequestBody.presentationDefinitionId
const definition = allDefinitions.find((d) => d.id === definitionId)
if (!definition) {
return response.status(404).json({
error: 'Definition not found',
})
}

const key = await agent.context.wallet.createKey({ keyType: KeyType.P256 })
const additionalPayloadClaims = { rp_eph_pub: getJwkFromKey(key).toJson() }
Expand All @@ -163,8 +181,7 @@ apiRouter.post('/requests/create', async (request: Request, response: Response)
issuer: `${AGENT_HOST}/siop/${verifier.verifierId}/authorize`,
},
presentationExchange: {
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
definition: definition as any,
definition,
},
additionalPayloadClaims,
responseMode: createPresentationRequestBody.responseMode,
Expand Down Expand Up @@ -209,14 +226,19 @@ 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 MdocDeviceResponse) {
return {
pretty: JsonTransformer.toJSON({}),
encoded: deviceSigned,
pretty: JsonTransformer.toJSON({
documents: presentation.documents.map((doc) => ({
doctype: doc.docType,
alg: doc.alg,
base64Url: doc.base64Url,
validityInfo: doc.validityInfo,
deviceSignedNamespaces: doc.deviceSignedNamespaces,
issuerSignedNamespaces: doc.issuerSignedNamespaces,
})),
}),
encoded: presentation.base64Url,
}
}

Expand Down
2 changes: 1 addition & 1 deletion agent/src/issuers/infrastruktur.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export const mobileDriversLicenseMdoc = {
cryptographic_binding_methods_supported: ['cose_key'],
cryptographic_suites_supported: [JwaSignatureAlgorithm.ES256],
id: 'mobile-drivers-license-mdoc',
doctype: 'eu.europa.ec.eudi.hiid.1',
doctype: 'org.iso.18013.5.1.mDL.1',
display: [mobileDriversLicenseDisplay],
} as const satisfies OpenId4VciCredentialSupportedWithId

Expand Down
69 changes: 69 additions & 0 deletions agent/src/verifiers/animo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import type { DifPresentationExchangeDefinitionV2 } from '@credo-ts/core'
import { pidMdocInputDescriptor, pidSdJwtInputDescriptor } from './util'

export const animoVerifier = {
presentationRequests: [
{
id: '4db74328-9e94-49bb-97b7-bbfcb2d11a06',
name: 'PID - Name and age verification (vc+sd-jwt)',
purpose: 'We need to verify your name and age',
input_descriptors: [
pidSdJwtInputDescriptor({
fields: ['given_name', 'family_name', 'age_equal_or_over.21'],
}),
],
},
{
id: '1e5fe154-183c-4bf5-b2c8-caa2264f1c99',
name: 'PID - City verification (vc+sd-jwt)',
purpose: 'We need to verify your city',
input_descriptors: [
pidSdJwtInputDescriptor({
fields: ['place_of_birth.locality', 'adress.locality'],
}),
],
},
{
id: 'f64dc30a-bcd7-48e8-b065-2bc3c7fc9588',
name: 'PID - Age in year and birth family name verification (vc+sd-jwt)',
purpose: 'We need to verify your name and age',
input_descriptors: [
pidSdJwtInputDescriptor({
fields: ['age_in_years', 'birth_family_name'],
}),
],
},
{
id: '5db54e62-d19d-495a-9d1d-58fac1f89a4d',
name: 'PID - Name and age verification (mso_mdoc)',
purpose: 'We need to verify your name and age',
input_descriptors: [
pidMdocInputDescriptor({
fields: ['given_name', 'family_name', 'age_over_21'],
}),
],
},
{
id: '8e80930c-6110-407a-a415-04791be81a35',
name: 'PID - City verification (mso_mdoc)',
purpose: 'We need to verify your city',
input_descriptors: [
pidMdocInputDescriptor({
fields: ['birth_place', 'resident_city'],
}),
],
},
{
id: '7df77c25-01bb-47ac-8778-454cb1031fe5',
name: 'PID - Age in year and birth family name verification (mso_mdoc)',
purpose: 'We need to verify your name and age',
input_descriptors: [
pidMdocInputDescriptor({
fields: ['age_in_years', 'family_name_birth'],
}),
],
},
],
} as const satisfies {
presentationRequests: Array<DifPresentationExchangeDefinitionV2>
}
6 changes: 6 additions & 0 deletions agent/src/verifiers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { DifPresentationExchangeDefinitionV2 } from '@credo-ts/core'
import { animoVerifier } from './animo'
import { sixtVerifier } from './sixt'

export const verifiers = [animoVerifier, sixtVerifier]
export const allDefinitions = verifiers.flatMap((v): DifPresentationExchangeDefinitionV2[] => v.presentationRequests)
39 changes: 39 additions & 0 deletions agent/src/verifiers/sixt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { DifPresentationExchangeDefinitionV2 } from '@credo-ts/core'
import { mobileDriversLicenseMdoc, mobileDriversLicenseSdJwt } from '../issuers/infrastruktur'
import { mdocInputDescriptor, pidMdocInputDescriptor, pidSdJwtInputDescriptor, sdJwtInputDescriptor } from './util'

export const sixtVerifier = {
presentationRequests: [
{
id: '1ad8ea6e-ec51-4e14-b316-dd76a6275480',
name: 'PID and MDL - Rent a Car (vc+sd-jwt)',
purpose: 'To secure your car reservations and finalize the transaction, we require the following attributes',
input_descriptors: [
sdJwtInputDescriptor({
vcts: [mobileDriversLicenseSdJwt.vct],
fields: ['document_number', 'portrait', 'issue_date', 'expiry_date', 'issuing_country', 'issuing_authority'],
}),
pidSdJwtInputDescriptor({
fields: ['given_name', 'family_name', 'birthdate'],
}),
],
},
{
id: '479ada7f-fff1-4f4a-ba0b-f0e7a8dbab04',
name: 'PID and MDL - Rent a Car (vc+sd-jwt/mso_mdoc)',
purpose: 'To secure your car reservations and finalize the transaction, we require the following attributes',
input_descriptors: [
mdocInputDescriptor({
doctype: mobileDriversLicenseMdoc.doctype,
namespace: mobileDriversLicenseMdoc.doctype,
fields: ['document_number', 'issue_date', 'expiry_date', 'issuing_country', 'issuing_authority'],
}),
pidSdJwtInputDescriptor({
fields: ['given_name', 'family_name', 'birthdate'],
}),
],
},
],
} as const satisfies {
presentationRequests: Array<DifPresentationExchangeDefinitionV2>
}
92 changes: 92 additions & 0 deletions agent/src/verifiers/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import type { DifPresentationExchangeDefinitionV2 } from '@credo-ts/core'

export function sdJwtInputDescriptor({
vcts,
fields,
issuers,
}: {
vcts: string[]
fields: string[]
issuers?: string[]
}): DifPresentationExchangeDefinitionV2['input_descriptors'][number] {
return {
id: crypto.randomUUID(),
format: {
'vc+sd-jwt': {
'sd-jwt_alg_values': ['ES256'],
'kb-jwt_alg_values': ['ES256'],
},
},
constraints: {
limit_disclosure: 'required',
fields: [
...fields.map((field) => ({
path: [`$.${field}`],
})),
{
path: ['$.vct'],
filter: {
type: 'string',
enum: vcts,
},
},
issuers
? {
path: ['$.iss'],
filter: {
type: 'string',
enum: issuers,
},
}
: undefined,
].filter((f): f is Exclude<typeof f, undefined> => f !== undefined),
},
}
}

export function mdocInputDescriptor({
doctype,
namespace,
fields,
}: {
doctype: string
namespace: string
fields: string[]
}): DifPresentationExchangeDefinitionV2['input_descriptors'][number] {
return {
id: doctype,
format: {
mso_mdoc: {
alg: ['ES256'],
},
},
constraints: {
limit_disclosure: 'required',
fields: [
...fields.map((field) => ({
path: [`$['${namespace}']['${field}']`],
intent_to_retain: false,
})),
].filter((f): f is Exclude<typeof f, undefined> => f !== undefined),
},
}
}

export function pidMdocInputDescriptor({ fields }: { fields: string[] }) {
return mdocInputDescriptor({
fields,
doctype: 'eu.europa.ec.eudi.pid.1',
namespace: 'eu.europa.ec.eudi.pid.1',
})
}
export function pidSdJwtInputDescriptor({ fields }: { fields: string[] }) {
return sdJwtInputDescriptor({
fields,
vcts: ['https://example.bmi.bund.de/credential/pid/1.0', 'urn:eu.europa.ec.eudi:pid:1'],
issuers: [
'https://demo.pid-issuer.bundesdruckerei.de/c',
'https://demo.pid-issuer.bundesdruckerei.de/c1',
'https://demo.pid-issuer.bundesdruckerei.de/b1',
],
})
}
Loading

0 comments on commit 279abe4

Please sign in to comment.