diff --git a/.changeset/beige-adults-compete.md b/.changeset/beige-adults-compete.md new file mode 100644 index 0000000000..c26da00e15 --- /dev/null +++ b/.changeset/beige-adults-compete.md @@ -0,0 +1,6 @@ +--- +"@credo-ts/core": patch +"@credo-ts/openid4vc": patch +--- + +feat: mdoc device response and presentation over oid4vp diff --git a/packages/core/package.json b/packages/core/package.json index 80f00cf4d2..90e7452f2e 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -35,16 +35,16 @@ "@peculiar/asn1-schema": "^2.3.8", "@peculiar/asn1-x509": "^2.3.8", "@peculiar/x509": "^1.11.0", - "@protokoll/mdoc-client": "0.2.35", + "@protokoll/mdoc-client": "0.2.36", "@sd-jwt/core": "^0.7.0", "@sd-jwt/decode": "^0.7.0", "@sd-jwt/jwt-status-list": "^0.7.0", "@sd-jwt/sd-jwt-vc": "^0.7.0", "@sd-jwt/types": "^0.7.0", "@sd-jwt/utils": "^0.7.0", - "@sphereon/pex": "5.0.0-unstable.18", + "@sphereon/pex": "5.0.0-unstable.25", "@sphereon/pex-models": "^2.3.1", - "@sphereon/ssi-types": "^0.30.1", + "@sphereon/ssi-types": "0.30.2-next.135", "@stablelib/ed25519": "^1.0.2", "@types/ws": "^8.5.4", "abort-controller": "^3.0.0", diff --git a/packages/core/src/crypto/jose/jwt/Jwt.ts b/packages/core/src/crypto/jose/jwt/Jwt.ts index 90302f72e8..6de8095c41 100644 --- a/packages/core/src/crypto/jose/jwt/Jwt.ts +++ b/packages/core/src/crypto/jose/jwt/Jwt.ts @@ -23,7 +23,7 @@ interface JwtOptions { } export class Jwt { - private static format = /^[A-Za-z0-9-_=]+\.[A-Za-z0-9-_=]+\.?[A-Za-z0-9-_.+/=]*$/ + public static format = /^[A-Za-z0-9-_=]+\.[A-Za-z0-9-_=]+\.?[A-Za-z0-9-_.+/=]*$/ public readonly payload: JwtPayload public readonly header: JwtHeader 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 d1c4e17a27..bcf4aa7498 100644 --- a/packages/core/src/modules/dif-presentation-exchange/models/DifPexCredentialsForRequest.ts +++ b/packages/core/src/modules/dif-presentation-exchange/models/DifPexCredentialsForRequest.ts @@ -1,7 +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' +import type { MdocNameSpaces } from '@protokoll/mdoc-client' export interface DifPexCredentialsForRequest { /** @@ -134,7 +134,7 @@ export type SubmissionEntryCredential = | { type: ClaimFormat.MsoMdoc credentialRecord: MdocRecord - disclosedPayload: Record + disclosedPayload: MdocNameSpaces } /** 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 5f78fa8aba..1a86a09526 100644 --- a/packages/core/src/modules/dif-presentation-exchange/models/index.ts +++ b/packages/core/src/modules/dif-presentation-exchange/models/index.ts @@ -1,6 +1,5 @@ export * from './DifPexCredentialsForRequest' -import type { Mdoc } from '../../mdoc' -import type { MdocVerifiablePresentation } from '../../mdoc/MdocVerifiablePresentation' +import type { Mdoc, MdocDeviceResponse } from '../../mdoc' import type { SdJwtVc } from '../../sd-jwt-vc' import type { W3cVerifiableCredential, W3cVerifiablePresentation } from '../../vc' import type { PresentationDefinitionV1, PresentationDefinitionV2, PresentationSubmission } from '@sphereon/pex-models' @@ -15,5 +14,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 | MdocVerifiablePresentation +export type VerifiablePresentation = W3cVerifiablePresentation | SdJwtVc | MdocDeviceResponse 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 38f798e797..d63b795cc9 100644 --- a/packages/core/src/modules/dif-presentation-exchange/utils/credentialSelection.ts +++ b/packages/core/src/modules/dif-presentation-exchange/utils/credentialSelection.ts @@ -8,6 +8,7 @@ import type { IPresentationDefinition, SelectResults, SubmissionRequirementMatch import type { InputDescriptorV1, InputDescriptorV2, SubmissionRequirement } from '@sphereon/pex-models' import { decodeSdJwtSync, getClaimsSync } from '@sd-jwt/decode' +import { Status } from '@sphereon/pex' import { SubmissionRequirementMatchType } from '@sphereon/pex/dist/main/lib/evaluation/core' import { Rules } from '@sphereon/pex-models' import { default as jp } from 'jsonpath' @@ -41,6 +42,11 @@ export async function getCredentialsForRequest( const selectResults: CredentialRecordSelectResults = { ...selectResultsRaw, + areRequiredCredentialsPresent: + nonMdocPresentationDefinition.input_descriptors.length === 0 && + mdocPresentationDefinition.input_descriptors.length > 0 + ? Status.INFO + : selectResultsRaw.areRequiredCredentialsPresent, // Map the encoded credential to their respective w3c credential record verifiableCredential: selectResultsRaw.verifiableCredential?.map((selectedEncoded): SubmissionEntryCredential => { const credentialRecordIndex = encodedCredentials.findIndex((encoded) => { 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 a92928e6f5..87bcafdfbb 100644 --- a/packages/core/src/modules/dif-presentation-exchange/utils/transform.ts +++ b/packages/core/src/modules/dif-presentation-exchange/utils/transform.ts @@ -8,9 +8,9 @@ import type { W3CVerifiablePresentation as SphereonW3CVerifiablePresentation, } from '@sphereon/ssi-types' -import { CredoError } from '../../../error' +import { Jwt } from '../../../crypto' import { JsonTransformer } from '../../../utils' -import { MdocVerifiablePresentation } from '../../mdoc/MdocVerifiablePresentation' +import { MdocDeviceResponse } from '../../mdoc' import { SdJwtVcApi } from '../../sd-jwt-vc' import { W3cCredentialRecord, W3cJsonLdVerifiablePresentation, W3cJwtVerifiablePresentation } from '../../vc' @@ -32,8 +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 if (verifiablePresentation instanceof MdocDeviceResponse) { + return verifiablePresentation.base64Url } else { return verifiablePresentation.compact } @@ -47,12 +47,11 @@ export function getVerifiablePresentationFromEncoded( if (typeof encodedVerifiablePresentation === 'string' && encodedVerifiablePresentation.includes('~')) { const sdJwtVcApi = agentContext.dependencyManager.resolve(SdJwtVcApi) return sdJwtVcApi.fromCompact(encodedVerifiablePresentation) - } else if (typeof encodedVerifiablePresentation === 'string') { + } else if (typeof encodedVerifiablePresentation === 'string' && Jwt.format.test(encodedVerifiablePresentation)) { return W3cJwtVerifiablePresentation.fromSerializedJwt(encodedVerifiablePresentation) } 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') + return MdocDeviceResponse.fromBase64Url(encodedVerifiablePresentation) } } diff --git a/packages/core/src/modules/mdoc/Mdoc.ts b/packages/core/src/modules/mdoc/Mdoc.ts index 7c3a8daf98..1239e4840d 100644 --- a/packages/core/src/modules/mdoc/Mdoc.ts +++ b/packages/core/src/modules/mdoc/Mdoc.ts @@ -34,24 +34,26 @@ export class Mdoc { return new Mdoc(issuerSignedDocument) } - public static fromIssuerSignedDocument( + public static fromIssuerSignedDocument(issuerSignedBase64Url: string, expectedDocType?: string): Mdoc { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + + return new Mdoc(parseIssuerSigned(TypedArrayEncoder.fromBase64(issuerSignedBase64Url), expectedDocType)) + } + + public static fromDeviceSignedDocument( issuerSignedBase64Url: string, - deviceSignedBase64Url?: string, + deviceSignedBase64Url: string, expectedDocType?: string ): Mdoc { // eslint-disable-next-line @typescript-eslint/no-explicit-any - if (deviceSignedBase64Url) { - return new Mdoc( - parseDeviceSigned( - TypedArrayEncoder.fromBase64(deviceSignedBase64Url), - TypedArrayEncoder.fromBase64(issuerSignedBase64Url), - expectedDocType - ) + return new Mdoc( + parseDeviceSigned( + TypedArrayEncoder.fromBase64(deviceSignedBase64Url), + TypedArrayEncoder.fromBase64(issuerSignedBase64Url), + expectedDocType ) - } else { - return new Mdoc(parseIssuerSigned(TypedArrayEncoder.fromBase64(issuerSignedBase64Url), expectedDocType)) - } + ) } public get docType(): string { diff --git a/packages/core/src/modules/mdoc/MdocDeviceResponse.ts b/packages/core/src/modules/mdoc/MdocDeviceResponse.ts index 9c842d3b3f..e894fa3a3d 100644 --- a/packages/core/src/modules/mdoc/MdocDeviceResponse.ts +++ b/packages/core/src/modules/mdoc/MdocDeviceResponse.ts @@ -5,7 +5,7 @@ import type { PresentationDefinition } from '@protokoll/mdoc-client' import type { InputDescriptorV2 } from '@sphereon/pex-models' import { - limitDisclosureToInputDescriptor as mdocLimitDisclosureToId, + limitDisclosureToInputDescriptor as mdocLimitDisclosureToInputDescriptor, COSEKey, DeviceResponse, MDoc, @@ -13,6 +13,7 @@ import { Verifier, MDocStatus, cborEncode, + parseDeviceResponse, } from '@protokoll/mdoc-client' import { CredoError } from '../../error' @@ -26,7 +27,29 @@ import { getMdocContext } from './MdocContext' import { MdocError } from './MdocError' export class MdocDeviceResponse { - public constructor() {} + private constructor(public base64Url: string, public documents: Mdoc[]) {} + + public static fromBase64Url(base64Url: string) { + const parsed = parseDeviceResponse(TypedArrayEncoder.fromBase64(base64Url)) + if (parsed.status !== MDocStatus.OK) { + throw new MdocError(`Parsing Mdoc Device Response failed.`) + } + + const documents = parsed.documents.map((doc) => { + const prepared = doc.prepare() + const docType = prepared.get('docType') as string + const issuerSigned = cborEncode(prepared.get('issuerSigned')) + const deviceSigned = cborEncode(prepared.get('deviceSigned')) + + return Mdoc.fromDeviceSignedDocument( + TypedArrayEncoder.toBase64URL(issuerSigned), + TypedArrayEncoder.toBase64URL(deviceSigned), + docType + ) + }) + + return new MdocDeviceResponse(base64Url, documents) + } private static assertMdocInputDescriptor(inputDescriptor: InputDescriptorV2) { if (!inputDescriptor.format || !inputDescriptor.format.mso_mdoc) { @@ -113,7 +136,18 @@ export class MdocDeviceResponse { const inputDescriptor = this.assertMdocInputDescriptor(options.inputDescriptor) const _mdoc = parseIssuerSigned(TypedArrayEncoder.fromBase64(mdoc.base64Url), mdoc.docType) - return mdocLimitDisclosureToId({ mdoc: _mdoc, inputDescriptor }) + + const disclosure = mdocLimitDisclosureToInputDescriptor(_mdoc, inputDescriptor) + const disclosedPayloadAsRecord = Object.fromEntries( + Object.entries(disclosure).map(([namespace, issuerSignedItem]) => { + return [ + namespace, + Object.fromEntries(issuerSignedItem.map((item) => [item.elementIdentifier, item.elementValue])), + ] + }) + ) + + return disclosedPayloadAsRecord } public static async createOpenId4VpDeviceResponse( @@ -160,32 +194,30 @@ export class MdocDeviceResponse { } } - public static async verify(agentContext: AgentContext, options: MdocDeviceResponseVerifyOptions) { + public async verify(agentContext: AgentContext, options: Omit) { const verifier = new Verifier() const mdocContext = getMdocContext(agentContext) - let trustedCerts: [string, ...string[]] | undefined - if (options?.trustedCertificates) { - trustedCerts = options.trustedCertificates - } else if (options?.verificationContext) { - agentContext.dependencyManager.resolve(X509ModuleConfig).getTrustedCertificatesForVerification - trustedCerts = await agentContext.dependencyManager - .resolve(X509ModuleConfig) - .getTrustedCertificatesForVerification?.(agentContext, options.verificationContext) - } else { - trustedCerts = agentContext.dependencyManager.resolve(X509ModuleConfig).trustedCertificates - } + const x509ModuleConfig = agentContext.dependencyManager.resolve(X509ModuleConfig) + const getTrustedCertificatesForVerification = x509ModuleConfig.getTrustedCertificatesForVerification - if (!trustedCerts) { + const trustedCertificates = + options.trustedCertificates ?? + (await getTrustedCertificatesForVerification?.(agentContext, options.verificationContext)) ?? + x509ModuleConfig?.trustedCertificates + + if (!trustedCertificates) { throw new MdocError('No trusted certificates found. Cannot verify mdoc.') } const result = await verifier.verifyDeviceResponse( { - encodedDeviceResponse: TypedArrayEncoder.fromBase64(options.deviceResponse), + encodedDeviceResponse: TypedArrayEncoder.fromBase64(this.base64Url), //ephemeralReaderKey: options.verifierKey ? getJwkFromKey(options.verifierKey).toJson() : undefined, encodedSessionTranscript: DeviceResponse.calculateSessionTranscriptForOID4VP(options.sessionTranscriptOptions), - trustedCertificates: trustedCerts.map((cert) => X509Certificate.fromEncodedCertificate(cert).rawCertificate), + trustedCertificates: trustedCertificates.map( + (cert) => X509Certificate.fromEncodedCertificate(cert).rawCertificate + ), now: options.now, }, mdocContext @@ -199,17 +231,6 @@ export class MdocDeviceResponse { throw new MdocError('Device response verification failed. An unknown error occurred.') } - return result.documents.map((doc) => { - const prepared = doc.prepare() - const docType = prepared.get('docType') as string - const issuerSigned = cborEncode(prepared.get('issuerSigned')) - const deviceSigned = cborEncode(prepared.get('deviceSigned')) - - return Mdoc.fromIssuerSignedDocument( - TypedArrayEncoder.toBase64URL(issuerSigned), - TypedArrayEncoder.toBase64URL(deviceSigned), - docType - ) - }) + return this.documents } } diff --git a/packages/core/src/modules/mdoc/MdocService.ts b/packages/core/src/modules/mdoc/MdocService.ts index b60b7eeed3..a3cdca3764 100644 --- a/packages/core/src/modules/mdoc/MdocService.ts +++ b/packages/core/src/modules/mdoc/MdocService.ts @@ -42,7 +42,8 @@ export class MdocService { } public async verifyDeviceResponse(agentContext: AgentContext, options: MdocDeviceResponseVerifyOptions) { - return MdocDeviceResponse.verify(agentContext, options) + const deviceResponse = MdocDeviceResponse.fromBase64Url(options.deviceResponse) + return deviceResponse.verify(agentContext, options) } public async store(agentContext: AgentContext, mdoc: Mdoc) { diff --git a/packages/core/src/modules/mdoc/MdocVerifiablePresentation.ts b/packages/core/src/modules/mdoc/MdocVerifiablePresentation.ts deleted file mode 100644 index 1637b2aaa7..0000000000 --- a/packages/core/src/modules/mdoc/MdocVerifiablePresentation.ts +++ /dev/null @@ -1,3 +0,0 @@ -export class MdocVerifiablePresentation { - public constructor(public readonly deviceSignedBase64Url: string) {} -} diff --git a/packages/core/src/modules/mdoc/__tests__/mdocDeviceResponse.test.ts b/packages/core/src/modules/mdoc/__tests__/mdocDeviceResponse.test.ts index 36025a044d..b1610bf528 100644 --- a/packages/core/src/modules/mdoc/__tests__/mdocDeviceResponse.test.ts +++ b/packages/core/src/modules/mdoc/__tests__/mdocDeviceResponse.test.ts @@ -71,12 +71,11 @@ describe('mdoc device-response test', () => { }, }) - 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') + expect(limitedDisclosedPayload).toStrictEqual({ + hello: { + world: 'from-mdoc', + nicer: 'dicer', + }, + }) }) }) diff --git a/packages/core/src/modules/mdoc/__tests__/mdocOpenId4VcDeviceResponse.test.ts b/packages/core/src/modules/mdoc/__tests__/mdocOpenId4VcDeviceResponse.test.ts index 6a0572b57d..b10bb19af4 100644 --- a/packages/core/src/modules/mdoc/__tests__/mdocOpenId4VcDeviceResponse.test.ts +++ b/packages/core/src/modules/mdoc/__tests__/mdocOpenId4VcDeviceResponse.test.ts @@ -211,7 +211,7 @@ describe('mdoc device-response openid4vp test', () => { const docType = prepared.get('docType') as string const issuerSigned = cborEncode(prepared.get('issuerSigned')) const deviceSigned = cborEncode(prepared.get('deviceSigned')) - parsedDocument = Mdoc.fromIssuerSignedDocument( + parsedDocument = Mdoc.fromDeviceSignedDocument( TypedArrayEncoder.toBase64URL(issuerSigned), TypedArrayEncoder.toBase64URL(deviceSigned), docType @@ -220,8 +220,8 @@ describe('mdoc device-response openid4vp test', () => { }) it('should be verifiable', async () => { - const res = await MdocDeviceResponse.verify(agent.context, { - deviceResponse, + const mdocDeviceResponse = MdocDeviceResponse.fromBase64Url(deviceResponse) + const res = await mdocDeviceResponse.verify(agent.context, { trustedCertificates: [ISSUER_CERTIFICATE], sessionTranscriptOptions: { clientId, @@ -246,9 +246,9 @@ describe('mdoc device-response openid4vp test', () => { } it(`with a different ${name}`, async () => { try { - await MdocDeviceResponse.verify(agent.context, { + const mdocDeviceResponse = MdocDeviceResponse.fromBase64Url(deviceResponse) + await mdocDeviceResponse.verify(agent.context, { trustedCertificates: [ISSUER_CERTIFICATE], - deviceResponse, sessionTranscriptOptions: { clientId: values.clientId, responseUri: values.responseUri, diff --git a/packages/core/src/modules/mdoc/index.ts b/packages/core/src/modules/mdoc/index.ts index 0944642cd2..f6503f4ae2 100644 --- a/packages/core/src/modules/mdoc/index.ts +++ b/packages/core/src/modules/mdoc/index.ts @@ -5,4 +5,4 @@ export * from './MdocError' export * from './MdocOptions' export * from './repository' export * from './Mdoc' -export * from './MdocVerifiablePresentation' +export * from './MdocDeviceResponse' 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 39dae088be..0a35d13af2 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,7 +39,7 @@ import { DifPresentationExchangeService, DifPresentationExchangeSubmissionLocation, } from '../../../dif-presentation-exchange' -import { MdocVerifiablePresentation } from '../../../mdoc' +import { MdocDeviceResponse } from '../../../mdoc' import { ANONCREDS_DATA_INTEGRITY_CRYPTOSUITE, AnonCredsDataIntegrityServiceSymbol, @@ -228,8 +228,8 @@ export class DifPresentationExchangeProofFormatService firstPresentation instanceof W3cJwtVerifiablePresentation || firstPresentation instanceof W3cJsonLdVerifiablePresentation ? firstPresentation.encoded - : firstPresentation instanceof MdocVerifiablePresentation - ? firstPresentation.deviceSignedBase64Url + : firstPresentation instanceof MdocDeviceResponse + ? firstPresentation.base64Url : firstPresentation?.compact const attachment = this.getFormatData(encodedFirstPresentation, format.attachmentId) diff --git a/packages/openid4vc/package.json b/packages/openid4vc/package.json index 0668288dd6..d56d6b0bae 100644 --- a/packages/openid4vc/package.json +++ b/packages/openid4vc/package.json @@ -27,12 +27,12 @@ }, "dependencies": { "@credo-ts/core": "workspace:*", - "@sphereon/did-auth-siop": "0.16.1-next.168", - "@sphereon/oid4vc-common": "0.16.1-next.168", - "@sphereon/oid4vci-client": "0.16.1-next.168", - "@sphereon/oid4vci-common": "0.16.1-next.168", - "@sphereon/oid4vci-issuer": "0.16.1-next.168", - "@sphereon/ssi-types": "^0.30.1", + "@sphereon/did-auth-siop": "0.16.1-fix.173", + "@sphereon/oid4vc-common": "0.16.1-fix.173", + "@sphereon/oid4vci-client": "0.16.1-fix.173", + "@sphereon/oid4vci-common": "0.16.1-fix.173", + "@sphereon/oid4vci-issuer": "0.16.1-fix.173", + "@sphereon/ssi-types": "0.30.2-next.135", "class-transformer": "^0.5.1", "rxjs": "^7.8.0" }, diff --git a/packages/openid4vc/src/openid4vc-holder/OpenId4vcSiopHolderService.ts b/packages/openid4vc/src/openid4vc-holder/OpenId4vcSiopHolderService.ts index ce98e14ddc..09f1629fe0 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4vcSiopHolderService.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4vcSiopHolderService.ts @@ -25,7 +25,7 @@ import { getJwkFromJson, injectable, parseDid, - MdocVerifiablePresentation, + MdocDeviceResponse, } from '@credo-ts/core' import { OP, ResponseIss, ResponseMode, ResponseType, SupportedVersion, VPTokenLocation } from '@sphereon/did-auth-siop' @@ -104,6 +104,13 @@ export class OpenId4VcSiopHolderService { throw new CredoError("Unable to extract 'client_id' from authorization request") } + const responseUri = + (await authorizationRequest.authorizationRequest.getMergedProperty('response_uri')) ?? + (await authorizationRequest.authorizationRequest.getMergedProperty('redirect_uri')) + if (!responseUri) { + throw new CredoError("Unable to extract 'response_uri' from authorization request") + } + const { verifiablePresentations, presentationSubmission } = await this.presentationExchangeService.createPresentation(agentContext, { credentialsForInputDescriptor: presentationExchange.credentials, @@ -113,9 +120,7 @@ export class OpenId4VcSiopHolderService { presentationSubmissionLocation: DifPresentationExchangeSubmissionLocation.EXTERNAL, openid4vp: { mdocGeneratedNonce: authorizationResponseNonce, - responseUri: - authorizationRequest.authorizationRequestPayload.response_uri ?? - authorizationRequest.authorizationRequestPayload.request_uri, + responseUri, }, }) @@ -283,7 +288,7 @@ 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) { + } else if (verifiablePresentation instanceof MdocDeviceResponse) { throw new CredoError('Mdoc Verifiable Presentations are not yet supported') } else { const cnf = verifiablePresentation.payload.cnf @@ -384,7 +389,7 @@ export class OpenId4VcSiopHolderService { kid: jwkJson.kid, }, encryptionAlgorithm: options.enc, - apu: TypedArrayEncoder.toBase64URL(TypedArrayEncoder.fromString(await agentContext.wallet.generateNonce())), + apu: TypedArrayEncoder.toBase64URL(TypedArrayEncoder.fromString(options.authorizationResponseNonce)), apv: TypedArrayEncoder.toBase64URL(TypedArrayEncoder.fromString(options.authorizationRequestNonce)), }) diff --git a/packages/openid4vc/src/openid4vc-verifier/OpenId4VcSiopVerifierService.ts b/packages/openid4vc/src/openid4vc-verifier/OpenId4VcSiopVerifierService.ts index b6e6450e22..7ff3e15a64 100644 --- a/packages/openid4vc/src/openid4vc-verifier/OpenId4VcSiopVerifierService.ts +++ b/packages/openid4vc/src/openid4vc-verifier/OpenId4VcSiopVerifierService.ts @@ -40,6 +40,9 @@ import { getDomainFromUrl, KeyType, getJwkFromKey, + MdocDeviceResponse, + TypedArrayEncoder, + Jwt, } from '@credo-ts/core' import { AuthorizationRequest, @@ -54,7 +57,7 @@ import { RP, SupportedVersion, } from '@sphereon/did-auth-siop' -import { extractPresentationsFromAuthorizationResponse } from '@sphereon/did-auth-siop/dist/authorization-response/OpenID4VP' +import { extractPresentationsFromVpToken } from '@sphereon/did-auth-siop/dist/authorization-response/OpenID4VP' import { filter, first, firstValueFrom, map, timeout } from 'rxjs' import { storeActorIdForContextCorrelationId } from '../shared/router' @@ -214,6 +217,7 @@ export class OpenId4VcSiopVerifierService { agentContext: AgentContext, options: OpenId4VcSiopVerifyAuthorizationResponseOptions & { verificationSession: OpenId4VcVerificationSessionRecord + jarmHeader?: { apu?: string; apv?: string } } ): Promise { // Assert state @@ -229,6 +233,7 @@ export class OpenId4VcSiopVerifierService { const requestClientId = await authorizationRequest.getMergedProperty('client_id') const requestNonce = await authorizationRequest.getMergedProperty('nonce') const requestState = await authorizationRequest.getMergedProperty('state') + const responseUri = await authorizationRequest.getMergedProperty('response_uri') const presentationDefinitionsWithLocation = await authorizationRequest.getPresentationDefinitions() if (!requestNonce || !requestClientId || !requestState) { @@ -286,6 +291,10 @@ export class OpenId4VcSiopVerifierService { correlationId: options.verificationSession.id, nonce: requestNonce, audience: requestClientId, + responseUri, + mdocGeneratedNonce: options.jarmHeader?.apu + ? TypedArrayEncoder.toUtf8String(TypedArrayEncoder.fromBase64(options.jarmHeader.apu)) + : undefined, }), }, }) @@ -323,9 +332,11 @@ export class OpenId4VcSiopVerifierService { const presentationDefinitions = await authorizationRequest.getPresentationDefinitions() if (presentationDefinitions && presentationDefinitions.length > 0) { - const presentations = await extractPresentationsFromAuthorizationResponse(authorizationResponse, { - hasher: Hasher.hash, - }) + const presentations = authorizationResponse.payload.vp_token + ? await extractPresentationsFromVpToken(authorizationResponse.payload.vp_token, { + hasher: Hasher.hash, + }) + : [] // TODO: Probably wise to check against request for the location of the submission_data const submission = @@ -553,9 +564,9 @@ export class OpenId4VcSiopVerifierService { // to fix that in Sphereon lib client_id: clientId, passBy: PassBy.VALUE, - responseTypesSupported: [ResponseType.VP_TOKEN], + response_types_supported: [ResponseType.VP_TOKEN], subject_syntax_types_supported: supportedDidMethods.map((m) => `did:${m}`), - vpFormatsSupported: { + vp_formats_supported: { mso_mdoc: { alg: supportedAlgs, }, @@ -597,7 +608,13 @@ export class OpenId4VcSiopVerifierService { private getPresentationVerificationCallback( agentContext: AgentContext, - options: { nonce: string; audience: string; correlationId: string } + options: { + nonce: string + audience: string + correlationId: string + responseUri?: string + mdocGeneratedNonce?: string + } ): PresentationVerificationCallback { return async (encodedPresentation, presentationSubmission) => { try { @@ -607,11 +624,12 @@ export class OpenId4VcSiopVerifierService { if (!encodedPresentation) throw new CredoError('Did not receive a presentation for verification.') let isValid: boolean - let reason: string | undefined + let reason: string | undefined = undefined - // TODO: it might be better here to look at the presentation submission to know - // If presentation includes a ~, we assume it's an SD-JWT-VC if (typeof encodedPresentation === 'string' && encodedPresentation.includes('~')) { + // TODO: it might be better here to look at the presentation submission to know + // If presentation includes a ~, we assume it's an SD-JWT-VC + const sdJwtVcApi = agentContext.dependencyManager.resolve(SdJwtVcApi) const verificationResult = await sdJwtVcApi.verify({ @@ -624,7 +642,26 @@ export class OpenId4VcSiopVerifierService { isValid = verificationResult.verification.isValid reason = verificationResult.isValid ? undefined : verificationResult.error.message - } else if (typeof encodedPresentation === 'string') { + } else if (typeof encodedPresentation === 'string' && !Jwt.format.test(encodedPresentation)) { + if (!options.responseUri || !options.mdocGeneratedNonce) { + isValid = false + reason = 'Mdoc device response verification failed. Response uri and the mdocGeneratedNonce are not set' + } else { + const mdocDeviceResponse = MdocDeviceResponse.fromBase64Url(encodedPresentation) + await mdocDeviceResponse.verify(agentContext, { + sessionTranscriptOptions: { + clientId: options.audience, + mdocGeneratedNonce: options.mdocGeneratedNonce, + responseUri: options.responseUri, + verifierGeneratedNonce: options.nonce, + }, + verificationContext: { + openId4VcVerificationSessionId: options.correlationId, + }, + }) + isValid = true + } + } else if (typeof encodedPresentation === 'string' && Jwt.format.test(encodedPresentation)) { const verificationResult = await this.w3cCredentialService.verifyPresentation(agentContext, { presentation: encodedPresentation, challenge: options.nonce, diff --git a/packages/openid4vc/src/openid4vc-verifier/router/authorizationEndpoint.ts b/packages/openid4vc/src/openid4vc-verifier/router/authorizationEndpoint.ts index 926104b419..ebbc133fcc 100644 --- a/packages/openid4vc/src/openid4vc-verifier/router/authorizationEndpoint.ts +++ b/packages/openid4vc/src/openid4vc-verifier/router/authorizationEndpoint.ts @@ -4,7 +4,7 @@ import type { AgentContext } from '@credo-ts/core' import type { AuthorizationResponsePayload, DecryptCompact } from '@sphereon/did-auth-siop' import type { Response, Router } from 'express' -import { CredoError, Key, TypedArrayEncoder } from '@credo-ts/core' +import { CredoError, Hasher, JsonEncoder, Key, TypedArrayEncoder } from '@credo-ts/core' import { AuthorizationRequest, RP } from '@sphereon/did-auth-siop' import { getRequestContext, sendErrorResponse } from '../../shared/router' @@ -73,6 +73,7 @@ export function configureAuthorizationEndpoint(router: Router, config: OpenId4Vc let verificationSession: OpenId4VcVerificationSessionRecord | undefined let authorizationResponsePayload: AuthorizationResponsePayload + let jarmHeader: { apu?: string; apv?: string } | undefined = undefined if (request.body.response) { const res = await RP.processJarmAuthorizationResponse(request.body.response, { @@ -91,8 +92,11 @@ export function configureAuthorizationEndpoint(router: Router, config: OpenId4Vc return { authRequestParams: requestObjectPayload } }, decryptCompact: decryptJarmResponse(agentContext), + hasher: Hasher.hash, }) + const [header] = request.body.response.split('.') + jarmHeader = JsonEncoder.fromBase64(header) // FIXME: verify the apv matches the nonce of the authorization reuqest authorizationResponsePayload = res.authResponseParams as AuthorizationResponsePayload } else { @@ -122,6 +126,7 @@ export function configureAuthorizationEndpoint(router: Router, config: OpenId4Vc await openId4VcVerifierService.verifyAuthorizationResponse(agentContext, { authorizationResponse: authorizationResponsePayload, verificationSession, + jarmHeader, }) response.status(200).send() } catch (error) { diff --git a/packages/openid4vc/src/shared/transform.ts b/packages/openid4vc/src/shared/transform.ts index a1f4ff2a94..9ea3273c72 100644 --- a/packages/openid4vc/src/shared/transform.ts +++ b/packages/openid4vc/src/shared/transform.ts @@ -15,7 +15,8 @@ import { W3cJsonLdVerifiableCredential, JsonEncoder, Mdoc, - MdocVerifiablePresentation, + TypedArrayEncoder, + MdocDeviceResponse, } from '@credo-ts/core' export function getSphereonVerifiableCredential( @@ -37,7 +38,7 @@ export function getSphereonVerifiableCredential( export function getSphereonVerifiablePresentation( verifiablePresentation: VerifiablePresentation -): SphereonW3cVerifiablePresentation | SphereonCompactSdJwtVc { +): SphereonW3cVerifiablePresentation | SphereonCompactSdJwtVc | string { // encoded sd-jwt or jwt if (typeof verifiablePresentation === 'string') { return verifiablePresentation @@ -45,11 +46,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 presentation is not yet supported.') - - // TODO: CHECK IF THIS IS WHAT IS EXPECTED - // return verifiablePresentation.deviceSignedBase64Url + } else if (verifiablePresentation instanceof MdocDeviceResponse) { + return verifiablePresentation.base64Url } else { return verifiablePresentation.compact } @@ -78,9 +76,12 @@ export function getVerifiablePresentationFromSphereonWrapped( } satisfies SdJwtVc } else if (wrappedVerifiablePresentation.format === 'mso_mdoc') { if (typeof wrappedVerifiablePresentation.original !== 'string') { - throw new CredoError('Invalid format of original verifiable presentation. DeviceResponseCbor is not supported') + const base64Url = TypedArrayEncoder.toBase64URL( + new Uint8Array(wrappedVerifiablePresentation.original.cborEncode()) + ) + return MdocDeviceResponse.fromBase64Url(base64Url) } - return new MdocVerifiablePresentation(wrappedVerifiablePresentation.original) + return MdocDeviceResponse.fromBase64Url(wrappedVerifiablePresentation.original) } throw new CredoError(`Unsupported presentation format: ${wrappedVerifiablePresentation.format}`) diff --git a/packages/openid4vc/tests/openid4vc.e2e.test.ts b/packages/openid4vc/tests/openid4vc.e2e.test.ts index 7000763bfc..44b416a0db 100644 --- a/packages/openid4vc/tests/openid4vc.e2e.test.ts +++ b/packages/openid4vc/tests/openid4vc.e2e.test.ts @@ -1,7 +1,7 @@ import type { AgentType, TenantType } from './utils' import type { OpenId4VciSignMdocCredential } from '../src' import type { OpenId4VciCredentialBindingResolver } from '../src/openid4vc-holder' -import type { DifPresentationExchangeDefinitionV2, Mdoc, SdJwtVc } from '@credo-ts/core' +import type { DifPresentationExchangeDefinitionV2, Mdoc, MdocDeviceResponse, SdJwtVc } from '@credo-ts/core' import type { Server } from 'http' import { @@ -22,6 +22,8 @@ import { Jwt, Jwk, X509ModuleConfig, + parseDid, + X509Service, } from '@credo-ts/core' import express, { type Express } from 'express' @@ -1610,15 +1612,8 @@ describe('OpenId4Vc', () => { it('e2e flow with tenants, issuer endpoints requesting a mdoc', async () => { const issuerTenant1 = await issuer.agent.modules.tenants.getTenantAgent({ tenantId: issuer1.tenantId }) - const currentDate = new Date() - currentDate.setDate(currentDate.getDate() - 1) - const nextDay = new Date(currentDate) - nextDay.setDate(currentDate.getDate() + 2) - const selfSignedIssuerCertificate = await issuerTenant1.x509.createSelfSignedCertificate({ key: await issuerTenant1.wallet.createKey({ keyType: KeyType.P256 }), - notBefore: currentDate, - notAfter: nextDay, extensions: [], name: 'C=DE', }) @@ -1748,4 +1743,735 @@ describe('OpenId4Vc', () => { await holderTenant1.endSession() }) + + it('e2e flow with verifier endpoints verifying a mdoc and sd-jwt (jarm)', async () => { + const openIdVerifier = await verifier.agent.modules.openId4VcVerifier.createVerifier() + + const signedSdJwtVc = await issuer.agent.sdJwtVc.sign({ + holder: { method: 'did', didUrl: holder.kid }, + issuer: { + method: 'did', + didUrl: issuer.kid, + }, + payload: { + vct: 'OpenBadgeCredential', + university: 'innsbruck', + degree: 'bachelor', + name: 'John Doe', + }, + disclosureFrame: { + _sd: ['university', 'name'], + }, + }) + await holder.agent.sdJwtVc.store(signedSdJwtVc.compact) + + const selfSignedCertificate = await X509Service.createSelfSignedCertificate(issuer.agent.context, { + key: await issuer.agent.context.wallet.createKey({ keyType: KeyType.P256 }), + extensions: [], + name: 'C=DE', + }) + + await verifier.agent.x509.setTrustedCertificates([selfSignedCertificate.toString('pem')]) + + const parsedDid = parseDid(issuer.kid) + if (!parsedDid.fragment) { + throw new Error(`didUrl '${parsedDid.didUrl}' does not contain a '#'. Unable to derive key from did document.`) + } + + const holderKey = await holder.agent.context.wallet.createKey({ keyType: KeyType.P256 }) + + const signedMdoc = await issuer.agent.mdoc.sign({ + docType: 'org.eu.university', + holderKey, + issuerCertificate: selfSignedCertificate.toString('pem'), + namespaces: { + 'eu.europa.ec.eudi.pid.1': { + university: 'innsbruck', + degree: 'bachelor', + name: 'John Doe', + not: 'disclosed', + }, + }, + }) + + const certificate = await verifier.agent.x509.createSelfSignedCertificate({ + key: await verifier.agent.wallet.createKey({ keyType: KeyType.Ed25519 }), + extensions: [[{ type: 'dns', value: 'localhost:1234' }]], + }) + + const rawCertificate = certificate.toString('base64') + await holder.agent.mdoc.store(signedMdoc) + + await holder.agent.x509.addTrustedCertificate(rawCertificate) + await verifier.agent.x509.addTrustedCertificate(rawCertificate) + + const presentationDefinition = { + id: 'mDL-sample-req', + input_descriptors: [ + { + id: 'org.eu.university', + format: { + mso_mdoc: { + alg: ['ES256', 'ES384', 'ES512', 'EdDSA', 'ESB256', 'ESB320', 'ESB384', 'ESB512'], + }, + }, + constraints: { + fields: [ + { + path: ["$['eu.europa.ec.eudi.pid.1']['name']"], + intent_to_retain: false, + }, + { + path: ["$['eu.europa.ec.eudi.pid.1']['degree']"], + intent_to_retain: false, + }, + ], + limit_disclosure: 'required', + }, + }, + { + id: 'OpenBadgeCredentialDescriptor', + format: { + 'vc+sd-jwt': { + 'sd-jwt_alg_values': ['EdDSA'], + }, + }, + constraints: { + limit_disclosure: 'required', + fields: [ + { + path: ['$.vct'], + filter: { + type: 'string', + const: 'OpenBadgeCredential', + }, + }, + { + path: ['$.university'], + }, + ], + }, + }, + ], + } satisfies DifPresentationExchangeDefinitionV2 + + // Hack to make it work with x5c check + // @ts-expect-error + verifier.agent.modules.openId4VcVerifier.config.options.baseUrl = + // @ts-expect-error + verifier.agent.modules.openId4VcVerifier.config.options.baseUrl.replace('http://', 'https://') + + const { authorizationRequest, verificationSession } = + await verifier.agent.modules.openId4VcVerifier.createAuthorizationRequest({ + responseMode: 'direct_post.jwt', + verifierId: openIdVerifier.verifierId, + requestSigner: { + method: 'x5c', + x5c: [rawCertificate], + issuer: 'https://example.com/hakuna/matadata', + }, + presentationExchange: { + definition: presentationDefinition, + }, + }) + + // Hack to make it work with x5c checks + verificationSession.authorizationRequestUri = verificationSession.authorizationRequestUri.replace('https', 'http') + const verificationSessionRepoitory = verifier.agent.dependencyManager.resolve( + OpenId4VcVerificationSessionRepository + ) + await verificationSessionRepoitory.update(verifier.agent.context, verificationSession) + + // Hack to make it work with x5c check + // @ts-expect-error + verifier.agent.modules.openId4VcVerifier.config.options.baseUrl = + // @ts-expect-error + verifier.agent.modules.openId4VcVerifier.config.options.baseUrl.replace('https://', 'http://') + + expect(authorizationRequest.replace('https', 'http')).toEqual( + `openid4vp://?client_id=localhost%3A1234&request_uri=${encodeURIComponent( + verificationSession.authorizationRequestUri + )}` + ) + + const resolvedAuthorizationRequest = await holder.agent.modules.openId4VcHolder.resolveSiopAuthorizationRequest( + // hack to make it work on localhost + authorizationRequest.replace('https', 'http') + ) + + expect(resolvedAuthorizationRequest.presentationExchange?.credentialsForRequest).toEqual({ + areRequirementsSatisfied: true, + name: undefined, + purpose: undefined, + requirements: expect.arrayContaining([ + { + isRequirementSatisfied: true, + needsCount: 1, + rule: 'pick', + submissionEntry: [ + { + name: undefined, + purpose: undefined, + inputDescriptorId: 'org.eu.university', + verifiableCredentials: [ + { + type: ClaimFormat.MsoMdoc, + credentialRecord: expect.objectContaining({ + base64Url: expect.any(String), + }), + // Name is NOT in here + disclosedPayload: { + 'eu.europa.ec.eudi.pid.1': { + degree: 'bachelor', + name: 'John Doe', + }, + }, + }, + ], + }, + ], + }, + + { + isRequirementSatisfied: true, + needsCount: 1, + rule: 'pick', + submissionEntry: [ + { + name: undefined, + purpose: undefined, + inputDescriptorId: 'OpenBadgeCredentialDescriptor', + verifiableCredentials: [ + { + type: ClaimFormat.SdJwtVc, + credentialRecord: expect.objectContaining({ + compactSdJwtVc: signedSdJwtVc.compact, + }), + // Name is NOT in here + disclosedPayload: { + cnf: { + kid: 'did:key:z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc#z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc', + }, + degree: 'bachelor', + iat: expect.any(Number), + iss: 'did:key:z6MkrzQPBr4pyqC776KKtrz13SchM5ePPbssuPuQZb5t4uKQ', + university: 'innsbruck', + vct: 'OpenBadgeCredential', + }, + }, + ], + }, + ], + }, + ]), + }) + + if (!resolvedAuthorizationRequest.presentationExchange) { + throw new Error('Presentation exchange not defined') + } + + // TODO: better way to auto-select + const presentationExchangeService = holder.agent.dependencyManager.resolve(DifPresentationExchangeService) + const selectedCredentials = presentationExchangeService.selectCredentialsForRequest( + resolvedAuthorizationRequest.presentationExchange.credentialsForRequest + ) + + // Hack to make it work with x5c + resolvedAuthorizationRequest.authorizationRequest.responseURI = + resolvedAuthorizationRequest.authorizationRequest.responseURI?.replace('https', 'http') + + const { serverResponse, submittedResponse } = + await holder.agent.modules.openId4VcHolder.acceptSiopAuthorizationRequest({ + authorizationRequest: resolvedAuthorizationRequest.authorizationRequest, + presentationExchange: { + credentials: selectedCredentials, + }, + }) + + // path_nested should not be used for sd-jwt + expect(submittedResponse.presentation_submission?.descriptor_map[0].path_nested).toBeUndefined() + expect(submittedResponse).toEqual({ + presentation_submission: { + id: expect.any(String), + definition_id: 'mDL-sample-req', + descriptor_map: [ + { + id: 'org.eu.university', + format: 'mso_mdoc', + path: '$[0]', + }, + { + format: 'vc+sd-jwt', + id: 'OpenBadgeCredentialDescriptor', + path: '$[1]', + }, + ], + }, + state: expect.any(String), + vp_token: expect.any(Array), + }) + expect(serverResponse).toMatchObject({ + status: 200, + }) + + // The RP MUST validate that the aud (audience) Claim contains the value of the client_id + // that the RP sent in the Authorization Request as an audience. + // When the request has been signed, the value might be an HTTPS URL, or a Decentralized Identifier. + await waitForVerificationSessionRecordSubject(verifier.replaySubject, { + contextCorrelationId: verifier.agent.context.contextCorrelationId, + state: OpenId4VcVerificationSessionState.ResponseVerified, + verificationSessionId: verificationSession.id, + }) + const { idToken, presentationExchange } = + await verifier.agent.modules.openId4VcVerifier.getVerifiedAuthorizationResponse(verificationSession.id) + + expect(idToken).toBeUndefined() + + const presentation = presentationExchange?.presentations[0] as MdocDeviceResponse + expect(presentation.documents).toHaveLength(1) + + const mdocResponse = presentation.documents[0] + + // name SHOULD NOT be disclosed + expect(mdocResponse.issuerSignedNamespaces).toStrictEqual({ + 'eu.europa.ec.eudi.pid.1': { + degree: 'bachelor', + name: 'John Doe', + }, + }) + + expect(presentationExchange).toEqual({ + definition: presentationDefinition, + submission: { + id: expect.any(String), + definition_id: 'mDL-sample-req', + descriptor_map: [ + { + id: 'org.eu.university', + format: 'mso_mdoc', + path: '$[0]', + }, + { + id: 'OpenBadgeCredentialDescriptor', + format: 'vc+sd-jwt', + path: '$[1]', + }, + ], + }, + presentations: [ + { + base64Url: expect.any(String), + documents: [ + { + issuerSignedDocument: { + docType: 'org.eu.university', + issuerSigned: { + nameSpaces: { + 'eu.europa.ec.eudi.pid.1': [{}, {}], + }, + issuerAuth: expect.any(Object), + }, + deviceSigned: expect.any(Object), + }, + base64Url: expect.any(String), + }, + ], + }, + { + compact: expect.any(String), + header: { + alg: 'EdDSA', + kid: '#z6MkrzQPBr4pyqC776KKtrz13SchM5ePPbssuPuQZb5t4uKQ', + typ: 'vc+sd-jwt', + }, + payload: { + _sd: [expect.any(String), expect.any(String)], + _sd_alg: 'sha-256', + cnf: { + kid: 'did:key:z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc#z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc', + }, + iat: expect.any(Number), + iss: 'did:key:z6MkrzQPBr4pyqC776KKtrz13SchM5ePPbssuPuQZb5t4uKQ', + vct: 'OpenBadgeCredential', + degree: 'bachelor', + }, + // university SHOULD be disclosed + prettyClaims: { + cnf: { + kid: 'did:key:z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc#z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc', + }, + iat: expect.any(Number), + iss: 'did:key:z6MkrzQPBr4pyqC776KKtrz13SchM5ePPbssuPuQZb5t4uKQ', + vct: 'OpenBadgeCredential', + degree: 'bachelor', + university: 'innsbruck', + }, + }, + ], + }) + }) + + it('e2e flow with verifier endpoints verifying two sd-jwt-vcs with selective disclosure', async () => { + const openIdVerifier = await verifier.agent.modules.openId4VcVerifier.createVerifier() + + const signedSdJwtVc = await issuer.agent.sdJwtVc.sign({ + holder: { method: 'did', didUrl: holder.kid }, + issuer: { + method: 'did', + didUrl: issuer.kid, + }, + payload: { + vct: 'OpenBadgeCredential', + university: 'innsbruck', + degree: 'bachelor', + name: 'John Doe', + }, + disclosureFrame: { + _sd: ['university', 'name'], + }, + }) + + const signedSdJwtVc2 = await issuer.agent.sdJwtVc.sign({ + holder: { method: 'did', didUrl: holder.kid }, + issuer: { + method: 'did', + didUrl: issuer.kid, + }, + payload: { + vct: 'OpenBadgeCredential2', + university: 'innsbruck2', + degree: 'bachelor2', + name: 'John Doe2', + }, + disclosureFrame: { + _sd: ['university', 'name'], + }, + }) + + const certificate = await verifier.agent.x509.createSelfSignedCertificate({ + key: await verifier.agent.wallet.createKey({ keyType: KeyType.Ed25519 }), + extensions: [[{ type: 'dns', value: 'localhost:1234' }]], + }) + + const rawCertificate = certificate.toString('base64') + await holder.agent.sdJwtVc.store(signedSdJwtVc.compact) + await holder.agent.sdJwtVc.store(signedSdJwtVc2.compact) + + await holder.agent.x509.addTrustedCertificate(rawCertificate) + await verifier.agent.x509.addTrustedCertificate(rawCertificate) + + const presentationDefinition = { + id: 'OpenBadgeCredentials', + input_descriptors: [ + { + id: 'OpenBadgeCredentialDescriptor', + format: { + 'vc+sd-jwt': { + 'sd-jwt_alg_values': ['EdDSA'], + }, + }, + constraints: { + limit_disclosure: 'required', + fields: [ + { + path: ['$.vct'], + filter: { + type: 'string', + const: 'OpenBadgeCredential', + }, + }, + { + path: ['$.university'], + }, + ], + }, + }, + { + id: 'OpenBadgeCredentialDescriptor2', + format: { + 'vc+sd-jwt': { + 'sd-jwt_alg_values': ['EdDSA'], + }, + }, + constraints: { + limit_disclosure: 'required', + fields: [ + { + path: ['$.vct'], + filter: { + type: 'string', + const: 'OpenBadgeCredential2', + }, + }, + { + path: ['$.name'], + }, + ], + }, + }, + ], + } satisfies DifPresentationExchangeDefinitionV2 + + // Hack to make it work with x5c check + // @ts-expect-error + verifier.agent.modules.openId4VcVerifier.config.options.baseUrl = + // @ts-expect-error + verifier.agent.modules.openId4VcVerifier.config.options.baseUrl.replace('http://', 'https://') + const { authorizationRequest, verificationSession } = + await verifier.agent.modules.openId4VcVerifier.createAuthorizationRequest({ + verifierId: openIdVerifier.verifierId, + + requestSigner: { + method: 'x5c', + x5c: [rawCertificate], + }, + presentationExchange: { + definition: presentationDefinition, + }, + }) + + // Hack to make it work with x5c checks + verificationSession.authorizationRequestUri = verificationSession.authorizationRequestUri.replace('https', 'http') + const verificationSessionRepoitory = verifier.agent.dependencyManager.resolve( + OpenId4VcVerificationSessionRepository + ) + await verificationSessionRepoitory.update(verifier.agent.context, verificationSession) + + // Hack to make it work with x5c check + // @ts-expect-error + verifier.agent.modules.openId4VcVerifier.config.options.baseUrl = + // @ts-expect-error + verifier.agent.modules.openId4VcVerifier.config.options.baseUrl.replace('https://', 'http://') + + expect(authorizationRequest.replace('https', 'http')).toEqual( + `openid4vp://?client_id=localhost%3A1234&request_uri=${encodeURIComponent( + verificationSession.authorizationRequestUri + )}` + ) + + const resolvedAuthorizationRequest = await holder.agent.modules.openId4VcHolder.resolveSiopAuthorizationRequest( + // hack to make it work on localhost + authorizationRequest.replace('https', 'http') + ) + + expect(resolvedAuthorizationRequest.presentationExchange?.credentialsForRequest).toEqual({ + areRequirementsSatisfied: true, + name: undefined, + purpose: undefined, + requirements: expect.arrayContaining([ + { + isRequirementSatisfied: true, + needsCount: 1, + rule: 'pick', + submissionEntry: [ + { + name: undefined, + purpose: undefined, + inputDescriptorId: 'OpenBadgeCredentialDescriptor', + verifiableCredentials: [ + { + type: ClaimFormat.SdJwtVc, + credentialRecord: expect.objectContaining({ + compactSdJwtVc: signedSdJwtVc.compact, + }), + // Name is NOT in here + disclosedPayload: { + cnf: { + kid: 'did:key:z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc#z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc', + }, + degree: 'bachelor', + iat: expect.any(Number), + iss: 'did:key:z6MkrzQPBr4pyqC776KKtrz13SchM5ePPbssuPuQZb5t4uKQ', + university: 'innsbruck', + vct: 'OpenBadgeCredential', + }, + }, + ], + }, + ], + }, + { + isRequirementSatisfied: true, + needsCount: 1, + rule: 'pick', + submissionEntry: [ + { + name: undefined, + purpose: undefined, + inputDescriptorId: 'OpenBadgeCredentialDescriptor2', + verifiableCredentials: [ + { + type: ClaimFormat.SdJwtVc, + credentialRecord: expect.objectContaining({ + compactSdJwtVc: signedSdJwtVc2.compact, + }), + disclosedPayload: { + cnf: { + kid: 'did:key:z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc#z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc', + }, + iat: expect.any(Number), + iss: 'did:key:z6MkrzQPBr4pyqC776KKtrz13SchM5ePPbssuPuQZb5t4uKQ', + vct: 'OpenBadgeCredential2', + degree: 'bachelor2', + name: 'John Doe2', + }, + }, + ], + }, + ], + }, + ]), + }) + + if (!resolvedAuthorizationRequest.presentationExchange) { + throw new Error('Presentation exchange not defined') + } + + // TODO: better way to auto-select + const presentationExchangeService = holder.agent.dependencyManager.resolve(DifPresentationExchangeService) + const selectedCredentials = presentationExchangeService.selectCredentialsForRequest( + resolvedAuthorizationRequest.presentationExchange.credentialsForRequest + ) + + // Hack to make it work with x5c + resolvedAuthorizationRequest.authorizationRequest.responseURI = + resolvedAuthorizationRequest.authorizationRequest.responseURI?.replace('https', 'http') + + const { serverResponse, submittedResponse } = + await holder.agent.modules.openId4VcHolder.acceptSiopAuthorizationRequest({ + authorizationRequest: resolvedAuthorizationRequest.authorizationRequest, + presentationExchange: { + credentials: selectedCredentials, + }, + }) + + // path_nested should not be used for sd-jwt + expect(submittedResponse.presentation_submission?.descriptor_map[0].path_nested).toBeUndefined() + expect(submittedResponse).toEqual({ + presentation_submission: { + definition_id: 'OpenBadgeCredentials', + descriptor_map: [ + { + format: 'vc+sd-jwt', + id: 'OpenBadgeCredentialDescriptor', + path: '$[0]', + }, + { + format: 'vc+sd-jwt', + id: 'OpenBadgeCredentialDescriptor2', + path: '$[1]', + }, + ], + id: expect.any(String), + }, + state: expect.any(String), + vp_token: [expect.any(String), expect.any(String)], + }) + expect(serverResponse).toMatchObject({ + status: 200, + }) + + // The RP MUST validate that the aud (audience) Claim contains the value of the client_id + // that the RP sent in the Authorization Request as an audience. + // When the request has been signed, the value might be an HTTPS URL, or a Decentralized Identifier. + await waitForVerificationSessionRecordSubject(verifier.replaySubject, { + contextCorrelationId: verifier.agent.context.contextCorrelationId, + state: OpenId4VcVerificationSessionState.ResponseVerified, + verificationSessionId: verificationSession.id, + }) + const { idToken, presentationExchange } = + await verifier.agent.modules.openId4VcVerifier.getVerifiedAuthorizationResponse(verificationSession.id) + + expect(idToken).toBeUndefined() + + const presentation = presentationExchange?.presentations[0] as SdJwtVc + + // name SHOULD NOT be disclosed + expect(presentation.prettyClaims).not.toHaveProperty('name') + + // university and name SHOULD NOT be in the signed payload + expect(presentation.payload).not.toHaveProperty('university') + expect(presentation.payload).not.toHaveProperty('name') + + expect(presentationExchange).toEqual({ + definition: presentationDefinition, + submission: { + definition_id: 'OpenBadgeCredentials', + descriptor_map: [ + { + format: 'vc+sd-jwt', + id: 'OpenBadgeCredentialDescriptor', + path: '$[0]', + }, + { + format: 'vc+sd-jwt', + id: 'OpenBadgeCredentialDescriptor2', + path: '$[1]', + }, + ], + id: expect.any(String), + }, + presentations: [ + { + compact: expect.any(String), + header: { + alg: 'EdDSA', + kid: '#z6MkrzQPBr4pyqC776KKtrz13SchM5ePPbssuPuQZb5t4uKQ', + typ: 'vc+sd-jwt', + }, + payload: { + _sd: [expect.any(String), expect.any(String)], + _sd_alg: 'sha-256', + cnf: { + kid: 'did:key:z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc#z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc', + }, + iat: expect.any(Number), + iss: 'did:key:z6MkrzQPBr4pyqC776KKtrz13SchM5ePPbssuPuQZb5t4uKQ', + vct: 'OpenBadgeCredential', + degree: 'bachelor', + }, + // university SHOULD be disclosed + prettyClaims: { + cnf: { + kid: 'did:key:z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc#z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc', + }, + iat: expect.any(Number), + iss: 'did:key:z6MkrzQPBr4pyqC776KKtrz13SchM5ePPbssuPuQZb5t4uKQ', + vct: 'OpenBadgeCredential', + degree: 'bachelor', + university: 'innsbruck', + }, + }, + { + compact: expect.any(String), + header: { + alg: 'EdDSA', + kid: '#z6MkrzQPBr4pyqC776KKtrz13SchM5ePPbssuPuQZb5t4uKQ', + typ: 'vc+sd-jwt', + }, + payload: { + _sd: [expect.any(String), expect.any(String)], + _sd_alg: 'sha-256', + cnf: { + kid: 'did:key:z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc#z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc', + }, + iat: expect.any(Number), + iss: 'did:key:z6MkrzQPBr4pyqC776KKtrz13SchM5ePPbssuPuQZb5t4uKQ', + vct: 'OpenBadgeCredential2', + degree: 'bachelor2', + }, + prettyClaims: { + cnf: { + kid: 'did:key:z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc#z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc', + }, + iat: expect.any(Number), + iss: 'did:key:z6MkrzQPBr4pyqC776KKtrz13SchM5ePPbssuPuQZb5t4uKQ', + vct: 'OpenBadgeCredential2', + name: 'John Doe2', + degree: 'bachelor2', + }, + }, + ], + }) + }) }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d27601d1cb..13c3e021d5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -433,8 +433,8 @@ importers: specifier: ^1.11.0 version: 1.12.1 '@protokoll/mdoc-client': - specifier: 0.2.35 - version: 0.2.35(typescript@5.5.4) + specifier: 0.2.36 + version: 0.2.36(typescript@5.5.4) '@sd-jwt/core': specifier: ^0.7.0 version: 0.7.2 @@ -454,14 +454,14 @@ importers: specifier: ^0.7.0 version: 0.7.2 '@sphereon/pex': - specifier: 5.0.0-unstable.18 - version: 5.0.0-unstable.18(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + specifier: 5.0.0-unstable.25 + version: 5.0.0-unstable.25 '@sphereon/pex-models': specifier: ^2.3.1 version: 2.3.1 '@sphereon/ssi-types': - specifier: ^0.30.1 - version: 0.30.1(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + specifier: 0.30.2-next.135 + version: 0.30.2-next.135 '@stablelib/ed25519': specifier: ^1.0.2 version: 1.0.3 @@ -693,23 +693,23 @@ importers: specifier: workspace:* version: link:../core '@sphereon/did-auth-siop': - specifier: 0.16.1-next.168 - version: 0.16.1-next.168(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4))(typescript@5.5.4) + specifier: 0.16.1-fix.173 + version: 0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4))(typescript@5.5.4) '@sphereon/oid4vc-common': - specifier: 0.16.1-next.168 - version: 0.16.1-next.168(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + specifier: 0.16.1-fix.173 + version: 0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) '@sphereon/oid4vci-client': - specifier: 0.16.1-next.168 - version: 0.16.1-next.168(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + specifier: 0.16.1-fix.173 + version: 0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) '@sphereon/oid4vci-common': - specifier: 0.16.1-next.168 - version: 0.16.1-next.168(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + specifier: 0.16.1-fix.173 + version: 0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) '@sphereon/oid4vci-issuer': - specifier: 0.16.1-next.168 - version: 0.16.1-next.168(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + specifier: 0.16.1-fix.173 + version: 0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) '@sphereon/ssi-types': - specifier: ^0.30.1 - version: 0.30.1(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + specifier: 0.30.2-next.135 + version: 0.30.2-next.135 class-transformer: specifier: ^0.5.1 version: 0.5.1 @@ -2299,11 +2299,11 @@ packages: '@protobufjs/utf8@1.1.0': resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} - '@protokoll/core@0.2.35': - resolution: {integrity: sha512-yJyp6iKrqmoMK/ltyGQecwV1Lw5vj6M5lgdKYYHqan2GOwh5PhIjKeptBs8NTSuNawCBJTVlBpbBXsufohJUyg==} + '@protokoll/core@0.2.36': + resolution: {integrity: sha512-4VHOLNX9XiExUXGCGuT1SnbhhJT4RZCRQnQFqFZ1b6bEnxLQvmy8W06ZdZYuXOcW4IdCfWq0qI/wWs+cKOV1Pw==} - '@protokoll/mdoc-client@0.2.35': - resolution: {integrity: sha512-pSnPA/ATXp8Mx4LN7oohuHYGYJmMeQtRJF65XdEgvnUHHgjyRHxmTZueXNqd+PFbP4ctqessjfUFhxYs1tDFtQ==} + '@protokoll/mdoc-client@0.2.36': + resolution: {integrity: sha512-8RXjZFLGERBS3qBcnB33Ldjv/HryT2F+gCrvVvzpFi6CvMH29NvuuLxd52GN8JBCNT7r03i/0GPfQX03jidDCg==} '@react-native-community/cli-clean@10.1.1': resolution: {integrity: sha512-iNsrjzjIRv9yb5y309SWJ8NDHdwYtnCpmxZouQDyOljUdC9MwdZ4ChbtA4rwQyAwgOVfS9F/j56ML3Cslmvrxg==} @@ -2451,34 +2451,34 @@ packages: resolution: {integrity: sha512-kQpk267uxB19X3X2T1mvNMjyvIEonpNSHrMlK5ZaBU6aZxw7wPbpgKJOjHN3+/GPVpXgAV9soVT2oyHpLkLtyw==} engines: {node: '>= 8'} - '@sphereon/did-auth-siop@0.16.1-next.168': - resolution: {integrity: sha512-m4kqeLErRpa0e7532x1HTfleK6ZzRp9g8ZA/J6QBUk4U/Ktp2zjDSZGFI1sGKHs0giDZiXImKRFy1QByBp1BsA==} + '@sphereon/did-auth-siop@0.16.1-fix.173': + resolution: {integrity: sha512-BurhxcEfd91y2o2zy0VAZ9STHRuWRNx/FwhR0jRSG8D7gJPspqiR2cJtdvAvLrKNaznpQSANQHniCgCiDRtjfA==} engines: {node: '>=18'} '@sphereon/did-uni-client@0.6.3': resolution: {integrity: sha512-g7LD7ofbE36slHN7Bhr5dwUrj6t0BuZeXBYJMaVY/pOeL1vJxW1cZHbZqu0NSfOmzyBg4nsYVlgTjyi/Aua2ew==} - '@sphereon/jarm@0.16.1-next.168': - resolution: {integrity: sha512-5+qEyh4uICYZN8g9gsgWLGsMCwM4GpkIee3T6AXe8+w/DLCOTCh5oqM3YfTOAeQ3Wmg1mXutzFjfJ6Sx3QZIag==} + '@sphereon/jarm@0.16.1-fix.173': + resolution: {integrity: sha512-VYNuNLV+x7hKKcynC8yOJymkXPrtBRQA/Gqj50Wfhl6kx6IoRsx39s/i6xGGYPwyrNTaVGFssKzg0IDcQfxToA==} engines: {node: '>=18'} '@sphereon/kmp-mdl-mdoc@0.2.0-SNAPSHOT.22': resolution: {integrity: sha512-uAZZExVy+ug9JLircejWa5eLtAZ7bnBP6xb7DO2+86LRsHNLh2k2jMWJYxp+iWtGHTsh6RYsZl14ScQLvjiQ/A==} - '@sphereon/oid4vc-common@0.16.1-next.168': - resolution: {integrity: sha512-QKLna4lY3V/0vLguaYa1Qc5L6AErpQ7aD+ITGW8hiVtqU9m/2Y/+wbQqwN8Nk0LnLWDDkBli+KGeZNDJO8g9sQ==} + '@sphereon/oid4vc-common@0.16.1-fix.173': + resolution: {integrity: sha512-+AAUvEEFs0vzz1mrgjSgvDkcBtr18d2XEVgJex7QlAqxCKVGfjzZlqL2Q2vOLKYVaXsazhD5LnYiY6B5WMTC3Q==} engines: {node: '>=18'} - '@sphereon/oid4vci-client@0.16.1-next.168': - resolution: {integrity: sha512-+k4AijUOagL+qtrLJcnFLpoPa4zP9jzeu2GQ6nG3AsiKdeAuPvGKx8htiirNqkjZ4Cl2eGQY5QjuDNGQW2CQig==} + '@sphereon/oid4vci-client@0.16.1-fix.173': + resolution: {integrity: sha512-t7i+iZwDHTQKeTq+u9NufXoE5Qsrt9bvspSD6uWYjwidQBAtD4LZHuGyHbygOyVAIX+LUMmZA3A2/ciL29Ra9w==} engines: {node: '>=18'} - '@sphereon/oid4vci-common@0.16.1-next.168': - resolution: {integrity: sha512-Tc5CHp+5s+lGN5QyAA/fvy5iP+QfE4SwQ7jYW/WXLe1sXtr8fTUkEexd63mRRe/YjEXlMFWCnKtK8eHrROaJBA==} + '@sphereon/oid4vci-common@0.16.1-fix.173': + resolution: {integrity: sha512-nSjOoR1SxF5+S10qjHHLG21bWx8MVbDjERag1zWb7ADlZDO8GXgPqzzEvm1BodW7yci5ugiKOyjIdbPMpg9aFQ==} engines: {node: '>=18'} - '@sphereon/oid4vci-issuer@0.16.1-next.168': - resolution: {integrity: sha512-DQxvUEfZo6wakrXqd+zEreQKFWkfnlVfRyQZzNQx/H2GKi3nN2dcjwbmdr+IwepFJMktE7+In5KB7KIZc8zyyA==} + '@sphereon/oid4vci-issuer@0.16.1-fix.173': + resolution: {integrity: sha512-pP1yH5O2Wplypb/lRHU3hKVzj5cbH4XTdC9CqvWYeWpfvrXfEGtI0amoaNC3NyWjR3FO904lwuSpxh28Xc+MLw==} engines: {node: '>=18'} peerDependencies: awesome-qr: ^2.1.5-rc.0 @@ -2489,8 +2489,12 @@ packages: '@sphereon/pex-models@2.3.1': resolution: {integrity: sha512-SByU4cJ0XYA6VZQ/L6lsSiRcFtBPHbFioCeQ4GP7/W/jQ+PSBD7uK2oTnKQ9/0iEiMK/6JYqhKgLs4a9UX3UTQ==} - '@sphereon/pex@5.0.0-unstable.18': - resolution: {integrity: sha512-DOKfmfa549RbX8Bqe4yOE2rjZbCQgoRcHO+i01k+gxubgulPPQvY8k0pmFkMFMgVI5fXCjkI3b3PgQ5u/vpGGw==} + '@sphereon/pex@5.0.0-unstable.24': + resolution: {integrity: sha512-CZc+kr8cJqPsFSpg4kHyamr5oB5xLVP2E5eJ0pbetOfOE2uSxqk0/A8zGazcPhU1zZILrO51hD4vW/hJRgtKJQ==} + engines: {node: '>=18'} + + '@sphereon/pex@5.0.0-unstable.25': + resolution: {integrity: sha512-EUWfGa6t20PPkYf+zbfWXhc1sSWiFNywbRah8R6grJPU738pfwWpZPunSEY3x0CoxAVaSVXn91wZ/sxmgPCFkA==} engines: {node: '>=18'} '@sphereon/ssi-sdk-ext.did-utils@0.24.1-unstable.130': @@ -2523,11 +2527,11 @@ packages: '@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==} + '@sphereon/ssi-types@0.30.2-next.129': + resolution: {integrity: sha512-F1TDy9S5ajDJDp21cINXseGSux9kGA+x0KScAS+5+B/RdMGRp7bLOM+3YpQw1QGPqKxVc7JAd2gAn7AI0pAkZA==} - '@sphereon/wellknown-dids-client@0.1.3': - resolution: {integrity: sha512-TAT24L3RoXD8ocrkTcsz7HuJmgjNjdoV6IXP1p3DdaI/GqkynytXE3J1+F7vUFMRYwY5nW2RaXSgDQhrFJemaA==} + '@sphereon/ssi-types@0.30.2-next.135': + resolution: {integrity: sha512-YLQfFMPUlOJUxHbOS9v01nG3cgLwTk3d95/rTnOmEQx9kXgXjoXdvt7D0uGcMRL3RHeQ9biT/jWY//mDvCirVQ==} '@sqltools/formatter@1.2.5': resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==} @@ -9927,15 +9931,15 @@ snapshots: '@protobufjs/utf8@1.1.0': {} - '@protokoll/core@0.2.35(typescript@5.5.4)': + '@protokoll/core@0.2.36(typescript@5.5.4)': dependencies: valibot: 0.37.0(typescript@5.5.4) transitivePeerDependencies: - typescript - '@protokoll/mdoc-client@0.2.35(typescript@5.5.4)': + '@protokoll/mdoc-client@0.2.36(typescript@5.5.4)': dependencies: - '@protokoll/core': 0.2.35(typescript@5.5.4) + '@protokoll/core': 0.2.36(typescript@5.5.4) compare-versions: 6.1.1 transitivePeerDependencies: - typescript @@ -10284,17 +10288,14 @@ snapshots: '@sovpro/delimited-stream@1.1.0': {} - '@sphereon/did-auth-siop@0.16.1-next.168(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4))(typescript@5.5.4)': + '@sphereon/did-auth-siop@0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4))(typescript@5.5.4)': dependencies: '@astronautlabs/jsonpath': 1.1.2 - '@sphereon/did-uni-client': 0.6.3 - '@sphereon/jarm': 0.16.1-next.168(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4))(typescript@5.5.4) - '@sphereon/kmp-mdl-mdoc': 0.2.0-SNAPSHOT.22 - '@sphereon/oid4vc-common': 0.16.1-next.168(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) - '@sphereon/pex': 5.0.0-unstable.18(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + '@sphereon/jarm': 0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4))(typescript@5.5.4) + '@sphereon/oid4vc-common': 0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + '@sphereon/pex': 5.0.0-unstable.24 '@sphereon/pex-models': 2.3.1 - '@sphereon/ssi-types': 0.30.1(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) - '@sphereon/wellknown-dids-client': 0.1.3 + '@sphereon/ssi-types': 0.30.2-next.129 cross-fetch: 4.0.0 debug: 4.3.6 events: 3.3.0 @@ -10332,9 +10333,9 @@ snapshots: transitivePeerDependencies: - encoding - '@sphereon/jarm@0.16.1-next.168(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4))(typescript@5.5.4)': + '@sphereon/jarm@0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4))(typescript@5.5.4)': dependencies: - '@sphereon/oid4vc-common': 0.16.1-next.168(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + '@sphereon/oid4vc-common': 0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) valibot: 0.42.1(typescript@5.5.4) transitivePeerDependencies: - '@google-cloud/spanner' @@ -10364,7 +10365,7 @@ snapshots: '@js-joda/timezone': 2.3.0(@js-joda/core@5.6.3) format-util: 1.0.5 - '@sphereon/oid4vc-common@0.16.1-next.168(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4))': + '@sphereon/oid4vc-common@0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4))': dependencies: '@sphereon/ssi-types': 0.30.1(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) jwt-decode: 4.0.0 @@ -10392,10 +10393,10 @@ snapshots: - ts-node - typeorm-aurora-data-api-driver - '@sphereon/oid4vci-client@0.16.1-next.168(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4))': + '@sphereon/oid4vci-client@0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4))': dependencies: - '@sphereon/oid4vc-common': 0.16.1-next.168(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) - '@sphereon/oid4vci-common': 0.16.1-next.168(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + '@sphereon/oid4vc-common': 0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + '@sphereon/oid4vci-common': 0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) '@sphereon/ssi-types': 0.30.1(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) cross-fetch: 3.1.8 debug: 4.3.6 @@ -10420,9 +10421,9 @@ snapshots: - ts-node - typeorm-aurora-data-api-driver - '@sphereon/oid4vci-common@0.16.1-next.168(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4))': + '@sphereon/oid4vci-common@0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4))': dependencies: - '@sphereon/oid4vc-common': 0.16.1-next.168(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + '@sphereon/oid4vc-common': 0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) '@sphereon/ssi-types': 0.30.1(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) cross-fetch: 3.1.8 debug: 4.3.6 @@ -10450,10 +10451,10 @@ snapshots: - ts-node - typeorm-aurora-data-api-driver - '@sphereon/oid4vci-issuer@0.16.1-next.168(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4))': + '@sphereon/oid4vci-issuer@0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4))': dependencies: - '@sphereon/oid4vc-common': 0.16.1-next.168(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) - '@sphereon/oid4vci-common': 0.16.1-next.168(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + '@sphereon/oid4vc-common': 0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + '@sphereon/oid4vci-common': 0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) '@sphereon/ssi-types': 0.30.1(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) uuid: 9.0.1 transitivePeerDependencies: @@ -10479,39 +10480,37 @@ snapshots: '@sphereon/pex-models@2.3.1': {} - '@sphereon/pex@5.0.0-unstable.18(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4))': + '@sphereon/pex@5.0.0-unstable.24': dependencies: '@astronautlabs/jsonpath': 1.1.2 '@sd-jwt/decode': 0.7.2 '@sd-jwt/present': 0.7.2 '@sd-jwt/types': 0.7.2 '@sphereon/pex-models': 2.3.1 - '@sphereon/ssi-types': 0.30.1(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + '@sphereon/ssi-types': 0.30.2-next.129 + ajv: 8.17.1 + ajv-formats: 2.1.1(ajv@8.17.1) + jwt-decode: 3.1.2 + nanoid: 3.3.7 + uint8arrays: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@sphereon/pex@5.0.0-unstable.25': + dependencies: + '@astronautlabs/jsonpath': 1.1.2 + '@sd-jwt/decode': 0.7.2 + '@sd-jwt/present': 0.7.2 + '@sd-jwt/types': 0.7.2 + '@sphereon/pex-models': 2.3.1 + '@sphereon/ssi-types': 0.30.2-next.135 ajv: 8.17.1 ajv-formats: 2.1.1(ajv@8.17.1) jwt-decode: 3.1.2 nanoid: 3.3.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.did-utils@0.24.1-unstable.130(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4))': dependencies: @@ -10737,17 +10736,25 @@ snapshots: - ts-node - typeorm-aurora-data-api-driver - '@sphereon/ssi-types@0.9.0': + '@sphereon/ssi-types@0.30.2-next.129': dependencies: + '@sd-jwt/decode': 0.7.2 + '@sphereon/kmp-mdl-mdoc': 0.2.0-SNAPSHOT.22 + debug: 4.3.6 + events: 3.3.0 jwt-decode: 3.1.2 + transitivePeerDependencies: + - supports-color - '@sphereon/wellknown-dids-client@0.1.3': + '@sphereon/ssi-types@0.30.2-next.135': dependencies: - '@sphereon/ssi-types': 0.9.0 - cross-fetch: 3.1.8 + '@sd-jwt/decode': 0.7.2 + '@sphereon/kmp-mdl-mdoc': 0.2.0-SNAPSHOT.22 + debug: 4.3.6 + events: 3.3.0 jwt-decode: 3.1.2 transitivePeerDependencies: - - encoding + - supports-color '@sqltools/formatter@1.2.5': {}