From 8c1467865349b3899b27ba57faafbf3cd3c5f593 Mon Sep 17 00:00:00 2001 From: Martin Auer Date: Tue, 8 Oct 2024 15:21:31 +0200 Subject: [PATCH 1/4] feat: mdoc alpha Signed-off-by: Martin Auer --- demo-openid/src/Holder.ts | 3 + demo-openid/src/HolderInquirer.ts | 11 +- packages/core/package.json | 8 +- packages/core/src/agent/AgentModules.ts | 2 + packages/core/src/agent/BaseAgent.ts | 3 + .../src/crypto/webcrypto/CredoWebCrypto.ts | 6 + packages/core/src/index.ts | 1 + .../DifPresentationExchangeService.ts | 77 +++- .../models/DifPexCredentialsForRequest.ts | 9 +- .../dif-presentation-exchange/models/index.ts | 6 +- .../utils/credentialSelection.ts | 60 ++- .../utils/presentationsToCreate.ts | 24 +- .../utils/transform.ts | 4 + packages/core/src/modules/mdoc/Mdoc.ts | 121 +++++ packages/core/src/modules/mdoc/MdocApi.ts | 75 ++++ packages/core/src/modules/mdoc/MdocContext.ts | 136 ++++++ .../src/modules/mdoc/MdocDeviceResponse.ts | 193 ++++++++ packages/core/src/modules/mdoc/MdocError.ts | 3 + packages/core/src/modules/mdoc/MdocModule.ts | 32 ++ packages/core/src/modules/mdoc/MdocOptions.ts | 56 +++ packages/core/src/modules/mdoc/MdocService.ts | 78 ++++ .../mdoc/MdocVerifiablePresentation.ts | 3 + .../mdoc.deviceResponse.openid4vp.test.ts | 305 +++++++++++++ .../__tests__/mdoc.deviceResponse.test.ts | 82 ++++ .../modules/mdoc/__tests__/mdoc.fixtures.ts | 65 +++ .../mdoc/__tests__/mdoc.service.test.ts | 132 ++++++ packages/core/src/modules/mdoc/index.ts | 8 + .../src/modules/mdoc/repository/MdocRecord.ts | 58 +++ .../modules/mdoc/repository/MdocRepository.ts | 17 + .../core/src/modules/mdoc/repository/index.ts | 2 + ...fPresentationExchangeProofFormatService.ts | 3 + .../core/src/modules/vc/models/ClaimFormat.ts | 1 + .../core/src/modules/x509/X509Certificate.ts | 50 ++- .../src/modules/x509/X509ServiceOptions.ts | 1 + .../OpenId4vcSiopHolderService.ts | 11 + packages/openid4vc/src/shared/transform.ts | 6 + pnpm-lock.yaml | 415 ++++++++---------- 37 files changed, 1783 insertions(+), 284 deletions(-) create mode 100644 packages/core/src/modules/mdoc/Mdoc.ts create mode 100644 packages/core/src/modules/mdoc/MdocApi.ts create mode 100644 packages/core/src/modules/mdoc/MdocContext.ts create mode 100644 packages/core/src/modules/mdoc/MdocDeviceResponse.ts create mode 100644 packages/core/src/modules/mdoc/MdocError.ts create mode 100644 packages/core/src/modules/mdoc/MdocModule.ts create mode 100644 packages/core/src/modules/mdoc/MdocOptions.ts create mode 100644 packages/core/src/modules/mdoc/MdocService.ts create mode 100644 packages/core/src/modules/mdoc/MdocVerifiablePresentation.ts create mode 100644 packages/core/src/modules/mdoc/__tests__/mdoc.deviceResponse.openid4vp.test.ts create mode 100644 packages/core/src/modules/mdoc/__tests__/mdoc.deviceResponse.test.ts create mode 100644 packages/core/src/modules/mdoc/__tests__/mdoc.fixtures.ts create mode 100644 packages/core/src/modules/mdoc/__tests__/mdoc.service.test.ts create mode 100644 packages/core/src/modules/mdoc/index.ts create mode 100644 packages/core/src/modules/mdoc/repository/MdocRecord.ts create mode 100644 packages/core/src/modules/mdoc/repository/MdocRepository.ts create mode 100644 packages/core/src/modules/mdoc/repository/index.ts diff --git a/demo-openid/src/Holder.ts b/demo-openid/src/Holder.ts index d16cdc7498..09187f59ab 100644 --- a/demo-openid/src/Holder.ts +++ b/demo-openid/src/Holder.ts @@ -5,6 +5,7 @@ import { W3cJwtVerifiableCredential, W3cJsonLdVerifiableCredential, DifPresentationExchangeService, + Mdoc, } from '@credo-ts/core' import { OpenId4VcHolderModule } from '@credo-ts/openid4vc' import { ariesAskar } from '@hyperledger/aries-askar-nodejs' @@ -56,6 +57,8 @@ export class Holder extends BaseAgent> const credential = response.credential if (credential instanceof W3cJwtVerifiableCredential || credential instanceof W3cJsonLdVerifiableCredential) { return this.agent.w3cCredentials.storeCredential({ credential }) + } else if (credential instanceof Mdoc) { + return this.agent.mdoc.store(credential) } else { return this.agent.sdJwtVc.store(credential.compact) } diff --git a/demo-openid/src/HolderInquirer.ts b/demo-openid/src/HolderInquirer.ts index f346e951e1..71bec45319 100644 --- a/demo-openid/src/HolderInquirer.ts +++ b/demo-openid/src/HolderInquirer.ts @@ -1,7 +1,7 @@ -import type { SdJwtVcRecord, W3cCredentialRecord } from '@credo-ts/core' +import type { MdocRecord, SdJwtVcRecord, W3cCredentialRecord } from '@credo-ts/core' import type { OpenId4VcSiopResolvedAuthorizationRequest, OpenId4VciResolvedCredentialOffer } from '@credo-ts/openid4vc' -import { DifPresentationExchangeService } from '@credo-ts/core' +import { DifPresentationExchangeService, Mdoc } from '@credo-ts/core' import console, { clear } from 'console' import { textSync } from 'figlet' import { prompt } from 'inquirer' @@ -181,11 +181,16 @@ export class HolderInquirer extends BaseInquirer { } } - private printCredential = (credential: W3cCredentialRecord | SdJwtVcRecord) => { + private printCredential = (credential: W3cCredentialRecord | SdJwtVcRecord | MdocRecord) => { if (credential.type === 'W3cCredentialRecord') { console.log(greenText(`W3cCredentialRecord with claim format ${credential.credential.claimFormat}`, true)) console.log(JSON.stringify(credential.credential.jsonCredential, null, 2)) console.log('') + } else if (credential.type === 'MdocRecord') { + console.log(greenText(`MdocRecord`, true)) + const namespaces = Mdoc.fromBase64Url(credential.base64Url).namespaces + console.log(JSON.stringify(namespaces, null, 2)) + console.log('') } else { console.log(greenText(`SdJwtVcRecord`, true)) const prettyClaims = this.holder.agent.sdJwtVc.fromCompact(credential.compactSdJwtVc).prettyClaims diff --git a/packages/core/package.json b/packages/core/package.json index aae49a085a..5aa90b3675 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -29,11 +29,15 @@ "@digitalcredentials/jsonld-signatures": "^9.4.0", "@digitalcredentials/vc": "^6.0.1", "@multiformats/base-x": "^4.0.1", - "@noble/hashes": "^1.4.0", + "@noble/curves": "^1.6.0", + "@noble/hashes": "^1.5.0", "@peculiar/asn1-ecc": "^2.3.8", "@peculiar/asn1-schema": "^2.3.8", "@peculiar/asn1-x509": "^2.3.8", "@peculiar/x509": "^1.11.0", + "@protokoll/core": "0.2.26", + "@protokoll/crypto": "0.2.26", + "@protokoll/mdoc-client": "0.2.26", "@sd-jwt/core": "^0.7.0", "@sd-jwt/decode": "^0.7.0", "@sd-jwt/jwt-status-list": "^0.7.0", @@ -54,7 +58,7 @@ "did-resolver": "^4.1.0", "jsonpath": "^1.1.1", "lru_map": "^0.4.1", - "luxon": "^3.3.0", + "luxon": "^3.5.0", "make-error": "^1.3.6", "object-inspect": "^1.10.3", "query-string": "^7.0.1", diff --git a/packages/core/src/agent/AgentModules.ts b/packages/core/src/agent/AgentModules.ts index 5b0727a94b..0c1aaf0aaf 100644 --- a/packages/core/src/agent/AgentModules.ts +++ b/packages/core/src/agent/AgentModules.ts @@ -10,6 +10,7 @@ import { DidsModule } from '../modules/dids' import { DifPresentationExchangeModule } from '../modules/dif-presentation-exchange' import { DiscoverFeaturesModule } from '../modules/discover-features' import { GenericRecordsModule } from '../modules/generic-records' +import { MdocModule } from '../modules/mdoc/MdocModule' import { MessagePickupModule } from '../modules/message-pickup' import { OutOfBandModule } from '../modules/oob' import { ProofsModule } from '../modules/proofs' @@ -137,6 +138,7 @@ function getDefaultAgentModules() { pex: () => new DifPresentationExchangeModule(), sdJwtVc: () => new SdJwtVcModule(), x509: () => new X509Module(), + mod: () => new MdocModule(), } as const } diff --git a/packages/core/src/agent/BaseAgent.ts b/packages/core/src/agent/BaseAgent.ts index e982bf81c4..d7bcff0a0c 100644 --- a/packages/core/src/agent/BaseAgent.ts +++ b/packages/core/src/agent/BaseAgent.ts @@ -14,6 +14,7 @@ import { CredentialsApi } from '../modules/credentials' import { DidsApi } from '../modules/dids' import { DiscoverFeaturesApi } from '../modules/discover-features' import { GenericRecordsApi } from '../modules/generic-records' +import { MdocApi } from '../modules/mdoc' import { MessagePickupApi } from '../modules/message-pickup/MessagePickupApi' import { OutOfBandApi } from '../modules/oob' import { ProofsApi } from '../modules/proofs' @@ -53,6 +54,7 @@ export abstract class BaseAgent public readonly basicMessages: BasicMessagesApi + public readonly mdoc: MdocApi public readonly genericRecords: GenericRecordsApi public readonly discovery: DiscoverFeaturesApi public readonly dids: DidsApi @@ -111,6 +113,7 @@ export abstract class BaseAgent(array: T): T { return this.walletWebCrypto.generateRandomValues(array) } + + public digest(algorithm: string, data: ArrayBuffer): ArrayBuffer { + return Hasher.hash(new Uint8Array(data), algorithm) + } } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 1f31a02648..1b98fd78cd 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -64,6 +64,7 @@ export * from './modules/vc' export * from './modules/cache' export * from './modules/dif-presentation-exchange' export * from './modules/sd-jwt-vc' +export * from './modules/mdoc' export { JsonEncoder, JsonTransformer, diff --git a/packages/core/src/modules/dif-presentation-exchange/DifPresentationExchangeService.ts b/packages/core/src/modules/dif-presentation-exchange/DifPresentationExchangeService.ts index c6ac3d30b7..ee4dbafd26 100644 --- a/packages/core/src/modules/dif-presentation-exchange/DifPresentationExchangeService.ts +++ b/packages/core/src/modules/dif-presentation-exchange/DifPresentationExchangeService.ts @@ -22,13 +22,15 @@ import type { W3CVerifiablePresentation, } from '@sphereon/ssi-types' -import { PEVersion, PEX, Status } from '@sphereon/pex' +import { PEVersion, PEX, PresentationSubmissionLocation, Status } from '@sphereon/pex' import { injectable } from 'tsyringe' import { Hasher, getJwkFromKey } from '../../crypto' import { CredoError } from '../../error' import { JsonTransformer } from '../../utils' import { DidsApi, getKeyFromVerificationMethod } from '../dids' +import { Mdoc, MdocApi, MdocOpenId4VpSessionTranscriptOptions, MdocRecord } from '../mdoc' +import { MdocDeviceResponse } from '../mdoc/MdocDeviceResponse' import { SdJwtVcApi } from '../sd-jwt-vc' import { ClaimFormat, @@ -151,9 +153,10 @@ export class DifPresentationExchangeService { presentationSubmissionLocation?: DifPresentationExchangeSubmissionLocation challenge: string domain?: string + openid4vp?: MdocOpenId4VpSessionTranscriptOptions } ) { - const { presentationDefinition, domain, challenge } = options + const { presentationDefinition, domain, challenge, openid4vp } = options const presentationSubmissionLocation = options.presentationSubmissionLocation ?? DifPresentationExchangeSubmissionLocation.PRESENTATION @@ -172,11 +175,6 @@ export class DifPresentationExchangeService { presentationDefinition as DifPresentationExchangeDefinitionV1 ).input_descriptors.filter((inputDescriptor) => inputDescriptorIds.includes(inputDescriptor.id)) - // Get all the credentials for the presentation - const credentialsForPresentation = presentationToCreate.verifiableCredentials.map((c) => - getSphereonOriginalVerifiableCredential(c.credential) - ) - const presentationDefinitionForSubject: DifPresentationExchangeDefinition = { ...presentationDefinition, input_descriptors: inputDescriptorsForPresentation, @@ -185,6 +183,42 @@ export class DifPresentationExchangeService { submission_requirements: undefined, } + if (presentationToCreate.claimFormat === ClaimFormat.MsoMdoc) { + if (presentationToCreate.verifiableCredentials.length !== 1) { + throw new DifPresentationExchangeError( + 'Currently a Mdoc presentation can only be created from a single credential' + ) + } + const mdocRecord = presentationToCreate.verifiableCredentials[0].credential + if (!openid4vp) { + throw new DifPresentationExchangeError('Missing openid4vp options for creating MDOC presentation.') + } + + const { deviceResponseBase64Url, presentationSubmission } = await MdocDeviceResponse.openId4Vp(agentContext, { + mdocs: [Mdoc.fromBase64Url(mdocRecord.base64Url)], + presentationDefinition: presentationDefinition as DifPresentationExchangeDefinitionV2, + sessionTranscriptOptions: { + ...openid4vp, + }, + }) + + verifiablePresentationResultsWithFormat.push({ + verifiablePresentationResult: { + presentationSubmission: presentationSubmission, + verifiablePresentation: deviceResponseBase64Url, + presentationSubmissionLocation: PresentationSubmissionLocation.EXTERNAL, + }, + claimFormat: presentationToCreate.claimFormat, + }) + + continue + } + + // Get all the credentials for the presentation + const credentialsForPresentation = presentationToCreate.verifiableCredentials.map((c) => + getSphereonOriginalVerifiableCredential(c.credential) + ) + const verifiablePresentationResult = await this.pex.verifiablePresentationFrom( presentationDefinitionForSubject, credentialsForPresentation, @@ -564,10 +598,12 @@ export class DifPresentationExchangeService { private async queryCredentialForPresentationDefinition( agentContext: AgentContext, presentationDefinition: DifPresentationExchangeDefinition - ): Promise> { + ): Promise> { const w3cCredentialRepository = agentContext.dependencyManager.resolve(W3cCredentialRepository) const w3cQuery: Array> = [] const sdJwtVcQuery: Array> = [] + const mdocQuery: Array> = [] + const presentationDefinitionVersion = PEX.definitionVersionDiscovery(presentationDefinition) if (!presentationDefinitionVersion.version) { @@ -591,6 +627,9 @@ export class DifPresentationExchangeService { w3cQuery.push({ $or: [{ expandedTypes: [schema.uri] }, { contexts: [schema.uri] }, { types: [schema.uri] }], }) + mdocQuery.push({ + docType: inputDescriptor.id, + }) } } } else if (presentationDefinitionVersion.version === PEVersion.v2) { @@ -603,33 +642,33 @@ export class DifPresentationExchangeService { ) } - const allRecords: Array = [] + const allRecords: Array = [] // query the wallet ourselves first to avoid the need to query the pex library for all // credentials for every proof request const w3cCredentialRecords = w3cQuery.length > 0 - ? await w3cCredentialRepository.findByQuery(agentContext, { - $or: w3cQuery, - }) + ? await w3cCredentialRepository.findByQuery(agentContext, { $or: w3cQuery }) : await w3cCredentialRepository.getAll(agentContext) - allRecords.push(...w3cCredentialRecords) const sdJwtVcApi = this.getSdJwtVcApi(agentContext) const sdJwtVcRecords = - sdJwtVcQuery.length > 0 - ? await sdJwtVcApi.findAllByQuery({ - $or: sdJwtVcQuery, - }) - : await sdJwtVcApi.getAll() - + sdJwtVcQuery.length > 0 ? await sdJwtVcApi.findAllByQuery({ $or: sdJwtVcQuery }) : await sdJwtVcApi.getAll() allRecords.push(...sdJwtVcRecords) + const mdocApi = this.getMdocApi(agentContext) + const mdocRecords = mdocQuery.length > 0 ? await mdocApi.findAllByQuery({ $or: mdocQuery }) : await mdocApi.getAll() + allRecords.push(...mdocRecords) + return allRecords } private getSdJwtVcApi(agentContext: AgentContext) { return agentContext.dependencyManager.resolve(SdJwtVcApi) } + + private getMdocApi(agentContext: AgentContext) { + return agentContext.dependencyManager.resolve(MdocApi) + } } diff --git a/packages/core/src/modules/dif-presentation-exchange/models/DifPexCredentialsForRequest.ts b/packages/core/src/modules/dif-presentation-exchange/models/DifPexCredentialsForRequest.ts index 70dcf4bbfe..d1c4e17a27 100644 --- a/packages/core/src/modules/dif-presentation-exchange/models/DifPexCredentialsForRequest.ts +++ b/packages/core/src/modules/dif-presentation-exchange/models/DifPexCredentialsForRequest.ts @@ -1,5 +1,7 @@ +import type { MdocRecord } from '../../mdoc' import type { SdJwtVcRecord } from '../../sd-jwt-vc' import type { ClaimFormat, W3cCredentialRecord } from '../../vc' +import type { IssuerSignedItem } from '@protokoll/mdoc-client' export interface DifPexCredentialsForRequest { /** @@ -129,8 +131,13 @@ export type SubmissionEntryCredential = type: ClaimFormat.JwtVc | ClaimFormat.LdpVc credentialRecord: W3cCredentialRecord } + | { + type: ClaimFormat.MsoMdoc + credentialRecord: MdocRecord + disclosedPayload: Record + } /** * Mapping of selected credentials for an input descriptor */ -export type DifPexInputDescriptorToCredentials = Record> +export type DifPexInputDescriptorToCredentials = Record> diff --git a/packages/core/src/modules/dif-presentation-exchange/models/index.ts b/packages/core/src/modules/dif-presentation-exchange/models/index.ts index a94c88e6c9..5f78fa8aba 100644 --- a/packages/core/src/modules/dif-presentation-exchange/models/index.ts +++ b/packages/core/src/modules/dif-presentation-exchange/models/index.ts @@ -1,4 +1,6 @@ export * from './DifPexCredentialsForRequest' +import type { Mdoc } from '../../mdoc' +import type { MdocVerifiablePresentation } from '../../mdoc/MdocVerifiablePresentation' import type { SdJwtVc } from '../../sd-jwt-vc' import type { W3cVerifiableCredential, W3cVerifiablePresentation } from '../../vc' import type { PresentationDefinitionV1, PresentationDefinitionV2, PresentationSubmission } from '@sphereon/pex-models' @@ -13,5 +15,5 @@ export type DifPresentationExchangeSubmission = PresentationSubmission export { PresentationSubmissionLocation as DifPresentationExchangeSubmissionLocation } // TODO: we might want to move this to another place at some point -export type VerifiablePresentation = W3cVerifiablePresentation | SdJwtVc -export type VerifiableCredential = W3cVerifiableCredential | SdJwtVc +export type VerifiablePresentation = W3cVerifiablePresentation | SdJwtVc | MdocVerifiablePresentation +export type VerifiableCredential = W3cVerifiableCredential | SdJwtVc | Mdoc diff --git a/packages/core/src/modules/dif-presentation-exchange/utils/credentialSelection.ts b/packages/core/src/modules/dif-presentation-exchange/utils/credentialSelection.ts index 975062de18..4d53f127d6 100644 --- a/packages/core/src/modules/dif-presentation-exchange/utils/credentialSelection.ts +++ b/packages/core/src/modules/dif-presentation-exchange/utils/credentialSelection.ts @@ -8,12 +8,16 @@ import type { IPresentationDefinition, SelectResults, SubmissionRequirementMatch import type { InputDescriptorV1, InputDescriptorV2, SubmissionRequirement } from '@sphereon/pex-models' import { decodeSdJwtSync, getClaimsSync } from '@sd-jwt/decode' +import { SubmissionRequirementMatchType } from '@sphereon/pex/dist/main/lib/evaluation/core' import { Rules } from '@sphereon/pex-models' import { default as jp } from 'jsonpath' import { Hasher } from '../../../crypto' import { CredoError } from '../../../error' import { deepEquality } from '../../../utils' +import { MdocRecord } from '../../mdoc' +import { Mdoc } from '../../mdoc/Mdoc' +import { MdocDeviceResponse } from '../../mdoc/MdocDeviceResponse' import { SdJwtVcRecord } from '../../sd-jwt-vc' import { ClaimFormat, W3cCredentialRecord } from '../../vc' import { DifPresentationExchangeError } from '../DifPresentationExchangeError' @@ -24,13 +28,25 @@ export async function getCredentialsForRequest( // PEX instance with hasher defined pex: PEX, presentationDefinition: IPresentationDefinition, - credentialRecords: Array + credentialRecords: Array ): Promise { - const encodedCredentials = credentialRecords.map((c) => getSphereonOriginalVerifiableCredential(c)) - const selectResultsRaw = pex.selectFrom(presentationDefinition, encodedCredentials) + const encodedCredentials = credentialRecords + .filter((c) => c instanceof MdocRecord === false) + .map((c) => getSphereonOriginalVerifiableCredential(c as SdJwtVcRecord | W3cCredentialRecord)) - const selectResults = { + const { mdocPresentationDefinition, nonMdocPresentationDefinition } = + MdocDeviceResponse.partitionPresentationDefinition(presentationDefinition) + + const selectResultsRaw = pex.selectFrom(nonMdocPresentationDefinition, encodedCredentials) + + const selectResults: CredentialRecordSelectResults = { ...selectResultsRaw, + areRequiredCredentialsPresent: + mdocPresentationDefinition.input_descriptors.length >= 1 + ? 'warn' // we don't know yet wheater the required credentials are present + : nonMdocPresentationDefinition.input_descriptors.length >= 1 + ? selectResultsRaw.areRequiredCredentialsPresent + : 'error', // Map the encoded credential to their respective w3c credential record verifiableCredential: selectResultsRaw.verifiableCredential?.map((selectedEncoded): SubmissionEntryCredential => { const credentialRecordIndex = encodedCredentials.findIndex((encoded) => { @@ -79,6 +95,40 @@ export async function getCredentialsForRequest( }), } + const mdocRecords = credentialRecords.filter((c) => c instanceof MdocRecord) + for (const mdocInputDescriptor of mdocPresentationDefinition.input_descriptors) { + if (!selectResults.verifiableCredential) selectResults.verifiableCredential = [] + if (!selectResults.matches) selectResults.matches = [] + + const mdocRecordsMatchingId = mdocRecords.filter( + (mdocRecord) => mdocRecord.getTags().docType === mdocInputDescriptor.id + ) + const submissionRequirementMatch: SubmissionRequirementMatch = { + id: mdocInputDescriptor.id, + type: SubmissionRequirementMatchType.InputDescriptor, + name: mdocInputDescriptor.id, + rule: Rules.Pick, + vc_path: [], + } + + for (const mdocRecordMatchingId of mdocRecordsMatchingId) { + selectResults.verifiableCredential.push({ + type: ClaimFormat.MsoMdoc, + credentialRecord: mdocRecordMatchingId, + disclosedPayload: MdocDeviceResponse.limitDisclosureToInputDescriptor({ + mdoc: Mdoc.fromBase64Url(mdocRecordMatchingId.base64Url), + inputDescriptor: mdocInputDescriptor as InputDescriptorV2, + }), + }) + + submissionRequirementMatch.vc_path.push( + `$.verifiableCredential[${selectResults.verifiableCredential.length - 1}]` + ) + } + + if (submissionRequirementMatch.vc_path.length >= 1) selectResults.matches.push(submissionRequirementMatch) + } + const presentationSubmission: DifPexCredentialsForRequest = { requirements: [], areRequirementsSatisfied: false, @@ -104,7 +154,7 @@ export async function getCredentialsForRequest( 'Presentation Definition does not require any credentials. Optional credentials are not included in the presentation submission.' ) } - if (selectResultsRaw.areRequiredCredentialsPresent === 'error') { + if (selectResults.areRequiredCredentialsPresent === 'error') { return presentationSubmission } diff --git a/packages/core/src/modules/dif-presentation-exchange/utils/presentationsToCreate.ts b/packages/core/src/modules/dif-presentation-exchange/utils/presentationsToCreate.ts index 17c17e01dd..8af5e05a8f 100644 --- a/packages/core/src/modules/dif-presentation-exchange/utils/presentationsToCreate.ts +++ b/packages/core/src/modules/dif-presentation-exchange/utils/presentationsToCreate.ts @@ -1,6 +1,7 @@ import type { SdJwtVcRecord } from '../../sd-jwt-vc' import type { DifPexInputDescriptorToCredentials } from '../models' +import { MdocRecord } from '../../mdoc' import { W3cCredentialRecord, ClaimFormat } from '../../vc' // - the credentials included in the presentation @@ -35,7 +36,22 @@ export interface LdpVpPresentationToCreate { }> // multiple credentials supported for LDP VP } -export type PresentationToCreate = SdJwtVcPresentationToCreate | JwtVpPresentationToCreate | LdpVpPresentationToCreate +export interface MdocPresentationToCreate { + claimFormat: ClaimFormat.MsoMdoc + subjectIds: [] + verifiableCredentials: [ + { + credential: MdocRecord + inputDescriptorId: string + } + ] // only one credential supported for MDOC +} + +export type PresentationToCreate = + | SdJwtVcPresentationToCreate + | JwtVpPresentationToCreate + | LdpVpPresentationToCreate + | MdocPresentationToCreate // FIXME: we should extract supported format form top-level presentation definition, and input_descriptor as well // to make sure the presentation we are going to create is a presentation format supported by the verifier. @@ -71,6 +87,12 @@ export function getPresentationsToCreate(credentialsForInputDescriptor: DifPexIn verifiableCredentials: [{ credential, inputDescriptorId }], }) } + } else if (credential instanceof MdocRecord) { + presentationsToCreate.push({ + claimFormat: ClaimFormat.MsoMdoc, + verifiableCredentials: [{ inputDescriptorId, credential }], + subjectIds: [], + }) } else { // SD-JWT-VC always needs it's own presentation presentationsToCreate.push({ diff --git a/packages/core/src/modules/dif-presentation-exchange/utils/transform.ts b/packages/core/src/modules/dif-presentation-exchange/utils/transform.ts index 7748ec7d65..a92928e6f5 100644 --- a/packages/core/src/modules/dif-presentation-exchange/utils/transform.ts +++ b/packages/core/src/modules/dif-presentation-exchange/utils/transform.ts @@ -10,6 +10,7 @@ import type { import { CredoError } from '../../../error' import { JsonTransformer } from '../../../utils' +import { MdocVerifiablePresentation } from '../../mdoc/MdocVerifiablePresentation' import { SdJwtVcApi } from '../../sd-jwt-vc' import { W3cCredentialRecord, W3cJsonLdVerifiablePresentation, W3cJwtVerifiablePresentation } from '../../vc' @@ -31,6 +32,8 @@ export function getSphereonOriginalVerifiablePresentation( verifiablePresentation instanceof W3cJsonLdVerifiablePresentation ) { return verifiablePresentation.encoded as SphereonOriginalVerifiablePresentation + } else if (verifiablePresentation instanceof MdocVerifiablePresentation) { + throw new CredoError('Mdoc verifiable presentation is not yet supported by Sphereon.') } else { return verifiablePresentation.compact } @@ -49,6 +52,7 @@ export function getVerifiablePresentationFromEncoded( } else if (typeof encodedVerifiablePresentation === 'object' && '@context' in encodedVerifiablePresentation) { return JsonTransformer.fromJSON(encodedVerifiablePresentation, W3cJsonLdVerifiablePresentation) } else { + // TODO: WE NEED TO ADD SUPPORT FOR MDOC VERIFIABLE PRESENTATION throw new CredoError('Unsupported verifiable presentation format') } } diff --git a/packages/core/src/modules/mdoc/Mdoc.ts b/packages/core/src/modules/mdoc/Mdoc.ts new file mode 100644 index 0000000000..d78754f74c --- /dev/null +++ b/packages/core/src/modules/mdoc/Mdoc.ts @@ -0,0 +1,121 @@ +import type { MdocCreateOptions, MdocNameSpaces, MdocVerifyOptions } from './MdocOptions' +import type { AgentContext } from '../../agent' +import type { IssuerSignedDocument } from '@protokoll/mdoc-client' + +import { DeviceSignedDocument, Document, Verifier, cborEncode, parseIssuerSigned } from '@protokoll/mdoc-client' + +import { getJwkFromKey, JwaSignatureAlgorithm } from '../../crypto' +import { X509Certificate, X509ModuleConfig } from '../x509' + +import { TypedArrayEncoder } from './../../utils' +import { getMdocContext } from './MdocContext' +import { MdocError } from './MdocError' + +/** + * This class represents a IssuerSigned Mdoc Document, + * which are the actual credentials being issued to holders. + */ +export class Mdoc { + public base64Url: string + private constructor(private issuerSignedDocument: IssuerSignedDocument) { + const issuerSigned = issuerSignedDocument.prepare().get('issuerSigned') + this.base64Url = TypedArrayEncoder.toBase64URL(cborEncode(issuerSigned)) + } + + public static _interalFromIssuerSignedDocument(issuerSignedDocument: IssuerSignedDocument) { + return new Mdoc(issuerSignedDocument) + } + + public static fromBase64Url(mdocBase64Url: string, expectedDocType?: string): Mdoc { + const issuerSignedDocument = parseIssuerSigned(TypedArrayEncoder.fromBase64(mdocBase64Url), expectedDocType) + return new Mdoc(issuerSignedDocument) + } + + public get docType(): string { + return this.issuerSignedDocument.docType + } + + public get alg(): JwaSignatureAlgorithm { + const algName = this.issuerSignedDocument.issuerSigned.issuerAuth.algName + if (!algName) { + throw new MdocError('Cannot extract the signature algorithm from the mdoc.') + } + if (Object.values(JwaSignatureAlgorithm).includes(algName as JwaSignatureAlgorithm)) { + return algName as JwaSignatureAlgorithm + } + throw new MdocError(`Cannot parse mdoc. The signature algorithm '${algName}' is not supported.`) + } + + public get validityInfo() { + return this.issuerSignedDocument.issuerSigned.issuerAuth.decodedPayload.validityInfo + } + + public get deviceSignedNamespaces(): MdocNameSpaces { + if (this.issuerSignedDocument instanceof DeviceSignedDocument === false) { + throw new MdocError(`Cannot get 'device-namespaces from a IssuerSignedDocument. Must be a DeviceSignedDocument.`) + } + + return this.issuerSignedDocument.allDeviceSignedNamespaces + } + + public get issuerSignedNamespaces(): MdocNameSpaces { + return this.issuerSignedDocument.allIssuerSignedNamespaces + } + + public static async create(agentContext: AgentContext, options: MdocCreateOptions) { + const { docType, validityInfo, namespaces, holderPublicKey, issuerCertificate } = options + const mdocContext = getMdocContext(agentContext) + + const holderPublicJwk = await getJwkFromKey(holderPublicKey) + const document = new Document(docType, mdocContext) + .useDigestAlgorithm('SHA-256') + .addValidityInfo(validityInfo) + .addDeviceKeyInfo({ deviceKey: holderPublicJwk.toJson() }) + + for (const [namespace, namespaceRecord] of Object.entries(namespaces)) { + document.addIssuerNameSpace(namespace, namespaceRecord) + } + + const cert = X509Certificate.fromEncodedCertificate(issuerCertificate) + const issuerPrivateJwk = await getJwkFromKey(options.issuerKey ?? cert.publicKey) + const issuerSignedDocument = await document.sign( + { + issuerPrivateKey: issuerPrivateJwk.toJson(), + alg: issuerPrivateJwk.supportedSignatureAlgorithms[0] as 'ES256' | 'ES384' | 'ES512' | 'EdDSA', + issuerCertificate, + kid: cert.publicKey.fingerprint, + }, + mdocContext + ) + + return new Mdoc(issuerSignedDocument) + } + + public async verify(agentContext: AgentContext, options?: MdocVerifyOptions): Promise { + const trustedCerts = + options?.trustedCertificates ?? agentContext.dependencyManager.resolve(X509ModuleConfig).trustedCertificates + + if (!trustedCerts) { + throw new MdocError('No trusted certificates found. Cannot verify mdoc.') + } + + const mdocContext = getMdocContext(agentContext) + try { + const verifier = new Verifier() + await verifier.verifyIssuerSignature( + { + trustedCertificates: trustedCerts.map((cert) => X509Certificate.fromEncodedCertificate(cert).rawCertificate), + issuerAuth: this.issuerSignedDocument.issuerSigned.issuerAuth, + disableCertificateChainValidation: false, + now: options?.now, + }, + getMdocContext(agentContext) + ) + + await verifier.verifyData({ mdoc: this.issuerSignedDocument }, mdocContext) + return true + } catch (error) { + return false + } + } +} diff --git a/packages/core/src/modules/mdoc/MdocApi.ts b/packages/core/src/modules/mdoc/MdocApi.ts new file mode 100644 index 0000000000..2287ed3ac9 --- /dev/null +++ b/packages/core/src/modules/mdoc/MdocApi.ts @@ -0,0 +1,75 @@ +import type { MdocCreateOptions, MdocVerifyOptions } from './MdocOptions' +import type { MdocRecord } from './repository' +import type { Query, QueryOptions } from '../../storage/StorageService' + +import { AgentContext } from '../../agent' +import { injectable } from '../../plugins' + +import { Mdoc } from './Mdoc' +import { MdocService } from './MdocService' + +/** + * @public + */ +@injectable() +export class MdocApi { + private agentContext: AgentContext + private mdocService: MdocService + + public constructor(agentContext: AgentContext, mdocService: MdocService) { + this.agentContext = agentContext + this.mdocService = mdocService + } + + /** + * Create a new Mdoc, with a spcific doctype, namespace, and validity info. + * + * @param options {MdocCreateOptions} + * @returns {Promise} + */ + public async create(options: MdocCreateOptions) { + return await this.mdocService.createMdoc(this.agentContext, options) + } + + /** + * + * Verify an incoming mdoc. It will check whether everything is valid, but also returns parts of the validation. + * + * For example, you might still want to continue with a flow if not all the claims are included, but the signature is valid. + * + */ + public async verify(mdoc: Mdoc, options: MdocVerifyOptions) { + return await this.mdocService.verifyMdoc(this.agentContext, mdoc, options) + } + + /** + * Create a Mdoc class from a base64url encoded Mdoc Issuer-Signed structure + */ + public fromBase64Url(base64Url: string) { + return Mdoc.fromBase64Url(base64Url) + } + + public async store(issuerSigned: Mdoc) { + return await this.mdocService.store(this.agentContext, issuerSigned) + } + + public async getById(id: string): Promise { + return await this.mdocService.getById(this.agentContext, id) + } + + public async getAll(): Promise> { + return await this.mdocService.getAll(this.agentContext) + } + + public async findAllByQuery(query: Query, queryOptions?: QueryOptions): Promise> { + return await this.mdocService.findByQuery(this.agentContext, query, queryOptions) + } + + public async deleteById(id: string) { + return await this.mdocService.deleteById(this.agentContext, id) + } + + public async update(mdocRecord: MdocRecord) { + return await this.mdocService.update(this.agentContext, mdocRecord) + } +} diff --git a/packages/core/src/modules/mdoc/MdocContext.ts b/packages/core/src/modules/mdoc/MdocContext.ts new file mode 100644 index 0000000000..50271133ec --- /dev/null +++ b/packages/core/src/modules/mdoc/MdocContext.ts @@ -0,0 +1,136 @@ +import type { AgentContext } from '../../agent' +import type { JwkJson } from '../../crypto' +import type { MdocContext, X509Context } from '@protokoll/mdoc-client' + +import { p256 } from '@noble/curves/p256' +import { hkdf } from '@noble/hashes/hkdf' +import { sha256 } from '@noble/hashes/sha2' +import * as x509 from '@peculiar/x509' +import { exportJwk, importX509, verifyWithJwk } from '@protokoll/crypto' + +import { CredoWebCrypto, getJwkFromJson, Hasher } from '../../crypto' +import { Buffer, TypedArrayEncoder } from '../../utils' +import { X509Certificate, X509Service } from '../x509' + +export const getMdocContext = (agentContext: AgentContext): MdocContext => { + const crypto = new CredoWebCrypto(agentContext) + return { + crypto: { + digest: async (input) => { + const { bytes, digestAlgorithm } = input + return new Uint8Array(crypto.digest(digestAlgorithm, bytes)) + }, + random: (length) => { + return crypto.getRandomValues(new Uint8Array(length)) + }, + calculateEphemeralMacKeyJwk: async (input) => { + const { privateKey, publicKey, sessionTranscriptBytes } = input + const ikm = p256 + .getSharedSecret(TypedArrayEncoder.toHex(privateKey), TypedArrayEncoder.toHex(publicKey), true) + .slice(1) + const salt = Hasher.hash(sessionTranscriptBytes, 'sha-256') + const info = Buffer.from('EMacKey', 'utf-8') + const hk1 = hkdf(sha256, ikm, salt, info, 32) + + return { + key_ops: ['sign', 'verify'], + ext: true, + kty: 'oct', + k: TypedArrayEncoder.toBase64URL(hk1), + alg: 'HS256', + } + }, + }, + + cose: { + mac0: { + sign: async (input) => { + const { jwk, mac0 } = input + const { data } = mac0.getRawSigningData() + return await agentContext.wallet.sign({ + data: Buffer.from(data), + key: getJwkFromJson(jwk as JwkJson).key, + }) + }, + verify: async (input) => { + const { mac0, jwk, options } = input + const { data, signature, alg } = mac0.getRawVerificationData(options) + return await verifyWithJwk({ jwk, signature, data, alg }) + }, + }, + sign1: { + sign: async (input) => { + const { jwk, sign1 } = input + const { data } = sign1.getRawSigningData() + return await agentContext.wallet.sign({ + data: Buffer.from(data), + key: getJwkFromJson(jwk as JwkJson).key, + }) + }, + verify: async (input) => { + const { sign1, jwk, options } = input + const { data, signature, alg } = sign1.getRawVerificationData(options) + return await verifyWithJwk({ jwk, signature, data, alg, crypto }) + }, + }, + }, + + x509: { + getIssuerNameField: (input) => { + const { certificate, field } = input + const x509Certificate = X509Certificate.fromRawCertificate(certificate) + return x509Certificate.getIssuerNameField(field) + }, + getPublicKey: async (input) => { + const certificate = new x509.X509Certificate(input.certificate) + const key = await importX509({ + x509: certificate.toString(), + alg: input.alg, + extractable: true, + }) + + // TODO: Key length missmatch + //expected + //{ + //kty: 'EC', + //x: 'OFBq4YMKg4w5fTifsytwBuJf_7E7VhRPXiNm52S3q1E', + //y: 'EyIAXV8gyt5FcRsYHhz4ryz97rjL0uogxHO6jMZr3bg', + //crv: 'P-256' + //} + + //actual + //{ + //kty: 'EC', + //crv: 'P-256', + //x: 'OFBq4YMKg4w5fTifsytwBuJf_7E7VhRPXiNm52S3q1ETIgBdXyDK3kVxGxgeHPiv', + //y: 'LP3uuMvS6iDEc7qMxmvduNeBp_oWscK1x-3_1KKYDayIctdDcpXHi8HcbehAfVIK' + //} + + //const comp = X509Certificate.fromRawCertificate(input.certificate) + //const x = getJwkFromKey(comp.publicKey).toJson() + //// eslint-disable-next-line @typescript-eslint/no-unused-vars + //const { use, ...jwk } = x + //return jwk + + return (await exportJwk({ key })) as JwkJson + }, + + validateCertificateChain: async (input) => { + const certificateChain = input.x5chain.map((cert) => X509Certificate.fromRawCertificate(cert).toString('pem')) + const trustedCertificates = input.trustedCertificates.map((cert) => + X509Certificate.fromRawCertificate(cert).toString('pem') + ) as [string, ...string[]] + + await X509Service.validateCertificateChain(agentContext, { + certificateChain, + trustedCertificates, + }) + }, + getCertificateData: async (input) => { + const { certificate } = input + const x509Certificate = X509Certificate.fromRawCertificate(certificate) + return x509Certificate.getData(crypto) + }, + } satisfies X509Context, + } +} diff --git a/packages/core/src/modules/mdoc/MdocDeviceResponse.ts b/packages/core/src/modules/mdoc/MdocDeviceResponse.ts new file mode 100644 index 0000000000..6f16ae5fac --- /dev/null +++ b/packages/core/src/modules/mdoc/MdocDeviceResponse.ts @@ -0,0 +1,193 @@ +import type { MdocDeviceResponseOpenId4VpOptions, MdocDeviceResponseVerifyOptions } from './MdocOptions' +import type { AgentContext } from '../../agent' +import type { DifPresentationExchangeDefinition } from '../dif-presentation-exchange' +import type { PresentationDefinition } from '@protokoll/mdoc-client' +import type { InputDescriptorV2 } from '@sphereon/pex-models' + +import { + limitDisclosureToInputDescriptor as mdocLimitDisclosureToId, + COSEKey, + DeviceResponse, + MDoc, + parseIssuerSigned, + Verifier, + MDocStatus, +} from '@protokoll/mdoc-client' + +import { getJwkFromKey } from '../../crypto/jose/jwk/transform' +import { CredoError } from '../../error' +import { X509Certificate } from '../x509/X509Certificate' +import { X509ModuleConfig } from '../x509/X509ModuleConfig' + +import { TypedArrayEncoder } from './../../utils' +import { Mdoc } from './Mdoc' +import { getMdocContext } from './MdocContext' +import { MdocError } from './MdocError' + +export class MdocDeviceResponse { + public constructor() {} + + private static assertMdocInputDescriptor(inputDescriptor: InputDescriptorV2) { + if (!inputDescriptor.format || !inputDescriptor.format.mso_mdoc) { + throw new MdocError(`Input descriptor must contain 'mso_mdoc' format property`) + } + + if (!inputDescriptor.format.mso_mdoc.alg) { + throw new MdocError(`Input descriptor mso_mdoc must contain 'alg' property`) + } + + if (!inputDescriptor.constraints?.limit_disclosure || inputDescriptor.constraints.limit_disclosure !== 'required') { + throw new MdocError( + `Input descriptor must contain 'limit_disclosure' constraints property which is set to required` + ) + } + + if (!inputDescriptor.constraints?.fields?.every((field) => field.intent_to_retain !== undefined)) { + throw new MdocError(`Input descriptor must contain 'intent_to_retain' constraints property`) + } + + return { + ...inputDescriptor, + format: { + mso_mdoc: inputDescriptor.format.mso_mdoc, + }, + constraints: { + ...inputDescriptor.constraints, + limit_disclosure: 'required', + fields: (inputDescriptor.constraints.fields ?? []).map((field) => { + return { + ...field, + intent_to_retain: field.intent_to_retain ?? false, + } + }), + }, + } satisfies PresentationDefinition['input_descriptors'][number] + } + + public static partitionPresentationDefinition = (pd: DifPresentationExchangeDefinition) => { + const nonMdocPresentationDefinition: DifPresentationExchangeDefinition = { + ...pd, + input_descriptors: pd.input_descriptors.filter( + (id) => !Object.keys((id as InputDescriptorV2).format ?? {}).includes('mso_mdoc') + ), + } as DifPresentationExchangeDefinition + + const mdocPresentationDefinition = { + ...pd, + format: { mso_mdoc: pd.format?.mso_mdoc }, + input_descriptors: (pd.input_descriptors as InputDescriptorV2[]) + .filter((id) => Object.keys(id.format ?? {}).includes('mso_mdoc')) + .map(this.assertMdocInputDescriptor), + } + + return { mdocPresentationDefinition, nonMdocPresentationDefinition } + } + + private static createPresentationSubmission(input: { + id: string + presentationDefinition: { + id: string + input_descriptors: ReturnType[] + } + }) { + const { id, presentationDefinition } = input + if (presentationDefinition.input_descriptors.length !== 1) { + throw new MdocError('Currently Mdoc Presentation Submissions can only be created for a sigle input descriptor') + } + return { + id, + definition_id: presentationDefinition.id, + descriptor_map: [ + { + id: presentationDefinition.input_descriptors[0].id, + format: 'mso_mdoc', + path: '$', + }, + ], + } + } + + public static limitDisclosureToInputDescriptor(options: { inputDescriptor: InputDescriptorV2; mdoc: Mdoc }) { + const { mdoc } = options + + const inputDescriptor = this.assertMdocInputDescriptor(options.inputDescriptor) + const _mdoc = parseIssuerSigned(TypedArrayEncoder.fromBase64(mdoc.base64Url), mdoc.docType) + return mdocLimitDisclosureToId({ mdoc: _mdoc, inputDescriptor }) + } + + public static async openId4Vp(agentContext: AgentContext, options: MdocDeviceResponseOpenId4VpOptions) { + const { sessionTranscriptOptions } = options + const presentationDefinition = this.partitionPresentationDefinition( + options.presentationDefinition + ).mdocPresentationDefinition + + const issuerSignedDocuments = options.mdocs.map((mdoc) => + parseIssuerSigned(TypedArrayEncoder.fromBase64(mdoc.base64Url), mdoc.docType) + ) + const mdoc = new MDoc(issuerSignedDocuments) + + // TODO: we need to implement this differently. + // TODO: Multiple Mdocs can have different device keys. + const mso = mdoc.documents[0].issuerSigned.issuerAuth.decodedPayload + const deviceKeyInfo = mso.deviceKeyInfo + if (!deviceKeyInfo?.deviceKey) { + throw new CredoError('Device key info is missing') + } + + const publicDeviceJwk = COSEKey.import(deviceKeyInfo.deviceKey).toJWK() + + deviceKeyInfo.deviceKey + const deviceResponseBuilder = await DeviceResponse.from(mdoc) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .usingPresentationDefinition(presentationDefinition as any) + .usingSessionTranscriptForOID4VP(sessionTranscriptOptions) + .authenticateWithSignature(publicDeviceJwk, 'ES256') + + for (const [nameSpace, nameSpaceValue] of Object.entries(options.deviceNameSpaces ?? {})) { + deviceResponseBuilder.addDeviceNameSpace(nameSpace, nameSpaceValue) + } + + const deviceResponseMdoc = await deviceResponseBuilder.sign(getMdocContext(agentContext)) + + return { + deviceResponseBase64Url: TypedArrayEncoder.toBase64URL(deviceResponseMdoc.encode()), + presentationSubmission: MdocDeviceResponse.createPresentationSubmission({ + id: 'MdocPresentationSubmission ' + agentContext.wallet.generateNonce(), + presentationDefinition, + }), + } + } + + public static async verify(agentContext: AgentContext, options: MdocDeviceResponseVerifyOptions) { + const verifier = new Verifier() + const mdocContext = getMdocContext(agentContext) + + const trustedCerts = + options?.trustedCertificates ?? agentContext.dependencyManager.resolve(X509ModuleConfig).trustedCertificates + + if (!trustedCerts) { + throw new MdocError('No trusted certificates found. Cannot verify mdoc.') + } + + const result = await verifier.verifyDeviceResponse( + { + encodedDeviceResponse: TypedArrayEncoder.fromBase64(options.deviceResponse), + ephemeralReaderKey: options.verifierKey ? getJwkFromKey(options.verifierKey).toJson() : undefined, + encodedSessionTranscript: DeviceResponse.calculateSessionTranscriptForOID4VP(options.sessionTranscriptOptions), + trustedCertificates: trustedCerts.map((cert) => X509Certificate.fromEncodedCertificate(cert).rawCertificate), + now: options.now, + }, + mdocContext + ) + + if (result.documentErrors.length > 1) { + throw new MdocError('Device response verification failed.') + } + + if (result.status !== MDocStatus.OK) { + throw new MdocError('Device response verification failed. An unknown error occurred.') + } + + return result.documents.map((doc) => Mdoc._interalFromIssuerSignedDocument(doc)) + } +} diff --git a/packages/core/src/modules/mdoc/MdocError.ts b/packages/core/src/modules/mdoc/MdocError.ts new file mode 100644 index 0000000000..ff9c8e49ab --- /dev/null +++ b/packages/core/src/modules/mdoc/MdocError.ts @@ -0,0 +1,3 @@ +import { CredoError } from '../../error' + +export class MdocError extends CredoError {} diff --git a/packages/core/src/modules/mdoc/MdocModule.ts b/packages/core/src/modules/mdoc/MdocModule.ts new file mode 100644 index 0000000000..98b140581e --- /dev/null +++ b/packages/core/src/modules/mdoc/MdocModule.ts @@ -0,0 +1,32 @@ +import type { Module, DependencyManager } from '../../plugins' + +import { AgentConfig } from '../../agent/AgentConfig' + +import { MdocApi } from './MdocApi' +import { MdocService } from './MdocService' +import { MdocRepository } from './repository' + +/** + * @public + */ +export class MdocModule implements Module { + public readonly api = MdocApi + + /** + * Registers the dependencies of the mdoc module on the dependency manager. + */ + public register(dependencyManager: DependencyManager) { + // Warn about experimental module + dependencyManager + .resolve(AgentConfig) + .logger.warn( + "The 'Mdoc' module is experimental and could have unexpected breaking changes. When using this module, make sure to use strict versions for all @credo-ts packages." + ) + + // Services + dependencyManager.registerSingleton(MdocService) + + // Repositories + dependencyManager.registerSingleton(MdocRepository) + } +} diff --git a/packages/core/src/modules/mdoc/MdocOptions.ts b/packages/core/src/modules/mdoc/MdocOptions.ts new file mode 100644 index 0000000000..29767f6251 --- /dev/null +++ b/packages/core/src/modules/mdoc/MdocOptions.ts @@ -0,0 +1,56 @@ +import type { Mdoc } from './Mdoc' +import type { Key } from '../../crypto/Key' +import type { DifPresentationExchangeDefinitionV2 } from '../dif-presentation-exchange' +import type { ValidityInfo, MdocNameSpaces } from '@protokoll/mdoc-client' + +export type { MdocNameSpaces } from '@protokoll/mdoc-client' + +export type MdocVerifyOptions = { + trustedCertificates?: [string, ...string[]] + now?: Date +} + +export type MdocOpenId4VpSessionTranscriptOptions = { + responseUri: string + clientId: string + verifierGeneratedNonce: string + mdocGeneratedNonce: string +} + +export type MdocDeviceResponseOpenId4VpOptions = { + mdocs: [Mdoc, ...Mdoc[]] + presentationDefinition: DifPresentationExchangeDefinitionV2 + deviceNameSpaces?: MdocNameSpaces + sessionTranscriptOptions: MdocOpenId4VpSessionTranscriptOptions +} + +export type MdocDeviceResponseVerifyOptions = { + mdoc: Mdoc + trustedCertificates?: [string, ...string[]] + sessionTranscriptOptions: MdocOpenId4VpSessionTranscriptOptions + /** + * The base64Url-encoded device response string. + */ + deviceResponse: string + + /** + * The private part of the ephemeral key used in the session where the DeviceResponse was obtained. This is only required if the DeviceResponse is using the MAC method for device authentication. + */ + verifierKey?: Key + now?: Date +} + +export type MdocCreateOptions = { + // eslint-disable-next-line @typescript-eslint/ban-types + docType: 'org.iso.18013.5.1.mDL' | (string & {}) + validityInfo?: Partial + namespaces: { [namespace: string]: Record } + + /** + * + * The trusted base64-encoded issuer certificate string in the DER-format. + */ + issuerCertificate: string + holderPublicKey: Key + issuerKey?: Key +} diff --git a/packages/core/src/modules/mdoc/MdocService.ts b/packages/core/src/modules/mdoc/MdocService.ts new file mode 100644 index 0000000000..3e8d0f72e7 --- /dev/null +++ b/packages/core/src/modules/mdoc/MdocService.ts @@ -0,0 +1,78 @@ +import type { + MdocCreateOptions, + MdocDeviceResponseOpenId4VpOptions, + MdocDeviceResponseVerifyOptions, + MdocVerifyOptions, +} from './MdocOptions' +import type { Query, QueryOptions } from '../../storage/StorageService' + +import { injectable } from 'tsyringe' + +import { AgentContext } from '../../agent' + +import { Mdoc } from './Mdoc' +import { MdocDeviceResponse } from './MdocDeviceResponse' +import { MdocRecord, MdocRepository } from './repository' + +/** + * @internal + */ +@injectable() +export class MdocService { + private MdocRepository: MdocRepository + + public constructor(mdocRepository: MdocRepository) { + this.MdocRepository = mdocRepository + } + + public mdocFromBase64Url(hexEncodedMdoc: string) { + return Mdoc.fromBase64Url(hexEncodedMdoc) + } + + public createMdoc(agentContext: AgentContext, options: MdocCreateOptions) { + return Mdoc.create(agentContext, options) + } + + public async verifyMdoc(agentContext: AgentContext, mdoc: Mdoc, options: MdocVerifyOptions) { + return await mdoc.verify(agentContext, options) + } + + public async createDeviceResponse(agentContext: AgentContext, options: MdocDeviceResponseOpenId4VpOptions) { + return MdocDeviceResponse.openId4Vp(agentContext, options) + } + + public async verifyDeviceResponse(agentContext: AgentContext, options: MdocDeviceResponseVerifyOptions) { + return MdocDeviceResponse.verify(agentContext, options) + } + + public async store(agentContext: AgentContext, mdoc: Mdoc) { + const mdocRecord = new MdocRecord({ mdoc }) + await this.MdocRepository.save(agentContext, mdocRecord) + + return mdocRecord + } + + public async getById(agentContext: AgentContext, id: string): Promise { + return await this.MdocRepository.getById(agentContext, id) + } + + public async getAll(agentContext: AgentContext): Promise> { + return await this.MdocRepository.getAll(agentContext) + } + + public async findByQuery( + agentContext: AgentContext, + query: Query, + queryOptions?: QueryOptions + ): Promise> { + return await this.MdocRepository.findByQuery(agentContext, query, queryOptions) + } + + public async deleteById(agentContext: AgentContext, id: string) { + await this.MdocRepository.deleteById(agentContext, id) + } + + public async update(agentContext: AgentContext, mdocRecord: MdocRecord) { + await this.MdocRepository.update(agentContext, mdocRecord) + } +} diff --git a/packages/core/src/modules/mdoc/MdocVerifiablePresentation.ts b/packages/core/src/modules/mdoc/MdocVerifiablePresentation.ts new file mode 100644 index 0000000000..1637b2aaa7 --- /dev/null +++ b/packages/core/src/modules/mdoc/MdocVerifiablePresentation.ts @@ -0,0 +1,3 @@ +export class MdocVerifiablePresentation { + public constructor(public readonly deviceSignedBase64Url: string) {} +} diff --git a/packages/core/src/modules/mdoc/__tests__/mdoc.deviceResponse.openid4vp.test.ts b/packages/core/src/modules/mdoc/__tests__/mdoc.deviceResponse.openid4vp.test.ts new file mode 100644 index 0000000000..4a44f79c15 --- /dev/null +++ b/packages/core/src/modules/mdoc/__tests__/mdoc.deviceResponse.openid4vp.test.ts @@ -0,0 +1,305 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { Key as AskarKey, Jwk } from '@hyperledger/aries-askar-nodejs' +import { parseDeviceResponse } from '@protokoll/mdoc-client' + +import { Agent, KeyType } from '../../..' +import { getInMemoryAgentOptions } from '../../../../tests' +import { getJwkFromJson } from '../../../crypto/jose/jwk/transform' +import { Buffer, TypedArrayEncoder } from '../../../utils' +import { Mdoc } from '../Mdoc' +import { MdocDeviceResponse } from '../MdocDeviceResponse' + +const DEVICE_JWK_PUBLIC = { + kty: 'EC', + x: 'iBh5ynojixm_D0wfjADpouGbp6b3Pq6SuFHU3htQhVk', + y: 'oxS1OAORJ7XNUHNfVFGeM8E0RQVFxWA62fJj-sxW03c', + crv: 'P-256', + use: undefined, +} + +const DEVICE_JWK_PRIVATE = { + ...DEVICE_JWK_PUBLIC, + d: 'eRpAZr3eV5xMMnPG3kWjg90Y-bBff9LqmlQuk49HUtA', +} + +export const ISSUER_PRIVATE_KEY_JWK = { + kty: 'EC', + kid: '1234', + x: 'iTwtg0eQbcbNabf2Nq9L_VM_lhhPCq2s0Qgw2kRx29s', + y: 'YKwXDRz8U0-uLZ3NSI93R_35eNkl6jHp6Qg8OCup7VM', + crv: 'P-256', + d: 'o6PrzBm1dCfSwqJHW6DVqmJOCQSIAosrCPfbFJDMNp4', +} + +const ISSUER_CERTIFICATE = `-----BEGIN CERTIFICATE----- +MIICKjCCAdCgAwIBAgIUV8bM0wi95D7KN0TyqHE42ru4hOgwCgYIKoZIzj0EAwIw +UzELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMQ8wDQYDVQQHDAZBbGJh +bnkxDzANBgNVBAoMBk5ZIERNVjEPMA0GA1UECwwGTlkgRE1WMB4XDTIzMDkxNDE0 +NTUxOFoXDTMzMDkxMTE0NTUxOFowUzELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5l +dyBZb3JrMQ8wDQYDVQQHDAZBbGJhbnkxDzANBgNVBAoMBk5ZIERNVjEPMA0GA1UE +CwwGTlkgRE1WMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEiTwtg0eQbcbNabf2 +Nq9L/VM/lhhPCq2s0Qgw2kRx29tgrBcNHPxTT64tnc1Ij3dH/fl42SXqMenpCDw4 +K6ntU6OBgTB/MB0GA1UdDgQWBBSrbS4DuR1JIkAzj7zK3v2TM+r2xzAfBgNVHSME +GDAWgBSrbS4DuR1JIkAzj7zK3v2TM+r2xzAPBgNVHRMBAf8EBTADAQH/MCwGCWCG +SAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAKBggqhkjO +PQQDAgNIADBFAiAJ/Qyrl7A+ePZOdNfc7ohmjEdqCvxaos6//gfTvncuqQIhANo4 +q8mKCA9J8k/+zh//yKbN1bLAtdqPx7dnrDqV3Lg+ +-----END CERTIFICATE-----` + +const PRESENTATION_DEFINITION_1 = { + id: 'mdl-test-all-data', + input_descriptors: [ + { + id: 'org.iso.18013.5.1.mDL', + format: { + mso_mdoc: { + alg: ['EdDSA', 'ES256'], + }, + }, + constraints: { + limit_disclosure: 'required', + fields: [ + { + path: ["$['org.iso.18013.5.1']['family_name']"], + intent_to_retain: false, + }, + { + path: ["$['org.iso.18013.5.1']['given_name']"], + intent_to_retain: false, + }, + { + path: ["$['org.iso.18013.5.1']['birth_date']"], + intent_to_retain: false, + }, + { + path: ["$['org.iso.18013.5.1']['issue_date']"], + intent_to_retain: false, + }, + { + path: ["$['org.iso.18013.5.1']['expiry_date']"], + intent_to_retain: false, + }, + { + path: ["$['org.iso.18013.5.1']['issuing_country']"], + intent_to_retain: false, + }, + { + path: ["$['org.iso.18013.5.1']['issuing_authority']"], + intent_to_retain: false, + }, + { + path: ["$['org.iso.18013.5.1']['issuing_jurisdiction']"], + intent_to_retain: false, + }, + { + path: ["$['org.iso.18013.5.1']['document_number']"], + intent_to_retain: false, + }, + { + path: ["$['org.iso.18013.5.1']['portrait']"], + intent_to_retain: false, + }, + { + path: ["$['org.iso.18013.5.1']['driving_privileges']"], + intent_to_retain: false, + }, + { + path: ["$['org.iso.18013.5.1']['un_distinguishing_sign']"], + intent_to_retain: false, + }, + ], + }, + }, + ], +} + +describe('mdoc device-response openid4vp test', () => { + let deviceResponse: string + let mdoc: Mdoc + let parsedDocument: Mdoc + + const verifierGeneratedNonce = 'abcdefg' + const mdocGeneratedNonce = '123456' + const clientId = 'Cq1anPb8vZU5j5C0d7hcsbuJLBpIawUJIDQRi2Ebwb4' + const responseUri = 'http://localhost:4000/api/presentation_request/dc8999df-d6ea-4c84-9985-37a8b81a82ec/callback' + + let agent: Agent + beforeEach(async () => { + agent = new Agent(getInMemoryAgentOptions('mdoc-test-agent', {})) + await agent.initialize() + + const devicePrivateAskar = AskarKey.fromJwk({ jwk: Jwk.fromJson(DEVICE_JWK_PRIVATE) }) + await agent.context.wallet.createKey({ + keyType: KeyType.P256, + privateKey: Buffer.from(devicePrivateAskar.secretBytes), + }) + + const issuerPrivateAskar = AskarKey.fromJwk({ jwk: Jwk.fromJson(ISSUER_PRIVATE_KEY_JWK) }) + const issuerPrivateKey = await agent.context.wallet.createKey({ + keyType: KeyType.P256, + privateKey: Buffer.from(issuerPrivateAskar.secretBytes), + }) + + // this is the ISSUER side + { + mdoc = await Mdoc.create(agent.context, { + docType: 'org.iso.18013.5.1.mDL', + validityInfo: { + signed: new Date('2023-10-24'), + validUntil: new Date('2050-10-24'), + }, + holderPublicKey: getJwkFromJson(DEVICE_JWK_PUBLIC).key, + issuerCertificate: ISSUER_CERTIFICATE, + issuerKey: issuerPrivateKey, + namespaces: { + 'org.iso.18013.5.1': { + family_name: 'Jones', + given_name: 'Ava', + birth_date: '2007-03-25', + issue_date: '2023-09-01', + expiry_date: '2028-09-31', + issuing_country: 'US', + issuing_authority: 'NY DMV', + document_number: '01-856-5050', + portrait: 'bstr', + driving_privileges: [ + { + vehicle_category_code: 'C', + issue_date: '2023-09-01', + expiry_date: '2028-09-31', + }, + ], + un_distinguishing_sign: 'tbd-us.ny.dmv', + + sex: 'F', + height: '5\' 8"', + weight: '120lb', + eye_colour: 'brown', + hair_colour: 'brown', + resident_addres: '123 Street Rd', + resident_city: 'Brooklyn', + resident_state: 'NY', + resident_postal_code: '19001', + resident_country: 'US', + issuing_jurisdiction: 'New York', + }, + }, + }) + } + + // This is the Device side + { + const result = await MdocDeviceResponse.openId4Vp(agent.context, { + mdocs: [mdoc], + // eslint-disable-next-line @typescript-eslint/no-explicit-any + presentationDefinition: PRESENTATION_DEFINITION_1 as any, + sessionTranscriptOptions: { + clientId, + responseUri, + verifierGeneratedNonce, + mdocGeneratedNonce, + }, + deviceNameSpaces: { + 'com.foobar-device': { test: 1234 }, + }, + }) + deviceResponse = result.deviceResponseBase64Url + + const parsed = parseDeviceResponse(TypedArrayEncoder.fromBase64(deviceResponse)) + expect(parsed.documents).toHaveLength(1) + parsedDocument = Mdoc._interalFromIssuerSignedDocument(parsed.documents[0]) + } + }) + + it('should be verifiable', async () => { + const res = await MdocDeviceResponse.verify(agent.context, { + deviceResponse, + trustedCertificates: [ISSUER_CERTIFICATE], + mdoc, + sessionTranscriptOptions: { + clientId, + responseUri, + verifierGeneratedNonce, + mdocGeneratedNonce, + }, + }) + expect(res).toHaveLength(1) + }) + + describe('should not be verifiable', () => { + ;[ + [ + 'clientId', + { + clientId: 'wrong', + responseUri, + verifierGeneratedNonce, + mdocGeneratedNonce, + }, + ] as const, + [ + 'responseUri', + { + clientId, + responseUri: 'wrong', + verifierGeneratedNonce, + mdocGeneratedNonce, + }, + ] as const, + [ + 'verifierGeneratedNonce', + { + clientId, + responseUri, + verifierGeneratedNonce: 'wrong', + mdocGeneratedNonce, + }, + ] as const, + [ + 'mdocGeneratedNonce', + { + clientId, + responseUri, + verifierGeneratedNonce, + mdocGeneratedNonce: 'wrong', + }, + ] as const, + ].forEach(([name, values]) => { + it(`with a different ${name}`, async () => { + try { + await MdocDeviceResponse.verify(agent.context, { + mdoc, + trustedCertificates: [ISSUER_CERTIFICATE], + deviceResponse, + sessionTranscriptOptions: { + clientId: values.clientId, + responseUri: values.responseUri, + verifierGeneratedNonce: values.verifierGeneratedNonce, + mdocGeneratedNonce: values.mdocGeneratedNonce, + }, + }) + throw new Error('should not validate with different transcripts') + } catch (error) { + expect((error as Error).message).toMatch( + 'Unable to verify deviceAuth signature (ECDSA/EdDSA): Device signature must be valid' + ) + } + }) + }) + }) + + it('should contain the validity info', () => { + expect(parsedDocument.validityInfo).toBeDefined() + expect(parsedDocument.validityInfo.signed).toEqual(new Date('2023-10-24')) + expect(parsedDocument.validityInfo.validFrom).toEqual(new Date('2023-10-24')) + expect(parsedDocument.validityInfo.validUntil).toEqual(new Date('2050-10-24')) + }) + + it('should contain the device namespaces', () => { + expect(parsedDocument.deviceSignedNamespaces).toEqual({ + 'com.foobar-device': { + test: 1234, + }, + }) + }) +}) diff --git a/packages/core/src/modules/mdoc/__tests__/mdoc.deviceResponse.test.ts b/packages/core/src/modules/mdoc/__tests__/mdoc.deviceResponse.test.ts new file mode 100644 index 0000000000..9a9fa4e82e --- /dev/null +++ b/packages/core/src/modules/mdoc/__tests__/mdoc.deviceResponse.test.ts @@ -0,0 +1,82 @@ +import { Optionality } from '@sphereon/pex-models' + +import { Agent, KeyType, X509Service } from '../../..' +import { getInMemoryAgentOptions } from '../../../../tests' +import { Mdoc } from '../Mdoc' +import { MdocDeviceResponse } from '../MdocDeviceResponse' + +describe('mdoc device-response test', () => { + const agent = new Agent(getInMemoryAgentOptions('mdoc-test-agent', {})) + beforeEach(async () => { + await agent.initialize() + }) + + test('can limit the disclosure', async () => { + const holderKey = await agent.context.wallet.createKey({ + keyType: KeyType.P256, + }) + const issuerKey = await agent.context.wallet.createKey({ + keyType: KeyType.P256, + }) + + const currentDate = new Date() + currentDate.setDate(currentDate.getDate() - 1) + const nextDay = new Date(currentDate) + nextDay.setDate(currentDate.getDate() + 2) + + const selfSignedCertificate = await X509Service.createSelfSignedCertificate(agent.context, { + key: issuerKey, + notBefore: currentDate, + notAfter: nextDay, + extensions: [], + }) + + const issuerCertificate = selfSignedCertificate.toString('pem') + + const mdoc = await Mdoc.create(agent.context, { + docType: 'org.iso.18013.5.1.mDL', + holderPublicKey: holderKey, + namespaces: { + hello: { + world: 'from-mdoc', + secret: 'value', + nicer: 'dicer', + }, + }, + issuerCertificate, + }) + + const limitedDisclosedPayload = MdocDeviceResponse.limitDisclosureToInputDescriptor({ + mdoc, + inputDescriptor: { + id: mdoc.docType, + format: { + mso_mdoc: { + alg: ['ES256'], + }, + }, + constraints: { + limit_disclosure: Optionality.Required, + fields: [ + { + path: ["$['hello']['world']"], + intent_to_retain: true, + }, + { + path: ["$['hello']['nicer']"], + intent_to_retain: false, + }, + ], + }, + }, + }) + + expect(Object.keys(limitedDisclosedPayload)).toHaveLength(1) + expect(limitedDisclosedPayload.hello).toBeDefined() + expect(limitedDisclosedPayload.hello).toHaveLength(2) + expect(limitedDisclosedPayload.hello[0].elementIdentifier).toEqual('world') + expect(limitedDisclosedPayload.hello[0].elementValue).toEqual('from-mdoc') + expect(limitedDisclosedPayload.hello[1].elementIdentifier).toEqual('nicer') + expect(limitedDisclosedPayload.hello[1].elementValue).toEqual('dicer') + }) +}) diff --git a/packages/core/src/modules/mdoc/__tests__/mdoc.fixtures.ts b/packages/core/src/modules/mdoc/__tests__/mdoc.fixtures.ts new file mode 100644 index 0000000000..2ec6c27298 --- /dev/null +++ b/packages/core/src/modules/mdoc/__tests__/mdoc.fixtures.ts @@ -0,0 +1,65 @@ +export const sprindFunkeTestVectorBase64Url = + 'omppc3N1ZXJBdXRohEOhASahGCGCWQJ4MIICdDCCAhugAwIBAgIBAjAKBggqhkjOPQQDAjCBiDELMAkGA1UEBhMCREUxDzANBgNVBAcMBkJlcmxpbjEdMBsGA1UECgwUQnVuZGVzZHJ1Y2tlcmVpIEdtYkgxETAPBgNVBAsMCFQgQ1MgSURFMTYwNAYDVQQDDC1TUFJJTkQgRnVua2UgRVVESSBXYWxsZXQgUHJvdG90eXBlIElzc3VpbmcgQ0EwHhcNMjQwNTMxMDgxMzE3WhcNMjUwNzA1MDgxMzE3WjBsMQswCQYDVQQGEwJERTEdMBsGA1UECgwUQnVuZGVzZHJ1Y2tlcmVpIEdtYkgxCjAIBgNVBAsMAUkxMjAwBgNVBAMMKVNQUklORCBGdW5rZSBFVURJIFdhbGxldCBQcm90b3R5cGUgSXNzdWVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEOFBq4YMKg4w5fTifsytwBuJf_7E7VhRPXiNm52S3q1ETIgBdXyDK3kVxGxgeHPivLP3uuMvS6iDEc7qMxmvduKOBkDCBjTAdBgNVHQ4EFgQUiPhCkLErDXPLW2_J0WVeghyw-mIwDAYDVR0TAQH_BAIwADAOBgNVHQ8BAf8EBAMCB4AwLQYDVR0RBCYwJIIiZGVtby5waWQtaXNzdWVyLmJ1bmRlc2RydWNrZXJlaS5kZTAfBgNVHSMEGDAWgBTUVhjAiTjoDliEGMl2Yr-ru8WQvjAKBggqhkjOPQQDAgNHADBEAiAbf5TzkcQzhfWoIoyi1VN7d8I9BsFKm1MWluRph2byGQIgKYkdrNf2xXPjVSbjW_U_5S5vAEC5XxcOanusOBroBbVZAn0wggJ5MIICIKADAgECAhQHkT1BVm2ZRhwO0KMoH8fdVC_vaDAKBggqhkjOPQQDAjCBiDELMAkGA1UEBhMCREUxDzANBgNVBAcMBkJlcmxpbjEdMBsGA1UECgwUQnVuZGVzZHJ1Y2tlcmVpIEdtYkgxETAPBgNVBAsMCFQgQ1MgSURFMTYwNAYDVQQDDC1TUFJJTkQgRnVua2UgRVVESSBXYWxsZXQgUHJvdG90eXBlIElzc3VpbmcgQ0EwHhcNMjQwNTMxMDY0ODA5WhcNMzQwNTI5MDY0ODA5WjCBiDELMAkGA1UEBhMCREUxDzANBgNVBAcMBkJlcmxpbjEdMBsGA1UECgwUQnVuZGVzZHJ1Y2tlcmVpIEdtYkgxETAPBgNVBAsMCFQgQ1MgSURFMTYwNAYDVQQDDC1TUFJJTkQgRnVua2UgRVVESSBXYWxsZXQgUHJvdG90eXBlIElzc3VpbmcgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARgbN3AUOdzv4qfmJsC8I4zyR7vtVDGp8xzBkvwhogD5YJE5wJ-Zj-CIf3aoyu7mn-TI6K8TREL8ht0w428OhTJo2YwZDAdBgNVHQ4EFgQU1FYYwIk46A5YhBjJdmK_q7vFkL4wHwYDVR0jBBgwFoAU1FYYwIk46A5YhBjJdmK_q7vFkL4wEgYDVR0TAQH_BAgwBgEB_wIBADAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwIDRwAwRAIgYSbvCRkoe39q1vgx0WddbrKufAxRPa7XfqB22XXRjqECIG5MWq9Vi2HWtvHMI_TFZkeZAr2RXLGfwY99fbsQjPOzWQRD2BhZBD6mZ2RvY1R5cGV3ZXUuZXVyb3BhLmVjLmV1ZGkucGlkLjFndmVyc2lvbmMxLjBsdmFsaWRpdHlJbmZvo2ZzaWduZWTAdDIwMjQtMDgtMTJUMTQ6NDk6NDJaaXZhbGlkRnJvbcB0MjAyNC0wOC0xMlQxNDo0OTo0MlpqdmFsaWRVbnRpbMB0MjAyNC0wOC0yNlQxNDo0OTo0MlpsdmFsdWVEaWdlc3RzoXdldS5ldXJvcGEuZWMuZXVkaS5waWQuMbYAWCC9r57n5-m6adygJJlxp5_XTAl8HplzngbppD01aqPm4QFYIBLw6crU9ONkaf7fkBFsRjQKzo_VrKPAYe0Bb0tmyz7ZAlggEqFksnSgEwJX8YahuUBuvjCvahAGnaBdk2qujSqpv-sDWCDkvVFFIzukd7PLRl0u7BdQ7QsgotN9HW-UuU9blZIdtwRYIEJwqwvBhBbZ-Fn37z3QeUVSHOj1GqOXKZwQVHgaLjQmBVggOXbVdn-8O8EMQAu8eSjFEx65vwWi45S0IXqUv-KKpNIGWCAhC1Ro14maILGe6rslCRA92TGb5UOl69fgsxRZhKSpNwdYIJxlHX2GIjrDAcK7PtJW12fWsEuE-tcSRzy0PeXgKeoTCFggV2cGcdPY4TCE4ZmITgB_yI2Qp4yqAX5N1IsEGa3DvboJWCCc5-atr9b0EiIMu8sJokyJT-Fj7SUBOYdXBRNkXiUUlApYIF2AAJGSEH4IIKpOJOaOlctv53zEEZ8ox7v7FlGPu9NKC1ggIYD5k0hQ7Ps2qlA-rxFqn4126RNG8u6n5Y12Q8B6hOUMWCAGyyMXqo_TEDOV6P0ZboHklUEPnUSPhppLnQ9RrcVzaA1YIGVLx6dVsoryOaNIrzkoVy8_MuiEQsUM3DSaBXxz6UIKDlggvuphScOPS5u1wrfY6OdoMHfetBfv_cSLcABmhS1p1nYPWCCTl-eeiWAugnVjip2YHOILjltZA7-R5jI9ciCTDpzk5hBYICm2XSJ5h_zorI6Dy5AlEcWPDU3FMg-d4KkYwddDFAT_EVggT0gN3xQ65XLUqpTkOzW7rCWhM29p0fmccFriFcjZRNUSWCAto46b8l3wCNN474AO6KlAxb6-_qvXLUip_hDrxPL0cxNYINs6L4scl4zD1VEzcBpiSoy-lrqiBT65I9pdq3S75N46FFggHxExDDKOkKN2GhItHmzyTmpS4xiQ0Tkw_nDr__Rg0FUVWCCuTGT6-ihKu-E3He_M9Yxz5sYg30g5AMLrt5dFk2y9Am1kZXZpY2VLZXlJbmZvoWlkZXZpY2VLZXmkAQIgASFYIG8Ne8ve1xptY9p_0JJfTnao3ZyarzfWHmbBHPsQydSPIlggpAG-y7pm3b_QWGdCXVg5ZkMRQXQYpkIe5hAIIrVTTytvZGlnZXN0QWxnb3JpdGhtZ1NIQS0yNTZYQBVjlI0_abnOyOetdAQDwMDpqDuYYAWu3GpNwf0nH5qRf7wzIcu6d-OwczpsV67r8cMPFn-SL_gk1sxaK8t-gY9qbmFtZVNwYWNlc6F3ZXUuZXVyb3BhLmVjLmV1ZGkucGlkLjGW2BhYVqRmcmFuZG9tUHEhN2kuf12OYaKToDuH4P9oZGlnZXN0SUQAbGVsZW1lbnRWYWx1ZWJERXFlbGVtZW50SWRlbnRpZmllcnByZXNpZGVudF9jb3VudHJ52BhYXaRmcmFuZG9tUCgO1EKXxF6VNoulQ66hh3VoZGlnZXN0SUQBbGVsZW1lbnRWYWx1ZWU1MTE0N3FlbGVtZW50SWRlbnRpZmllcnRyZXNpZGVudF9wb3N0YWxfY29kZdgYWFSkZnJhbmRvbVAnBBAzmk2aU5CQkQsWS_kWaGRpZ2VzdElEAmxlbGVtZW50VmFsdWUZB8BxZWxlbWVudElkZW50aWZpZXJuYWdlX2JpcnRoX3llYXLYGFhPpGZyYW5kb21QNYLVHLGTsQVyobJnPCPo2GhkaWdlc3RJRANsZWxlbWVudFZhbHVl9XFlbGVtZW50SWRlbnRpZmllcmthZ2Vfb3Zlcl8xONgYWFakZnJhbmRvbVAwW0rn-cCj5HLb4wU3mFUgaGRpZ2VzdElEBGxlbGVtZW50VmFsdWVlS8OWTE5xZWxlbWVudElkZW50aWZpZXJtcmVzaWRlbnRfY2l0edgYWE-kZnJhbmRvbVDTjniWHGc-zyDfFcOugtzlaGRpZ2VzdElEBWxlbGVtZW50VmFsdWX1cWVsZW1lbnRJZGVudGlmaWVya2FnZV9vdmVyXzE22BhYYqRmcmFuZG9tUJJ1bPoRCPNn5MFSIlwl5hRoZGlnZXN0SUQGbGVsZW1lbnRWYWx1ZW9IRUlERVNUUkFTU0UgMTdxZWxlbWVudElkZW50aWZpZXJvcmVzaWRlbnRfc3RyZWV02BhYWKRmcmFuZG9tUGx2TWa2eitcmEGxaX_gpFZoZGlnZXN0SUQHbGVsZW1lbnRWYWx1ZWoxOTg0LTAxLTI2cWVsZW1lbnRJZGVudGlmaWVyamJpcnRoX2RhdGXYGFhspGZyYW5kb21QNwbzolJxH5q6muxF60rQvWhkaWdlc3RJRAhsZWxlbWVudFZhbHVlomV2YWx1ZWJERWtjb3VudHJ5TmFtZWdHZXJtYW55cWVsZW1lbnRJZGVudGlmaWVya25hdGlvbmFsaXR52BhYT6RmcmFuZG9tUO-yJI9Dsuyiae8wPe1cvpdoZGlnZXN0SUQJbGVsZW1lbnRWYWx1ZfVxZWxlbWVudElkZW50aWZpZXJrYWdlX292ZXJfMjHYGFhVpGZyYW5kb21QGfpq4ykqTt_fDPVmEQShq2hkaWdlc3RJRApsZWxlbWVudFZhbHVlYkRFcWVsZW1lbnRJZGVudGlmaWVyb2lzc3VpbmdfY291bnRyedgYWFekZnJhbmRvbVDsjptGsOLGOuOwPppYmPNlaGRpZ2VzdElEC2xlbGVtZW50VmFsdWViREVxZWxlbWVudElkZW50aWZpZXJxaXNzdWluZ19hdXRob3JpdHnYGFhbpGZyYW5kb21QURhliFa8BCP_O5jhnNiESGhkaWdlc3RJRAxsZWxlbWVudFZhbHVlZkdBQkxFUnFlbGVtZW50SWRlbnRpZmllcnFmYW1pbHlfbmFtZV9iaXJ0aNgYWE-kZnJhbmRvbVAawjmoQYQSPbPL6VbjcNF-aGRpZ2VzdElEDWxlbGVtZW50VmFsdWX1cWVsZW1lbnRJZGVudGlmaWVya2FnZV9vdmVyXzE02BhYVaRmcmFuZG9tUBA_hZquh2Ijw0U_IGqCDudoZGlnZXN0SUQObGVsZW1lbnRWYWx1ZWZCRVJMSU5xZWxlbWVudElkZW50aWZpZXJrYmlydGhfcGxhY2XYGFhZpGZyYW5kb21QTiyAd2V_Ecv0u6UzXCkl7WhkaWdlc3RJRA9sZWxlbWVudFZhbHVlak1VU1RFUk1BTk5xZWxlbWVudElkZW50aWZpZXJrZmFtaWx5X25hbWXYGFhTpGZyYW5kb21QEJxB3fr8hop0-boRDfWdN2hkaWdlc3RJRBBsZWxlbWVudFZhbHVlZUVSSUtBcWVsZW1lbnRJZGVudGlmaWVyamdpdmVuX25hbWXYGFhPpGZyYW5kb21Q77KAq5Owg2xvgzLWQgWKU2hkaWdlc3RJRBFsZWxlbWVudFZhbHVl9XFlbGVtZW50SWRlbnRpZmllcmthZ2Vfb3Zlcl8xMtgYWGukZnJhbmRvbVDcWl_JEvrUn2HcXbk91CeaaGRpZ2VzdElEEmxlbGVtZW50VmFsdWXAeBgyMDI0LTA4LTEyVDE0OjQ5OjQyLjEyNFpxZWxlbWVudElkZW50aWZpZXJtaXNzdWFuY2VfZGF0ZdgYWGmkZnJhbmRvbVAYOq7D1aD0NJ7XaaSS6ARJaGRpZ2VzdElEE2xlbGVtZW50VmFsdWXAeBgyMDI0LTA4LTI2VDE0OjQ5OjQyLjEyNFpxZWxlbWVudElkZW50aWZpZXJrZXhwaXJ5X2RhdGXYGFhRpGZyYW5kb21Q89JVE6aHaufqWF9OqTxpmWhkaWdlc3RJRBRsZWxlbWVudFZhbHVlGChxZWxlbWVudElkZW50aWZpZXJsYWdlX2luX3llYXJz2BhYT6RmcmFuZG9tUCnjB_bOLqoRtc72SrV68ddoZGlnZXN0SUQVbGVsZW1lbnRWYWx1ZfRxZWxlbWVudElkZW50aWZpZXJrYWdlX292ZXJfNjU' + +export const sprindFunkeX509TrustedCertificate = + 'MIICdDCCAhugAwIBAgIBAjAKBggqhkjOPQQDAjCBiDELMAkGA1UEBhMCREUxDzANBgNVBAcMBkJlcmxpbjEdMBsGA1UECgwUQnVuZGVzZHJ1Y2tlcmVpIEdtYkgxETAPBgNVBAsMCFQgQ1MgSURFMTYwNAYDVQQDDC1TUFJJTkQgRnVua2UgRVVESSBXYWxsZXQgUHJvdG90eXBlIElzc3VpbmcgQ0EwHhcNMjQwNTMxMDgxMzE3WhcNMjUwNzA1MDgxMzE3WjBsMQswCQYDVQQGEwJERTEdMBsGA1UECgwUQnVuZGVzZHJ1Y2tlcmVpIEdtYkgxCjAIBgNVBAsMAUkxMjAwBgNVBAMMKVNQUklORCBGdW5rZSBFVURJIFdhbGxldCBQcm90b3R5cGUgSXNzdWVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEOFBq4YMKg4w5fTifsytwBuJf/7E7VhRPXiNm52S3q1ETIgBdXyDK3kVxGxgeHPivLP3uuMvS6iDEc7qMxmvduKOBkDCBjTAdBgNVHQ4EFgQUiPhCkLErDXPLW2/J0WVeghyw+mIwDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCB4AwLQYDVR0RBCYwJIIiZGVtby5waWQtaXNzdWVyLmJ1bmRlc2RydWNrZXJlaS5kZTAfBgNVHSMEGDAWgBTUVhjAiTjoDliEGMl2Yr+ru8WQvjAKBggqhkjOPQQDAgNHADBEAiAbf5TzkcQzhfWoIoyi1VN7d8I9BsFKm1MWluRph2byGQIgKYkdrNf2xXPjVSbjW/U/5S5vAEC5XxcOanusOBroBbU=' + +export const iso18013_5_IssuerAuthTestVector = + '8443a10126a118215901f3308201ef30820195a00302010202143c4416eed784f3b413e48f56f075abfa6d87e' + + 'b84300a06082a8648ce3d04030230233114301206035504030c0b75746f7069612069616361310b3009060355' + + '040613025553301e170d3230313030313030303030305a170d3231313030313030303030305a302131123010' + + '06035504030c0975746f706961206473310b30090603550406130255533059301306072a8648ce3d020106082' + + 'a8648ce3d03010703420004ace7ab7340e5d9648c5a72a9a6f56745c7aad436a03a43efea77b5fa7b88f0197d' + + '57d8983e1b37d3a539f4d588365e38cbbf5b94d68c547b5bc8731dcd2f146ba381a83081a5301e0603551d120' + + '417301581136578616d706c65406578616d706c652e636f6d301c0603551d1f041530133011a00fa00d820b65' + + '78616d706c652e636f6d301d0603551d0e0416041414e29017a6c35621ffc7a686b7b72db06cd12351301f0603' + + '551d2304183016801454fa2383a04c28e0d930792261c80c4881d2c00b300e0603551d0f0101ff040403020780' + + '30150603551d250101ff040b3009060728818c5d050102300a06082a8648ce3d04030203480030450221009771' + + '7ab9016740c8d7bcdaa494a62c053bbdecce1383c1aca72ad08dbc04cbb202203bad859c13a63c6d1ad67d814d' + + '43e2425caf90d422422c04a8ee0304c0d3a68d5903a2d81859039da66776657273696f6e63312e306f64696765' + + '7374416c676f726974686d675348412d3235366c76616c756544696765737473a2716f72672e69736f2e313830' + + '31332e352e31ad00582075167333b47b6c2bfb86eccc1f438cf57af055371ac55e1e359e20f254adcebf015820' + + '67e539d6139ebd131aef441b445645dd831b2b375b390ca5ef6279b205ed45710258203394372ddb78053f36d5' + + 'd869780e61eda313d44a392092ad8e0527a2fbfe55ae0358202e35ad3c4e514bb67b1a9db51ce74e4cb9b7146e' + + '41ac52dac9ce86b8613db555045820ea5c3304bb7c4a8dcb51c4c13b65264f845541341342093cca786e058fac' + + '2d59055820fae487f68b7a0e87a749774e56e9e1dc3a8ec7b77e490d21f0e1d3475661aa1d0658207d83e507ae' + + '77db815de4d803b88555d0511d894c897439f5774056416a1c7533075820f0549a145f1cf75cbeeffa881d4857d' + + 'd438d627cf32174b1731c4c38e12ca936085820b68c8afcb2aaf7c581411d2877def155be2eb121a42bc9ba5b7' + + '312377e068f660958200b3587d1dd0c2a07a35bfb120d99a0abfb5df56865bb7fa15cc8b56a66df6e0c0a5820c' + + '98a170cf36e11abb724e98a75a5343dfa2b6ed3df2ecfbb8ef2ee55dd41c8810b5820b57dd036782f7b14c6a30' + + 'faaaae6ccd5054ce88bdfa51a016ba75eda1edea9480c5820651f8736b18480fe252a03224ea087b5d10ca5485' + + '146c67c74ac4ec3112d4c3a746f72672e69736f2e31383031332e352e312e5553a4005820d80b83d25173c484c' + + '5640610ff1a31c949c1d934bf4cf7f18d5223b15dd4f21c0158204d80e1e2e4fb246d97895427ce7000bb59bb24' + + 'c8cd003ecf94bf35bbd2917e340258208b331f3b685bca372e85351a25c9484ab7afcdf0d2233105511f778d98' + + 'c2f544035820c343af1bd1690715439161aba73702c474abf992b20c9fb55c36a336ebe01a876d646576696365' + + '4b6579496e666fa1696465766963654b6579a40102200121582096313d6c63e24e3372742bfdb1a33ba2c897dc' + + 'd68ab8c753e4fbd48dca6b7f9a2258201fb3269edd418857de1b39a4e4a44b92fa484caa722c228288f01d0c03' + + 'a2c3d667646f6354797065756f72672e69736f2e31383031332e352e312e6d444c6c76616c6964697479496e66' + + '6fa3667369676e6564c074323032302d31302d30315431333a33303a30325a6976616c696446726f6dc0743230' + + '32302d31302d30315431333a33303a30325a6a76616c6964556e74696cc074323032312d31302d30315431333a' + + '33303a30325a584059e64205df1e2f708dd6db0847aed79fc7c0201d80fa55badcaf2e1bcf5902e1e5a62e4832' + + '044b890ad85aa53f129134775d733754d7cb7a413766aeff13cb2e'.replace(' ', '') + +export const iso18013_5_SignatureStructureTestVector = + '846a5369676e61747572653143a10126405903a2d81859039da66776657273696f6e63312e3' + + '06f646967657374416c676f726974686d675348412d3235366c76616c756544696765737473a2716f72672e697' + + '36f2e31383031332e352e31ad00582075167333b47b6c2bfb86eccc1f438cf57af055371ac55e1e359e20f254a' + + 'dcebf01582067e539d6139ebd131aef441b445645dd831b2b375b390ca5ef6279b205ed45710258203394372dd' + + 'b78053f36d5d869780e61eda313d44a392092ad8e0527a2fbfe55ae0358202e35ad3c4e514bb67b1a9db51ce74' + + 'e4cb9b7146e41ac52dac9ce86b8613db555045820ea5c3304bb7c4a8dcb51c4c13b65264f845541341342093cc' + + 'a786e058fac2d59055820fae487f68b7a0e87a749774e56e9e1dc3a8ec7b77e490d21f0e1d3475661aa1d06582' + + '07d83e507ae77db815de4d803b88555d0511d894c897439f5774056416a1c7533075820f0549a145f1cf75cbee' + + 'ffa881d4857dd438d627cf32174b1731c4c38e12ca936085820b68c8afcb2aaf7c581411d2877def155be2eb121' + + 'a42bc9ba5b7312377e068f660958200b3587d1dd0c2a07a35bfb120d99a0abfb5df56865bb7fa15cc8b56a66df' + + '6e0c0a5820c98a170cf36e11abb724e98a75a5343dfa2b6ed3df2ecfbb8ef2ee55dd41c8810b5820b57dd03678' + + '2f7b14c6a30faaaae6ccd5054ce88bdfa51a016ba75eda1edea9480c5820651f8736b18480fe252a03224ea087' + + 'b5d10ca5485146c67c74ac4ec3112d4c3a746f72672e69736f2e31383031332e352e312e5553a4005820d80b83' + + 'd25173c484c5640610ff1a31c949c1d934bf4cf7f18d5223b15dd4f21c0158204d80e1e2e4fb246d97895427ce7' + + '000bb59bb24c8cd003ecf94bf35bbd2917e340258208b331f3b685bca372e85351a25c9484ab7afcdf0d223310' + + '5511f778d98c2f544035820c343af1bd1690715439161aba73702c474abf992b20c9fb55c36a336ebe01a876d6' + + '465766963654b6579496e666fa1696465766963654b6579a40102200121582096313d6c63e24e3372742bfdb1a' + + '33ba2c897dcd68ab8c753e4fbd48dca6b7f9a2258201fb3269edd418857de1b39a4e4a44b92fa484caa722c228' + + '288f01d0c03a2c3d667646f6354797065756f72672e69736f2e31383031332e352e312e6d444c6c76616c69646' + + '97479496e666fa3667369676e6564c074323032302d31302d30315431333a33303a30325a6976616c696446726' + + 'f6dc074323032302d31302d30315431333a33303a30325a6a76616c6964556e74696cc074323032312d31302d3' + + '0315431333a33303a30325a'.replace(' ', '') diff --git a/packages/core/src/modules/mdoc/__tests__/mdoc.service.test.ts b/packages/core/src/modules/mdoc/__tests__/mdoc.service.test.ts new file mode 100644 index 0000000000..ea36c4bb12 --- /dev/null +++ b/packages/core/src/modules/mdoc/__tests__/mdoc.service.test.ts @@ -0,0 +1,132 @@ +import type { AgentContext } from '../../..' + +import { KeyType, X509Service } from '../../..' +import { InMemoryWallet } from '../../../../../../tests/InMemoryWallet' +import { getAgentConfig, getAgentContext } from '../../../../tests' +import { Mdoc } from '../Mdoc' + +import { sprindFunkeTestVectorBase64Url, sprindFunkeX509TrustedCertificate } from './mdoc.fixtures' + +describe('mdoc service test', () => { + let wallet: InMemoryWallet + let agentContext: AgentContext + + beforeAll(async () => { + const agentConfig = getAgentConfig('mdoc') + wallet = new InMemoryWallet() + agentContext = getAgentContext({ wallet }) + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + await wallet.createAndOpen(agentConfig.walletConfig!) + }) + + test('can get issuer-auth protected-header alg', async () => { + const mdoc = Mdoc.fromBase64Url(sprindFunkeTestVectorBase64Url) + expect(mdoc.alg).toBe('ES256') + }) + + test('can get doctype', async () => { + const mdoc = Mdoc.fromBase64Url(sprindFunkeTestVectorBase64Url) + expect(mdoc.docType).toBe('eu.europa.ec.eudi.pid.1') + }) + + test('can create and verify mdoc', async () => { + const holderKey = await agentContext.wallet.createKey({ + keyType: KeyType.P256, + }) + const issuerKey = await agentContext.wallet.createKey({ + keyType: KeyType.P256, + }) + + const currentDate = new Date() + currentDate.setDate(currentDate.getDate() - 1) + const nextDay = new Date(currentDate) + nextDay.setDate(currentDate.getDate() + 2) + + const selfSignedCertificate = await X509Service.createSelfSignedCertificate(agentContext, { + key: issuerKey, + notBefore: currentDate, + notAfter: nextDay, + extensions: [], + countryName: 'DE', + }) + + const issuerCertificate = selfSignedCertificate.toString('pem') + + const mdoc = await Mdoc.create(agentContext, { + docType: 'org.iso.18013.5.1.mDL', + holderPublicKey: holderKey, + namespaces: { + hello: { + world: 'world', + nicer: 'dicer', + }, + }, + issuerCertificate, + issuerKey, + }) + + expect(mdoc.alg).toBe('ES256') + expect(mdoc.docType).toBe('org.iso.18013.5.1.mDL') + expect(mdoc.issuerSignedNamespaces).toStrictEqual({ + hello: { + world: 'world', + nicer: 'dicer', + }, + }) + + expect(() => mdoc.deviceSignedNamespaces).toThrow() + + const res = await mdoc.verify(agentContext, { + trustedCertificates: [selfSignedCertificate.toString('base64')], + }) + expect(res).toBeTruthy() + }) + + test('can decode claims from namespaces', async () => { + const mdoc = Mdoc.fromBase64Url(sprindFunkeTestVectorBase64Url) + const namespaces = mdoc.issuerSignedNamespaces + expect(Object.entries(namespaces)).toHaveLength(1) + + expect(namespaces).toBeDefined() + const eudiPidNamespace = namespaces['eu.europa.ec.eudi.pid.1'] + expect(eudiPidNamespace).toBeDefined() + expect(eudiPidNamespace).toStrictEqual({ + resident_country: 'DE', + age_over_12: true, + family_name_birth: 'GABLER', + given_name: 'ERIKA', + age_birth_year: 1984, + age_over_18: true, + age_over_21: true, + resident_city: 'KÖLN', + family_name: 'MUSTERMANN', + birth_place: 'BERLIN', + expiry_date: new Date('2024-08-26T14:49:42.124Z'), + issuing_country: 'DE', + age_over_65: false, + issuance_date: new Date('2024-08-12T14:49:42.124Z'), + resident_street: 'HEIDESTRASSE 17', + age_over_16: true, + resident_postal_code: '51147', + birth_date: '1984-01-26', + issuing_authority: 'DE', + age_over_14: true, + age_in_years: 40, + nationality: new Map([ + ['value', 'DE'], + ['countryName', 'Germany'], + ]), + }) + }) + + test('can verify sprindFunkeTestVector Issuer Signed', async () => { + const mdoc = Mdoc.fromBase64Url(sprindFunkeTestVectorBase64Url) + const now = new Date('2024-08-12T14:50:42.124Z') + const isValid = await mdoc.verify(agentContext, { + trustedCertificates: [sprindFunkeX509TrustedCertificate], + now, + }) + expect(isValid).toBeTruthy() + }) +}) diff --git a/packages/core/src/modules/mdoc/index.ts b/packages/core/src/modules/mdoc/index.ts new file mode 100644 index 0000000000..0944642cd2 --- /dev/null +++ b/packages/core/src/modules/mdoc/index.ts @@ -0,0 +1,8 @@ +export * from './MdocApi' +export * from './MdocModule' +export * from './MdocService' +export * from './MdocError' +export * from './MdocOptions' +export * from './repository' +export * from './Mdoc' +export * from './MdocVerifiablePresentation' diff --git a/packages/core/src/modules/mdoc/repository/MdocRecord.ts b/packages/core/src/modules/mdoc/repository/MdocRecord.ts new file mode 100644 index 0000000000..afcceead8e --- /dev/null +++ b/packages/core/src/modules/mdoc/repository/MdocRecord.ts @@ -0,0 +1,58 @@ +import type { TagsBase } from '../../../storage/BaseRecord' +import type { Constructable } from '../../../utils/mixins' + +import { type JwaSignatureAlgorithm } from '../../../crypto' +import { BaseRecord } from '../../../storage/BaseRecord' +import { JsonTransformer } from '../../../utils' +import { uuid } from '../../../utils/uuid' +import { Mdoc } from '../Mdoc' + +export type DefaultMdocRecordTags = { + docType: string + + /** + * + * The Jwa Signature Algorithm used to sign the Mdoc. + */ + alg: JwaSignatureAlgorithm +} + +export type MdocRecordStorageProps = { + id?: string + createdAt?: Date + tags?: TagsBase + mdoc: Mdoc +} + +export class MdocRecord extends BaseRecord { + public static readonly type = 'MdocRecord' + public readonly type = MdocRecord.type + public base64Url!: string + + public constructor(props: MdocRecordStorageProps) { + super() + + if (props) { + this.id = props.id ?? uuid() + this.createdAt = props.createdAt ?? new Date() + this.base64Url = props.mdoc.base64Url + this._tags = props.tags ?? {} + } + } + + public getTags() { + const mdoc = Mdoc.fromBase64Url(this.base64Url) + const docType = mdoc.docType + const alg = mdoc.alg + + return { + ...this._tags, + docType, + alg, + } + } + + public clone(): this { + return JsonTransformer.fromJSON(JsonTransformer.toJSON(this), this.constructor as Constructable) + } +} diff --git a/packages/core/src/modules/mdoc/repository/MdocRepository.ts b/packages/core/src/modules/mdoc/repository/MdocRepository.ts new file mode 100644 index 0000000000..885ece6db0 --- /dev/null +++ b/packages/core/src/modules/mdoc/repository/MdocRepository.ts @@ -0,0 +1,17 @@ +import { EventEmitter } from '../../../agent/EventEmitter' +import { InjectionSymbols } from '../../../constants' +import { inject, injectable } from '../../../plugins' +import { Repository } from '../../../storage/Repository' +import { StorageService } from '../../../storage/StorageService' + +import { MdocRecord } from './MdocRecord' + +@injectable() +export class MdocRepository extends Repository { + public constructor( + @inject(InjectionSymbols.StorageService) storageService: StorageService, + eventEmitter: EventEmitter + ) { + super(MdocRecord, storageService, eventEmitter) + } +} diff --git a/packages/core/src/modules/mdoc/repository/index.ts b/packages/core/src/modules/mdoc/repository/index.ts new file mode 100644 index 0000000000..f211d1f7ae --- /dev/null +++ b/packages/core/src/modules/mdoc/repository/index.ts @@ -0,0 +1,2 @@ +export * from './MdocRecord' +export * from './MdocRepository' diff --git a/packages/core/src/modules/proofs/formats/dif-presentation-exchange/DifPresentationExchangeProofFormatService.ts b/packages/core/src/modules/proofs/formats/dif-presentation-exchange/DifPresentationExchangeProofFormatService.ts index befb69dca4..bccf0f46f1 100644 --- a/packages/core/src/modules/proofs/formats/dif-presentation-exchange/DifPresentationExchangeProofFormatService.ts +++ b/packages/core/src/modules/proofs/formats/dif-presentation-exchange/DifPresentationExchangeProofFormatService.ts @@ -39,6 +39,7 @@ import { DifPresentationExchangeService, DifPresentationExchangeSubmissionLocation, } from '../../../dif-presentation-exchange' +import { MdocVerifiablePresentation } from '../../../mdoc' import { ANONCREDS_DATA_INTEGRITY_CRYPTOSUITE, AnonCredsDataIntegrityServiceSymbol, @@ -223,6 +224,8 @@ export class DifPresentationExchangeProofFormatService firstPresentation instanceof W3cJwtVerifiablePresentation || firstPresentation instanceof W3cJsonLdVerifiablePresentation ? firstPresentation.encoded + : firstPresentation instanceof MdocVerifiablePresentation + ? firstPresentation.deviceSignedBase64Url : firstPresentation?.compact const attachment = this.getFormatData(encodedFirstPresentation, format.attachmentId) diff --git a/packages/core/src/modules/vc/models/ClaimFormat.ts b/packages/core/src/modules/vc/models/ClaimFormat.ts index 47e1b48c52..dfe094c69d 100644 --- a/packages/core/src/modules/vc/models/ClaimFormat.ts +++ b/packages/core/src/modules/vc/models/ClaimFormat.ts @@ -13,4 +13,5 @@ export enum ClaimFormat { DiVc = 'di_vc', DiVp = 'di_vp', SdJwtVc = 'vc+sd-jwt', + MsoMdoc = 'mso_mdoc', } diff --git a/packages/core/src/modules/x509/X509Certificate.ts b/packages/core/src/modules/x509/X509Certificate.ts index 22b5fe8634..0e32dd02d0 100644 --- a/packages/core/src/modules/x509/X509Certificate.ts +++ b/packages/core/src/modules/x509/X509Certificate.ts @@ -1,3 +1,4 @@ +import type { X509CreateSelfSignedCertificateOptions } from './X509ServiceOptions' import type { CredoWebCrypto } from '../../crypto/webcrypto' import { AsnParser } from '@peculiar/asn1-schema' @@ -7,6 +8,7 @@ import * as x509 from '@peculiar/x509' import { Key } from '../../crypto/Key' import { CredoWebCryptoKey } from '../../crypto/webcrypto' import { credoKeyTypeIntoCryptoKeyAlgorithm, spkiAlgorithmIntoCredoKeyType } from '../../crypto/webcrypto/utils' +import { TypedArrayEncoder } from '../../utils' import { X509Error } from './X509Error' @@ -83,20 +85,7 @@ export class X509Certificate { } public static async createSelfSigned( - { - key, - extensions, - notAfter, - notBefore, - name, - }: { - key: Key - // For now we only support the SubjectAlternativeName as `dns` or `uri` - extensions?: ExtensionInput - notBefore?: Date - notAfter?: Date - name?: string - }, + { key, extensions, notAfter, notBefore, name, countryName }: X509CreateSelfSignedCertificateOptions, webCrypto: CredoWebCrypto ) { const cryptoKeyAlgorithm = credoKeyTypeIntoCryptoKeyAlgorithm(key.keyType) @@ -104,10 +93,20 @@ export class X509Certificate { const publicKey = new CredoWebCryptoKey(key, cryptoKeyAlgorithm, true, 'public', ['verify']) const privateKey = new CredoWebCryptoKey(key, cryptoKeyAlgorithm, false, 'private', ['sign']) + const issuerName = + name || countryName + ? [ + { + ...(name && { CN: [name] }), + ...(countryName && { C: [countryName] }), + }, + ] + : undefined + const certificate = await x509.X509CertificateGenerator.createSelfSigned( { keys: { publicKey, privateKey }, - name, + name: issuerName, extensions: extensions?.map((extension) => new x509.SubjectAlternativeNameExtension(extension)), notAfter, notBefore, @@ -154,6 +153,27 @@ export class X509Certificate { } } + public async getData(crypto?: CredoWebCrypto) { + const certificate = new x509.X509Certificate(this.rawCertificate) + + const thumbprint = await certificate.getThumbprint(crypto) + const thumbprintHex = TypedArrayEncoder.toHex(new Uint8Array(thumbprint)) + return { + issuerName: certificate.issuerName.toString(), + subjectName: certificate.subjectName.toString(), + serialNumber: certificate.serialNumber, + thumbprint: thumbprintHex, + pem: certificate.toString(), + notBefore: certificate.notBefore, + notAfter: certificate.notAfter, + } + } + + public getIssuerNameField(field: string) { + const certificate = new x509.X509Certificate(this.rawCertificate) + return certificate.issuerName.getField(field) + } + public toString(format: 'asn' | 'pem' | 'hex' | 'base64' | 'text' | 'base64url') { const certificate = new x509.X509Certificate(this.rawCertificate) return certificate.toString(format) diff --git a/packages/core/src/modules/x509/X509ServiceOptions.ts b/packages/core/src/modules/x509/X509ServiceOptions.ts index dbbfafe0c0..3a9d0dd19c 100644 --- a/packages/core/src/modules/x509/X509ServiceOptions.ts +++ b/packages/core/src/modules/x509/X509ServiceOptions.ts @@ -22,6 +22,7 @@ export interface X509CreateSelfSignedCertificateOptions { notBefore?: Date notAfter?: Date name?: string + countryName?: string } export interface X509GetLefCertificateOptions { diff --git a/packages/openid4vc/src/openid4vc-holder/OpenId4vcSiopHolderService.ts b/packages/openid4vc/src/openid4vc-holder/OpenId4vcSiopHolderService.ts index 6b8fff7b28..06e9292691 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4vcSiopHolderService.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4vcSiopHolderService.ts @@ -25,6 +25,7 @@ import { getJwkFromJson, injectable, parseDid, + MdocVerifiablePresentation, } from '@credo-ts/core' import { OP, ResponseIss, ResponseMode, ResponseType, SupportedVersion, VPTokenLocation } from '@sphereon/did-auth-siop' @@ -109,6 +110,14 @@ export class OpenId4VcSiopHolderService { challenge: nonce, domain: clientId, presentationSubmissionLocation: DifPresentationExchangeSubmissionLocation.EXTERNAL, + openid4vp: { + clientId, + verifierGeneratedNonce: nonce, + mdocGeneratedNonce: await agentContext.wallet.generateNonce(), + responseUri: + authorizationRequest.authorizationRequestPayload.response_uri ?? + authorizationRequest.authorizationRequestPayload.response_uri, + }, }) presentationExchangeOptions = { @@ -272,6 +281,8 @@ export class OpenId4VcSiopHolderService { "JWT W3C Verifiable presentation does not include did in JWT header 'kid'. Unable to extract openIdTokenIssuer from verifiable presentation" ) } + } else if (verifiablePresentation instanceof MdocVerifiablePresentation) { + throw new CredoError('Mdoc Verifiable Presentations are not yet supported') } else { const cnf = verifiablePresentation.payload.cnf // FIXME: SD-JWT VC should have better payload typing, so this doesn't become so ugly diff --git a/packages/openid4vc/src/shared/transform.ts b/packages/openid4vc/src/shared/transform.ts index bf2cebf80e..6e416322de 100644 --- a/packages/openid4vc/src/shared/transform.ts +++ b/packages/openid4vc/src/shared/transform.ts @@ -14,6 +14,8 @@ import { W3cJwtVerifiableCredential, W3cJsonLdVerifiableCredential, JsonEncoder, + Mdoc, + MdocVerifiablePresentation, } from '@credo-ts/core' export function getSphereonVerifiableCredential( @@ -26,6 +28,8 @@ export function getSphereonVerifiableCredential( return JsonTransformer.toJSON(verifiableCredential) as SphereonW3cVerifiableCredential } else if (verifiableCredential instanceof W3cJwtVerifiableCredential) { return verifiableCredential.serializedJwt + } else if (verifiableCredential instanceof Mdoc) { + throw new CredoError('Mdoc verifiable credential is not yet supported.') } else { return verifiableCredential.compact } @@ -41,6 +45,8 @@ export function getSphereonVerifiablePresentation( return JsonTransformer.toJSON(verifiablePresentation) as SphereonW3cVerifiablePresentation } else if (verifiablePresentation instanceof W3cJwtVerifiablePresentation) { return verifiablePresentation.serializedJwt + } else if (verifiablePresentation instanceof MdocVerifiablePresentation) { + throw new CredoError('Mdoc verifiable credential is not yet supported.') } else { return verifiablePresentation.compact } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a8eae3b63d..13dd2daa05 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -238,7 +238,7 @@ importers: version: link:../core '@sphereon/pex-models': specifier: ^2.2.4 - version: 2.2.4 + version: 2.3.1 big-integer: specifier: ^1.6.51 version: 1.6.52 @@ -414,9 +414,12 @@ importers: '@multiformats/base-x': specifier: ^4.0.1 version: 4.0.1 + '@noble/curves': + specifier: ^1.6.0 + version: 1.6.0 '@noble/hashes': - specifier: ^1.4.0 - version: 1.4.0 + specifier: ^1.5.0 + version: 1.5.0 '@peculiar/asn1-ecc': specifier: ^2.3.8 version: 2.3.13 @@ -429,6 +432,15 @@ importers: '@peculiar/x509': specifier: ^1.11.0 version: 1.12.1 + '@protokoll/core': + specifier: 0.2.26 + version: 0.2.26(typescript@5.5.4) + '@protokoll/crypto': + specifier: 0.2.26 + version: 0.2.26(typescript@5.5.4) + '@protokoll/mdoc-client': + specifier: 0.2.26 + version: 0.2.26(typescript@5.5.4) '@sd-jwt/core': specifier: ^0.7.0 version: 0.7.2 @@ -449,10 +461,10 @@ importers: version: 0.7.2 '@sphereon/pex': specifier: ^5.0.0-unstable.8 - version: 5.0.0-unstable.17(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) + version: 5.0.0-unstable.8(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) '@sphereon/pex-models': specifier: ^2.2.4 - version: 2.2.4 + version: 2.3.1 '@sphereon/ssi-types': specifier: 0.29.1-unstable.121 version: 0.29.1-unstable.121(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) @@ -490,7 +502,7 @@ importers: specifier: ^0.4.1 version: 0.4.1 luxon: - specifier: ^3.3.0 + specifier: ^3.5.0 version: 3.5.0 make-error: specifier: ^1.3.6 @@ -1677,6 +1689,36 @@ packages: '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + '@cbor-extract/cbor-extract-darwin-arm64@2.2.0': + resolution: {integrity: sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w==} + cpu: [arm64] + os: [darwin] + + '@cbor-extract/cbor-extract-darwin-x64@2.2.0': + resolution: {integrity: sha512-1liF6fgowph0JxBbYnAS7ZlqNYLf000Qnj4KjqPNW4GViKrEql2MgZnAsExhY9LSy8dnvA4C0qHEBgPrll0z0w==} + cpu: [x64] + os: [darwin] + + '@cbor-extract/cbor-extract-linux-arm64@2.2.0': + resolution: {integrity: sha512-rQvhNmDuhjTVXSPFLolmQ47/ydGOFXtbR7+wgkSY0bdOxCFept1hvg59uiLPT2fVDuJFuEy16EImo5tE2x3RsQ==} + cpu: [arm64] + os: [linux] + + '@cbor-extract/cbor-extract-linux-arm@2.2.0': + resolution: {integrity: sha512-QeBcBXk964zOytiedMPQNZr7sg0TNavZeuUCD6ON4vEOU/25+pLhNN6EDIKJ9VLTKaZ7K7EaAriyYQ1NQ05s/Q==} + cpu: [arm] + os: [linux] + + '@cbor-extract/cbor-extract-linux-x64@2.2.0': + resolution: {integrity: sha512-cWLAWtT3kNLHSvP4RKDzSTX9o0wvQEEAj4SKvhWuOVZxiDAeQazr9A+PSiRILK1VYMLeDml89ohxCnUNQNQNCw==} + cpu: [x64] + os: [linux] + + '@cbor-extract/cbor-extract-win32-x64@2.2.0': + resolution: {integrity: sha512-l2M+Z8DO2vbvADOBNLbbh9y5ST1RY5sqkWOg/58GkUPBYou/cuNZ68SGQ644f1CvZ8kcOxyZtw06+dxWHIoN/w==} + cpu: [x64] + os: [win32] + '@changesets/apply-release-plan@7.0.4': resolution: {integrity: sha512-HLFwhKWayKinWAul0Vj+76jVx1Pc2v55MGPVjZ924Y/ROeSsBMFutv9heHmCUj48lJyRfOTJG5+ar+29FUky/A==} @@ -2128,6 +2170,9 @@ packages: resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jfromaniello/typedmap@1.4.0': + resolution: {integrity: sha512-uHGCjkzZvdi1Kg90jmmm5H5lckH5seL5Z+dxdDjEu98ixmKfVSbFX9DvE/m5Stnw1uIGgmVD/OCSiNKZ+YT5mA==} + '@jridgewell/gen-mapping@0.3.5': resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} engines: {node: '>=6.0.0'} @@ -2195,10 +2240,6 @@ packages: resolution: {integrity: sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==} engines: {node: ^14.21.3 || >=16} - '@noble/hashes@1.4.0': - resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} - engines: {node: '>= 16'} - '@noble/hashes@1.5.0': resolution: {integrity: sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==} engines: {node: ^14.21.3 || >=16} @@ -2294,6 +2335,15 @@ packages: '@protobufjs/utf8@1.1.0': resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + '@protokoll/core@0.2.26': + resolution: {integrity: sha512-mZeFrBKBvjS++c/ooi0sI0yN/RlgrJKWIfSz4QJMmUKBlRpGvGVF6UPxlDWKBZwkxZAAWLoW/GzMg20gPKqW5g==} + + '@protokoll/crypto@0.2.26': + resolution: {integrity: sha512-CpJm0n7QvwITwOUa+E0jiiv2KayAAOOrW6bufZ1XIdxED7v2+0zQh/Li5hJip1N13T3YZpyYDX8qonE6e1ZXVA==} + + '@protokoll/mdoc-client@0.2.26': + resolution: {integrity: sha512-LN7Rk5UWtD6VHl7jLh/Qlsa3XPssfypJ8tzIEjOnRtDxLO5I1RrFWdcZgT4IMcJRswTx0MheIQ1+jSayjGo/7w==} + '@react-native-community/cli-clean@10.1.1': resolution: {integrity: sha512-iNsrjzjIRv9yb5y309SWJ8NDHdwYtnCpmxZouQDyOljUdC9MwdZ4ChbtA4rwQyAwgOVfS9F/j56ML3Cslmvrxg==} @@ -2372,8 +2422,8 @@ packages: resolution: {integrity: sha512-lzD84av1ZQhYUS+jsGqJiCMaJO2dn9u+RTT9n9q6D3SaKVwWqv+7AoRKqBu19bkwyE+iFRl1ymr40QS90jVFYg==} engines: {node: '>=14.15'} - '@scure/base@1.1.9': - resolution: {integrity: sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==} + '@scure/base@1.1.8': + resolution: {integrity: sha512-6CyAclxj3Nb0XT7GHK6K4zK6k2xJm6E4Ft0Ohjt4WgegiFUHEtFb2CGzmPmGBwoIhrLsqNLYfLr04Y1GePrzZg==} '@sd-jwt/core@0.7.2': resolution: {integrity: sha512-vix1GplUFc1A9H42r/yXkg7cKYthggyqZEwlFdsBbn4xdZNE+AHVF4N7kPa1pPxipwN3UIHd4XnQ5MJV15mhsQ==} @@ -2457,6 +2507,7 @@ packages: '@sphereon/kmp-mdl-mdoc@0.2.0-SNAPSHOT.22': resolution: {integrity: sha512-uAZZExVy+ug9JLircejWa5eLtAZ7bnBP6xb7DO2+86LRsHNLh2k2jMWJYxp+iWtGHTsh6RYsZl14ScQLvjiQ/A==} + bundledDependencies: [] '@sphereon/oid4vc-common@0.16.1-next.66': resolution: {integrity: sha512-PqMFDMfWHn9jiUxa5k018huQTYxHfYJJstQNyRrle/pV37gv6Lh+yjwML6LE8eUQksw05tfXeq1hk8M2Gl1HWA==} @@ -2479,56 +2530,35 @@ packages: awesome-qr: optional: true - '@sphereon/pex-models@2.2.4': - resolution: {integrity: sha512-pGlp+wplneE1+Lk3U48/2htYKTbONMeG5/x7vhO6AnPUOsnOXeJdftPrBYWVSzz/JH5GJptAc6+pAyYE1zMu4Q==} - '@sphereon/pex-models@2.3.1': resolution: {integrity: sha512-SByU4cJ0XYA6VZQ/L6lsSiRcFtBPHbFioCeQ4GP7/W/jQ+PSBD7uK2oTnKQ9/0iEiMK/6JYqhKgLs4a9UX3UTQ==} - '@sphereon/pex@5.0.0-unstable.17': - resolution: {integrity: sha512-wriK0JGw/ONrISvYFbE//+ITVLZnvcPjPesFrMpZcDhxlOLf+TMOqVtUjjg2DHWy7HINM8WQ2eUKz1d96vyarw==} - engines: {node: '>=18'} - '@sphereon/pex@5.0.0-unstable.2': resolution: {integrity: sha512-mA6lY/OBKKzsh4Jf4btm9Tj4ymVsX6xuVATn85LurD4bt3fhZwNJMkxhFy4tT/QyAtp05E4aaEq0wTVvOjVa7w==} engines: {node: '>=18'} + '@sphereon/pex@5.0.0-unstable.8': + resolution: {integrity: sha512-DD85XvyK2F+7VH4lRmfalqSiET8SJysY3ryIPuTVfYrZ3KssYCFfxTcip0+mgjAgbTUldjZYIXuhJO42TDjA/A==} + engines: {node: '>=18'} + '@sphereon/ssi-sdk-ext.did-utils@0.24.1-unstable.112': resolution: {integrity: sha512-nc0jFPOWg0H20S8m83aQUpNym0Wx0rJCGkgpH6GdK8gBtgza8Y9DvAap1AYZug18WbqPcF6rBjvtIJqAKsSvlQ==} - '@sphereon/ssi-sdk-ext.did-utils@0.24.1-unstable.130': - resolution: {integrity: sha512-I+0VjitRjisABWm8RtTPQG57tFwfUS13Wud30OvBoADRxnaA0guUrkS82AYtV6YD0TBHdrd0D6a0RCJwK9SvDg==} - '@sphereon/ssi-sdk-ext.identifier-resolution@0.24.1-unstable.112': resolution: {integrity: sha512-VBkJjHokFNsQ0wsHUbyCysMuShTOEuK6yrvyW64uOFcB2hzq1J/wi9CewI+YRHv7mnejBlu46uYNycvOKKRcsQ==} - '@sphereon/ssi-sdk-ext.identifier-resolution@0.24.1-unstable.130': - resolution: {integrity: sha512-9mY+qgXmbZCC8aic99R7B3vKBHBakDiC6Sktgd7Q9AknR8cCmvdrmTgnOETrLng9L43uNOJnNTMG/4T6LqmtsA==} - '@sphereon/ssi-sdk-ext.jwt-service@0.24.1-unstable.112': resolution: {integrity: sha512-OrBaSg5wLSehkJ4MyuyDWKD4CRIBERnJqRT0o/y5DbaCF3k02+/lN/rWP+4qwk2w192fIEAExG4L2GwZM/5PLQ==} - '@sphereon/ssi-sdk-ext.jwt-service@0.24.1-unstable.130': - resolution: {integrity: sha512-MHLGRmJODEYJyFoXKwlKMYzf48vS5JcUkGk0W4sqmrY1wwcw+ro3l8adIprG37mNuknXBs9Mv0x/tvibE9wwCQ==} - '@sphereon/ssi-sdk-ext.key-manager@0.24.1-unstable.112': resolution: {integrity: sha512-XdXV4qj+BYTZWyGHduWQxl0mxCYt5CF0Q93p4Thbm2/hjfaAC6aJi2WAXFGTIri95QVbKW1Uscob0CjNCVkWdg==} - '@sphereon/ssi-sdk-ext.key-manager@0.24.1-unstable.130': - resolution: {integrity: sha512-O/6NlKmlYRnEyP/mAI2Diu0qptMSqZfVwqog8KAOG/G8JUmktfSQmclBW8RoJ6AD9uY65BGzNk1oAVuuMv4Dog==} - '@sphereon/ssi-sdk-ext.key-utils@0.24.1-unstable.112': resolution: {integrity: sha512-er6TwGUWxlao2lSP97r1DTFlUXcPSMsIToULOWQJp6wKbvCuvV6pN5luS0qKB/W0/TOUE5kXzFwNx086BPnPRA==} - '@sphereon/ssi-sdk-ext.key-utils@0.24.1-unstable.130': - resolution: {integrity: sha512-DCyXW18g1OAuZ+aFHzQGrbZSx793DX94LSFnrWlOTMnYeILmrizuFksUlWSb3lTqQGAqWBC48NoR3I1H6lSMEQ==} - '@sphereon/ssi-sdk-ext.x509-utils@0.24.1-unstable.112': resolution: {integrity: sha512-bbx2jFoqWhW/xYABVwg3HiUo15yztPt3s+9bJtdB8n4PCjin4Nq3+vFvaHsmu70yAGkbWfsBcBVW6Y3oFtvpAA==} - '@sphereon/ssi-sdk-ext.x509-utils@0.24.1-unstable.130': - resolution: {integrity: sha512-JDX8i0WrwONaOivZXB+OxJQGkln7vuSLS61tOYl7M1RyPGixdBYuEuACsdvWf6egYOpaWmhmXZzaAOj18eDddw==} - '@sphereon/ssi-sdk.agent-config@0.29.1-unstable.161': resolution: {integrity: sha512-ZP/TjapF/Gv/AwnNr9e1U3rjyRwdLtAj4un9j1csnKcgYe9ff2fhYbe06y9mU4tfQilH69mAW4Tz1t6N5U7XbA==} @@ -2544,9 +2574,6 @@ packages: '@sphereon/ssi-types@0.29.1-unstable.208': resolution: {integrity: sha512-3YAFzy//BojsYN+RYoEjndWP3w5a8a3qRZi5dS0Gh6s4yMCiykqTJM1agJVeoaLce8JxFFaCWSpkzwbmJYGTaQ==} - '@sphereon/ssi-types@0.30.1': - resolution: {integrity: sha512-vbYaxQXb71sOPwDj7TRDlUGfIHKVVs8PiHfImPBgSBshrD7VpEHOrB+EwwavMm5MAQvWK/yblGmzk7FHds7SHA==} - '@sphereon/ssi-types@0.9.0': resolution: {integrity: sha512-umCr/syNcmvMMbQ+i/r/mwjI1Qw2aFPp9AwBTvTo1ailAVaaJjJGPkkVz1K9/2NZATNdDiQ3A8yGzdVJoKh9pA==} @@ -3364,6 +3391,13 @@ packages: canonicalize@2.0.0: resolution: {integrity: sha512-ulDEYPv7asdKvqahuAY35c1selLdzDwHqugK92hfkzvlDCwXRRelDkR+Er33md/PtnpqHemgkuDPanZ4fiYZ8w==} + cbor-extract@2.2.0: + resolution: {integrity: sha512-Ig1zM66BjLfTXpNgKpvBePq271BPOvu8MR0Jl080yG7Jsl+wAZunfrwiwA+9ruzm/WEdIV5QF/bjDZTqyAIVHA==} + hasBin: true + + cbor-x@1.6.0: + resolution: {integrity: sha512-0kareyRwHSkL6ws5VXHEf8uY1liitysCVJjlmhaLG+IXLqhSaOO+t63coaso7yjwEzWZzLy8fJo06gZDVQM9Qg==} + chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -3527,6 +3561,9 @@ packages: compare-versions@3.6.0: resolution: {integrity: sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==} + compare-versions@6.1.1: + resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} + component-type@1.2.2: resolution: {integrity: sha512-99VUHREHiN5cLeHm3YLq312p6v+HUEcwtLCAtelvUDI6+SH5g5Cr85oNR2S1o6ywzL0ykMbuwLzM2ANocjEOIA==} @@ -4034,6 +4071,7 @@ packages: eslint@8.57.0: resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true esniff@2.0.1: @@ -5758,6 +5796,10 @@ packages: resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} engines: {node: '>= 6.13.0'} + node-gyp-build-optional-packages@5.1.1: + resolution: {integrity: sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==} + hasBin: true + node-gyp-build@4.8.1: resolution: {integrity: sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==} hasBin: true @@ -7212,6 +7254,14 @@ packages: resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} engines: {node: '>=10.12.0'} + valibot@0.37.0: + resolution: {integrity: sha512-FQz52I8RXgFgOHym3XHYSREbNtkgSjF9prvMFH1nBsRyfL6SfCzoT1GuSDTlbsuPubM7/6Kbw0ZMQb8A+V+VsQ==} + peerDependencies: + typescript: '>=5' + peerDependenciesMeta: + typescript: + optional: true + valibot@0.42.1: resolution: {integrity: sha512-3keXV29Ar5b//Hqi4MbSdV7lfVp6zuYLZuA9V1PvQUsXqogr+u5lvLPLk3A4f74VUXDnf/JfWMN6sB+koJ/FFw==} peerDependencies: @@ -8517,6 +8567,24 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} + '@cbor-extract/cbor-extract-darwin-arm64@2.2.0': + optional: true + + '@cbor-extract/cbor-extract-darwin-x64@2.2.0': + optional: true + + '@cbor-extract/cbor-extract-linux-arm64@2.2.0': + optional: true + + '@cbor-extract/cbor-extract-linux-arm@2.2.0': + optional: true + + '@cbor-extract/cbor-extract-linux-x64@2.2.0': + optional: true + + '@cbor-extract/cbor-extract-win32-x64@2.2.0': + optional: true + '@changesets/apply-release-plan@7.0.4': dependencies: '@babel/runtime': 7.25.0 @@ -8706,7 +8774,7 @@ snapshots: '@confio/ics23@0.6.8': dependencies: - '@noble/hashes': 1.4.0 + '@noble/hashes': 1.5.0 protobufjs: 6.11.4 '@cosmjs/amino@0.30.1': @@ -8721,7 +8789,7 @@ snapshots: '@cosmjs/encoding': 0.30.1 '@cosmjs/math': 0.30.1 '@cosmjs/utils': 0.30.1 - '@noble/hashes': 1.4.0 + '@noble/hashes': 1.5.0 bn.js: 5.2.1 elliptic: 6.5.7 libsodium-wrappers: 0.7.15 @@ -9618,6 +9686,8 @@ snapshots: '@types/yargs': 17.0.33 chalk: 4.1.2 + '@jfromaniello/typedmap@1.4.0': {} + '@jridgewell/gen-mapping@0.3.5': dependencies: '@jridgewell/set-array': 1.2.1 @@ -9719,8 +9789,6 @@ snapshots: dependencies: '@noble/hashes': 1.5.0 - '@noble/hashes@1.4.0': {} - '@noble/hashes@1.5.0': {} '@nodelib/fs.scandir@2.1.5': @@ -9868,6 +9936,30 @@ snapshots: '@protobufjs/utf8@1.1.0': {} + '@protokoll/core@0.2.26(typescript@5.5.4)': + dependencies: + '@credo-ts/core': link:packages/core + jwt-decode: 4.0.0 + valibot: 0.37.0(typescript@5.5.4) + transitivePeerDependencies: + - typescript + + '@protokoll/crypto@0.2.26(typescript@5.5.4)': + dependencies: + '@protokoll/core': 0.2.26(typescript@5.5.4) + valibot: 0.37.0(typescript@5.5.4) + transitivePeerDependencies: + - typescript + + '@protokoll/mdoc-client@0.2.26(typescript@5.5.4)': + dependencies: + '@jfromaniello/typedmap': 1.4.0 + '@protokoll/core': 0.2.26(typescript@5.5.4) + cbor-x: 1.6.0 + compare-versions: 6.1.1 + transitivePeerDependencies: + - typescript + '@react-native-community/cli-clean@10.1.1': dependencies: '@react-native-community/cli-tools': 10.1.1 @@ -10136,7 +10228,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@scure/base@1.1.9': {} + '@scure/base@1.1.8': {} '@sd-jwt/core@0.7.2': dependencies: @@ -10324,22 +10416,21 @@ snapshots: - encoding - supports-color - '@sphereon/pex-models@2.2.4': {} - '@sphereon/pex-models@2.3.1': {} - '@sphereon/pex@5.0.0-unstable.17(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4))': + '@sphereon/pex@5.0.0-unstable.2(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4))': dependencies: '@astronautlabs/jsonpath': 1.1.2 - '@sd-jwt/decode': 0.7.2 - '@sd-jwt/present': 0.7.2 - '@sd-jwt/types': 0.7.2 + '@sd-jwt/decode': 0.6.1 + '@sd-jwt/present': 0.6.1 + '@sd-jwt/types': 0.6.1 '@sphereon/pex-models': 2.3.1 - '@sphereon/ssi-types': 0.30.1(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) + '@sphereon/ssi-types': 0.29.1-unstable.121(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) ajv: 8.17.1 ajv-formats: 2.1.1(ajv@8.17.1) jwt-decode: 3.1.2 nanoid: 3.3.7 + string.prototype.matchall: 4.0.11 uint8arrays: 3.1.1 transitivePeerDependencies: - '@google-cloud/spanner' @@ -10362,7 +10453,7 @@ snapshots: - ts-node - typeorm-aurora-data-api-driver - '@sphereon/pex@5.0.0-unstable.2(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4))': + '@sphereon/pex@5.0.0-unstable.8(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4))': dependencies: '@astronautlabs/jsonpath': 1.1.2 '@sd-jwt/decode': 0.6.1 @@ -10435,44 +10526,6 @@ snapshots: - ts-node - typeorm-aurora-data-api-driver - '@sphereon/ssi-sdk-ext.did-utils@0.24.1-unstable.130(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4))': - dependencies: - '@ethersproject/networks': 5.7.1 - '@ethersproject/transactions': 5.7.0 - '@sphereon/did-uni-client': 0.6.3 - '@sphereon/ssi-sdk-ext.key-utils': 0.24.1-unstable.130 - '@sphereon/ssi-sdk-ext.x509-utils': 0.24.1-unstable.130 - '@sphereon/ssi-sdk.agent-config': 0.29.1-unstable.161(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) - '@sphereon/ssi-sdk.core': 0.29.1-unstable.161 - '@sphereon/ssi-types': 0.29.1-unstable.161 - '@stablelib/ed25519': 1.0.3 - '@veramo/core': 4.2.0 - '@veramo/utils': 4.2.0 - did-jwt: 6.11.6 - did-resolver: 4.1.0 - elliptic: 6.5.7 - uint8arrays: 3.1.1 - transitivePeerDependencies: - - '@google-cloud/spanner' - - '@sap/hana-client' - - better-sqlite3 - - encoding - - hdb-pool - - ioredis - - mongodb - - mssql - - mysql2 - - oracledb - - pg - - pg-native - - pg-query-stream - - redis - - sql.js - - sqlite3 - - supports-color - - ts-node - - typeorm-aurora-data-api-driver - '@sphereon/ssi-sdk-ext.identifier-resolution@0.24.1-unstable.112(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4))': dependencies: '@sphereon/ssi-sdk-ext.did-utils': 0.24.1-unstable.112(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) @@ -10506,39 +10559,6 @@ snapshots: - ts-node - typeorm-aurora-data-api-driver - '@sphereon/ssi-sdk-ext.identifier-resolution@0.24.1-unstable.130(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4))': - dependencies: - '@sphereon/ssi-sdk-ext.did-utils': 0.24.1-unstable.130(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) - '@sphereon/ssi-sdk-ext.key-utils': 0.24.1-unstable.130 - '@sphereon/ssi-sdk-ext.x509-utils': 0.24.1-unstable.130 - '@sphereon/ssi-sdk.agent-config': 0.29.1-unstable.161(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) - '@sphereon/ssi-types': 0.29.1-unstable.161 - '@veramo/core': 4.2.0 - '@veramo/utils': 4.2.0 - debug: 4.3.6 - pkijs: 3.2.4 - uint8arrays: 3.1.1 - transitivePeerDependencies: - - '@google-cloud/spanner' - - '@sap/hana-client' - - better-sqlite3 - - encoding - - hdb-pool - - ioredis - - mongodb - - mssql - - mysql2 - - oracledb - - pg - - pg-native - - pg-query-stream - - redis - - sql.js - - sqlite3 - - supports-color - - ts-node - - typeorm-aurora-data-api-driver - '@sphereon/ssi-sdk-ext.jwt-service@0.24.1-unstable.112(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4))': dependencies: '@sphereon/ssi-sdk-ext.did-utils': 0.24.1-unstable.112(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) @@ -10574,41 +10594,6 @@ snapshots: - ts-node - typeorm-aurora-data-api-driver - '@sphereon/ssi-sdk-ext.jwt-service@0.24.1-unstable.130(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4))': - dependencies: - '@sphereon/ssi-sdk-ext.did-utils': 0.24.1-unstable.130(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) - '@sphereon/ssi-sdk-ext.identifier-resolution': 0.24.1-unstable.130(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) - '@sphereon/ssi-sdk-ext.key-manager': 0.24.1-unstable.130 - '@sphereon/ssi-sdk-ext.key-utils': 0.24.1-unstable.130 - '@sphereon/ssi-sdk-ext.x509-utils': 0.24.1-unstable.130 - '@sphereon/ssi-sdk.agent-config': 0.29.1-unstable.161(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) - '@sphereon/ssi-types': 0.29.1-unstable.161 - '@veramo/core': 4.2.0 - '@veramo/utils': 4.2.0 - debug: 4.3.6 - jwt-decode: 4.0.0 - uint8arrays: 3.1.1 - transitivePeerDependencies: - - '@google-cloud/spanner' - - '@sap/hana-client' - - better-sqlite3 - - encoding - - hdb-pool - - ioredis - - mongodb - - mssql - - mysql2 - - oracledb - - pg - - pg-native - - pg-query-stream - - redis - - sql.js - - sqlite3 - - supports-color - - ts-node - - typeorm-aurora-data-api-driver - '@sphereon/ssi-sdk-ext.key-manager@0.24.1-unstable.112': dependencies: '@veramo/core': 4.2.0 @@ -10617,14 +10602,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@sphereon/ssi-sdk-ext.key-manager@0.24.1-unstable.130': - dependencies: - '@veramo/core': 4.2.0 - '@veramo/key-manager': 4.2.0 - uint8arrays: 3.1.1 - transitivePeerDependencies: - - supports-color - '@sphereon/ssi-sdk-ext.key-utils@0.24.1-unstable.112': dependencies: '@ethersproject/random': 5.7.0 @@ -10647,28 +10624,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@sphereon/ssi-sdk-ext.key-utils@0.24.1-unstable.130': - dependencies: - '@ethersproject/random': 5.7.0 - '@sphereon/ssi-sdk-ext.x509-utils': 0.24.1-unstable.130 - '@sphereon/ssi-types': 0.29.1-unstable.161 - '@stablelib/ed25519': 1.0.3 - '@stablelib/sha256': 1.0.1 - '@stablelib/sha512': 1.0.1 - '@trust/keyto': 1.0.1 - '@veramo/core': 4.2.0 - base64url: 3.0.1 - debug: 4.3.6 - did-resolver: 4.1.0 - elliptic: 6.5.7 - lodash.isplainobject: 4.0.6 - multiformats: 9.9.0 - uint8arrays: 3.1.1 - varint: 6.0.0 - web-encoding: 1.1.5 - transitivePeerDependencies: - - supports-color - '@sphereon/ssi-sdk-ext.x509-utils@0.24.1-unstable.112': dependencies: '@trust/keyto': 1.0.1 @@ -10679,16 +10634,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@sphereon/ssi-sdk-ext.x509-utils@0.24.1-unstable.130': - dependencies: - '@trust/keyto': 1.0.1 - debug: 4.3.6 - js-x509-utils: 1.0.7 - pkijs: 3.2.4 - uint8arrays: 3.1.1 - transitivePeerDependencies: - - supports-color - '@sphereon/ssi-sdk.agent-config@0.29.1-unstable.161(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4))': dependencies: '@veramo/core': 4.2.0 @@ -10777,35 +10722,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@sphereon/ssi-types@0.30.1(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4))': - dependencies: - '@sd-jwt/decode': 0.7.2 - '@sphereon/kmp-mdl-mdoc': 0.2.0-SNAPSHOT.22 - '@sphereon/ssi-sdk-ext.jwt-service': 0.24.1-unstable.130(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) - debug: 4.3.6 - events: 3.3.0 - jwt-decode: 3.1.2 - transitivePeerDependencies: - - '@google-cloud/spanner' - - '@sap/hana-client' - - better-sqlite3 - - encoding - - hdb-pool - - ioredis - - mongodb - - mssql - - mysql2 - - oracledb - - pg - - pg-native - - pg-query-stream - - redis - - sql.js - - sqlite3 - - supports-color - - ts-node - - typeorm-aurora-data-api-driver - '@sphereon/ssi-types@0.9.0': dependencies: jwt-decode: 3.1.2 @@ -11861,6 +11777,22 @@ snapshots: canonicalize@2.0.0: {} + cbor-extract@2.2.0: + dependencies: + node-gyp-build-optional-packages: 5.1.1 + optionalDependencies: + '@cbor-extract/cbor-extract-darwin-arm64': 2.2.0 + '@cbor-extract/cbor-extract-darwin-x64': 2.2.0 + '@cbor-extract/cbor-extract-linux-arm': 2.2.0 + '@cbor-extract/cbor-extract-linux-arm64': 2.2.0 + '@cbor-extract/cbor-extract-linux-x64': 2.2.0 + '@cbor-extract/cbor-extract-win32-x64': 2.2.0 + optional: true + + cbor-x@1.6.0: + optionalDependencies: + cbor-extract: 2.2.0 + chalk@2.4.2: dependencies: ansi-styles: 3.2.1 @@ -12018,6 +11950,8 @@ snapshots: compare-versions@3.6.0: optional: true + compare-versions@6.1.1: {} + component-type@1.2.2: {} compressible@2.0.18: @@ -12299,8 +12233,8 @@ snapshots: dependencies: '@noble/ciphers': 0.4.1 '@noble/curves': 1.6.0 - '@noble/hashes': 1.4.0 - '@scure/base': 1.1.9 + '@noble/hashes': 1.5.0 + '@scure/base': 1.1.8 canonicalize: 2.0.0 did-resolver: 4.1.0 multibase: 4.0.6 @@ -12311,8 +12245,8 @@ snapshots: dependencies: '@noble/ciphers': 0.5.3 '@noble/curves': 1.6.0 - '@noble/hashes': 1.4.0 - '@scure/base': 1.1.9 + '@noble/hashes': 1.5.0 + '@scure/base': 1.1.8 canonicalize: 2.0.0 did-resolver: 4.1.0 multibase: 4.0.6 @@ -12547,7 +12481,7 @@ snapshots: debug: 4.3.6 enhanced-resolve: 5.17.1 eslint: 8.57.0 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.6 @@ -12559,7 +12493,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0): + eslint-module-utils@2.8.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: @@ -12580,7 +12514,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.15.0 is-glob: 4.0.3 @@ -14902,6 +14836,11 @@ snapshots: node-forge@1.3.1: {} + node-gyp-build-optional-packages@5.1.1: + dependencies: + detect-libc: 2.0.3 + optional: true + node-gyp-build@4.8.1: {} node-int64@0.4.0: {} @@ -15177,7 +15116,7 @@ snapshots: pkijs@3.2.4: dependencies: - '@noble/hashes': 1.4.0 + '@noble/hashes': 1.5.0 asn1js: 3.0.5 bytestreamjs: 2.0.1 pvtsutils: 1.3.5 @@ -16398,6 +16337,10 @@ snapshots: '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 + valibot@0.37.0(typescript@5.5.4): + optionalDependencies: + typescript: 5.5.4 + valibot@0.42.1(typescript@5.5.4): optionalDependencies: typescript: 5.5.4 From 1be0fbfd8c4d2a66624f80853463f88f7d6ca57f Mon Sep 17 00:00:00 2001 From: Martin Auer Date: Tue, 8 Oct 2024 15:53:11 +0200 Subject: [PATCH 2/4] feat: fix: some nits Signed-off-by: Martin Auer --- packages/core/src/agent/AgentModules.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/agent/AgentModules.ts b/packages/core/src/agent/AgentModules.ts index 0c1aaf0aaf..7ca8b8851f 100644 --- a/packages/core/src/agent/AgentModules.ts +++ b/packages/core/src/agent/AgentModules.ts @@ -138,7 +138,7 @@ function getDefaultAgentModules() { pex: () => new DifPresentationExchangeModule(), sdJwtVc: () => new SdJwtVcModule(), x509: () => new X509Module(), - mod: () => new MdocModule(), + mdoc: () => new MdocModule(), } as const } From 2fcde60226d59f7324f7f6b4e231847e8f7da5ae Mon Sep 17 00:00:00 2001 From: Martin Auer Date: Tue, 8 Oct 2024 15:57:08 +0200 Subject: [PATCH 3/4] docs(changeset): feat: mdoc-support Signed-off-by: Martin Auer --- .changeset/metal-carrots-hang.md | 5 +++++ pnpm-lock.yaml | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 .changeset/metal-carrots-hang.md diff --git a/.changeset/metal-carrots-hang.md b/.changeset/metal-carrots-hang.md new file mode 100644 index 0000000000..f1fb6af247 --- /dev/null +++ b/.changeset/metal-carrots-hang.md @@ -0,0 +1,5 @@ +--- +'@credo-ts/core': patch +--- + +feat: mdoc-support diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 13dd2daa05..68bfd2bec9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2507,7 +2507,6 @@ packages: '@sphereon/kmp-mdl-mdoc@0.2.0-SNAPSHOT.22': resolution: {integrity: sha512-uAZZExVy+ug9JLircejWa5eLtAZ7bnBP6xb7DO2+86LRsHNLh2k2jMWJYxp+iWtGHTsh6RYsZl14ScQLvjiQ/A==} - bundledDependencies: [] '@sphereon/oid4vc-common@0.16.1-next.66': resolution: {integrity: sha512-PqMFDMfWHn9jiUxa5k018huQTYxHfYJJstQNyRrle/pV37gv6Lh+yjwML6LE8eUQksw05tfXeq1hk8M2Gl1HWA==} From 95eb0db28ae8391dde365c89475bbfcc52528324 Mon Sep 17 00:00:00 2001 From: Martin Auer Date: Tue, 8 Oct 2024 20:17:22 +0200 Subject: [PATCH 4/4] fix: some nits Signed-off-by: Martin Auer --- packages/core/package.json | 6 +- .../DifPresentationExchangeService.ts | 134 +++++++++--------- .../utils/credentialSelection.ts | 16 +-- packages/core/src/modules/mdoc/Mdoc.ts | 38 +++-- packages/core/src/modules/mdoc/MdocApi.ts | 6 +- packages/core/src/modules/mdoc/MdocContext.ts | 24 +++- .../src/modules/mdoc/MdocDeviceResponse.ts | 4 +- packages/core/src/modules/mdoc/MdocOptions.ts | 6 +- packages/core/src/modules/mdoc/MdocService.ts | 6 +- .../mdoc.deviceResponse.openid4vp.test.ts | 2 +- .../__tests__/mdoc.deviceResponse.test.ts | 2 +- .../mdoc/__tests__/mdoc.service.test.ts | 8 +- ...fPresentationExchangeProofFormatService.ts | 4 + .../OpenId4vcSiopHolderService.ts | 2 +- packages/openid4vc/src/shared/transform.ts | 2 +- pnpm-lock.yaml | 34 ++--- 16 files changed, 165 insertions(+), 129 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index f33db3b5ec..4a9fc5dcda 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -35,9 +35,9 @@ "@peculiar/asn1-schema": "^2.3.8", "@peculiar/asn1-x509": "^2.3.8", "@peculiar/x509": "^1.11.0", - "@protokoll/core": "0.2.26", - "@protokoll/crypto": "0.2.26", - "@protokoll/mdoc-client": "0.2.26", + "@protokoll/core": "0.2.27", + "@protokoll/crypto": "0.2.27", + "@protokoll/mdoc-client": "0.2.27", "@sd-jwt/core": "^0.7.0", "@sd-jwt/decode": "^0.7.0", "@sd-jwt/jwt-status-list": "^0.7.0", diff --git a/packages/core/src/modules/dif-presentation-exchange/DifPresentationExchangeService.ts b/packages/core/src/modules/dif-presentation-exchange/DifPresentationExchangeService.ts index ca4da46665..e2925a9898 100644 --- a/packages/core/src/modules/dif-presentation-exchange/DifPresentationExchangeService.ts +++ b/packages/core/src/modules/dif-presentation-exchange/DifPresentationExchangeService.ts @@ -22,7 +22,7 @@ import type { W3CVerifiablePresentation, } from '@sphereon/ssi-types' -import { PEVersion, PEX, Status } from '@sphereon/pex' +import { PEVersion, PEX, PresentationSubmissionLocation, Status } from '@sphereon/pex' import { PartialSdJwtDecodedVerifiableCredential } from '@sphereon/pex/dist/main/lib' import { injectable } from 'tsyringe' @@ -197,7 +197,7 @@ export class DifPresentationExchangeService { const { deviceResponseBase64Url, presentationSubmission } = await MdocDeviceResponse.openId4Vp(agentContext, { mdocs: [Mdoc.fromBase64Url(mdocRecord.base64Url)], - presentationDefinition: presentationDefinition as DifPresentationExchangeDefinitionV2, + presentationDefinition: presentationDefinition, sessionTranscriptOptions: { ...openid4vp, }, @@ -211,81 +211,79 @@ export class DifPresentationExchangeService { }, claimFormat: presentationToCreate.claimFormat, }) + } else { + // Get all the credentials for the presentation + const credentialsForPresentation = presentationToCreate.verifiableCredentials.map((c) => + getSphereonOriginalVerifiableCredential(c.credential) + ) - continue - } - - // Get all the credentials for the presentation - const credentialsForPresentation = presentationToCreate.verifiableCredentials.map((c) => - getSphereonOriginalVerifiableCredential(c.credential) - ) - - const verifiablePresentationResult = await this.pex.verifiablePresentationFrom( - presentationDefinitionForSubject, - credentialsForPresentation, - this.getPresentationSignCallback(agentContext, presentationToCreate), - { - proofOptions: { - challenge, - domain, - }, - signatureOptions: {}, - presentationSubmissionLocation: - presentationSubmissionLocation ?? DifPresentationExchangeSubmissionLocation.PRESENTATION, - } - ) + const verifiablePresentationResult = await this.pex.verifiablePresentationFrom( + presentationDefinitionForSubject, + credentialsForPresentation, + this.getPresentationSignCallback(agentContext, presentationToCreate), + { + proofOptions: { + challenge, + domain, + }, + signatureOptions: {}, + presentationSubmissionLocation: + presentationSubmissionLocation ?? DifPresentationExchangeSubmissionLocation.PRESENTATION, + } + ) - verifiablePresentationResultsWithFormat.push({ - verifiablePresentationResult, - claimFormat: presentationToCreate.claimFormat, - }) - } + verifiablePresentationResultsWithFormat.push({ + verifiablePresentationResult, + claimFormat: presentationToCreate.claimFormat, + }) + } - if (verifiablePresentationResultsWithFormat.length === 0) { - throw new DifPresentationExchangeError('No verifiable presentations created') - } + if (verifiablePresentationResultsWithFormat.length === 0) { + throw new DifPresentationExchangeError('No verifiable presentations created') + } - if (presentationsToCreate.length !== verifiablePresentationResultsWithFormat.length) { - throw new DifPresentationExchangeError('Invalid amount of verifiable presentations created') - } + if (presentationsToCreate.length !== verifiablePresentationResultsWithFormat.length) { + throw new DifPresentationExchangeError('Invalid amount of verifiable presentations created') + } - const presentationSubmission: DifPresentationExchangeSubmission = { - id: verifiablePresentationResultsWithFormat[0].verifiablePresentationResult.presentationSubmission.id, - definition_id: - verifiablePresentationResultsWithFormat[0].verifiablePresentationResult.presentationSubmission.definition_id, - descriptor_map: [], - } + const presentationSubmission: DifPresentationExchangeSubmission = { + id: verifiablePresentationResultsWithFormat[0].verifiablePresentationResult.presentationSubmission.id, + definition_id: + verifiablePresentationResultsWithFormat[0].verifiablePresentationResult.presentationSubmission.definition_id, + descriptor_map: [], + } - verifiablePresentationResultsWithFormat.forEach(({ verifiablePresentationResult }, index) => { - const descriptorMap = verifiablePresentationResult.presentationSubmission.descriptor_map.map((d) => { - const descriptor = { ...d } - - // when multiple presentations are submitted, path should be $[0], $[1] - // FIXME: this should be addressed in the PEX/OID4VP lib. - // See https://github.com/Sphereon-Opensource/SIOP-OID4VP/issues/62 - if ( - presentationSubmissionLocation === DifPresentationExchangeSubmissionLocation.EXTERNAL && - verifiablePresentationResultsWithFormat.length > 1 - ) { - descriptor.path = `$[${index}]` - } + verifiablePresentationResultsWithFormat.forEach(({ verifiablePresentationResult }, index) => { + const descriptorMap = verifiablePresentationResult.presentationSubmission.descriptor_map.map((d) => { + const descriptor = { ...d } + + // when multiple presentations are submitted, path should be $[0], $[1] + // FIXME: this should be addressed in the PEX/OID4VP lib. + // See https://github.com/Sphereon-Opensource/SIOP-OID4VP/issues/62 + if ( + presentationSubmissionLocation === DifPresentationExchangeSubmissionLocation.EXTERNAL && + verifiablePresentationResultsWithFormat.length > 1 + ) { + descriptor.path = `$[${index}]` + } + + return descriptor + }) - return descriptor + presentationSubmission.descriptor_map.push(...descriptorMap) }) - presentationSubmission.descriptor_map.push(...descriptorMap) - }) - - return { - verifiablePresentations: verifiablePresentationResultsWithFormat.map((resultWithFormat) => - getVerifiablePresentationFromEncoded( - agentContext, - resultWithFormat.verifiablePresentationResult.verifiablePresentation - ) - ), - presentationSubmission, - presentationSubmissionLocation: - verifiablePresentationResultsWithFormat[0].verifiablePresentationResult.presentationSubmissionLocation, + return { + verifiablePresentations: verifiablePresentationResultsWithFormat.map((resultWithFormat) => + getVerifiablePresentationFromEncoded( + agentContext, + resultWithFormat.verifiablePresentationResult.verifiablePresentation + ) + ), + presentationSubmission, + presentationSubmissionLocation: + verifiablePresentationResultsWithFormat[0].verifiablePresentationResult.presentationSubmissionLocation, + } } } diff --git a/packages/core/src/modules/dif-presentation-exchange/utils/credentialSelection.ts b/packages/core/src/modules/dif-presentation-exchange/utils/credentialSelection.ts index 4d53f127d6..38f798e797 100644 --- a/packages/core/src/modules/dif-presentation-exchange/utils/credentialSelection.ts +++ b/packages/core/src/modules/dif-presentation-exchange/utils/credentialSelection.ts @@ -31,8 +31,8 @@ export async function getCredentialsForRequest( credentialRecords: Array ): Promise { const encodedCredentials = credentialRecords - .filter((c) => c instanceof MdocRecord === false) - .map((c) => getSphereonOriginalVerifiableCredential(c as SdJwtVcRecord | W3cCredentialRecord)) + .filter((c): c is Exclude => c instanceof MdocRecord === false) + .map((c) => getSphereonOriginalVerifiableCredential(c)) const { mdocPresentationDefinition, nonMdocPresentationDefinition } = MdocDeviceResponse.partitionPresentationDefinition(presentationDefinition) @@ -41,12 +41,6 @@ export async function getCredentialsForRequest( const selectResults: CredentialRecordSelectResults = { ...selectResultsRaw, - areRequiredCredentialsPresent: - mdocPresentationDefinition.input_descriptors.length >= 1 - ? 'warn' // we don't know yet wheater the required credentials are present - : nonMdocPresentationDefinition.input_descriptors.length >= 1 - ? selectResultsRaw.areRequiredCredentialsPresent - : 'error', // Map the encoded credential to their respective w3c credential record verifiableCredential: selectResultsRaw.verifiableCredential?.map((selectedEncoded): SubmissionEntryCredential => { const credentialRecordIndex = encodedCredentials.findIndex((encoded) => { @@ -126,7 +120,11 @@ export async function getCredentialsForRequest( ) } - if (submissionRequirementMatch.vc_path.length >= 1) selectResults.matches.push(submissionRequirementMatch) + if (submissionRequirementMatch.vc_path.length >= 1) { + selectResults.matches.push(submissionRequirementMatch) + } else { + selectResultsRaw.areRequiredCredentialsPresent = 'error' + } } const presentationSubmission: DifPexCredentialsForRequest = { diff --git a/packages/core/src/modules/mdoc/Mdoc.ts b/packages/core/src/modules/mdoc/Mdoc.ts index d78754f74c..182ea2e052 100644 --- a/packages/core/src/modules/mdoc/Mdoc.ts +++ b/packages/core/src/modules/mdoc/Mdoc.ts @@ -1,4 +1,4 @@ -import type { MdocCreateOptions, MdocNameSpaces, MdocVerifyOptions } from './MdocOptions' +import type { MdocSignOptions, MdocNameSpaces, MdocVerifyOptions } from './MdocOptions' import type { AgentContext } from '../../agent' import type { IssuerSignedDocument } from '@protokoll/mdoc-client' @@ -62,7 +62,7 @@ export class Mdoc { return this.issuerSignedDocument.allIssuerSignedNamespaces } - public static async create(agentContext: AgentContext, options: MdocCreateOptions) { + public static async sign(agentContext: AgentContext, options: MdocSignOptions) { const { docType, validityInfo, namespaces, holderPublicKey, issuerCertificate } = options const mdocContext = getMdocContext(agentContext) @@ -77,11 +77,30 @@ export class Mdoc { } const cert = X509Certificate.fromEncodedCertificate(issuerCertificate) - const issuerPrivateJwk = await getJwkFromKey(options.issuerKey ?? cert.publicKey) + const issuerKey = await getJwkFromKey(options.issuerKey ?? cert.publicKey) + + const alg = issuerKey.supportedSignatureAlgorithms.find( + (alg): alg is JwaSignatureAlgorithm.ES256 | JwaSignatureAlgorithm.ES384 | JwaSignatureAlgorithm.ES512 => { + return ( + alg === JwaSignatureAlgorithm.ES256 || + alg === JwaSignatureAlgorithm.ES384 || + alg === JwaSignatureAlgorithm.ES512 + ) + } + ) + + if (!alg) { + throw new MdocError( + `Cannot find a suitable JwaSignatureAlgorithm for signing the mdoc. Supported algorithms are 'ES256', 'ES384', 'ES512'. The issuer key supports: ${issuerKey.supportedSignatureAlgorithms.join( + ', ' + )}` + ) + } + const issuerSignedDocument = await document.sign( { - issuerPrivateKey: issuerPrivateJwk.toJson(), - alg: issuerPrivateJwk.supportedSignatureAlgorithms[0] as 'ES256' | 'ES384' | 'ES512' | 'EdDSA', + issuerPrivateKey: issuerKey.toJson(), + alg, issuerCertificate, kid: cert.publicKey.fingerprint, }, @@ -91,7 +110,10 @@ export class Mdoc { return new Mdoc(issuerSignedDocument) } - public async verify(agentContext: AgentContext, options?: MdocVerifyOptions): Promise { + public async verify( + agentContext: AgentContext, + options?: MdocVerifyOptions + ): Promise<{ isValid: true } | { isValid: false; error: string }> { const trustedCerts = options?.trustedCertificates ?? agentContext.dependencyManager.resolve(X509ModuleConfig).trustedCertificates @@ -113,9 +135,9 @@ export class Mdoc { ) await verifier.verifyData({ mdoc: this.issuerSignedDocument }, mdocContext) - return true + return { isValid: true } } catch (error) { - return false + return { isValid: false, error: error.message } } } } diff --git a/packages/core/src/modules/mdoc/MdocApi.ts b/packages/core/src/modules/mdoc/MdocApi.ts index 2287ed3ac9..a9e71f302b 100644 --- a/packages/core/src/modules/mdoc/MdocApi.ts +++ b/packages/core/src/modules/mdoc/MdocApi.ts @@ -1,4 +1,4 @@ -import type { MdocCreateOptions, MdocVerifyOptions } from './MdocOptions' +import type { MdocSignOptions, MdocVerifyOptions } from './MdocOptions' import type { MdocRecord } from './repository' import type { Query, QueryOptions } from '../../storage/StorageService' @@ -24,10 +24,10 @@ export class MdocApi { /** * Create a new Mdoc, with a spcific doctype, namespace, and validity info. * - * @param options {MdocCreateOptions} + * @param options {MdocSignOptions} * @returns {Promise} */ - public async create(options: MdocCreateOptions) { + public async create(options: MdocSignOptions) { return await this.mdocService.createMdoc(this.agentContext, options) } diff --git a/packages/core/src/modules/mdoc/MdocContext.ts b/packages/core/src/modules/mdoc/MdocContext.ts index 50271133ec..ce50b862c7 100644 --- a/packages/core/src/modules/mdoc/MdocContext.ts +++ b/packages/core/src/modules/mdoc/MdocContext.ts @@ -6,7 +6,7 @@ import { p256 } from '@noble/curves/p256' import { hkdf } from '@noble/hashes/hkdf' import { sha256 } from '@noble/hashes/sha2' import * as x509 from '@peculiar/x509' -import { exportJwk, importX509, verifyWithJwk } from '@protokoll/crypto' +import { exportJwk, importX509 } from '@protokoll/crypto' import { CredoWebCrypto, getJwkFromJson, Hasher } from '../../crypto' import { Buffer, TypedArrayEncoder } from '../../utils' @@ -54,8 +54,13 @@ export const getMdocContext = (agentContext: AgentContext): MdocContext => { }, verify: async (input) => { const { mac0, jwk, options } = input - const { data, signature, alg } = mac0.getRawVerificationData(options) - return await verifyWithJwk({ jwk, signature, data, alg }) + const { data, signature } = mac0.getRawVerificationData(options) + + return await agentContext.wallet.verify({ + key: getJwkFromJson(jwk as JwkJson).key, + data: Buffer.from(data), + signature: new Buffer(signature), + }) }, }, sign1: { @@ -69,8 +74,12 @@ export const getMdocContext = (agentContext: AgentContext): MdocContext => { }, verify: async (input) => { const { sign1, jwk, options } = input - const { data, signature, alg } = sign1.getRawVerificationData(options) - return await verifyWithJwk({ jwk, signature, data, alg, crypto }) + const { data, signature } = sign1.getRawVerificationData(options) + return await agentContext.wallet.verify({ + key: getJwkFromJson(jwk as JwkJson).key, + data: Buffer.from(data), + signature: new Buffer(signature), + }) }, }, }, @@ -82,6 +91,11 @@ export const getMdocContext = (agentContext: AgentContext): MdocContext => { return x509Certificate.getIssuerNameField(field) }, getPublicKey: async (input) => { + //const comp = X509Certificate.fromRawCertificate(input.certificate) + //const x = getJwkFromKey(comp.publicKey).toJson() + //////// eslint-disable-next-line @typescript-eslint/no-unused-vars + //return x + const certificate = new x509.X509Certificate(input.certificate) const key = await importX509({ x509: certificate.toString(), diff --git a/packages/core/src/modules/mdoc/MdocDeviceResponse.ts b/packages/core/src/modules/mdoc/MdocDeviceResponse.ts index 6f16ae5fac..eab94919dd 100644 --- a/packages/core/src/modules/mdoc/MdocDeviceResponse.ts +++ b/packages/core/src/modules/mdoc/MdocDeviceResponse.ts @@ -16,6 +16,7 @@ import { import { getJwkFromKey } from '../../crypto/jose/jwk/transform' import { CredoError } from '../../error' +import { uuid } from '../../utils/uuid' import { X509Certificate } from '../x509/X509Certificate' import { X509ModuleConfig } from '../x509/X509ModuleConfig' @@ -136,7 +137,6 @@ export class MdocDeviceResponse { const publicDeviceJwk = COSEKey.import(deviceKeyInfo.deviceKey).toJWK() - deviceKeyInfo.deviceKey const deviceResponseBuilder = await DeviceResponse.from(mdoc) // eslint-disable-next-line @typescript-eslint/no-explicit-any .usingPresentationDefinition(presentationDefinition as any) @@ -152,7 +152,7 @@ export class MdocDeviceResponse { return { deviceResponseBase64Url: TypedArrayEncoder.toBase64URL(deviceResponseMdoc.encode()), presentationSubmission: MdocDeviceResponse.createPresentationSubmission({ - id: 'MdocPresentationSubmission ' + agentContext.wallet.generateNonce(), + id: 'MdocPresentationSubmission ' + uuid(), presentationDefinition, }), } diff --git a/packages/core/src/modules/mdoc/MdocOptions.ts b/packages/core/src/modules/mdoc/MdocOptions.ts index 29767f6251..8a73a1391b 100644 --- a/packages/core/src/modules/mdoc/MdocOptions.ts +++ b/packages/core/src/modules/mdoc/MdocOptions.ts @@ -1,6 +1,6 @@ import type { Mdoc } from './Mdoc' import type { Key } from '../../crypto/Key' -import type { DifPresentationExchangeDefinitionV2 } from '../dif-presentation-exchange' +import type { DifPresentationExchangeDefinition } from '../dif-presentation-exchange' import type { ValidityInfo, MdocNameSpaces } from '@protokoll/mdoc-client' export type { MdocNameSpaces } from '@protokoll/mdoc-client' @@ -19,7 +19,7 @@ export type MdocOpenId4VpSessionTranscriptOptions = { export type MdocDeviceResponseOpenId4VpOptions = { mdocs: [Mdoc, ...Mdoc[]] - presentationDefinition: DifPresentationExchangeDefinitionV2 + presentationDefinition: DifPresentationExchangeDefinition deviceNameSpaces?: MdocNameSpaces sessionTranscriptOptions: MdocOpenId4VpSessionTranscriptOptions } @@ -40,7 +40,7 @@ export type MdocDeviceResponseVerifyOptions = { now?: Date } -export type MdocCreateOptions = { +export type MdocSignOptions = { // eslint-disable-next-line @typescript-eslint/ban-types docType: 'org.iso.18013.5.1.mDL' | (string & {}) validityInfo?: Partial diff --git a/packages/core/src/modules/mdoc/MdocService.ts b/packages/core/src/modules/mdoc/MdocService.ts index 3e8d0f72e7..df966f2b30 100644 --- a/packages/core/src/modules/mdoc/MdocService.ts +++ b/packages/core/src/modules/mdoc/MdocService.ts @@ -1,5 +1,5 @@ import type { - MdocCreateOptions, + MdocSignOptions, MdocDeviceResponseOpenId4VpOptions, MdocDeviceResponseVerifyOptions, MdocVerifyOptions, @@ -29,8 +29,8 @@ export class MdocService { return Mdoc.fromBase64Url(hexEncodedMdoc) } - public createMdoc(agentContext: AgentContext, options: MdocCreateOptions) { - return Mdoc.create(agentContext, options) + public createMdoc(agentContext: AgentContext, options: MdocSignOptions) { + return Mdoc.sign(agentContext, options) } public async verifyMdoc(agentContext: AgentContext, mdoc: Mdoc, options: MdocVerifyOptions) { diff --git a/packages/core/src/modules/mdoc/__tests__/mdoc.deviceResponse.openid4vp.test.ts b/packages/core/src/modules/mdoc/__tests__/mdoc.deviceResponse.openid4vp.test.ts index 4a44f79c15..6f0140d1f1 100644 --- a/packages/core/src/modules/mdoc/__tests__/mdoc.deviceResponse.openid4vp.test.ts +++ b/packages/core/src/modules/mdoc/__tests__/mdoc.deviceResponse.openid4vp.test.ts @@ -142,7 +142,7 @@ describe('mdoc device-response openid4vp test', () => { // this is the ISSUER side { - mdoc = await Mdoc.create(agent.context, { + mdoc = await Mdoc.sign(agent.context, { docType: 'org.iso.18013.5.1.mDL', validityInfo: { signed: new Date('2023-10-24'), diff --git a/packages/core/src/modules/mdoc/__tests__/mdoc.deviceResponse.test.ts b/packages/core/src/modules/mdoc/__tests__/mdoc.deviceResponse.test.ts index 9a9fa4e82e..5609c93ff0 100644 --- a/packages/core/src/modules/mdoc/__tests__/mdoc.deviceResponse.test.ts +++ b/packages/core/src/modules/mdoc/__tests__/mdoc.deviceResponse.test.ts @@ -33,7 +33,7 @@ describe('mdoc device-response test', () => { const issuerCertificate = selfSignedCertificate.toString('pem') - const mdoc = await Mdoc.create(agent.context, { + const mdoc = await Mdoc.sign(agent.context, { docType: 'org.iso.18013.5.1.mDL', holderPublicKey: holderKey, namespaces: { diff --git a/packages/core/src/modules/mdoc/__tests__/mdoc.service.test.ts b/packages/core/src/modules/mdoc/__tests__/mdoc.service.test.ts index ea36c4bb12..c6cd67ae4d 100644 --- a/packages/core/src/modules/mdoc/__tests__/mdoc.service.test.ts +++ b/packages/core/src/modules/mdoc/__tests__/mdoc.service.test.ts @@ -53,7 +53,7 @@ describe('mdoc service test', () => { const issuerCertificate = selfSignedCertificate.toString('pem') - const mdoc = await Mdoc.create(agentContext, { + const mdoc = await Mdoc.sign(agentContext, { docType: 'org.iso.18013.5.1.mDL', holderPublicKey: holderKey, namespaces: { @@ -77,10 +77,10 @@ describe('mdoc service test', () => { expect(() => mdoc.deviceSignedNamespaces).toThrow() - const res = await mdoc.verify(agentContext, { + const { isValid } = await mdoc.verify(agentContext, { trustedCertificates: [selfSignedCertificate.toString('base64')], }) - expect(res).toBeTruthy() + expect(isValid).toBeTruthy() }) test('can decode claims from namespaces', async () => { @@ -123,7 +123,7 @@ describe('mdoc service test', () => { test('can verify sprindFunkeTestVector Issuer Signed', async () => { const mdoc = Mdoc.fromBase64Url(sprindFunkeTestVectorBase64Url) const now = new Date('2024-08-12T14:50:42.124Z') - const isValid = await mdoc.verify(agentContext, { + const { isValid } = await mdoc.verify(agentContext, { trustedCertificates: [sprindFunkeX509TrustedCertificate], now, }) diff --git a/packages/core/src/modules/proofs/formats/dif-presentation-exchange/DifPresentationExchangeProofFormatService.ts b/packages/core/src/modules/proofs/formats/dif-presentation-exchange/DifPresentationExchangeProofFormatService.ts index 9ca9168512..39dae088be 100644 --- a/packages/core/src/modules/proofs/formats/dif-presentation-exchange/DifPresentationExchangeProofFormatService.ts +++ b/packages/core/src/modules/proofs/formats/dif-presentation-exchange/DifPresentationExchangeProofFormatService.ts @@ -209,6 +209,10 @@ export class DifPresentationExchangeProofFormatService domain: options?.domain, }) + if (!presentation) { + throw new CredoError('Failed to create presentation for request.') + } + if (presentation.verifiablePresentations.length > 1) { throw new CredoError('Invalid amount of verifiable presentations. Only one is allowed.') } diff --git a/packages/openid4vc/src/openid4vc-holder/OpenId4vcSiopHolderService.ts b/packages/openid4vc/src/openid4vc-holder/OpenId4vcSiopHolderService.ts index 06e9292691..5c7ccb7486 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4vcSiopHolderService.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4vcSiopHolderService.ts @@ -116,7 +116,7 @@ export class OpenId4VcSiopHolderService { mdocGeneratedNonce: await agentContext.wallet.generateNonce(), responseUri: authorizationRequest.authorizationRequestPayload.response_uri ?? - authorizationRequest.authorizationRequestPayload.response_uri, + authorizationRequest.authorizationRequestPayload.request_uri, }, }) diff --git a/packages/openid4vc/src/shared/transform.ts b/packages/openid4vc/src/shared/transform.ts index 6e416322de..d73cfa638c 100644 --- a/packages/openid4vc/src/shared/transform.ts +++ b/packages/openid4vc/src/shared/transform.ts @@ -46,7 +46,7 @@ export function getSphereonVerifiablePresentation( } else if (verifiablePresentation instanceof W3cJwtVerifiablePresentation) { return verifiablePresentation.serializedJwt } else if (verifiablePresentation instanceof MdocVerifiablePresentation) { - throw new CredoError('Mdoc verifiable credential is not yet supported.') + throw new CredoError('Mdoc verifiable presentation is not yet supported.') } else { return verifiablePresentation.compact } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ed95e2ff16..8b3c4a2fda 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -433,14 +433,14 @@ importers: specifier: ^1.11.0 version: 1.12.1 '@protokoll/core': - specifier: 0.2.26 - version: 0.2.26(typescript@5.5.4) + specifier: 0.2.27 + version: 0.2.27(typescript@5.5.4) '@protokoll/crypto': - specifier: 0.2.26 - version: 0.2.26(typescript@5.5.4) + specifier: 0.2.27 + version: 0.2.27(typescript@5.5.4) '@protokoll/mdoc-client': - specifier: 0.2.26 - version: 0.2.26(typescript@5.5.4) + specifier: 0.2.27 + version: 0.2.27(typescript@5.5.4) '@sd-jwt/core': specifier: ^0.7.0 version: 0.7.2 @@ -2335,14 +2335,14 @@ packages: '@protobufjs/utf8@1.1.0': resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} - '@protokoll/core@0.2.26': - resolution: {integrity: sha512-mZeFrBKBvjS++c/ooi0sI0yN/RlgrJKWIfSz4QJMmUKBlRpGvGVF6UPxlDWKBZwkxZAAWLoW/GzMg20gPKqW5g==} + '@protokoll/core@0.2.27': + resolution: {integrity: sha512-9+SOTmrehKxfb3UJBleplC8tZ99TBZDCqgB54L9JAsApqJqSRVhU/kTm4XWknn7pG5Sq4oUtQifo4Eo2nrmOhw==} - '@protokoll/crypto@0.2.26': - resolution: {integrity: sha512-CpJm0n7QvwITwOUa+E0jiiv2KayAAOOrW6bufZ1XIdxED7v2+0zQh/Li5hJip1N13T3YZpyYDX8qonE6e1ZXVA==} + '@protokoll/crypto@0.2.27': + resolution: {integrity: sha512-dIp5t7T5muW+G0leFNC80ra8VCXOnQNebQ9bIGuwskrzdEdY4KS2ZrljaQIu61fKx/YBFLhHkM3TO0hgrATlSg==} - '@protokoll/mdoc-client@0.2.26': - resolution: {integrity: sha512-LN7Rk5UWtD6VHl7jLh/Qlsa3XPssfypJ8tzIEjOnRtDxLO5I1RrFWdcZgT4IMcJRswTx0MheIQ1+jSayjGo/7w==} + '@protokoll/mdoc-client@0.2.27': + resolution: {integrity: sha512-1z7ZLVgsInGsFW8b+VhhLGZR0rS8emZxXfRTkcZTyePSSpQilT4IoINBTfUeNMj47ANqkMBsgHmZs9UEKNSYSQ==} '@react-native-community/cli-clean@10.1.1': resolution: {integrity: sha512-iNsrjzjIRv9yb5y309SWJ8NDHdwYtnCpmxZouQDyOljUdC9MwdZ4ChbtA4rwQyAwgOVfS9F/j56ML3Cslmvrxg==} @@ -9931,7 +9931,7 @@ snapshots: '@protobufjs/utf8@1.1.0': {} - '@protokoll/core@0.2.26(typescript@5.5.4)': + '@protokoll/core@0.2.27(typescript@5.5.4)': dependencies: '@credo-ts/core': link:packages/core jwt-decode: 4.0.0 @@ -9939,17 +9939,17 @@ snapshots: transitivePeerDependencies: - typescript - '@protokoll/crypto@0.2.26(typescript@5.5.4)': + '@protokoll/crypto@0.2.27(typescript@5.5.4)': dependencies: - '@protokoll/core': 0.2.26(typescript@5.5.4) + '@protokoll/core': 0.2.27(typescript@5.5.4) valibot: 0.37.0(typescript@5.5.4) transitivePeerDependencies: - typescript - '@protokoll/mdoc-client@0.2.26(typescript@5.5.4)': + '@protokoll/mdoc-client@0.2.27(typescript@5.5.4)': dependencies: '@jfromaniello/typedmap': 1.4.0 - '@protokoll/core': 0.2.26(typescript@5.5.4) + '@protokoll/core': 0.2.27(typescript@5.5.4) cbor-x: 1.6.0 compare-versions: 6.1.1 transitivePeerDependencies: