From 88e8f579ed1962363b84c66f126aaebda0742d1e Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Tue, 26 Nov 2024 15:25:39 +0100 Subject: [PATCH] feat: single use credentials for BLE and DCQL Signed-off-by: Timo Glastra --- .../src/features/proximity/mdocProximity.ts | 21 ++++++++++++++----- .../FunkeCredentialNotificationScreen.tsx | 2 ++ packages/agent/src/batch.ts | 12 +++++------ packages/agent/src/invitation/handler.ts | 13 +++++++++++- 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/apps/easypid/src/features/proximity/mdocProximity.ts b/apps/easypid/src/features/proximity/mdocProximity.ts index 4d228e96..4123f8e0 100644 --- a/apps/easypid/src/features/proximity/mdocProximity.ts +++ b/apps/easypid/src/features/proximity/mdocProximity.ts @@ -12,6 +12,7 @@ import { import { TypedArrayEncoder } from '@credo-ts/core' import { getMdocContext } from '@credo-ts/core/build/modules/mdoc/MdocContext' import type { EasyPIDAppAgent, FormattedSubmission, MdocRecord } from '@package/agent' +import { handleBatchCredential } from '@package/agent/src/batch' import { type Permission, PermissionsAndroid, Platform } from 'react-native' const requireMdocDataTransfer = () => @@ -82,12 +83,22 @@ export const shareDeviceResponse = async (options: ShareDeviceResponseOptions) = throw new Error('Not all requirements are satisfied') } - const issuerSignedDocuments = options.submission.entries.map((e) => { - if (!e.isSatisfied) throw new Error(`Requirement for doctype ${e.inputDescriptorId} not satisfied`) + if (options.submission.entries.length > 1) { + throw new Error('Only one mdoc supported at the moment due to only being able to sign with one device key') + } + + const issuerSignedDocuments = await Promise.all( + options.submission.entries.map(async (e) => { + if (!e.isSatisfied) throw new Error(`Requirement for doctype ${e.inputDescriptorId} not satisfied`) + + const credential = e.credentials[0].credential.record as MdocRecord + + // Optionally handle batch issuance + const credentialRecord = await handleBatchCredential(options.agent, credential) - const credential = e.credentials[0].credential.record as MdocRecord - return parseIssuerSigned(TypedArrayEncoder.fromBase64(credential.base64Url), credential.getTags().docType) - }) + return parseIssuerSigned(TypedArrayEncoder.fromBase64(credentialRecord.base64Url), credential.getTags().docType) + }) + ) const mdoc = new MDoc(issuerSignedDocuments) diff --git a/apps/easypid/src/features/receive/FunkeCredentialNotificationScreen.tsx b/apps/easypid/src/features/receive/FunkeCredentialNotificationScreen.tsx index 589ceb32..e7194bf8 100644 --- a/apps/easypid/src/features/receive/FunkeCredentialNotificationScreen.tsx +++ b/apps/easypid/src/features/receive/FunkeCredentialNotificationScreen.tsx @@ -134,6 +134,8 @@ export function FunkeCredentialNotificationScreen() { credentialConfigurationIdsToRequest: [configurationId], accessToken: tokenResponse, clientId: resolvedAuthorizationRequest ? authorization.clientId : undefined, + // Always request batch for non pid credentials + requestBatch: true, }) const credentialRecord = credentialResponses[0].credential diff --git a/packages/agent/src/batch.ts b/packages/agent/src/batch.ts index b99e5e02..cf921916 100644 --- a/packages/agent/src/batch.ts +++ b/packages/agent/src/batch.ts @@ -9,10 +9,10 @@ import { updateCredential } from './storage' * * @todo: what if batch is gone? */ -export async function handleBatchCredential( +export async function handleBatchCredential( agent: EitherAgent, - credentialRecord: W3cCredentialRecord | SdJwtVcRecord | MdocRecord -) { + credentialRecord: CredentialRecord +): Promise { const batchMetadata = getBatchCredentialMetadata(credentialRecord) if (batchMetadata) { @@ -26,18 +26,18 @@ export async function handleBatchCredential( if (credentialRecord instanceof MdocRecord) { return new MdocRecord({ mdoc: Mdoc.fromBase64Url(batchCredential as string), - }) + }) as CredentialRecord } if (credentialRecord instanceof SdJwtVcRecord) { return new SdJwtVcRecord({ compactSdJwtVc: batchCredential as string, - }) + }) as CredentialRecord } if (credentialRecord instanceof W3cCredentialRecord) { return new W3cCredentialRecord({ tags: { expandedTypes: [] }, credential: decodeW3cCredential(batchCredential), - }) + }) as CredentialRecord } } } diff --git a/packages/agent/src/invitation/handler.ts b/packages/agent/src/invitation/handler.ts index 966d33ec..98978eac 100644 --- a/packages/agent/src/invitation/handler.ts +++ b/packages/agent/src/invitation/handler.ts @@ -578,7 +578,18 @@ export const shareProof = async ({ // TODO: support credential selection for DCQL const dcqlCredentials = resolvedRequest.queryResult - ? agent.modules.openId4VcHolder.selectCredentialsForDcqlRequest(resolvedRequest.queryResult) + ? Object.fromEntries( + await Promise.all( + Object.entries( + agent.modules.openId4VcHolder.selectCredentialsForDcqlRequest(resolvedRequest.queryResult) + ).map(async ([queryCredentialId, credential]) => { + // Optionally use a batch credential + const credentialRecord = await handleBatchCredential(agent, credential.credentialRecord) + + return [queryCredentialId, { ...credential, credentialRecord }] + }) + ) + ) : undefined try {