From 4b7e8aecba5825cdee14fe12b50e9dc57f64d9ab Mon Sep 17 00:00:00 2001 From: Martin Auer Date: Wed, 20 Nov 2024 13:13:36 +0100 Subject: [PATCH 01/11] feat: dcql alpha --- package.json | 4 +- .../AuthorizationRequest.ts | 30 ++++-- .../lib/authorization-request/Payload.ts | 18 +++- .../lib/authorization-request/URI.ts | 8 +- .../lib/authorization-request/types.ts | 4 +- .../AuthorizationResponse.ts | 33 ++++--- .../lib/authorization-response/Dcql.ts | 44 +++++++++ .../lib/authorization-response/OpenID4VP.ts | 99 ++++++++++++------- .../PresentationExchange.ts | 4 +- .../lib/authorization-response/types.ts | 16 ++- .../lib/helpers/SIOPSpecVersion.ts | 3 +- packages/siop-oid4vp/lib/op/OP.ts | 34 ++++--- .../siop-oid4vp/lib/request-object/Payload.ts | 1 + packages/siop-oid4vp/lib/rp/RP.ts | 4 + packages/siop-oid4vp/lib/rp/RPBuilder.ts | 18 ++++ packages/siop-oid4vp/lib/types/Errors.ts | 2 +- packages/siop-oid4vp/lib/types/SIOP.types.ts | 7 ++ packages/siop-oid4vp/package.json | 1 + pnpm-lock.yaml | 3 + 19 files changed, 245 insertions(+), 88 deletions(-) create mode 100644 packages/siop-oid4vp/lib/authorization-response/Dcql.ts diff --git a/package.json b/package.json index 35f5b75b..ac994988 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,6 @@ "OIDC4VP", "OID4VCI", "OID4VP" - ] + ], + "packageManager": "pnpm@9.6.0+sha256.dae0f7e822c56b20979bb5965e3b73b8bdabb6b8b8ef121da6d857508599ca35" } - diff --git a/packages/siop-oid4vp/lib/authorization-request/AuthorizationRequest.ts b/packages/siop-oid4vp/lib/authorization-request/AuthorizationRequest.ts index 13316a70..aeb4b15b 100644 --- a/packages/siop-oid4vp/lib/authorization-request/AuthorizationRequest.ts +++ b/packages/siop-oid4vp/lib/authorization-request/AuthorizationRequest.ts @@ -1,6 +1,8 @@ import { parseJWT } from '@sphereon/oid4vc-common' +import { DcqlQuery } from 'dcql' import { PresentationDefinitionWithLocation } from '../authorization-response' +import { findValidDcqlQuery } from '../authorization-response/Dcql' import { PresentationExchange } from '../authorization-response/PresentationExchange' import { fetchByReferenceOrUseByValue, removeNullUndefined } from '../helpers' import { authorizationRequestVersionDiscovery } from '../helpers/SIOPSpecVersion' @@ -66,7 +68,7 @@ export class AuthorizationRequest { const requestObjectArg = opts.requestObject.passBy !== PassBy.NONE ? (requestObject ? requestObject : await RequestObject.fromOpts(opts)) : undefined - // opts?.payload was removed before, but it's not clear atm why opts?.payload was removed + // opts?.payload was removed before, but it's not clear atm why opts?.payload was removed const requestPayload = opts?.payload ? await createAuthorizationRequestPayload(opts, requestObjectArg) : undefined return new AuthorizationRequest(requestPayload, requestObjectArg, opts) } @@ -190,14 +192,22 @@ export class AuthorizationRequest { // TODO see if this is too naive. The OpenID conformance test explicitly tests for this // But the spec says: The client_id and client_id_scheme MUST be omitted in unsigned requests defined in Appendix A.3.1. // So I would expect client_id_scheme and client_id to be undefined when the JWT header has alg: none - if(mergedPayload.client_id && mergedPayload.client_id_scheme === 'redirect_uri' && mergedPayload.client_id !== responseURI) { - throw Error(`${SIOPErrors.INVALID_REQUEST}, response_uri does not match the client_id provided by the verifier which is required for client_id_scheme redirect_uri`) + if (mergedPayload.client_id && mergedPayload.client_id_scheme === 'redirect_uri' && mergedPayload.client_id !== responseURI) { + throw Error( + `${SIOPErrors.INVALID_REQUEST}, response_uri does not match the client_id provided by the verifier which is required for client_id_scheme redirect_uri`, + ) } - + // TODO: we need to verify somewhere that if response_mode is direct_post, that the response_uri may be present, // BUT not both redirect_uri and response_uri. What is the best place to do this? - const presentationDefinitions: PresentationDefinitionWithLocation[] = await PresentationExchange.findValidPresentationDefinitions(mergedPayload, await this.getSupportedVersion()) + const presentationDefinitions: PresentationDefinitionWithLocation[] = await PresentationExchange.findValidPresentationDefinitions( + mergedPayload, + await this.getSupportedVersion(), + ) + + const dcqlQuery = await findValidDcqlQuery(mergedPayload) + return { jwt, payload: parsedJwt?.payload, @@ -208,6 +218,7 @@ export class AuthorizationRequest { correlationId: opts.correlationId, authorizationRequest: this, verifyOpts: opts, + dcqlQuery, presentationDefinitions, registrationMetadataPayload, requestObject: this.requestObject, @@ -267,8 +278,9 @@ export class AuthorizationRequest { } public async mergedPayloads(): Promise { - const requestObjectPayload = { ...this.payload, ...(this.requestObject && (await this.requestObject.getPayload())) } - if (requestObjectPayload.scope && typeof requestObjectPayload.scope !== 'string') { // test mattr.launchpad.spec.ts does not supply a scope value + const requestObjectPayload = { ...this.payload, ...(this.requestObject && (await this.requestObject.getPayload())) } + if (requestObjectPayload.scope && typeof requestObjectPayload.scope !== 'string') { + // test mattr.launchpad.spec.ts does not supply a scope value throw new Error('Invalid scope value') } return requestObjectPayload as RequestObjectPayload @@ -277,4 +289,8 @@ export class AuthorizationRequest { public async getPresentationDefinitions(version?: SupportedVersion): Promise { return await PresentationExchange.findValidPresentationDefinitions(await this.mergedPayloads(), version) } + + public async getDcqlQuery(): Promise { + return await findValidDcqlQuery(await this.mergedPayloads()) + } } diff --git a/packages/siop-oid4vp/lib/authorization-request/Payload.ts b/packages/siop-oid4vp/lib/authorization-request/Payload.ts index 3219f700..b21d7c70 100644 --- a/packages/siop-oid4vp/lib/authorization-request/Payload.ts +++ b/packages/siop-oid4vp/lib/authorization-request/Payload.ts @@ -18,20 +18,28 @@ import { createRequestRegistration } from './RequestRegistration' import { ClaimPayloadOptsVID1, CreateAuthorizationRequestOpts, PropertyTarget } from './types' export const createPresentationDefinitionClaimsProperties = (opts: ClaimPayloadOptsVID1): ClaimPayloadVID1 => { - if (!opts || !opts.vp_token || (!opts.vp_token.presentation_definition && !opts.vp_token.presentation_definition_uri)) { + if ( + !opts || + !opts.vp_token || + (!opts.vp_token.presentation_definition && !opts.vp_token.presentation_definition_uri && !opts.vp_token.dcql_query) + ) { return undefined } - const discoveryResult = PEX.definitionVersionDiscovery(opts.vp_token.presentation_definition) - if (discoveryResult.error) { - throw new Error(SIOPErrors.REQUEST_CLAIMS_PRESENTATION_DEFINITION_NOT_VALID) + + if (opts.vp_token.presentation_definition || opts.vp_token.presentation_definition_uri) { + const discoveryResult = PEX.definitionVersionDiscovery(opts.vp_token.presentation_definition) + if (discoveryResult.error) { + throw new Error(SIOPErrors.REQUEST_CLAIMS_PRESENTATION_DEFINITION_NOT_VALID) + } } return { ...(opts.id_token ? { id_token: opts.id_token } : {}), - ...((opts.vp_token.presentation_definition || opts.vp_token.presentation_definition_uri) && { + ...((opts.vp_token.presentation_definition || opts.vp_token.presentation_definition_uri || opts.vp_token.dcql_query) && { vp_token: { ...(!opts.vp_token.presentation_definition_uri && { presentation_definition: opts.vp_token.presentation_definition }), ...(opts.vp_token.presentation_definition_uri && { presentation_definition_uri: opts.vp_token.presentation_definition_uri }), + ...(opts.vp_token.dcql_query && { dcql_query: opts.vp_token.dcql_query }), }, }), } diff --git a/packages/siop-oid4vp/lib/authorization-request/URI.ts b/packages/siop-oid4vp/lib/authorization-request/URI.ts index 7aa04815..f6bf42bc 100644 --- a/packages/siop-oid4vp/lib/authorization-request/URI.ts +++ b/packages/siop-oid4vp/lib/authorization-request/URI.ts @@ -1,5 +1,6 @@ import { parseJWT } from '@sphereon/oid4vc-common' +import { findValidDcqlQuery } from '../authorization-response/Dcql' import { PresentationExchange } from '../authorization-response/PresentationExchange' import { decodeUriAsJson, encodeJsonAsURI, fetchByReferenceOrUseByValue } from '../helpers' import { assertValidRequestObjectPayload, RequestObject } from '../request-object' @@ -126,7 +127,6 @@ export class URI implements AuthorizationRequestURI { ...authorizationRequest.options.requestObject, version: authorizationRequest.options.version, uriScheme: authorizationRequest.options.uriScheme, - }, authorizationRequest.payload, authorizationRequest.requestObject, @@ -164,8 +164,9 @@ export class URI implements AuthorizationRequestURI { const requestObjectPayload: RequestObjectPayload = requestObjectJwt ? (parseJWT(requestObjectJwt).payload as RequestObjectPayload) : undefined if (requestObjectPayload) { - // Only used to validate if the request object contains presentation definition(s) + // Only used to validate if the request object contains presentation definition(s) | a dcql query await PresentationExchange.findValidPresentationDefinitions({ ...authorizationRequestPayload, ...requestObjectPayload }) + await findValidDcqlQuery({ ...authorizationRequestPayload, ...requestObjectPayload }) assertValidRequestObjectPayload(requestObjectPayload) if (requestObjectPayload.registration) { @@ -194,7 +195,8 @@ export class URI implements AuthorizationRequestURI { } } else { try { - scheme = (await authorizationRequest.getSupportedVersion()) === SupportedVersion.JWT_VC_PRESENTATION_PROFILE_v1 ? 'openid-vc://' : 'openid4vp://' + scheme = + (await authorizationRequest.getSupportedVersion()) === SupportedVersion.JWT_VC_PRESENTATION_PROFILE_v1 ? 'openid-vc://' : 'openid4vp://' } catch (error: unknown) { scheme = 'openid4vp://' } diff --git a/packages/siop-oid4vp/lib/authorization-request/types.ts b/packages/siop-oid4vp/lib/authorization-request/types.ts index 64956db9..da1abd0c 100644 --- a/packages/siop-oid4vp/lib/authorization-request/types.ts +++ b/packages/siop-oid4vp/lib/authorization-request/types.ts @@ -1,7 +1,7 @@ import { SigningAlgo } from '@sphereon/oid4vc-common' import { Hasher } from '@sphereon/ssi-types' -import { PresentationDefinitionPayloadOpts } from '../authorization-response' +import { DcqlQueryPayloadOpts, PresentationDefinitionPayloadOpts } from '../authorization-response' import { RequestObjectOpts } from '../request-object' import { ClientIdScheme, @@ -19,7 +19,7 @@ import { VerifyJwtCallback } from '../types/VpJwtVerifier' export interface ClaimPayloadOptsVID1 extends ClaimPayloadCommonOpts { id_token?: IdTokenClaimPayload - vp_token?: PresentationDefinitionPayloadOpts + vp_token?: PresentationDefinitionPayloadOpts | DcqlQueryPayloadOpts } export interface ClaimPayloadCommonOpts { diff --git a/packages/siop-oid4vp/lib/authorization-response/AuthorizationResponse.ts b/packages/siop-oid4vp/lib/authorization-response/AuthorizationResponse.ts index 64c3cc54..de5f1b3a 100644 --- a/packages/siop-oid4vp/lib/authorization-response/AuthorizationResponse.ts +++ b/packages/siop-oid4vp/lib/authorization-response/AuthorizationResponse.ts @@ -124,21 +124,25 @@ export class AuthorizationResponse { }) if (hasVpToken) { - const wrappedPresentations = response.payload.vp_token - ? await extractPresentationsFromVpToken(response.payload.vp_token, { + if (responseOpts.presentationExchange) { + const wrappedPresentations = response.payload.vp_token + ? await extractPresentationsFromVpToken(response.payload.vp_token, { + hasher: verifyOpts.hasher, + }) + : [] + + await assertValidVerifiablePresentations({ + presentationDefinitions, + presentations: wrappedPresentations, + verificationCallback: verifyOpts.verification.presentationVerificationCallback, + opts: { + ...responseOpts.presentationExchange, hasher: verifyOpts.hasher, - }) - : [] - - await assertValidVerifiablePresentations({ - presentationDefinitions, - presentations: wrappedPresentations, - verificationCallback: verifyOpts.verification.presentationVerificationCallback, - opts: { - ...responseOpts.presentationExchange, - hasher: verifyOpts.hasher, - }, - }) + }, + }) + } else { + throw new Error('TODO: VALIDATE PRESENTATION AGAINST DEFINITION') + } } return response @@ -219,7 +223,6 @@ export class AuthorizationResponse { } const presentationsArray = Array.isArray(presentations) ? presentations : [presentations] - // We do not verify them, as that is done elsewhere. So we simply can take the first nonce nonce = presentationsArray // FIXME toWrappedVerifiablePresentation() does not extract the nonce yet from mdocs. diff --git a/packages/siop-oid4vp/lib/authorization-response/Dcql.ts b/packages/siop-oid4vp/lib/authorization-response/Dcql.ts new file mode 100644 index 00000000..e67fb783 --- /dev/null +++ b/packages/siop-oid4vp/lib/authorization-response/Dcql.ts @@ -0,0 +1,44 @@ +import { DcqlQuery } from 'dcql' + +import { extractDataFromPath } from '../helpers' +import { AuthorizationRequestPayload, SIOPErrors } from '../types' + +/** + * Finds a valid DcqlQuery inside the given AuthenticationRequestPayload + * throws exception if the DcqlQuery is not valid + * returns the decoded dcql query if a valid instance found + * @param authorizationRequestPayload object that can have a dcql_query inside + * @param version + */ +export const findValidDcqlQuery = async (authorizationRequestPayload: AuthorizationRequestPayload): Promise => { + const vpTokens: string[] = extractDataFromPath(authorizationRequestPayload, '$..vp_token.dcql_query').map((d) => d.value) + const vpTokensList: string[] = extractDataFromPath(authorizationRequestPayload, '$..vp_token.dcql_query[*]').map((d) => d.value) + const definitions = extractDataFromPath(authorizationRequestPayload, '$.presentation_definition') + const definitionsFromList = extractDataFromPath(authorizationRequestPayload, '$.presentation_definition[*]') + const definitionRefs = extractDataFromPath(authorizationRequestPayload, '$.presentation_definition_uri') + const definitionRefsFromList = extractDataFromPath(authorizationRequestPayload, '$.presentation_definition_uri[*]') + + const hasPD = (definitions && definitions.length > 0) || (definitionsFromList && definitionsFromList.length > 0) + const hasPdRef = (definitionRefs && definitionRefs.length > 0) || (definitionRefsFromList && definitionRefsFromList.length > 0) + const hasDcql = (vpTokens && vpTokens.length > 0) || (vpTokensList && vpTokensList.length > 0) + + if ([hasPD, hasPdRef, hasDcql].filter(Boolean).length > 1) { + throw new Error(SIOPErrors.REQUEST_CLAIMS_PRESENTATION_NON_EXCLUSIVE) + } + + if (!(vpTokens && vpTokens.length) && !(vpTokensList && vpTokensList.length)) { + throw new Error('Cannot find dcql_query in vp_token. Presentation definition is present') + } + + if (vpTokens.length > 1 && vpTokensList.length > 1) { + throw new Error('Found multiple dcql_query in vp_token. Only one is allowed') + } + + const encoded = vpTokens.length ? vpTokens[0] : vpTokensList[0] + if (!encoded) return undefined + + const dcqlQuery = DcqlQuery.parse(JSON.parse(encoded)) + DcqlQuery.validate(dcqlQuery) + + return dcqlQuery +} diff --git a/packages/siop-oid4vp/lib/authorization-response/OpenID4VP.ts b/packages/siop-oid4vp/lib/authorization-response/OpenID4VP.ts index 0db13b81..ec76bd4d 100644 --- a/packages/siop-oid4vp/lib/authorization-response/OpenID4VP.ts +++ b/packages/siop-oid4vp/lib/authorization-response/OpenID4VP.ts @@ -10,6 +10,7 @@ import { W3CVerifiablePresentation, WrappedVerifiablePresentation, } from '@sphereon/ssi-types' +import { DcqlQuery, DcqlQueryVpToken } from 'dcql' import { AuthorizationRequest } from '../authorization-request' import { verifyRevocation } from '../helpers' @@ -70,46 +71,76 @@ export const verifyPresentations = async ( authorizationResponse: AuthorizationResponse, verifyOpts: VerifyAuthorizationResponseOpts, ): Promise => { - if (!authorizationResponse.payload.vp_token || Array.isArray(authorizationResponse.payload.vp_token) && authorizationResponse.payload.vp_token.length === 0) { + if ( + !authorizationResponse.payload.vp_token || + (Array.isArray(authorizationResponse.payload.vp_token) && authorizationResponse.payload.vp_token.length === 0) + ) { return Promise.reject(Error('the payload is missing a vp_token')) } - - const presentations = await extractPresentationsFromVpToken(authorizationResponse.payload.vp_token, { hasher: verifyOpts.hasher }) - const presentationDefinitions = verifyOpts.presentationDefinitions - ? Array.isArray(verifyOpts.presentationDefinitions) - ? verifyOpts.presentationDefinitions - : [verifyOpts.presentationDefinitions] - : [] + let idPayload: IDTokenPayload | undefined if (authorizationResponse.idToken) { idPayload = await authorizationResponse.idToken.payload() } + // todo: Probably wise to check against request for the location of the submission_data const presentationSubmission = idPayload?._vp_token?.presentation_submission ?? authorizationResponse.payload.presentation_submission - await assertValidVerifiablePresentations({ - presentationDefinitions, - presentations, - verificationCallback: verifyOpts.verification.presentationVerificationCallback, - opts: { - presentationSubmission, - restrictToFormats: verifyOpts.restrictToFormats, - restrictToDIDMethods: verifyOpts.restrictToDIDMethods, - hasher: verifyOpts.hasher, - }, - }) + let wrappedPresentations: WrappedVerifiablePresentation[] = [] + const presentationDefinitions = verifyOpts.presentationDefinitions + ? Array.isArray(verifyOpts.presentationDefinitions) + ? verifyOpts.presentationDefinitions + : [verifyOpts.presentationDefinitions] + : [] + + const dcqlQuery = verifyOpts.dcqlQuery ?? authorizationResponse.authorizationRequest.payload.dcql_query + if (dcqlQuery) { + const dcqlQueryVpToken = DcqlQueryVpToken.parse(JSON.parse(authorizationResponse.payload.vp_token as string)) + + const parsedQuery = DcqlQuery.parse(dcqlQuery) + DcqlQuery.validate(parsedQuery) + + const presentations = Object.values(dcqlQueryVpToken) as Array + wrappedPresentations = presentations.map((vp) => CredentialMapper.toWrappedVerifiablePresentation(vp, { hasher: verifyOpts.hasher })) + + const verifiedPresentations = await Promise.all( + presentations.map((presentation) => verifyOpts.verification.presentationVerificationCallback(presentation, presentationSubmission)), + ) + + // TODO: assert the submission against the definition + + if (verifiedPresentations.some((verified) => !verified)) { + const message = verifiedPresentations + .map((verified) => verified.reason) + .filter(Boolean) + .join(', ') + + throw Error(`Failed to verify presentations. ${message}`) + } + } else { + const presentations = await extractPresentationsFromVpToken(authorizationResponse.payload.vp_token, { hasher: verifyOpts.hasher }) + wrappedPresentations = Array.isArray(presentations) ? presentations : [presentations] + + await assertValidVerifiablePresentations({ + presentationDefinitions, + presentations, + verificationCallback: verifyOpts.verification.presentationVerificationCallback, + opts: { + presentationSubmission, + restrictToFormats: verifyOpts.restrictToFormats, + restrictToDIDMethods: verifyOpts.restrictToDIDMethods, + hasher: verifyOpts.hasher, + }, + }) + } // If there are no presentations, and the `assertValidVerifiablePresentations` did not fail // it means there's no oid4vp response and also not requested - if (Array.isArray(presentations) && presentations.length === 0) { + if (wrappedPresentations.length === 0) { return null } - if (!presentations || (Array.isArray(presentations) && presentations.length === 0)) { - return Promise.reject(Error('missing presentation(s)')) - } - const presentationsArray = Array.isArray(presentations) ? presentations : [presentations] - const presentationsWithoutMdoc = presentationsArray.filter((p) => p.format !== 'mso_mdoc') + const presentationsWithoutMdoc = wrappedPresentations.filter((p) => p.format !== 'mso_mdoc') const nonces = new Set(presentationsWithoutMdoc.map(extractNonceFromWrappedVerifiablePresentation)) if (presentationsWithoutMdoc.length > 0 && nonces.size !== 1) { throw Error(`${nonces.size} nonce values found for ${presentationsWithoutMdoc.length}. Should be 1`) @@ -128,24 +159,22 @@ export const verifyPresentations = async ( if (!verifyOpts.verification.revocationOpts?.revocationVerificationCallback) { throw Error(`Please provide a revocation callback as revocation checking of credentials and presentations is not disabled`) } - for (const vp of presentationsArray) { + for (const vp of wrappedPresentations) { await verifyRevocation(vp, verifyOpts.verification.revocationOpts.revocationVerificationCallback, revocationVerification) } } - return { nonce, presentations: presentationsArray, presentationDefinitions, submissionData: presentationSubmission } + return { nonce, presentations: wrappedPresentations, presentationDefinitions, submissionData: presentationSubmission } } export const extractPresentationsFromVpToken = async ( vpToken: Array | W3CVerifiablePresentation | CompactSdJwtVc | string, opts?: { hasher?: Hasher }, ): Promise => { - const tokens = Array.isArray(vpToken) ? vpToken : [vpToken]; - const wrappedTokens = tokens.map(vp => - CredentialMapper.toWrappedVerifiablePresentation(vp, { hasher: opts?.hasher }) - ); + const tokens = Array.isArray(vpToken) ? vpToken : [vpToken] + const wrappedTokens = tokens.map((vp) => CredentialMapper.toWrappedVerifiablePresentation(vp, { hasher: opts?.hasher })) - return tokens.length === 1 ? wrappedTokens[0] : wrappedTokens; - } + return tokens.length === 1 ? wrappedTokens[0] : wrappedTokens +} export const createPresentationSubmission = async ( verifiablePresentations: W3CVerifiablePresentation[], @@ -285,8 +314,8 @@ export const assertValidVerifiablePresentations = async (args: { presentationSubmission?: PresentationSubmission hasher?: Hasher } -}) : Promise => { - const {presentations} = args +}): Promise => { + const { presentations } = args if (!presentations || (Array.isArray(presentations) && presentations.length === 0)) { return Promise.reject(Error('missing presentation(s)')) } diff --git a/packages/siop-oid4vp/lib/authorization-response/PresentationExchange.ts b/packages/siop-oid4vp/lib/authorization-response/PresentationExchange.ts index 18764cb1..cae54fb5 100644 --- a/packages/siop-oid4vp/lib/authorization-response/PresentationExchange.ts +++ b/packages/siop-oid4vp/lib/authorization-response/PresentationExchange.ts @@ -195,7 +195,7 @@ export class PresentationExchange { ).map((d) => d.value) const vpTokenRefs = extractDataFromPath(authorizationRequestPayload, '$..vp_token.presentation_definition_uri') if (vpTokens && vpTokens.length && vpTokenRefs && vpTokenRefs.length) { - throw new Error(SIOPErrors.REQUEST_CLAIMS_PRESENTATION_DEFINITION_BY_REF_AND_VALUE_NON_EXCLUSIVE) + throw new Error(SIOPErrors.REQUEST_CLAIMS_PRESENTATION_NON_EXCLUSIVE) } if (vpTokens && vpTokens.length) { vpTokens.forEach((vpToken: PresentationDefinitionV1 | PresentationDefinitionV2) => { @@ -252,7 +252,7 @@ export class PresentationExchange { const hasPD = (definitions && definitions.length > 0) || (definitionsFromList && definitionsFromList.length > 0) const hasPdRef = (definitionRefs && definitionRefs.length > 0) || (definitionRefsFromList && definitionRefsFromList.length > 0) if (hasPD && hasPdRef) { - throw new Error(SIOPErrors.REQUEST_CLAIMS_PRESENTATION_DEFINITION_BY_REF_AND_VALUE_NON_EXCLUSIVE) + throw new Error(SIOPErrors.REQUEST_CLAIMS_PRESENTATION_NON_EXCLUSIVE) } if (definitions && definitions.length > 0) { definitions.forEach((definition) => { diff --git a/packages/siop-oid4vp/lib/authorization-response/types.ts b/packages/siop-oid4vp/lib/authorization-response/types.ts index 9ec19ddc..9a6e1bf2 100644 --- a/packages/siop-oid4vp/lib/authorization-response/types.ts +++ b/packages/siop-oid4vp/lib/authorization-response/types.ts @@ -5,10 +5,11 @@ import { CompactSdJwtVc, Hasher, MdocOid4vpIssuerSigned, + MdocOid4vpMdocVpToken, PresentationSubmission, W3CVerifiablePresentation, - MdocOid4vpMdocVpToken, } from '@sphereon/ssi-types' +import { DcqlQuery, DcqlQueryVpToken } from 'dcql' import { ResponseMode, @@ -41,6 +42,7 @@ export interface AuthorizationResponseOpts { tokenType?: string refreshToken?: string presentationExchange?: PresentationExchangeResponseOpts + dcqlQuery?: DcqlQueryResponseOpts } export interface PresentationExchangeResponseOpts { @@ -59,6 +61,10 @@ export interface PresentationExchangeResponseOpts { restrictToDIDMethods?: string[] } +export interface DcqlQueryResponseOpts { + credentialQueryIdToPresentation: DcqlQueryVpToken +} + export interface PresentationExchangeRequestOpts { presentationVerificationCallback?: PresentationVerificationCallback } @@ -66,6 +72,13 @@ export interface PresentationExchangeRequestOpts { export interface PresentationDefinitionPayloadOpts { presentation_definition?: IPresentationDefinition presentation_definition_uri?: string + dcql_query?: never +} + +export interface DcqlQueryPayloadOpts { + dcql_query?: string + presentation_definition?: never + presentation_definition_uri?: never } export interface PresentationDefinitionWithLocation { @@ -108,6 +121,7 @@ export interface VerifyAuthorizationResponseOpts { nonce?: string // To verify the response against the supplied nonce state?: string // To verify the response against the supplied state presentationDefinitions?: PresentationDefinitionWithLocation | PresentationDefinitionWithLocation[] // The presentation definitions to match against VPs in the response + dcqlQuery?: DcqlQuery audience?: string // The audience/redirect_uri restrictToFormats?: Format // Further restrict to certain VC formats, not expressed in the presentation definition restrictToDIDMethods?: string[] diff --git a/packages/siop-oid4vp/lib/helpers/SIOPSpecVersion.ts b/packages/siop-oid4vp/lib/helpers/SIOPSpecVersion.ts index 2e0d9d77..5e41e4e8 100644 --- a/packages/siop-oid4vp/lib/helpers/SIOPSpecVersion.ts +++ b/packages/siop-oid4vp/lib/helpers/SIOPSpecVersion.ts @@ -29,7 +29,8 @@ function isID1Payload(authorizationRequest: AuthorizationRequestPayload) { !authorizationRequest.client_metadata_uri && !authorizationRequest.client_metadata && !authorizationRequest.presentation_definition && - !authorizationRequest.presentation_definition_uri + !authorizationRequest.presentation_definition_uri && + !authorizationRequest.dcql_query ) } diff --git a/packages/siop-oid4vp/lib/op/OP.ts b/packages/siop-oid4vp/lib/op/OP.ts index 7fee9a0a..e4fa29fb 100644 --- a/packages/siop-oid4vp/lib/op/OP.ts +++ b/packages/siop-oid4vp/lib/op/OP.ts @@ -1,15 +1,16 @@ -import { EventEmitter } from 'events'; +import { EventEmitter } from 'events' import { jarmAuthResponseSend, JarmClientMetadata, jarmMetadataValidate, JarmServerMetadata } from '@sphereon/jarm' -import { JwtIssuer, uuidv4 } from '@sphereon/oid4vc-common'; -import { IIssuerId } from '@sphereon/ssi-types'; +import { JwtIssuer, uuidv4 } from '@sphereon/oid4vc-common' +import { IIssuerId } from '@sphereon/ssi-types' -import { AuthorizationRequest, URI, VerifyAuthorizationRequestOpts } from '../authorization-request'; -import { mergeVerificationOpts } from '../authorization-request/Opts'; +import { AuthorizationRequest, URI, VerifyAuthorizationRequestOpts } from '../authorization-request' +import { mergeVerificationOpts } from '../authorization-request/Opts' import { AuthorizationResponse, AuthorizationResponseOpts, AuthorizationResponseWithCorrelationId, + DcqlQueryResponseOpts, PresentationExchangeResponseOpts, } from '../authorization-response' import { encodeJsonAsURI, post } from '../helpers' @@ -29,11 +30,11 @@ import { SupportedVersion, UrlEncodingFormat, Verification, - VerifiedAuthorizationRequest -} from '../types'; + VerifiedAuthorizationRequest, +} from '../types' -import { OPBuilder } from './OPBuilder'; -import { createResponseOptsFromBuilderOrExistingOpts, createVerifyRequestOptsFromBuilderOrExistingOpts } from './Opts'; +import { OPBuilder } from './OPBuilder' +import { createResponseOptsFromBuilderOrExistingOpts, createVerifyRequestOptsFromBuilderOrExistingOpts } from './Opts' // The OP publishes the formats it supports using the vp_formats_supported metadata parameter as defined above in its "openid-configuration". export class OP { @@ -78,7 +79,7 @@ export class OP { try { const verifiedAuthorizationRequest = await authorizationRequest.verify( - this.newVerifyAuthorizationRequestOpts({ ...requestOpts, correlationId }) + this.newVerifyAuthorizationRequestOpts({ ...requestOpts, correlationId }), ) await this.emitEvent(AuthorizationEvents.ON_AUTH_REQUEST_VERIFIED_SUCCESS, { @@ -106,6 +107,7 @@ export class OP { issuer?: ResponseIss | string verification?: Verification presentationExchange?: PresentationExchangeResponseOpts + dcqlQuery?: DcqlQueryResponseOpts }, ): Promise { if ( @@ -235,7 +237,7 @@ export class OP { const { response } = await createJarmResponse({ requestObjectPayload, authorizationResponsePayload: payload, - clientMetadata + clientMetadata, }) try { @@ -243,9 +245,9 @@ export class OP { authRequestParams: { response_uri: responseUri, response_mode: responseMode, - response_type: responseType + response_type: responseType, }, - authResponse: response + authResponse: response, }) void this.emitEvent(AuthorizationEvents.ON_AUTH_RESPONSE_SENT_SUCCESS, { correlationId, subject: response }) return jarmResponse @@ -253,7 +255,7 @@ export class OP { void this.emitEvent(AuthorizationEvents.ON_AUTH_RESPONSE_SENT_FAILED, { correlationId, subject: response, - error + error, }) throw error } @@ -294,6 +296,7 @@ export class OP { issuer?: IIssuerId | ResponseIss audience?: string presentationExchange?: PresentationExchangeResponseOpts + dcqlQuery?: DcqlQueryResponseOpts }): AuthorizationResponseOpts { const version = opts.version ?? this._createResponseOptions.version let issuer = opts.issuer ?? this._createResponseOptions?.registration?.issuer @@ -308,11 +311,14 @@ export class OP { } // We are taking the whole presentationExchange object from a certain location const presentationExchange = opts.presentationExchange ?? this._createResponseOptions.presentationExchange + const dcqlQuery = opts.dcqlQuery ?? this._createResponseOptions.dcqlQuery + const responseURI = opts.audience ?? this._createResponseOptions.responseURI return { ...this._createResponseOptions, ...opts, ...(presentationExchange && { presentationExchange }), + ...(dcqlQuery && { dcqlQuery }), registration: { ...this._createResponseOptions?.registration, issuer }, responseURI, responseURIType: diff --git a/packages/siop-oid4vp/lib/request-object/Payload.ts b/packages/siop-oid4vp/lib/request-object/Payload.ts index 59991031..661c7ee3 100644 --- a/packages/siop-oid4vp/lib/request-object/Payload.ts +++ b/packages/siop-oid4vp/lib/request-object/Payload.ts @@ -48,6 +48,7 @@ export const createRequestObjectPayload = async (opts: CreateAuthorizationReques claims, presentation_definition_uri: payload.presentation_definition_uri, presentation_definition: payload.presentation_definition, + dcql_query: payload.dcql_query, client_metadata: payload.client_metadata, iat, nbf, diff --git a/packages/siop-oid4vp/lib/rp/RP.ts b/packages/siop-oid4vp/lib/rp/RP.ts index d165dac2..addc9a23 100644 --- a/packages/siop-oid4vp/lib/rp/RP.ts +++ b/packages/siop-oid4vp/lib/rp/RP.ts @@ -8,6 +8,7 @@ import { } from '@sphereon/jarm' import { decodeProtectedHeader, JwtIssuer, uuidv4 } from '@sphereon/oid4vc-common' import { Hasher } from '@sphereon/ssi-types' +import { DcqlQuery } from 'dcql' import { AuthorizationRequest, @@ -202,6 +203,7 @@ export class RP { nonce?: string verification?: Verification presentationDefinitions?: PresentationDefinitionWithLocation | PresentationDefinitionWithLocation[] + dcqlQuery?: DcqlQuery }, ): Promise { const state = opts?.state || this.verifyResponseOptions.state @@ -376,6 +378,7 @@ export class RP { verification?: Verification audience?: string presentationDefinitions?: PresentationDefinitionWithLocation | PresentationDefinitionWithLocation[] + dcqlQuery?: DcqlQuery }, ): Promise { let correlationId = opts?.correlationId ?? this._verifyResponseOptions.correlationId @@ -418,6 +421,7 @@ export class RP { nonce, verification: mergeVerificationOpts(this._verifyResponseOptions, opts), presentationDefinitions: opts?.presentationDefinitions ?? this._verifyResponseOptions.presentationDefinitions, + dcqlQuery: opts?.dcqlQuery ?? this._verifyResponseOptions.dcqlQuery, } } diff --git a/packages/siop-oid4vp/lib/rp/RPBuilder.ts b/packages/siop-oid4vp/lib/rp/RPBuilder.ts index fbbdec63..c27b01b1 100644 --- a/packages/siop-oid4vp/lib/rp/RPBuilder.ts +++ b/packages/siop-oid4vp/lib/rp/RPBuilder.ts @@ -218,6 +218,24 @@ export class RPBuilder { return this } + withDcqlQuery(dcqlQuery: string, targets?: PropertyTargets): RPBuilder { + this._authorizationRequestPayload.dcql_query = assignIfAuth( + { + propertyValue: dcqlQuery, + targets, + }, + false, + ) + this._requestObjectPayload.dcql_query = assignIfRequestObject( + { + propertyValue: dcqlQuery, + targets, + }, + true, + ) + return this + } + withPresentationDefinition(definitionOpts: { definition: IPresentationDefinition; definitionUri?: string }, targets?: PropertyTargets): RPBuilder { const { definition, definitionUri } = definitionOpts diff --git a/packages/siop-oid4vp/lib/types/Errors.ts b/packages/siop-oid4vp/lib/types/Errors.ts index 24c36f92..780d8c17 100644 --- a/packages/siop-oid4vp/lib/types/Errors.ts +++ b/packages/siop-oid4vp/lib/types/Errors.ts @@ -38,7 +38,7 @@ enum SIOPErrors { REFERENCE_URI_NO_PAYLOAD = 'referenceUri specified, but object to host there is not present', NO_SELF_ISSUED_ISS = 'The Response Token Issuer Claim (iss) MUST start with https://self-isued.me/v2', REGISTRATION_NOT_SET = 'Registration metadata not set.', - REQUEST_CLAIMS_PRESENTATION_DEFINITION_BY_REF_AND_VALUE_NON_EXCLUSIVE = "Request claims can't have both 'presentation_definition' and 'presentation_definition_uri'", + REQUEST_CLAIMS_PRESENTATION_NON_EXCLUSIVE = "Request claims can't multiple of 'presentation_definition' and 'presentation_definition_uri' 'dcql_query", REQUEST_CLAIMS_PRESENTATION_DEFINITION_NOT_VALID = 'Presentation definition in the request claims is not valid', REQUEST_OBJECT_TYPE_NOT_SET = 'Request object type is not set.', RESPONSE_OPTS_PRESENTATIONS_SUBMISSION_IS_NOT_VALID = 'presentation_submission object inside the response opts vp should be valid', diff --git a/packages/siop-oid4vp/lib/types/SIOP.types.ts b/packages/siop-oid4vp/lib/types/SIOP.types.ts index a104d0b7..30619c0f 100644 --- a/packages/siop-oid4vp/lib/types/SIOP.types.ts +++ b/packages/siop-oid4vp/lib/types/SIOP.types.ts @@ -13,6 +13,7 @@ import { W3CVerifiablePresentation, WrappedVerifiablePresentation, } from '@sphereon/ssi-types' +import { DcqlQuery } from 'dcql' import { AuthorizationRequest, CreateAuthorizationRequestOpts, PropertyTargets, VerifyAuthorizationRequestOpts } from '../authorization-request' import { @@ -98,6 +99,7 @@ export interface AuthorizationRequestPayloadVD12OID4VPD20 presentation_definition_uri?: string client_id_scheme?: ClientIdSchemeOID4VPD20 response_uri?: string // New since OID4VP18 OPTIONAL. The Response URI to which the Wallet MUST send the Authorization Response using an HTTPS POST request as defined by the Response Mode direct_post. The Response URI receives all Authorization Response parameters as defined by the respective Response Type. When the response_uri parameter is present, the redirect_uri Authorization Request parameter MUST NOT be present. If the redirect_uri Authorization Request parameter is present when the Response Mode is direct_post, the Wallet MUST return an invalid_request Authorization Response error. + dcql_query?: string } export type ClientIdSchemeOID4VPD18 = 'pre-registered' | 'redirect_uri' | 'entity_id' | 'did' @@ -139,6 +141,7 @@ export interface VerifiedAuthorizationRequest extends Partial { requestObject?: RequestObject // The Request object registrationMetadataPayload: RPRegistrationMetadataPayload presentationDefinitions?: PresentationDefinitionWithLocation[] // The optional presentation definition objects that the RP requests + dcqlQuery?: DcqlQuery verifyOpts: VerifyAuthorizationRequestOpts // The verification options for the authentication request versions: SupportedVersion[] } @@ -164,6 +167,8 @@ export interface IDTokenPayload extends JWTPayload { } } +export type DcqlQueryVpToken = string + export interface AuthorizationResponsePayload { access_token?: string token_type?: string @@ -176,6 +181,7 @@ export interface AuthorizationResponsePayload { | W3CVerifiablePresentation | CompactSdJwtVc | MdocOid4vpMdocVpToken + | DcqlQueryVpToken presentation_submission?: PresentationSubmission verifiedData?: IPresentation | AdditionalClaims // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -190,6 +196,7 @@ export interface IdTokenClaimPayload { export interface VpTokenClaimPayload { presentation_definition?: PresentationDefinitionV1 | PresentationDefinitionV2 presentation_definition_uri?: string + dcql_query?: string } export interface ClaimPayloadCommon { diff --git a/packages/siop-oid4vp/package.json b/packages/siop-oid4vp/package.json index bc687cf5..ad8ea863 100644 --- a/packages/siop-oid4vp/package.json +++ b/packages/siop-oid4vp/package.json @@ -18,6 +18,7 @@ "@sphereon/jarm": "workspace:*", "@sphereon/oid4vc-common": "workspace:*", "@sphereon/pex": "5.0.0-unstable.24", + "dcql": "link:../../../dcql/dcql", "@sphereon/pex-models": "^2.3.1", "@sphereon/ssi-types": "0.30.2-next.129", "cross-fetch": "^4.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7378a309..b16db23d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -460,6 +460,9 @@ importers: cross-fetch: specifier: ^4.0.0 version: 4.0.0(encoding@0.1.13) + dcql: + specifier: link:../../../dcql/dcql + version: link:../../../dcql/dcql debug: specifier: ^4.3.5 version: 4.3.7 From dc1c318fa130dc7fec493b82f69a1f563f62713c Mon Sep 17 00:00:00 2001 From: Martin Auer Date: Thu, 21 Nov 2024 08:31:10 +0100 Subject: [PATCH 02/11] feat: dcql alpha --- .../AuthorizationResponse.ts | 19 ++++-- .../lib/authorization-response/Dcql.ts | 20 +++--- .../lib/authorization-response/OpenID4VP.ts | 62 +++++++++++-------- .../lib/authorization-response/Payload.ts | 6 +- .../lib/authorization-response/types.ts | 6 +- packages/siop-oid4vp/lib/rp/RP.ts | 6 +- ...ationRequestPayloadVD12OID4VPD20.schema.ts | 3 + .../AuthorizationRequestPayloadVID1.schema.ts | 3 + .../AuthorizationResponseOpts.schema.ts | 26 ++++++++ packages/siop-oid4vp/lib/types/SIOP.types.ts | 7 +++ 10 files changed, 111 insertions(+), 47 deletions(-) diff --git a/packages/siop-oid4vp/lib/authorization-response/AuthorizationResponse.ts b/packages/siop-oid4vp/lib/authorization-response/AuthorizationResponse.ts index de5f1b3a..df4d1288 100644 --- a/packages/siop-oid4vp/lib/authorization-response/AuthorizationResponse.ts +++ b/packages/siop-oid4vp/lib/authorization-response/AuthorizationResponse.ts @@ -1,4 +1,4 @@ -import { CredentialMapper, Hasher } from '@sphereon/ssi-types' +import { CredentialMapper, Hasher, WrappedVerifiablePresentation } from '@sphereon/ssi-types' import { AuthorizationRequest, VerifyAuthorizationRequestOpts } from '../authorization-request' import { assertValidVerifyAuthorizationRequestOpts } from '../authorization-request/Opts' @@ -11,6 +11,7 @@ import { extractPresentationsFromVpToken, verifyPresentations, } from './OpenID4VP' +import { extractPresentationsFromDcqlVpToken } from './OpenID4VP' import { assertValidResponseOpts } from './Opts' import { createResponsePayload } from './Payload' import { AuthorizationResponseOpts, PresentationDefinitionWithLocation, VerifyAuthorizationResponseOpts } from './types' @@ -141,7 +142,7 @@ export class AuthorizationResponse { }, }) } else { - throw new Error('TODO: VALIDATE PRESENTATION AGAINST DEFINITION') + console.error('TODO: VALIDATE PRESENTATION AGAINST DEFINITION') } } @@ -189,7 +190,8 @@ export class AuthorizationResponse { state, correlationId: verifyOpts.correlationId, ...(this.idToken && { idToken: verifiedIdToken }), - ...(oid4vp && { oid4vpSubmission: oid4vp }), + ...(oid4vp && 'presentationDefinitions' in oid4vp && { oid4vpSubmission: oid4vp }), + ...(oid4vp && 'dcqlQuery' in oid4vp && { oid4vpSubmissionDcql: oid4vp }), } } @@ -217,8 +219,15 @@ export class AuthorizationResponse { public async mergedPayloads(opts?: { consistencyCheck?: boolean; hasher?: Hasher }): Promise { let nonce: string | undefined = this._payload.nonce if (this._payload?.vp_token) { - const presentations = this.payload.vp_token ? await extractPresentationsFromVpToken(this.payload.vp_token, opts) : [] - if (!presentations || (Array.isArray(presentations) && presentations.length === 0)) { + let presentations: WrappedVerifiablePresentation | WrappedVerifiablePresentation[] + + try { + presentations = extractPresentationsFromDcqlVpToken(this._payload.vp_token as string, opts) + } catch (e) { + presentations = extractPresentationsFromVpToken(this._payload.vp_token, opts) + } + + if (presentations && Array.isArray(presentations) && presentations.length === 0) { return Promise.reject(Error('missing presentation(s)')) } const presentationsArray = Array.isArray(presentations) ? presentations : [presentations] diff --git a/packages/siop-oid4vp/lib/authorization-response/Dcql.ts b/packages/siop-oid4vp/lib/authorization-response/Dcql.ts index e67fb783..16cf9672 100644 --- a/packages/siop-oid4vp/lib/authorization-response/Dcql.ts +++ b/packages/siop-oid4vp/lib/authorization-response/Dcql.ts @@ -11,8 +11,8 @@ import { AuthorizationRequestPayload, SIOPErrors } from '../types' * @param version */ export const findValidDcqlQuery = async (authorizationRequestPayload: AuthorizationRequestPayload): Promise => { - const vpTokens: string[] = extractDataFromPath(authorizationRequestPayload, '$..vp_token.dcql_query').map((d) => d.value) - const vpTokensList: string[] = extractDataFromPath(authorizationRequestPayload, '$..vp_token.dcql_query[*]').map((d) => d.value) + const dcqlQuery: string[] = extractDataFromPath(authorizationRequestPayload, '$.dcql_query').map((d) => d.value) + const dcqlQueryList: string[] = extractDataFromPath(authorizationRequestPayload, '$.dcql_query[*]').map((d) => d.value) const definitions = extractDataFromPath(authorizationRequestPayload, '$.presentation_definition') const definitionsFromList = extractDataFromPath(authorizationRequestPayload, '$.presentation_definition[*]') const definitionRefs = extractDataFromPath(authorizationRequestPayload, '$.presentation_definition_uri') @@ -20,25 +20,21 @@ export const findValidDcqlQuery = async (authorizationRequestPayload: Authorizat const hasPD = (definitions && definitions.length > 0) || (definitionsFromList && definitionsFromList.length > 0) const hasPdRef = (definitionRefs && definitionRefs.length > 0) || (definitionRefsFromList && definitionRefsFromList.length > 0) - const hasDcql = (vpTokens && vpTokens.length > 0) || (vpTokensList && vpTokensList.length > 0) + const hasDcql = (dcqlQuery && dcqlQuery.length > 0) || (dcqlQueryList && dcqlQueryList.length > 0) if ([hasPD, hasPdRef, hasDcql].filter(Boolean).length > 1) { throw new Error(SIOPErrors.REQUEST_CLAIMS_PRESENTATION_NON_EXCLUSIVE) } - if (!(vpTokens && vpTokens.length) && !(vpTokensList && vpTokensList.length)) { - throw new Error('Cannot find dcql_query in vp_token. Presentation definition is present') - } - - if (vpTokens.length > 1 && vpTokensList.length > 1) { + if (dcqlQuery.length > 1 || dcqlQueryList.length > 1) { throw new Error('Found multiple dcql_query in vp_token. Only one is allowed') } - const encoded = vpTokens.length ? vpTokens[0] : vpTokensList[0] + const encoded = dcqlQuery.length ? dcqlQuery[0] : dcqlQueryList[0] if (!encoded) return undefined - const dcqlQuery = DcqlQuery.parse(JSON.parse(encoded)) - DcqlQuery.validate(dcqlQuery) + const parsedDcqlQuery = DcqlQuery.parse(JSON.parse(encoded)) + DcqlQuery.validate(parsedDcqlQuery) - return dcqlQuery + return parsedDcqlQuery } diff --git a/packages/siop-oid4vp/lib/authorization-response/OpenID4VP.ts b/packages/siop-oid4vp/lib/authorization-response/OpenID4VP.ts index ec76bd4d..4ff909cf 100644 --- a/packages/siop-oid4vp/lib/authorization-response/OpenID4VP.ts +++ b/packages/siop-oid4vp/lib/authorization-response/OpenID4VP.ts @@ -10,7 +10,7 @@ import { W3CVerifiablePresentation, WrappedVerifiablePresentation, } from '@sphereon/ssi-types' -import { DcqlQuery, DcqlQueryVpToken } from 'dcql' +import { DcqlQuery, DcqlVpToken } from 'dcql' import { AuthorizationRequest } from '../authorization-request' import { verifyRevocation } from '../helpers' @@ -22,6 +22,7 @@ import { SIOPErrors, SupportedVersion, VerifiedOpenID4VPSubmission, + VerifiedOpenID4VPSubmissionDcql, } from '../types' import { AuthorizationResponse } from './AuthorizationResponse' @@ -70,10 +71,12 @@ export const extractNonceFromWrappedVerifiablePresentation = (wrappedVp: Wrapped export const verifyPresentations = async ( authorizationResponse: AuthorizationResponse, verifyOpts: VerifyAuthorizationResponseOpts, -): Promise => { +): Promise => { + if (!authorizationResponse.payload.vp_token) return null if ( - !authorizationResponse.payload.vp_token || - (Array.isArray(authorizationResponse.payload.vp_token) && authorizationResponse.payload.vp_token.length === 0) + authorizationResponse.payload.vp_token && + Array.isArray(authorizationResponse.payload.vp_token) && + authorizationResponse.payload.vp_token.length === 0 ) { return Promise.reject(Error('the payload is missing a vp_token')) } @@ -83,9 +86,6 @@ export const verifyPresentations = async ( idPayload = await authorizationResponse.idToken.payload() } - // todo: Probably wise to check against request for the location of the submission_data - const presentationSubmission = idPayload?._vp_token?.presentation_submission ?? authorizationResponse.payload.presentation_submission - let wrappedPresentations: WrappedVerifiablePresentation[] = [] const presentationDefinitions = verifyOpts.presentationDefinitions ? Array.isArray(verifyOpts.presentationDefinitions) @@ -93,18 +93,19 @@ export const verifyPresentations = async ( : [verifyOpts.presentationDefinitions] : [] - const dcqlQuery = verifyOpts.dcqlQuery ?? authorizationResponse.authorizationRequest.payload.dcql_query - if (dcqlQuery) { - const dcqlQueryVpToken = DcqlQueryVpToken.parse(JSON.parse(authorizationResponse.payload.vp_token as string)) + let presentationSubmission: PresentationSubmission | undefined - const parsedQuery = DcqlQuery.parse(dcqlQuery) - DcqlQuery.validate(parsedQuery) + let dcqlQuery = verifyOpts.dcqlQuery ?? authorizationResponse?.authorizationRequest?.payload.dcql_query + if (dcqlQuery) { + dcqlQuery = DcqlQuery.parse(dcqlQuery) + DcqlQuery.validate(dcqlQuery) - const presentations = Object.values(dcqlQueryVpToken) as Array - wrappedPresentations = presentations.map((vp) => CredentialMapper.toWrappedVerifiablePresentation(vp, { hasher: verifyOpts.hasher })) + wrappedPresentations = extractPresentationsFromDcqlVpToken(authorizationResponse.payload.vp_token as string, { hasher: verifyOpts.hasher }) const verifiedPresentations = await Promise.all( - presentations.map((presentation) => verifyOpts.verification.presentationVerificationCallback(presentation, presentationSubmission)), + wrappedPresentations.map((presentation) => + verifyOpts.verification.presentationVerificationCallback(presentation.original as W3CVerifiablePresentation), + ), ) // TODO: assert the submission against the definition @@ -118,9 +119,14 @@ export const verifyPresentations = async ( throw Error(`Failed to verify presentations. ${message}`) } } else { - const presentations = await extractPresentationsFromVpToken(authorizationResponse.payload.vp_token, { hasher: verifyOpts.hasher }) + const presentations = authorizationResponse.payload.vp_token + ? await extractPresentationsFromVpToken(authorizationResponse.payload.vp_token, { hasher: verifyOpts.hasher }) + : [] wrappedPresentations = Array.isArray(presentations) ? presentations : [presentations] + // todo: Probably wise to check against request for the location of the submission_data + presentationSubmission = idPayload?._vp_token?.presentation_submission ?? authorizationResponse.payload.presentation_submission + await assertValidVerifiablePresentations({ presentationDefinitions, presentations, @@ -134,12 +140,6 @@ export const verifyPresentations = async ( }) } - // If there are no presentations, and the `assertValidVerifiablePresentations` did not fail - // it means there's no oid4vp response and also not requested - if (wrappedPresentations.length === 0) { - return null - } - const presentationsWithoutMdoc = wrappedPresentations.filter((p) => p.format !== 'mso_mdoc') const nonces = new Set(presentationsWithoutMdoc.map(extractNonceFromWrappedVerifiablePresentation)) if (presentationsWithoutMdoc.length > 0 && nonces.size !== 1) { @@ -163,13 +163,25 @@ export const verifyPresentations = async ( await verifyRevocation(vp, verifyOpts.verification.revocationOpts.revocationVerificationCallback, revocationVerification) } } - return { nonce, presentations: wrappedPresentations, presentationDefinitions, submissionData: presentationSubmission } + if (presentationDefinitions) { + return { nonce, presentations: wrappedPresentations, presentationDefinitions, submissionData: presentationSubmission } + } else { + return { nonce, presentations: wrappedPresentations, dcqlQuery } + } +} + +export const extractPresentationsFromDcqlVpToken = ( + vpToken: DcqlVpToken.Input | string, + opts?: { hasher?: Hasher }, +): WrappedVerifiablePresentation[] => { + const presentations = Object.values(DcqlVpToken.parse(vpToken)) as Array + return presentations.map((vp) => CredentialMapper.toWrappedVerifiablePresentation(vp, { hasher: opts.hasher })) } -export const extractPresentationsFromVpToken = async ( +export const extractPresentationsFromVpToken = ( vpToken: Array | W3CVerifiablePresentation | CompactSdJwtVc | string, opts?: { hasher?: Hasher }, -): Promise => { +): WrappedVerifiablePresentation[] | WrappedVerifiablePresentation => { const tokens = Array.isArray(vpToken) ? vpToken : [vpToken] const wrappedTokens = tokens.map((vp) => CredentialMapper.toWrappedVerifiablePresentation(vp, { hasher: opts?.hasher })) diff --git a/packages/siop-oid4vp/lib/authorization-response/Payload.ts b/packages/siop-oid4vp/lib/authorization-response/Payload.ts index bdd7cc80..286c3705 100644 --- a/packages/siop-oid4vp/lib/authorization-response/Payload.ts +++ b/packages/siop-oid4vp/lib/authorization-response/Payload.ts @@ -28,7 +28,11 @@ export const createResponsePayload = async ( } // vp tokens - await putPresentationSubmissionInLocation(authorizationRequest, responsePayload, responseOpts, idTokenPayload) + if (responseOpts.dcqlQuery) { + responsePayload.vp_token = JSON.stringify(responseOpts.dcqlQuery.credentialQueryIdToPresentation) + } else { + await putPresentationSubmissionInLocation(authorizationRequest, responsePayload, responseOpts, idTokenPayload) + } if (idTokenPayload) { const idToken = await IDToken.fromIDTokenPayload(idTokenPayload, responseOpts) responsePayload.id_token = await idToken.jwt(responseOpts.jwtIssuer) diff --git a/packages/siop-oid4vp/lib/authorization-response/types.ts b/packages/siop-oid4vp/lib/authorization-response/types.ts index 9a6e1bf2..944e0296 100644 --- a/packages/siop-oid4vp/lib/authorization-response/types.ts +++ b/packages/siop-oid4vp/lib/authorization-response/types.ts @@ -9,7 +9,7 @@ import { PresentationSubmission, W3CVerifiablePresentation, } from '@sphereon/ssi-types' -import { DcqlQuery, DcqlQueryVpToken } from 'dcql' +import { DcqlQuery } from 'dcql' import { ResponseMode, @@ -62,7 +62,7 @@ export interface PresentationExchangeResponseOpts { } export interface DcqlQueryResponseOpts { - credentialQueryIdToPresentation: DcqlQueryVpToken + credentialQueryIdToPresentation: Record | string> } export interface PresentationExchangeRequestOpts { @@ -108,7 +108,7 @@ export type PresentationVerificationResult = { verified: boolean; reason?: strin export type PresentationVerificationCallback = ( args: W3CVerifiablePresentation | CompactSdJwtVc | MdocOid4vpIssuerSigned, - presentationSubmission: PresentationSubmission, + presentationSubmission?: PresentationSubmission, ) => Promise export type PresentationSignCallback = (args: PresentationSignCallBackParams) => Promise diff --git a/packages/siop-oid4vp/lib/rp/RP.ts b/packages/siop-oid4vp/lib/rp/RP.ts index addc9a23..827db4c4 100644 --- a/packages/siop-oid4vp/lib/rp/RP.ts +++ b/packages/siop-oid4vp/lib/rp/RP.ts @@ -22,6 +22,7 @@ import { import { mergeVerificationOpts } from '../authorization-request/Opts' import { AuthorizationResponse, + extractPresentationsFromDcqlVpToken, extractPresentationsFromVpToken, PresentationDefinitionWithLocation, VerifyAuthorizationResponseOpts, @@ -169,7 +170,10 @@ export class RP { }, ) - const presentations = await extractPresentationsFromVpToken(validatedResponse.authResponseParams.vp_token, { hasher }) + const presentations = validatedResponse.authRequestParams.dcql_query + ? extractPresentationsFromDcqlVpToken(validatedResponse.authResponseParams.vp_token as string, { hasher }) + : extractPresentationsFromVpToken(validatedResponse.authResponseParams.vp_token, { hasher }) + const mdocVerifiablePresentations = (Array.isArray(presentations) ? presentations : [presentations]).filter((p) => p.format === 'mso_mdoc') if (mdocVerifiablePresentations.length) { diff --git a/packages/siop-oid4vp/lib/schemas/AuthorizationRequestPayloadVD12OID4VPD20.schema.ts b/packages/siop-oid4vp/lib/schemas/AuthorizationRequestPayloadVD12OID4VPD20.schema.ts index 42961fb7..e740951a 100644 --- a/packages/siop-oid4vp/lib/schemas/AuthorizationRequestPayloadVD12OID4VPD20.schema.ts +++ b/packages/siop-oid4vp/lib/schemas/AuthorizationRequestPayloadVD12OID4VPD20.schema.ts @@ -122,6 +122,9 @@ export const AuthorizationRequestPayloadVD12OID4VPD20SchemaObj = { }, "response_uri": { "type": "string" + }, + "dcql_query": { + "type": "string" } } }, diff --git a/packages/siop-oid4vp/lib/schemas/AuthorizationRequestPayloadVID1.schema.ts b/packages/siop-oid4vp/lib/schemas/AuthorizationRequestPayloadVID1.schema.ts index 0b78c774..59cc00bf 100644 --- a/packages/siop-oid4vp/lib/schemas/AuthorizationRequestPayloadVID1.schema.ts +++ b/packages/siop-oid4vp/lib/schemas/AuthorizationRequestPayloadVID1.schema.ts @@ -414,6 +414,9 @@ export const AuthorizationRequestPayloadVID1SchemaObj = { }, "presentation_definition_uri": { "type": "string" + }, + "dcql_query": { + "type": "string" } }, "additionalProperties": false diff --git a/packages/siop-oid4vp/lib/schemas/AuthorizationResponseOpts.schema.ts b/packages/siop-oid4vp/lib/schemas/AuthorizationResponseOpts.schema.ts index ffb4eee2..f4665713 100644 --- a/packages/siop-oid4vp/lib/schemas/AuthorizationResponseOpts.schema.ts +++ b/packages/siop-oid4vp/lib/schemas/AuthorizationResponseOpts.schema.ts @@ -52,6 +52,9 @@ export const AuthorizationResponseOptsSchemaObj = { }, "presentationExchange": { "$ref": "#/definitions/PresentationExchangeResponseOpts" + }, + "dcqlQuery": { + "$ref": "#/definitions/DcqlQueryResponseOpts" } }, "required": [ @@ -2335,6 +2338,29 @@ export const AuthorizationResponseOptsSchemaObj = { "id_token", "token_response" ] + }, + "DcqlQueryResponseOpts": { + "type": "object", + "properties": { + "credentialQueryIdToPresentation": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "object", + "additionalProperties": {} + }, + { + "type": "string" + } + ] + } + } + }, + "required": [ + "credentialQueryIdToPresentation" + ], + "additionalProperties": false } } }; \ No newline at end of file diff --git a/packages/siop-oid4vp/lib/types/SIOP.types.ts b/packages/siop-oid4vp/lib/types/SIOP.types.ts index 30619c0f..3933216e 100644 --- a/packages/siop-oid4vp/lib/types/SIOP.types.ts +++ b/packages/siop-oid4vp/lib/types/SIOP.types.ts @@ -516,6 +516,12 @@ export interface VerifiedIDToken { verifyOpts: VerifyAuthorizationResponseOpts } +export interface VerifiedOpenID4VPSubmissionDcql { + dcqlQuery: DcqlQuery + presentations: WrappedVerifiablePresentation[] + nonce?: string +} + export interface VerifiedOpenID4VPSubmission { submissionData: PresentationSubmission presentationDefinitions: PresentationDefinitionWithLocation[] @@ -529,6 +535,7 @@ export interface VerifiedAuthorizationResponse { authorizationResponse: AuthorizationResponse oid4vpSubmission?: VerifiedOpenID4VPSubmission + oid4vpSubmissionDcql?: VerifiedOpenID4VPSubmissionDcql nonce?: string state: string From 6ff33553305bf59f7a55259ab7d63ae5398c37fa Mon Sep 17 00:00:00 2001 From: Martin Auer Date: Thu, 21 Nov 2024 09:09:09 +0100 Subject: [PATCH 03/11] fix: dcql alpha --- .../__tests__/PresentationExchange.spec.ts | 4 +-- .../AuthorizationResponse.ts | 23 ++++++++++-- .../lib/authorization-response/Dcql.ts | 33 +++++++++++------ .../lib/authorization-response/OpenID4VP.ts | 35 ++++++++++--------- .../lib/authorization-response/Payload.ts | 4 ++- .../lib/authorization-response/types.ts | 6 +--- .../AuthorizationResponseOpts.schema.ts | 4 +-- packages/siop-oid4vp/lib/types/SIOP.types.ts | 4 +-- packages/siop-oid4vp/package.json | 2 +- pnpm-lock.yaml | 25 +++++++++++-- 10 files changed, 95 insertions(+), 45 deletions(-) diff --git a/packages/siop-oid4vp/lib/__tests__/PresentationExchange.spec.ts b/packages/siop-oid4vp/lib/__tests__/PresentationExchange.spec.ts index 8d69fd5b..5f907d40 100644 --- a/packages/siop-oid4vp/lib/__tests__/PresentationExchange.spec.ts +++ b/packages/siop-oid4vp/lib/__tests__/PresentationExchange.spec.ts @@ -337,9 +337,7 @@ describe('presentation exchange manager tests', () => { const payload = await getPayloadVID1Val() // eslint-disable-next-line @typescript-eslint/no-explicit-any ;(payload.claims?.vp_token as any).presentation_definition_uri = EXAMPLE_PD_URL - await expect(PresentationExchange.findValidPresentationDefinitions(payload)).rejects.toThrow( - SIOPErrors.REQUEST_CLAIMS_PRESENTATION_DEFINITION_BY_REF_AND_VALUE_NON_EXCLUSIVE, - ) + await expect(PresentationExchange.findValidPresentationDefinitions(payload)).rejects.toThrow(SIOPErrors.REQUEST_CLAIMS_PRESENTATION_NON_EXCLUSIVE) }) it('validatePresentationAgainstDefinition: should pass if provided VP match the PD', async function () { diff --git a/packages/siop-oid4vp/lib/authorization-response/AuthorizationResponse.ts b/packages/siop-oid4vp/lib/authorization-response/AuthorizationResponse.ts index df4d1288..8e64938f 100644 --- a/packages/siop-oid4vp/lib/authorization-response/AuthorizationResponse.ts +++ b/packages/siop-oid4vp/lib/authorization-response/AuthorizationResponse.ts @@ -1,10 +1,12 @@ import { CredentialMapper, Hasher, WrappedVerifiablePresentation } from '@sphereon/ssi-types' +import { DcqlPresentationRecord } from 'dcql' import { AuthorizationRequest, VerifyAuthorizationRequestOpts } from '../authorization-request' import { assertValidVerifyAuthorizationRequestOpts } from '../authorization-request/Opts' import { IDToken } from '../id-token' import { AuthorizationResponsePayload, ResponseType, SIOPErrors, VerifiedAuthorizationRequest, VerifiedAuthorizationResponse } from '../types' +import { assertValidDcqlPresentationRecrod } from './Dcql' import { assertValidVerifiablePresentations, extractNonceFromWrappedVerifiablePresentation, @@ -142,7 +144,13 @@ export class AuthorizationResponse { }, }) } else { - console.error('TODO: VALIDATE PRESENTATION AGAINST DEFINITION') + const dcqlQuery = verifiedAuthorizationRequest.dcqlQuery + if (!dcqlQuery) { + throw new Error('vp_token is present, but no presentation definitions or dcql query provided') + } + assertValidDcqlPresentationRecrod(responseOpts.dcqlQuery.encodedPresentationRecord as DcqlPresentationRecord, dcqlQuery, { + hasher: verifyOpts.hasher, + }) } } @@ -160,7 +168,16 @@ export class AuthorizationResponse { } const verifiedIdToken = await this.idToken?.verify(verifyOpts) - const oid4vp = await verifyPresentations(this, verifyOpts) + if (this.payload.vp_token && !verifyOpts.presentationDefinitions && !verifyOpts.dcqlQuery) { + throw Promise.reject(Error('vp_token is present, but no presentation definitions or dcql query provided')) + } + + const emptyPresentationDefinitions = Array.isArray(verifyOpts.presentationDefinitions) && verifyOpts.presentationDefinitions.length === 0 + if (!this.payload.vp_token && ((verifyOpts.presentationDefinitions && !emptyPresentationDefinitions) || verifyOpts.dcqlQuery)) { + throw Promise.reject(Error('Presentation definitions or dcql query provided, but no vp_token present')) + } + + const oid4vp = this.payload.vp_token ? await verifyPresentations(this, verifyOpts) : undefined // Gather all nonces const allNonces = new Set() @@ -227,7 +244,7 @@ export class AuthorizationResponse { presentations = extractPresentationsFromVpToken(this._payload.vp_token, opts) } - if (presentations && Array.isArray(presentations) && presentations.length === 0) { + if (!presentations || (Array.isArray(presentations) && presentations.length === 0)) { return Promise.reject(Error('missing presentation(s)')) } const presentationsArray = Array.isArray(presentations) ? presentations : [presentations] diff --git a/packages/siop-oid4vp/lib/authorization-response/Dcql.ts b/packages/siop-oid4vp/lib/authorization-response/Dcql.ts index 16cf9672..b2488e2e 100644 --- a/packages/siop-oid4vp/lib/authorization-response/Dcql.ts +++ b/packages/siop-oid4vp/lib/authorization-response/Dcql.ts @@ -1,8 +1,11 @@ -import { DcqlQuery } from 'dcql' +import { Hasher } from '@sphereon/ssi-types' +import { DcqlMdocRepresentation, DcqlPresentationRecord, DcqlQuery, DcqlSdJwtVcRepresentation } from 'dcql' import { extractDataFromPath } from '../helpers' import { AuthorizationRequestPayload, SIOPErrors } from '../types' +import { extractPresentationRecordFromDcqlVpToken } from './OpenID4VP' + /** * Finds a valid DcqlQuery inside the given AuthenticationRequestPayload * throws exception if the DcqlQuery is not valid @@ -12,7 +15,6 @@ import { AuthorizationRequestPayload, SIOPErrors } from '../types' */ export const findValidDcqlQuery = async (authorizationRequestPayload: AuthorizationRequestPayload): Promise => { const dcqlQuery: string[] = extractDataFromPath(authorizationRequestPayload, '$.dcql_query').map((d) => d.value) - const dcqlQueryList: string[] = extractDataFromPath(authorizationRequestPayload, '$.dcql_query[*]').map((d) => d.value) const definitions = extractDataFromPath(authorizationRequestPayload, '$.presentation_definition') const definitionsFromList = extractDataFromPath(authorizationRequestPayload, '$.presentation_definition[*]') const definitionRefs = extractDataFromPath(authorizationRequestPayload, '$.presentation_definition_uri') @@ -20,21 +22,32 @@ export const findValidDcqlQuery = async (authorizationRequestPayload: Authorizat const hasPD = (definitions && definitions.length > 0) || (definitionsFromList && definitionsFromList.length > 0) const hasPdRef = (definitionRefs && definitionRefs.length > 0) || (definitionRefsFromList && definitionRefsFromList.length > 0) - const hasDcql = (dcqlQuery && dcqlQuery.length > 0) || (dcqlQueryList && dcqlQueryList.length > 0) + const hasDcql = dcqlQuery && dcqlQuery.length > 0 if ([hasPD, hasPdRef, hasDcql].filter(Boolean).length > 1) { throw new Error(SIOPErrors.REQUEST_CLAIMS_PRESENTATION_NON_EXCLUSIVE) } - if (dcqlQuery.length > 1 || dcqlQueryList.length > 1) { + if (dcqlQuery.length === 0) return undefined + + if (dcqlQuery.length > 1) { throw new Error('Found multiple dcql_query in vp_token. Only one is allowed') } - const encoded = dcqlQuery.length ? dcqlQuery[0] : dcqlQueryList[0] - if (!encoded) return undefined - - const parsedDcqlQuery = DcqlQuery.parse(JSON.parse(encoded)) - DcqlQuery.validate(parsedDcqlQuery) + return DcqlQuery.parse(JSON.parse(dcqlQuery[0])) +} - return parsedDcqlQuery +export const assertValidDcqlPresentationRecrod = async (record: DcqlPresentationRecord | string, dcqlQuery: DcqlQuery, opts: { hasher?: Hasher }) => { + const wrappedPresentations = Object.values(extractPresentationRecordFromDcqlVpToken(record, opts)) + const credentials = wrappedPresentations.map((p) => { + if (p.format === 'mso_mdoc') { + return { docType: p.vcs[0].credential.toJson().docType, namespaces: p.vcs[0].decoded } satisfies DcqlMdocRepresentation + } else if (p.format === 'vc+sd-jwt') { + return { vct: p.vcs[0].decoded.vct, claims: p.vcs[0].decoded } satisfies DcqlSdJwtVcRepresentation + } else { + throw new Error('DcqlPresentation atm only supports mso_mdoc and vc+sd-jwt') + } + }) + + DcqlPresentationRecord.validate(credentials, { dcqlQuery }) } diff --git a/packages/siop-oid4vp/lib/authorization-response/OpenID4VP.ts b/packages/siop-oid4vp/lib/authorization-response/OpenID4VP.ts index 4ff909cf..d1a4e72e 100644 --- a/packages/siop-oid4vp/lib/authorization-response/OpenID4VP.ts +++ b/packages/siop-oid4vp/lib/authorization-response/OpenID4VP.ts @@ -10,7 +10,7 @@ import { W3CVerifiablePresentation, WrappedVerifiablePresentation, } from '@sphereon/ssi-types' -import { DcqlQuery, DcqlVpToken } from 'dcql' +import { DcqlPresentationRecord, DcqlQuery } from 'dcql' import { AuthorizationRequest } from '../authorization-request' import { verifyRevocation } from '../helpers' @@ -26,6 +26,7 @@ import { } from '../types' import { AuthorizationResponse } from './AuthorizationResponse' +import { assertValidDcqlPresentationRecrod } from './Dcql' import { PresentationExchange } from './PresentationExchange' import { AuthorizationResponseOpts, @@ -72,15 +73,6 @@ export const verifyPresentations = async ( authorizationResponse: AuthorizationResponse, verifyOpts: VerifyAuthorizationResponseOpts, ): Promise => { - if (!authorizationResponse.payload.vp_token) return null - if ( - authorizationResponse.payload.vp_token && - Array.isArray(authorizationResponse.payload.vp_token) && - authorizationResponse.payload.vp_token.length === 0 - ) { - return Promise.reject(Error('the payload is missing a vp_token')) - } - let idPayload: IDTokenPayload | undefined if (authorizationResponse.idToken) { idPayload = await authorizationResponse.idToken.payload() @@ -98,8 +90,6 @@ export const verifyPresentations = async ( let dcqlQuery = verifyOpts.dcqlQuery ?? authorizationResponse?.authorizationRequest?.payload.dcql_query if (dcqlQuery) { dcqlQuery = DcqlQuery.parse(dcqlQuery) - DcqlQuery.validate(dcqlQuery) - wrappedPresentations = extractPresentationsFromDcqlVpToken(authorizationResponse.payload.vp_token as string, { hasher: verifyOpts.hasher }) const verifiedPresentations = await Promise.all( @@ -108,7 +98,7 @@ export const verifyPresentations = async ( ), ) - // TODO: assert the submission against the definition + assertValidDcqlPresentationRecrod(authorizationResponse.payload.vp_token as string, dcqlQuery, { hasher: verifyOpts.hasher }) if (verifiedPresentations.some((verified) => !verified)) { const message = verifiedPresentations @@ -170,12 +160,25 @@ export const verifyPresentations = async ( } } +export const extractPresentationRecordFromDcqlVpToken = ( + vpToken: DcqlPresentationRecord.Input | string, + opts?: { hasher?: Hasher }, +): Record => { + const presentationRecord = Object.fromEntries( + Object.entries(DcqlPresentationRecord.parse(vpToken)).map(([credentialQueryId, vp]) => [ + credentialQueryId, + CredentialMapper.toWrappedVerifiablePresentation(vp as W3CVerifiablePresentation | CompactSdJwtVc | string, { hasher: opts.hasher }), + ]), + ) + + return presentationRecord +} + export const extractPresentationsFromDcqlVpToken = ( - vpToken: DcqlVpToken.Input | string, + vpToken: DcqlPresentationRecord.Input | string, opts?: { hasher?: Hasher }, ): WrappedVerifiablePresentation[] => { - const presentations = Object.values(DcqlVpToken.parse(vpToken)) as Array - return presentations.map((vp) => CredentialMapper.toWrappedVerifiablePresentation(vp, { hasher: opts.hasher })) + return Object.values(extractPresentationRecordFromDcqlVpToken(vpToken, opts)) } export const extractPresentationsFromVpToken = ( diff --git a/packages/siop-oid4vp/lib/authorization-response/Payload.ts b/packages/siop-oid4vp/lib/authorization-response/Payload.ts index 286c3705..3016f1b9 100644 --- a/packages/siop-oid4vp/lib/authorization-response/Payload.ts +++ b/packages/siop-oid4vp/lib/authorization-response/Payload.ts @@ -1,3 +1,5 @@ +import { DcqlPresentationRecord } from 'dcql' + import { AuthorizationRequest } from '../authorization-request' import { IDToken } from '../id-token' import { RequestObject } from '../request-object' @@ -29,7 +31,7 @@ export const createResponsePayload = async ( // vp tokens if (responseOpts.dcqlQuery) { - responsePayload.vp_token = JSON.stringify(responseOpts.dcqlQuery.credentialQueryIdToPresentation) + responsePayload.vp_token = DcqlPresentationRecord.encode(responseOpts.dcqlQuery.encodedPresentationRecord as DcqlPresentationRecord) } else { await putPresentationSubmissionInLocation(authorizationRequest, responsePayload, responseOpts, idTokenPayload) } diff --git a/packages/siop-oid4vp/lib/authorization-response/types.ts b/packages/siop-oid4vp/lib/authorization-response/types.ts index 944e0296..aa684320 100644 --- a/packages/siop-oid4vp/lib/authorization-response/types.ts +++ b/packages/siop-oid4vp/lib/authorization-response/types.ts @@ -62,11 +62,7 @@ export interface PresentationExchangeResponseOpts { } export interface DcqlQueryResponseOpts { - credentialQueryIdToPresentation: Record | string> -} - -export interface PresentationExchangeRequestOpts { - presentationVerificationCallback?: PresentationVerificationCallback + encodedPresentationRecord: Record | string> } export interface PresentationDefinitionPayloadOpts { diff --git a/packages/siop-oid4vp/lib/schemas/AuthorizationResponseOpts.schema.ts b/packages/siop-oid4vp/lib/schemas/AuthorizationResponseOpts.schema.ts index f4665713..6f3b54f8 100644 --- a/packages/siop-oid4vp/lib/schemas/AuthorizationResponseOpts.schema.ts +++ b/packages/siop-oid4vp/lib/schemas/AuthorizationResponseOpts.schema.ts @@ -2342,7 +2342,7 @@ export const AuthorizationResponseOptsSchemaObj = { "DcqlQueryResponseOpts": { "type": "object", "properties": { - "credentialQueryIdToPresentation": { + "encodedPresentationRecord": { "type": "object", "additionalProperties": { "anyOf": [ @@ -2358,7 +2358,7 @@ export const AuthorizationResponseOptsSchemaObj = { } }, "required": [ - "credentialQueryIdToPresentation" + "encodedPresentationRecord" ], "additionalProperties": false } diff --git a/packages/siop-oid4vp/lib/types/SIOP.types.ts b/packages/siop-oid4vp/lib/types/SIOP.types.ts index 3933216e..abb6bcab 100644 --- a/packages/siop-oid4vp/lib/types/SIOP.types.ts +++ b/packages/siop-oid4vp/lib/types/SIOP.types.ts @@ -167,7 +167,7 @@ export interface IDTokenPayload extends JWTPayload { } } -export type DcqlQueryVpToken = string +export type EcodedDcqlQueryVpToken = string export interface AuthorizationResponsePayload { access_token?: string @@ -181,7 +181,7 @@ export interface AuthorizationResponsePayload { | W3CVerifiablePresentation | CompactSdJwtVc | MdocOid4vpMdocVpToken - | DcqlQueryVpToken + | EcodedDcqlQueryVpToken presentation_submission?: PresentationSubmission verifiedData?: IPresentation | AdditionalClaims // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/packages/siop-oid4vp/package.json b/packages/siop-oid4vp/package.json index ad8ea863..213f2c2d 100644 --- a/packages/siop-oid4vp/package.json +++ b/packages/siop-oid4vp/package.json @@ -18,7 +18,7 @@ "@sphereon/jarm": "workspace:*", "@sphereon/oid4vc-common": "workspace:*", "@sphereon/pex": "5.0.0-unstable.24", - "dcql": "link:../../../dcql/dcql", + "dcql": "^0.2.7", "@sphereon/pex-models": "^2.3.1", "@sphereon/ssi-types": "0.30.2-next.129", "cross-fetch": "^4.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b16db23d..c5b50416 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -461,8 +461,8 @@ importers: specifier: ^4.0.0 version: 4.0.0(encoding@0.1.13) dcql: - specifier: link:../../../dcql/dcql - version: link:../../../dcql/dcql + specifier: ^0.2.7 + version: 0.2.7(typescript@5.4.5) debug: specifier: ^4.3.5 version: 4.3.7 @@ -4079,6 +4079,9 @@ packages: dayjs@1.11.13: resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + dcql@0.2.7: + resolution: {integrity: sha512-Aap1BFjFDHYmtwDWk4v/lhpfZXfnNeQ4WDSIR4pU48kb5FQjnwXkI0csr2Vij8F1nxn0SHWdDiq5iIasJeUDBQ==} + debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -8506,6 +8509,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: @@ -14455,6 +14466,12 @@ snapshots: dayjs@1.11.13: {} + dcql@0.2.7(typescript@5.4.5): + dependencies: + valibot: 0.37.0(typescript@5.4.5) + transitivePeerDependencies: + - typescript + debug@2.6.9: dependencies: ms: 2.0.0 @@ -20076,6 +20093,10 @@ snapshots: '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 + valibot@0.37.0(typescript@5.4.5): + optionalDependencies: + typescript: 5.4.5 + valibot@0.42.1(typescript@5.5.3): optionalDependencies: typescript: 5.5.3 From 6ad4d89e6ad97b4d4d1155722e71477e36decc59 Mon Sep 17 00:00:00 2001 From: Martin Auer Date: Thu, 21 Nov 2024 14:12:14 +0100 Subject: [PATCH 04/11] feat: add things --- packages/siop-oid4vp/package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/siop-oid4vp/package.json b/packages/siop-oid4vp/package.json index 213f2c2d..007e4490 100644 --- a/packages/siop-oid4vp/package.json +++ b/packages/siop-oid4vp/package.json @@ -18,7 +18,7 @@ "@sphereon/jarm": "workspace:*", "@sphereon/oid4vc-common": "workspace:*", "@sphereon/pex": "5.0.0-unstable.24", - "dcql": "^0.2.7", + "dcql": "^0.2.8", "@sphereon/pex-models": "^2.3.1", "@sphereon/ssi-types": "0.30.2-next.129", "cross-fetch": "^4.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c5b50416..258681de 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -461,8 +461,8 @@ importers: specifier: ^4.0.0 version: 4.0.0(encoding@0.1.13) dcql: - specifier: ^0.2.7 - version: 0.2.7(typescript@5.4.5) + specifier: ^0.2.8 + version: 0.2.8(typescript@5.4.5) debug: specifier: ^4.3.5 version: 4.3.7 @@ -4079,8 +4079,8 @@ packages: dayjs@1.11.13: resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} - dcql@0.2.7: - resolution: {integrity: sha512-Aap1BFjFDHYmtwDWk4v/lhpfZXfnNeQ4WDSIR4pU48kb5FQjnwXkI0csr2Vij8F1nxn0SHWdDiq5iIasJeUDBQ==} + dcql@0.2.8: + resolution: {integrity: sha512-/2TbRz3Itj/as4JnmzkupNxq6slN/w07EEx9iAwb/LRI8M8ajhnSN7YQ8rFopImuZAZkPYzOs4zga7zH6xf8eg==} debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} @@ -14466,7 +14466,7 @@ snapshots: dayjs@1.11.13: {} - dcql@0.2.7(typescript@5.4.5): + dcql@0.2.8(typescript@5.4.5): dependencies: valibot: 0.37.0(typescript@5.4.5) transitivePeerDependencies: From 6d94367ad7c64a6ca4e170b54cd83e7e490b8d6b Mon Sep 17 00:00:00 2001 From: Martin Auer Date: Thu, 21 Nov 2024 17:52:24 +0100 Subject: [PATCH 05/11] feat: update dcql lib --- package.json | 3 +- .../AuthorizationResponse.ts | 4 +- .../lib/authorization-response/Dcql.ts | 10 +++- .../lib/authorization-response/OpenID4VP.ts | 4 +- .../lib/authorization-response/index.ts | 1 + packages/siop-oid4vp/package.json | 2 +- pnpm-lock.yaml | 49 ++++++++----------- 7 files changed, 36 insertions(+), 37 deletions(-) diff --git a/package.json b/package.json index ac994988..7d119b6c 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,5 @@ "OIDC4VP", "OID4VCI", "OID4VP" - ], - "packageManager": "pnpm@9.6.0+sha256.dae0f7e822c56b20979bb5965e3b73b8bdabb6b8b8ef121da6d857508599ca35" + ] } diff --git a/packages/siop-oid4vp/lib/authorization-response/AuthorizationResponse.ts b/packages/siop-oid4vp/lib/authorization-response/AuthorizationResponse.ts index 8e64938f..0e2c8112 100644 --- a/packages/siop-oid4vp/lib/authorization-response/AuthorizationResponse.ts +++ b/packages/siop-oid4vp/lib/authorization-response/AuthorizationResponse.ts @@ -6,7 +6,7 @@ import { assertValidVerifyAuthorizationRequestOpts } from '../authorization-requ import { IDToken } from '../id-token' import { AuthorizationResponsePayload, ResponseType, SIOPErrors, VerifiedAuthorizationRequest, VerifiedAuthorizationResponse } from '../types' -import { assertValidDcqlPresentationRecrod } from './Dcql' +import { assertValidDcqlPresentationRecord } from './Dcql' import { assertValidVerifiablePresentations, extractNonceFromWrappedVerifiablePresentation, @@ -148,7 +148,7 @@ export class AuthorizationResponse { if (!dcqlQuery) { throw new Error('vp_token is present, but no presentation definitions or dcql query provided') } - assertValidDcqlPresentationRecrod(responseOpts.dcqlQuery.encodedPresentationRecord as DcqlPresentationRecord, dcqlQuery, { + assertValidDcqlPresentationRecord(responseOpts.dcqlQuery.encodedPresentationRecord as DcqlPresentationRecord, dcqlQuery, { hasher: verifyOpts.hasher, }) } diff --git a/packages/siop-oid4vp/lib/authorization-response/Dcql.ts b/packages/siop-oid4vp/lib/authorization-response/Dcql.ts index b2488e2e..86bd5e83 100644 --- a/packages/siop-oid4vp/lib/authorization-response/Dcql.ts +++ b/packages/siop-oid4vp/lib/authorization-response/Dcql.ts @@ -1,5 +1,6 @@ import { Hasher } from '@sphereon/ssi-types' import { DcqlMdocRepresentation, DcqlPresentationRecord, DcqlQuery, DcqlSdJwtVcRepresentation } from 'dcql' +import { DcqlPresentationQueryResult } from 'dcql' import { extractDataFromPath } from '../helpers' import { AuthorizationRequestPayload, SIOPErrors } from '../types' @@ -37,7 +38,7 @@ export const findValidDcqlQuery = async (authorizationRequestPayload: Authorizat return DcqlQuery.parse(JSON.parse(dcqlQuery[0])) } -export const assertValidDcqlPresentationRecrod = async (record: DcqlPresentationRecord | string, dcqlQuery: DcqlQuery, opts: { hasher?: Hasher }) => { +export const getDcqlPresentationResult = (record: DcqlPresentationRecord | string, dcqlQuery: DcqlQuery, opts: { hasher?: Hasher }) => { const wrappedPresentations = Object.values(extractPresentationRecordFromDcqlVpToken(record, opts)) const credentials = wrappedPresentations.map((p) => { if (p.format === 'mso_mdoc') { @@ -49,5 +50,10 @@ export const assertValidDcqlPresentationRecrod = async (record: DcqlPresentation } }) - DcqlPresentationRecord.validate(credentials, { dcqlQuery }) + return DcqlPresentationQueryResult.query(credentials, { dcqlQuery }) +} + +export const assertValidDcqlPresentationRecord = async (record: DcqlPresentationRecord | string, dcqlQuery: DcqlQuery, opts: { hasher?: Hasher }) => { + const result = getDcqlPresentationResult(record, dcqlQuery, opts) + return DcqlPresentationQueryResult.validate(result) } diff --git a/packages/siop-oid4vp/lib/authorization-response/OpenID4VP.ts b/packages/siop-oid4vp/lib/authorization-response/OpenID4VP.ts index d1a4e72e..7de528a8 100644 --- a/packages/siop-oid4vp/lib/authorization-response/OpenID4VP.ts +++ b/packages/siop-oid4vp/lib/authorization-response/OpenID4VP.ts @@ -26,7 +26,7 @@ import { } from '../types' import { AuthorizationResponse } from './AuthorizationResponse' -import { assertValidDcqlPresentationRecrod } from './Dcql' +import { assertValidDcqlPresentationRecord } from './Dcql' import { PresentationExchange } from './PresentationExchange' import { AuthorizationResponseOpts, @@ -98,7 +98,7 @@ export const verifyPresentations = async ( ), ) - assertValidDcqlPresentationRecrod(authorizationResponse.payload.vp_token as string, dcqlQuery, { hasher: verifyOpts.hasher }) + assertValidDcqlPresentationRecord(authorizationResponse.payload.vp_token as string, dcqlQuery, { hasher: verifyOpts.hasher }) if (verifiedPresentations.some((verified) => !verified)) { const message = verifiedPresentations diff --git a/packages/siop-oid4vp/lib/authorization-response/index.ts b/packages/siop-oid4vp/lib/authorization-response/index.ts index c8ae3c97..5fff5253 100644 --- a/packages/siop-oid4vp/lib/authorization-response/index.ts +++ b/packages/siop-oid4vp/lib/authorization-response/index.ts @@ -3,3 +3,4 @@ export * from './types' export * from './Payload' export * from './ResponseRegistration' export * from './OpenID4VP' +export * from './Dcql' diff --git a/packages/siop-oid4vp/package.json b/packages/siop-oid4vp/package.json index 007e4490..ba6634e1 100644 --- a/packages/siop-oid4vp/package.json +++ b/packages/siop-oid4vp/package.json @@ -18,7 +18,7 @@ "@sphereon/jarm": "workspace:*", "@sphereon/oid4vc-common": "workspace:*", "@sphereon/pex": "5.0.0-unstable.24", - "dcql": "^0.2.8", + "dcql": "^0.2.11", "@sphereon/pex-models": "^2.3.1", "@sphereon/ssi-types": "0.30.2-next.129", "cross-fetch": "^4.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 258681de..d62e60f5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -461,8 +461,8 @@ importers: specifier: ^4.0.0 version: 4.0.0(encoding@0.1.13) dcql: - specifier: ^0.2.8 - version: 0.2.8(typescript@5.4.5) + specifier: ^0.2.11 + version: 0.2.11(typescript@5.4.5) debug: specifier: ^4.3.5 version: 4.3.7 @@ -1867,7 +1867,7 @@ packages: '@expo/bunyan@4.0.1': resolution: {integrity: sha512-+Lla7nYSiHZirgK+U/uYzsLv/X+HaJienbD5AKX1UQZHYfWaP+9uuQluRB4GrEVWF0GZ7vEVp/jzaOT9k/SQlg==} - engines: {'0': node >=0.10.0} + engines: {node: '>=0.10.0'} '@expo/cli@0.7.3': resolution: {integrity: sha512-uMGHbAhApqXR2sd1KPhgvpbOhBBnspad8msEqHleT2PHXwKIwTUDzBGO9+jdOAWwCx2MJfw3+asYjzoD3DN9Bg==} @@ -2547,7 +2547,6 @@ packages: '@sphereon/kmp-mdl-mdoc@0.2.0-SNAPSHOT.22': resolution: {integrity: sha512-uAZZExVy+ug9JLircejWa5eLtAZ7bnBP6xb7DO2+86LRsHNLh2k2jMWJYxp+iWtGHTsh6RYsZl14ScQLvjiQ/A==} - bundledDependencies: [] '@sphereon/pex-models@2.3.1': resolution: {integrity: sha512-SByU4cJ0XYA6VZQ/L6lsSiRcFtBPHbFioCeQ4GP7/W/jQ+PSBD7uK2oTnKQ9/0iEiMK/6JYqhKgLs4a9UX3UTQ==} @@ -3052,6 +3051,7 @@ packages: '@xmldom/xmldom@0.7.13': resolution: {integrity: sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==} engines: {node: '>=10.0.0'} + deprecated: this version is no longer supported, please update to at least 0.8.* '@xmldom/xmldom@0.8.10': resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==} @@ -3477,6 +3477,7 @@ packages: boolean@3.2.0: resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. bplist-creator@0.1.0: resolution: {integrity: sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg==} @@ -4079,8 +4080,8 @@ packages: dayjs@1.11.13: resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} - dcql@0.2.8: - resolution: {integrity: sha512-/2TbRz3Itj/as4JnmzkupNxq6slN/w07EEx9iAwb/LRI8M8ajhnSN7YQ8rFopImuZAZkPYzOs4zga7zH6xf8eg==} + dcql@0.2.11: + resolution: {integrity: sha512-hR8MuSx49b7JPoZztcFMSKEHc6iEE4l/Zs6aUsvMCWVa3qFWpuJRiJEp5Rh2+UkCAhsce94fbDpMdBTcS9zn7g==} debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} @@ -4238,10 +4239,6 @@ packages: did-context@3.1.1: resolution: {integrity: sha512-iFpszgSxc7d1kNBJWC+PAzNTpe5LPalzsIunTMIpbG3O37Q7Zi7u4iIaedaM7UhziBhT+Agr9DyvAiXSUyfepQ==} - did-jwt-vc@3.1.3: - resolution: {integrity: sha512-qB1FiQ0sT/FUR5+mQ//P5lS0Gllrtes2OxC3WVMOt8ND0LolF92ohozv50ukyOvB2zBzgfm5durcIPqQcoI+LA==} - engines: {node: '>=14'} - did-jwt-vc@3.2.15: resolution: {integrity: sha512-M/WPiL34CQUiN4bvWnZ0OFHJ3usPtstfQfbNbHAWHvwjeCGi7nAdv62VXHgy2xIhJMc790hH7PsMN3i6SCGEyg==} engines: {node: '>=18'} @@ -4518,6 +4515,7 @@ packages: eslint@8.57.1: resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} 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 esm@3.2.25: @@ -10795,7 +10793,7 @@ snapshots: '@expo/bunyan': 4.0.0 '@expo/metro-config': 0.7.1 '@expo/osascript': 2.0.33 - '@expo/spawn-async': 1.5.0 + '@expo/spawn-async': 1.7.2 body-parser: 1.20.3 chalk: 4.1.2 connect: 3.7.0 @@ -10871,7 +10869,7 @@ snapshots: '@expo/osascript@2.0.33': dependencies: - '@expo/spawn-async': 1.5.0 + '@expo/spawn-async': 1.7.2 exec-async: 2.2.0 '@expo/osascript@2.1.3': @@ -10882,7 +10880,7 @@ snapshots: '@expo/package-manager@1.0.3': dependencies: '@expo/json-file': 8.3.3 - '@expo/spawn-async': 1.5.0 + '@expo/spawn-async': 1.7.2 ansi-regex: 5.0.1 chalk: 4.1.2 find-up: 5.0.0 @@ -13199,7 +13197,7 @@ snapshots: cross-fetch: 3.1.8(encoding@0.1.13) debug: 4.3.7 did-jwt: 6.11.6 - did-jwt-vc: 3.1.3 + did-jwt-vc: 3.2.15 did-resolver: 4.1.0 elliptic: 6.5.7 multiformats: 9.7.1 @@ -14466,7 +14464,7 @@ snapshots: dayjs@1.11.13: {} - dcql@0.2.8(typescript@5.4.5): + dcql@0.2.11(typescript@5.4.5): dependencies: valibot: 0.37.0(typescript@5.4.5) transitivePeerDependencies: @@ -14600,11 +14598,6 @@ snapshots: did-context@3.1.1: {} - did-jwt-vc@3.1.3: - dependencies: - did-jwt: 6.11.6 - did-resolver: 4.1.0 - did-jwt-vc@3.2.15: dependencies: did-jwt: 7.4.7 @@ -15125,9 +15118,9 @@ snapshots: execa@5.0.0: dependencies: cross-spawn: 7.0.3 - get-stream: 6.0.0 + get-stream: 6.0.1 human-signals: 2.1.0 - is-stream: 2.0.0 + is-stream: 2.0.1 merge-stream: 2.0.0 npm-run-path: 4.0.1 onetime: 5.1.2 @@ -16468,7 +16461,7 @@ snapshots: jest-diff@29.7.0: dependencies: - chalk: 4.1.0 + chalk: 4.1.2 diff-sequences: 29.6.3 jest-get-type: 29.6.3 pretty-format: 29.7.0 @@ -17253,7 +17246,7 @@ snapshots: log-symbols@4.1.0: dependencies: - chalk: 4.1.0 + chalk: 4.1.2 is-unicode-supported: 0.1.0 logkitty@0.7.1: @@ -17885,7 +17878,7 @@ snapshots: array-differ: 3.0.0 array-union: 2.1.0 arrify: 2.0.1 - minimatch: 3.0.5 + minimatch: 3.1.2 mute-stream@0.0.8: {} @@ -18116,7 +18109,7 @@ snapshots: '@yarnpkg/parsers': 3.0.0-rc.46 '@zkochan/js-yaml': 0.0.7 axios: 1.7.7(debug@4.3.7) - chalk: 4.1.0 + chalk: 4.1.2 cli-cursor: 3.1.0 cli-spinners: 2.6.1 cliui: 8.0.1 @@ -18299,9 +18292,9 @@ snapshots: ora@5.3.0: dependencies: bl: 4.1.0 - chalk: 4.1.0 + chalk: 4.1.2 cli-cursor: 3.1.0 - cli-spinners: 2.6.1 + cli-spinners: 2.9.2 is-interactive: 1.0.0 log-symbols: 4.1.0 strip-ansi: 6.0.1 From 413ecb9af2fa4010ef177272d320045f6148747f Mon Sep 17 00:00:00 2001 From: Martin Auer Date: Fri, 22 Nov 2024 19:58:53 +0100 Subject: [PATCH 06/11] fix: feedback --- package.json | 3 +- .../AuthorizationRequest.ts | 2 +- .../AuthorizationResponse.ts | 56 +++++++++---------- .../lib/authorization-response/Dcql.ts | 36 ++++++------ .../lib/authorization-response/OpenID4VP.ts | 33 ++++++----- .../lib/authorization-response/Payload.ts | 4 +- .../lib/authorization-response/types.ts | 2 +- .../AuthorizationResponseOpts.schema.ts | 4 +- packages/siop-oid4vp/lib/types/Errors.ts | 2 +- packages/siop-oid4vp/lib/types/SIOP.types.ts | 6 +- packages/siop-oid4vp/package.json | 2 +- pnpm-lock.yaml | 13 +++-- 12 files changed, 84 insertions(+), 79 deletions(-) diff --git a/package.json b/package.json index 7d119b6c..ac994988 100644 --- a/package.json +++ b/package.json @@ -63,5 +63,6 @@ "OIDC4VP", "OID4VCI", "OID4VP" - ] + ], + "packageManager": "pnpm@9.6.0+sha256.dae0f7e822c56b20979bb5965e3b73b8bdabb6b8b8ef121da6d857508599ca35" } diff --git a/packages/siop-oid4vp/lib/authorization-request/AuthorizationRequest.ts b/packages/siop-oid4vp/lib/authorization-request/AuthorizationRequest.ts index aeb4b15b..c3bfd6ed 100644 --- a/packages/siop-oid4vp/lib/authorization-request/AuthorizationRequest.ts +++ b/packages/siop-oid4vp/lib/authorization-request/AuthorizationRequest.ts @@ -290,7 +290,7 @@ export class AuthorizationRequest { return await PresentationExchange.findValidPresentationDefinitions(await this.mergedPayloads(), version) } - public async getDcqlQuery(): Promise { + public async getDcqlQuery(): Promise { return await findValidDcqlQuery(await this.mergedPayloads()) } } diff --git a/packages/siop-oid4vp/lib/authorization-response/AuthorizationResponse.ts b/packages/siop-oid4vp/lib/authorization-response/AuthorizationResponse.ts index 0e2c8112..26fa302c 100644 --- a/packages/siop-oid4vp/lib/authorization-response/AuthorizationResponse.ts +++ b/packages/siop-oid4vp/lib/authorization-response/AuthorizationResponse.ts @@ -1,12 +1,12 @@ import { CredentialMapper, Hasher, WrappedVerifiablePresentation } from '@sphereon/ssi-types' -import { DcqlPresentationRecord } from 'dcql' +import { DcqlPresentation } from 'dcql' import { AuthorizationRequest, VerifyAuthorizationRequestOpts } from '../authorization-request' import { assertValidVerifyAuthorizationRequestOpts } from '../authorization-request/Opts' import { IDToken } from '../id-token' import { AuthorizationResponsePayload, ResponseType, SIOPErrors, VerifiedAuthorizationRequest, VerifiedAuthorizationResponse } from '../types' -import { assertValidDcqlPresentationRecord } from './Dcql' +import { assertValidDcqlPresentation } from './Dcql' import { assertValidVerifiablePresentations, extractNonceFromWrappedVerifiablePresentation, @@ -126,32 +126,32 @@ export class AuthorizationResponse { authorizationRequest, }) - if (hasVpToken) { - if (responseOpts.presentationExchange) { - const wrappedPresentations = response.payload.vp_token - ? await extractPresentationsFromVpToken(response.payload.vp_token, { - hasher: verifyOpts.hasher, - }) - : [] - - await assertValidVerifiablePresentations({ - presentationDefinitions, - presentations: wrappedPresentations, - verificationCallback: verifyOpts.verification.presentationVerificationCallback, - opts: { - ...responseOpts.presentationExchange, + if (!hasVpToken) return response + + if (responseOpts.presentationExchange) { + const wrappedPresentations = response.payload.vp_token + ? await extractPresentationsFromVpToken(response.payload.vp_token, { hasher: verifyOpts.hasher, - }, - }) - } else { - const dcqlQuery = verifiedAuthorizationRequest.dcqlQuery - if (!dcqlQuery) { - throw new Error('vp_token is present, but no presentation definitions or dcql query provided') - } - assertValidDcqlPresentationRecord(responseOpts.dcqlQuery.encodedPresentationRecord as DcqlPresentationRecord, dcqlQuery, { + }) + : [] + + await assertValidVerifiablePresentations({ + presentationDefinitions, + presentations: wrappedPresentations, + verificationCallback: verifyOpts.verification.presentationVerificationCallback, + opts: { + ...responseOpts.presentationExchange, hasher: verifyOpts.hasher, - }) + }, + }) + } else { + const dcqlQuery = verifiedAuthorizationRequest.dcqlQuery + if (!dcqlQuery) { + throw new Error('vp_token is present, but no presentation definitions or dcql query provided') } + assertValidDcqlPresentation(responseOpts.dcqlQuery.dcqlPresentation as DcqlPresentation, dcqlQuery, { + hasher: verifyOpts.hasher, + }) } return response @@ -181,7 +181,7 @@ export class AuthorizationResponse { // Gather all nonces const allNonces = new Set() - if (oid4vp && oid4vp.nonce) allNonces.add(oid4vp.nonce) + if (oid4vp && (oid4vp.dcql.nonce || oid4vp.presentationExchange.nonce)) allNonces.add(oid4vp.dcql.nonce ?? oid4vp.presentationExchange.nonce) if (verifiedIdToken) allNonces.add(verifiedIdToken.payload.nonce) if (merged.nonce) allNonces.add(merged.nonce) @@ -207,8 +207,8 @@ export class AuthorizationResponse { state, correlationId: verifyOpts.correlationId, ...(this.idToken && { idToken: verifiedIdToken }), - ...(oid4vp && 'presentationDefinitions' in oid4vp && { oid4vpSubmission: oid4vp }), - ...(oid4vp && 'dcqlQuery' in oid4vp && { oid4vpSubmissionDcql: oid4vp }), + ...(oid4vp.presentationExchange && { oid4vpSubmission: oid4vp.presentationExchange }), + ...(oid4vp.dcql && { oid4vpSubmissionDcql: oid4vp.dcql }), } } diff --git a/packages/siop-oid4vp/lib/authorization-response/Dcql.ts b/packages/siop-oid4vp/lib/authorization-response/Dcql.ts index 86bd5e83..71662d91 100644 --- a/packages/siop-oid4vp/lib/authorization-response/Dcql.ts +++ b/packages/siop-oid4vp/lib/authorization-response/Dcql.ts @@ -1,11 +1,10 @@ import { Hasher } from '@sphereon/ssi-types' -import { DcqlMdocRepresentation, DcqlPresentationRecord, DcqlQuery, DcqlSdJwtVcRepresentation } from 'dcql' -import { DcqlPresentationQueryResult } from 'dcql' +import { DcqlMdocCredential, DcqlPresentation, DcqlPresentationResult, DcqlQuery, DcqlSdJwtVcCredential } from 'dcql' import { extractDataFromPath } from '../helpers' import { AuthorizationRequestPayload, SIOPErrors } from '../types' -import { extractPresentationRecordFromDcqlVpToken } from './OpenID4VP' +import { extractDcqlPresentationFromDcqlVpToken } from './OpenID4VP' /** * Finds a valid DcqlQuery inside the given AuthenticationRequestPayload @@ -38,22 +37,23 @@ export const findValidDcqlQuery = async (authorizationRequestPayload: Authorizat return DcqlQuery.parse(JSON.parse(dcqlQuery[0])) } -export const getDcqlPresentationResult = (record: DcqlPresentationRecord | string, dcqlQuery: DcqlQuery, opts: { hasher?: Hasher }) => { - const wrappedPresentations = Object.values(extractPresentationRecordFromDcqlVpToken(record, opts)) - const credentials = wrappedPresentations.map((p) => { - if (p.format === 'mso_mdoc') { - return { docType: p.vcs[0].credential.toJson().docType, namespaces: p.vcs[0].decoded } satisfies DcqlMdocRepresentation - } else if (p.format === 'vc+sd-jwt') { - return { vct: p.vcs[0].decoded.vct, claims: p.vcs[0].decoded } satisfies DcqlSdJwtVcRepresentation - } else { - throw new Error('DcqlPresentation atm only supports mso_mdoc and vc+sd-jwt') - } - }) - - return DcqlPresentationQueryResult.query(credentials, { dcqlQuery }) +export const getDcqlPresentationResult = (record: DcqlPresentation | string, dcqlQuery: DcqlQuery, opts: { hasher?: Hasher }) => { + const dcqlPresentation = Object.fromEntries( + Object.entries(extractDcqlPresentationFromDcqlVpToken(record, opts)).map(([queryId, p]) => { + if (p.format === 'mso_mdoc') { + return [queryId, { docType: p.vcs[0].credential.toJson().docType, namespaces: p.vcs[0].decoded } satisfies DcqlMdocCredential] + } else if (p.format === 'vc+sd-jwt') { + return [queryId, { vct: p.vcs[0].decoded.vct, claims: p.vcs[0].decoded } satisfies DcqlSdJwtVcCredential] + } else { + throw new Error('DcqlPresentation atm only supports mso_mdoc and vc+sd-jwt') + } + }), + ) + + return DcqlPresentationResult.fromDcqlPresentation(dcqlPresentation, { dcqlQuery }) } -export const assertValidDcqlPresentationRecord = async (record: DcqlPresentationRecord | string, dcqlQuery: DcqlQuery, opts: { hasher?: Hasher }) => { +export const assertValidDcqlPresentation = async (record: DcqlPresentation | string, dcqlQuery: DcqlQuery, opts: { hasher?: Hasher }) => { const result = getDcqlPresentationResult(record, dcqlQuery, opts) - return DcqlPresentationQueryResult.validate(result) + return DcqlPresentationResult.validate(result) } diff --git a/packages/siop-oid4vp/lib/authorization-response/OpenID4VP.ts b/packages/siop-oid4vp/lib/authorization-response/OpenID4VP.ts index 7de528a8..3f1c8b30 100644 --- a/packages/siop-oid4vp/lib/authorization-response/OpenID4VP.ts +++ b/packages/siop-oid4vp/lib/authorization-response/OpenID4VP.ts @@ -10,7 +10,7 @@ import { W3CVerifiablePresentation, WrappedVerifiablePresentation, } from '@sphereon/ssi-types' -import { DcqlPresentationRecord, DcqlQuery } from 'dcql' +import { DcqlPresentation, DcqlQuery } from 'dcql' import { AuthorizationRequest } from '../authorization-request' import { verifyRevocation } from '../helpers' @@ -26,7 +26,7 @@ import { } from '../types' import { AuthorizationResponse } from './AuthorizationResponse' -import { assertValidDcqlPresentationRecord } from './Dcql' +import { assertValidDcqlPresentation as assertValidDcqlPresentation } from './Dcql' import { PresentationExchange } from './PresentationExchange' import { AuthorizationResponseOpts, @@ -72,7 +72,7 @@ export const extractNonceFromWrappedVerifiablePresentation = (wrappedVp: Wrapped export const verifyPresentations = async ( authorizationResponse: AuthorizationResponse, verifyOpts: VerifyAuthorizationResponseOpts, -): Promise => { +): Promise<{ presentationExchange?: VerifiedOpenID4VPSubmission; dcql?: VerifiedOpenID4VPSubmissionDcql }> => { let idPayload: IDTokenPayload | undefined if (authorizationResponse.idToken) { idPayload = await authorizationResponse.idToken.payload() @@ -87,10 +87,13 @@ export const verifyPresentations = async ( let presentationSubmission: PresentationSubmission | undefined + let dcqlPresentation: { [credentialQueryId: string]: WrappedVerifiablePresentation } | undefined + let dcqlQuery = verifyOpts.dcqlQuery ?? authorizationResponse?.authorizationRequest?.payload.dcql_query if (dcqlQuery) { dcqlQuery = DcqlQuery.parse(dcqlQuery) - wrappedPresentations = extractPresentationsFromDcqlVpToken(authorizationResponse.payload.vp_token as string, { hasher: verifyOpts.hasher }) + dcqlPresentation = extractDcqlPresentationFromDcqlVpToken(authorizationResponse.payload.vp_token as string, { hasher: verifyOpts.hasher }) + wrappedPresentations = Object.values(dcqlPresentation) const verifiedPresentations = await Promise.all( wrappedPresentations.map((presentation) => @@ -98,7 +101,7 @@ export const verifyPresentations = async ( ), ) - assertValidDcqlPresentationRecord(authorizationResponse.payload.vp_token as string, dcqlQuery, { hasher: verifyOpts.hasher }) + assertValidDcqlPresentation(authorizationResponse.payload.vp_token as string, dcqlQuery, { hasher: verifyOpts.hasher }) if (verifiedPresentations.some((verified) => !verified)) { const message = verifiedPresentations @@ -154,31 +157,31 @@ export const verifyPresentations = async ( } } if (presentationDefinitions) { - return { nonce, presentations: wrappedPresentations, presentationDefinitions, submissionData: presentationSubmission } + return { presentationExchange: { nonce, presentations: wrappedPresentations, presentationDefinitions, submissionData: presentationSubmission } } } else { - return { nonce, presentations: wrappedPresentations, dcqlQuery } + return { dcql: { nonce, presentation: dcqlPresentation, dcqlQuery } } } } -export const extractPresentationRecordFromDcqlVpToken = ( - vpToken: DcqlPresentationRecord.Input | string, +export const extractDcqlPresentationFromDcqlVpToken = ( + vpToken: DcqlPresentation.Input | string, opts?: { hasher?: Hasher }, -): Record => { - const presentationRecord = Object.fromEntries( - Object.entries(DcqlPresentationRecord.parse(vpToken)).map(([credentialQueryId, vp]) => [ +): { [credentialQueryId: string]: WrappedVerifiablePresentation } => { + const dcqlPresentation = Object.fromEntries( + Object.entries(DcqlPresentation.parse(vpToken)).map(([credentialQueryId, vp]) => [ credentialQueryId, CredentialMapper.toWrappedVerifiablePresentation(vp as W3CVerifiablePresentation | CompactSdJwtVc | string, { hasher: opts.hasher }), ]), ) - return presentationRecord + return dcqlPresentation } export const extractPresentationsFromDcqlVpToken = ( - vpToken: DcqlPresentationRecord.Input | string, + vpToken: DcqlPresentation.Input | string, opts?: { hasher?: Hasher }, ): WrappedVerifiablePresentation[] => { - return Object.values(extractPresentationRecordFromDcqlVpToken(vpToken, opts)) + return Object.values(extractDcqlPresentationFromDcqlVpToken(vpToken, opts)) } export const extractPresentationsFromVpToken = ( diff --git a/packages/siop-oid4vp/lib/authorization-response/Payload.ts b/packages/siop-oid4vp/lib/authorization-response/Payload.ts index 3016f1b9..a2bc60b9 100644 --- a/packages/siop-oid4vp/lib/authorization-response/Payload.ts +++ b/packages/siop-oid4vp/lib/authorization-response/Payload.ts @@ -1,4 +1,4 @@ -import { DcqlPresentationRecord } from 'dcql' +import { DcqlPresentation } from 'dcql' import { AuthorizationRequest } from '../authorization-request' import { IDToken } from '../id-token' @@ -31,7 +31,7 @@ export const createResponsePayload = async ( // vp tokens if (responseOpts.dcqlQuery) { - responsePayload.vp_token = DcqlPresentationRecord.encode(responseOpts.dcqlQuery.encodedPresentationRecord as DcqlPresentationRecord) + responsePayload.vp_token = DcqlPresentation.encode(responseOpts.dcqlQuery.dcqlPresentation as DcqlPresentation) } else { await putPresentationSubmissionInLocation(authorizationRequest, responsePayload, responseOpts, idTokenPayload) } diff --git a/packages/siop-oid4vp/lib/authorization-response/types.ts b/packages/siop-oid4vp/lib/authorization-response/types.ts index aa684320..5f8f2b6f 100644 --- a/packages/siop-oid4vp/lib/authorization-response/types.ts +++ b/packages/siop-oid4vp/lib/authorization-response/types.ts @@ -62,7 +62,7 @@ export interface PresentationExchangeResponseOpts { } export interface DcqlQueryResponseOpts { - encodedPresentationRecord: Record | string> + dcqlPresentation: Record | string> } export interface PresentationDefinitionPayloadOpts { diff --git a/packages/siop-oid4vp/lib/schemas/AuthorizationResponseOpts.schema.ts b/packages/siop-oid4vp/lib/schemas/AuthorizationResponseOpts.schema.ts index 6f3b54f8..1478d7b7 100644 --- a/packages/siop-oid4vp/lib/schemas/AuthorizationResponseOpts.schema.ts +++ b/packages/siop-oid4vp/lib/schemas/AuthorizationResponseOpts.schema.ts @@ -2342,7 +2342,7 @@ export const AuthorizationResponseOptsSchemaObj = { "DcqlQueryResponseOpts": { "type": "object", "properties": { - "encodedPresentationRecord": { + "dcqlPresentation": { "type": "object", "additionalProperties": { "anyOf": [ @@ -2358,7 +2358,7 @@ export const AuthorizationResponseOptsSchemaObj = { } }, "required": [ - "encodedPresentationRecord" + "dcqlPresentation" ], "additionalProperties": false } diff --git a/packages/siop-oid4vp/lib/types/Errors.ts b/packages/siop-oid4vp/lib/types/Errors.ts index 780d8c17..fa08b9d8 100644 --- a/packages/siop-oid4vp/lib/types/Errors.ts +++ b/packages/siop-oid4vp/lib/types/Errors.ts @@ -38,7 +38,7 @@ enum SIOPErrors { REFERENCE_URI_NO_PAYLOAD = 'referenceUri specified, but object to host there is not present', NO_SELF_ISSUED_ISS = 'The Response Token Issuer Claim (iss) MUST start with https://self-isued.me/v2', REGISTRATION_NOT_SET = 'Registration metadata not set.', - REQUEST_CLAIMS_PRESENTATION_NON_EXCLUSIVE = "Request claims can't multiple of 'presentation_definition' and 'presentation_definition_uri' 'dcql_query", + REQUEST_CLAIMS_PRESENTATION_NON_EXCLUSIVE = "Request claims can't have multiple of 'presentation_definition', 'presentation_definition_uri' and 'dcql_query", REQUEST_CLAIMS_PRESENTATION_DEFINITION_NOT_VALID = 'Presentation definition in the request claims is not valid', REQUEST_OBJECT_TYPE_NOT_SET = 'Request object type is not set.', RESPONSE_OPTS_PRESENTATIONS_SUBMISSION_IS_NOT_VALID = 'presentation_submission object inside the response opts vp should be valid', diff --git a/packages/siop-oid4vp/lib/types/SIOP.types.ts b/packages/siop-oid4vp/lib/types/SIOP.types.ts index abb6bcab..e0b78ff8 100644 --- a/packages/siop-oid4vp/lib/types/SIOP.types.ts +++ b/packages/siop-oid4vp/lib/types/SIOP.types.ts @@ -167,7 +167,7 @@ export interface IDTokenPayload extends JWTPayload { } } -export type EcodedDcqlQueryVpToken = string +export type EncodedDcqlQueryVpToken = string export interface AuthorizationResponsePayload { access_token?: string @@ -181,7 +181,7 @@ export interface AuthorizationResponsePayload { | W3CVerifiablePresentation | CompactSdJwtVc | MdocOid4vpMdocVpToken - | EcodedDcqlQueryVpToken + | EncodedDcqlQueryVpToken presentation_submission?: PresentationSubmission verifiedData?: IPresentation | AdditionalClaims // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -518,7 +518,7 @@ export interface VerifiedIDToken { export interface VerifiedOpenID4VPSubmissionDcql { dcqlQuery: DcqlQuery - presentations: WrappedVerifiablePresentation[] + presentation: { [credentialQueryId: string]: WrappedVerifiablePresentation } nonce?: string } diff --git a/packages/siop-oid4vp/package.json b/packages/siop-oid4vp/package.json index ba6634e1..dd294260 100644 --- a/packages/siop-oid4vp/package.json +++ b/packages/siop-oid4vp/package.json @@ -18,7 +18,7 @@ "@sphereon/jarm": "workspace:*", "@sphereon/oid4vc-common": "workspace:*", "@sphereon/pex": "5.0.0-unstable.24", - "dcql": "^0.2.11", + "dcql": "^0.2.12", "@sphereon/pex-models": "^2.3.1", "@sphereon/ssi-types": "0.30.2-next.129", "cross-fetch": "^4.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d62e60f5..9b647e54 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -461,8 +461,8 @@ importers: specifier: ^4.0.0 version: 4.0.0(encoding@0.1.13) dcql: - specifier: ^0.2.11 - version: 0.2.11(typescript@5.4.5) + specifier: ^0.2.12 + version: 0.2.12(typescript@5.4.5) debug: specifier: ^4.3.5 version: 4.3.7 @@ -1867,7 +1867,7 @@ packages: '@expo/bunyan@4.0.1': resolution: {integrity: sha512-+Lla7nYSiHZirgK+U/uYzsLv/X+HaJienbD5AKX1UQZHYfWaP+9uuQluRB4GrEVWF0GZ7vEVp/jzaOT9k/SQlg==} - engines: {node: '>=0.10.0'} + engines: {'0': node >=0.10.0} '@expo/cli@0.7.3': resolution: {integrity: sha512-uMGHbAhApqXR2sd1KPhgvpbOhBBnspad8msEqHleT2PHXwKIwTUDzBGO9+jdOAWwCx2MJfw3+asYjzoD3DN9Bg==} @@ -2547,6 +2547,7 @@ packages: '@sphereon/kmp-mdl-mdoc@0.2.0-SNAPSHOT.22': resolution: {integrity: sha512-uAZZExVy+ug9JLircejWa5eLtAZ7bnBP6xb7DO2+86LRsHNLh2k2jMWJYxp+iWtGHTsh6RYsZl14ScQLvjiQ/A==} + bundledDependencies: [] '@sphereon/pex-models@2.3.1': resolution: {integrity: sha512-SByU4cJ0XYA6VZQ/L6lsSiRcFtBPHbFioCeQ4GP7/W/jQ+PSBD7uK2oTnKQ9/0iEiMK/6JYqhKgLs4a9UX3UTQ==} @@ -4080,8 +4081,8 @@ packages: dayjs@1.11.13: resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} - dcql@0.2.11: - resolution: {integrity: sha512-hR8MuSx49b7JPoZztcFMSKEHc6iEE4l/Zs6aUsvMCWVa3qFWpuJRiJEp5Rh2+UkCAhsce94fbDpMdBTcS9zn7g==} + dcql@0.2.12: + resolution: {integrity: sha512-Ag2HTeMio8Gz8/K2T+VrT9wx+nxBst4goOeYbD/UXfiRnbH1MuPW1JW8zlmHmWFfzxl7KikaiQtr1ipLwNCYNA==} debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} @@ -14464,7 +14465,7 @@ snapshots: dayjs@1.11.13: {} - dcql@0.2.11(typescript@5.4.5): + dcql@0.2.12(typescript@5.4.5): dependencies: valibot: 0.37.0(typescript@5.4.5) transitivePeerDependencies: From 9ff62bd5127a69f33a158fe8f0be21ca9820879a Mon Sep 17 00:00:00 2001 From: Martin Auer Date: Sat, 23 Nov 2024 09:59:56 +0100 Subject: [PATCH 07/11] fix: update --- .../AuthorizationResponse.ts | 10 +++++----- .../siop-oid4vp/lib/authorization-response/Dcql.ts | 9 ++++++--- .../lib/authorization-response/OpenID4VP.ts | 14 +++++++------- packages/siop-oid4vp/package.json | 2 +- pnpm-lock.yaml | 12 ++++++------ 5 files changed, 25 insertions(+), 22 deletions(-) diff --git a/packages/siop-oid4vp/lib/authorization-response/AuthorizationResponse.ts b/packages/siop-oid4vp/lib/authorization-response/AuthorizationResponse.ts index 26fa302c..c1c71212 100644 --- a/packages/siop-oid4vp/lib/authorization-response/AuthorizationResponse.ts +++ b/packages/siop-oid4vp/lib/authorization-response/AuthorizationResponse.ts @@ -6,7 +6,7 @@ import { assertValidVerifyAuthorizationRequestOpts } from '../authorization-requ import { IDToken } from '../id-token' import { AuthorizationResponsePayload, ResponseType, SIOPErrors, VerifiedAuthorizationRequest, VerifiedAuthorizationResponse } from '../types' -import { assertValidDcqlPresentation } from './Dcql' +import { assertValidDcqlPresentationResult } from './Dcql' import { assertValidVerifiablePresentations, extractNonceFromWrappedVerifiablePresentation, @@ -149,7 +149,7 @@ export class AuthorizationResponse { if (!dcqlQuery) { throw new Error('vp_token is present, but no presentation definitions or dcql query provided') } - assertValidDcqlPresentation(responseOpts.dcqlQuery.dcqlPresentation as DcqlPresentation, dcqlQuery, { + assertValidDcqlPresentationResult(responseOpts.dcqlQuery.dcqlPresentation as DcqlPresentation, dcqlQuery, { hasher: verifyOpts.hasher, }) } @@ -169,19 +169,19 @@ export class AuthorizationResponse { const verifiedIdToken = await this.idToken?.verify(verifyOpts) if (this.payload.vp_token && !verifyOpts.presentationDefinitions && !verifyOpts.dcqlQuery) { - throw Promise.reject(Error('vp_token is present, but no presentation definitions or dcql query provided')) + throw new Error('vp_token is present, but no presentation definitions or dcql query provided') } const emptyPresentationDefinitions = Array.isArray(verifyOpts.presentationDefinitions) && verifyOpts.presentationDefinitions.length === 0 if (!this.payload.vp_token && ((verifyOpts.presentationDefinitions && !emptyPresentationDefinitions) || verifyOpts.dcqlQuery)) { - throw Promise.reject(Error('Presentation definitions or dcql query provided, but no vp_token present')) + throw new Error('Presentation definitions or dcql query provided, but no vp_token present') } const oid4vp = this.payload.vp_token ? await verifyPresentations(this, verifyOpts) : undefined // Gather all nonces const allNonces = new Set() - if (oid4vp && (oid4vp.dcql.nonce || oid4vp.presentationExchange.nonce)) allNonces.add(oid4vp.dcql.nonce ?? oid4vp.presentationExchange.nonce) + if (oid4vp && (oid4vp.dcql?.nonce || oid4vp.presentationExchange?.nonce)) allNonces.add(oid4vp.dcql?.nonce ?? oid4vp.presentationExchange?.nonce) if (verifiedIdToken) allNonces.add(verifiedIdToken.payload.nonce) if (merged.nonce) allNonces.add(merged.nonce) diff --git a/packages/siop-oid4vp/lib/authorization-response/Dcql.ts b/packages/siop-oid4vp/lib/authorization-response/Dcql.ts index 71662d91..77ab1da5 100644 --- a/packages/siop-oid4vp/lib/authorization-response/Dcql.ts +++ b/packages/siop-oid4vp/lib/authorization-response/Dcql.ts @@ -41,9 +41,12 @@ export const getDcqlPresentationResult = (record: DcqlPresentation | string, dcq const dcqlPresentation = Object.fromEntries( Object.entries(extractDcqlPresentationFromDcqlVpToken(record, opts)).map(([queryId, p]) => { if (p.format === 'mso_mdoc') { - return [queryId, { docType: p.vcs[0].credential.toJson().docType, namespaces: p.vcs[0].decoded } satisfies DcqlMdocCredential] + return [ + queryId, + { credentialFormat: 'mso_mdoc', doctype: p.vcs[0].credential.toJson().docType, namespaces: p.vcs[0].decoded } satisfies DcqlMdocCredential, + ] } else if (p.format === 'vc+sd-jwt') { - return [queryId, { vct: p.vcs[0].decoded.vct, claims: p.vcs[0].decoded } satisfies DcqlSdJwtVcCredential] + return [queryId, { credentialFormat: 'vc+sd-jwt', vct: p.vcs[0].decoded.vct, claims: p.vcs[0].decoded } satisfies DcqlSdJwtVcCredential] } else { throw new Error('DcqlPresentation atm only supports mso_mdoc and vc+sd-jwt') } @@ -53,7 +56,7 @@ export const getDcqlPresentationResult = (record: DcqlPresentation | string, dcq return DcqlPresentationResult.fromDcqlPresentation(dcqlPresentation, { dcqlQuery }) } -export const assertValidDcqlPresentation = async (record: DcqlPresentation | string, dcqlQuery: DcqlQuery, opts: { hasher?: Hasher }) => { +export const assertValidDcqlPresentationResult = async (record: DcqlPresentation | string, dcqlQuery: DcqlQuery, opts: { hasher?: Hasher }) => { const result = getDcqlPresentationResult(record, dcqlQuery, opts) return DcqlPresentationResult.validate(result) } diff --git a/packages/siop-oid4vp/lib/authorization-response/OpenID4VP.ts b/packages/siop-oid4vp/lib/authorization-response/OpenID4VP.ts index 9dc1aeef..d1177ef4 100644 --- a/packages/siop-oid4vp/lib/authorization-response/OpenID4VP.ts +++ b/packages/siop-oid4vp/lib/authorization-response/OpenID4VP.ts @@ -26,7 +26,7 @@ import { } from '../types' import { AuthorizationResponse } from './AuthorizationResponse' -import { assertValidDcqlPresentation as assertValidDcqlPresentation } from './Dcql' +import { assertValidDcqlPresentationResult } from './Dcql' import { PresentationExchange } from './PresentationExchange' import { AuthorizationResponseOpts, @@ -101,7 +101,7 @@ export const verifyPresentations = async ( ), ) - assertValidDcqlPresentation(authorizationResponse.payload.vp_token as string, dcqlQuery, { hasher: verifyOpts.hasher }) + assertValidDcqlPresentationResult(authorizationResponse.payload.vp_token as string, dcqlQuery, { hasher: verifyOpts.hasher }) if (verifiedPresentations.some((verified) => !verified)) { const message = verifiedPresentations @@ -337,14 +337,14 @@ export const assertValidVerifiablePresentations = async (args: { if (!presentations || (Array.isArray(presentations) && presentations.length === 0)) { return Promise.reject(Error('missing presentation(s)')) } - + // Handle mdocs, keep them out of pex - let presentationsArray = (Array.isArray(presentations) ? presentations : [presentations]) - if (presentationsArray.every(p => p.format === 'mso_mdoc')) { + let presentationsArray = Array.isArray(presentations) ? presentations : [presentations] + if (presentationsArray.every((p) => p.format === 'mso_mdoc')) { return - } + } presentationsArray = presentationsArray.filter((p) => p.format !== 'mso_mdoc') - + if ( (!args.presentationDefinitions || args.presentationDefinitions.filter((a) => a.definition).length === 0) && (!presentationsArray || (Array.isArray(presentationsArray) && presentationsArray.filter((vp) => vp.presentation).length === 0)) diff --git a/packages/siop-oid4vp/package.json b/packages/siop-oid4vp/package.json index 52e37a7f..937ce385 100644 --- a/packages/siop-oid4vp/package.json +++ b/packages/siop-oid4vp/package.json @@ -18,7 +18,7 @@ "@sphereon/jarm": "workspace:*", "@sphereon/oid4vc-common": "workspace:*", "@sphereon/pex": "5.0.0-unstable.24", - "dcql": "^0.2.12", + "dcql": "^0.2.13", "@sphereon/pex-models": "^2.3.1", "@sphereon/ssi-types": "0.30.2-next.279", "cross-fetch": "^4.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5e89464f..f4e47bc6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -461,8 +461,8 @@ importers: specifier: ^4.0.0 version: 4.0.0(encoding@0.1.13) dcql: - specifier: ^0.2.12 - version: 0.2.12(typescript@5.4.5) + specifier: ^0.2.13 + version: 0.2.13(typescript@5.4.5) debug: specifier: ^4.3.5 version: 4.3.7 @@ -1867,7 +1867,7 @@ packages: '@expo/bunyan@4.0.1': resolution: {integrity: sha512-+Lla7nYSiHZirgK+U/uYzsLv/X+HaJienbD5AKX1UQZHYfWaP+9uuQluRB4GrEVWF0GZ7vEVp/jzaOT9k/SQlg==} - engines: {'0': node >=0.10.0} + engines: {node: '>=0.10.0'} '@expo/cli@0.7.3': resolution: {integrity: sha512-uMGHbAhApqXR2sd1KPhgvpbOhBBnspad8msEqHleT2PHXwKIwTUDzBGO9+jdOAWwCx2MJfw3+asYjzoD3DN9Bg==} @@ -4015,8 +4015,8 @@ packages: dayjs@1.11.13: resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} - dcql@0.2.12: - resolution: {integrity: sha512-Ag2HTeMio8Gz8/K2T+VrT9wx+nxBst4goOeYbD/UXfiRnbH1MuPW1JW8zlmHmWFfzxl7KikaiQtr1ipLwNCYNA==} + dcql@0.2.13: + resolution: {integrity: sha512-XfePsSz9ULj9HH3VFNguzK/xlFnliKDX2iUDb1tIrn97S+TfftcFo+jipw16m9jPlWLhhBx48QniF0D8KotIWA==} debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} @@ -13835,7 +13835,7 @@ snapshots: dayjs@1.11.13: {} - dcql@0.2.12(typescript@5.4.5): + dcql@0.2.13(typescript@5.4.5): dependencies: valibot: 0.37.0(typescript@5.4.5) transitivePeerDependencies: From c654a7bce3dd659d48d7cf3fdfa5d6f23b23a3f4 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Sun, 24 Nov 2024 14:07:27 +0100 Subject: [PATCH 08/11] fix: check if oid4vp defined Signed-off-by: Timo Glastra --- .../lib/authorization-response/AuthorizationResponse.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/siop-oid4vp/lib/authorization-response/AuthorizationResponse.ts b/packages/siop-oid4vp/lib/authorization-response/AuthorizationResponse.ts index c1c71212..09d91a6c 100644 --- a/packages/siop-oid4vp/lib/authorization-response/AuthorizationResponse.ts +++ b/packages/siop-oid4vp/lib/authorization-response/AuthorizationResponse.ts @@ -207,8 +207,8 @@ export class AuthorizationResponse { state, correlationId: verifyOpts.correlationId, ...(this.idToken && { idToken: verifiedIdToken }), - ...(oid4vp.presentationExchange && { oid4vpSubmission: oid4vp.presentationExchange }), - ...(oid4vp.dcql && { oid4vpSubmissionDcql: oid4vp.dcql }), + ...(oid4vp?.presentationExchange && { oid4vpSubmission: oid4vp.presentationExchange }), + ...(oid4vp?.dcql && { oid4vpSubmissionDcql: oid4vp.dcql }), } } From 76be4cc85ae2be951574385ba9a6f0aa1c62d18f Mon Sep 17 00:00:00 2001 From: Martin Auer Date: Mon, 25 Nov 2024 14:43:26 +0100 Subject: [PATCH 09/11] fix: update dcql and incorporate feedback --- .../siop-oid4vp/lib/authorization-request/Payload.ts | 4 ++-- .../authorization-response/AuthorizationResponse.ts | 12 +++++------- .../siop-oid4vp/lib/authorization-response/Dcql.ts | 4 ++-- .../lib/authorization-response/OpenID4VP.ts | 2 +- packages/siop-oid4vp/package.json | 2 +- pnpm-lock.yaml | 12 ++++++------ 6 files changed, 17 insertions(+), 19 deletions(-) diff --git a/packages/siop-oid4vp/lib/authorization-request/Payload.ts b/packages/siop-oid4vp/lib/authorization-request/Payload.ts index b21d7c70..07290006 100644 --- a/packages/siop-oid4vp/lib/authorization-request/Payload.ts +++ b/packages/siop-oid4vp/lib/authorization-request/Payload.ts @@ -35,13 +35,13 @@ export const createPresentationDefinitionClaimsProperties = (opts: ClaimPayloadO return { ...(opts.id_token ? { id_token: opts.id_token } : {}), - ...((opts.vp_token.presentation_definition || opts.vp_token.presentation_definition_uri || opts.vp_token.dcql_query) && { + ...((opts.vp_token.presentation_definition || opts.vp_token.presentation_definition_uri) && { vp_token: { ...(!opts.vp_token.presentation_definition_uri && { presentation_definition: opts.vp_token.presentation_definition }), ...(opts.vp_token.presentation_definition_uri && { presentation_definition_uri: opts.vp_token.presentation_definition_uri }), - ...(opts.vp_token.dcql_query && { dcql_query: opts.vp_token.dcql_query }), }, }), + ...(opts.vp_token.dcql_query && { vp_token: { dcql_query: opts.vp_token.dcql_query } }), } } diff --git a/packages/siop-oid4vp/lib/authorization-response/AuthorizationResponse.ts b/packages/siop-oid4vp/lib/authorization-response/AuthorizationResponse.ts index 09d91a6c..9bb0dcf4 100644 --- a/packages/siop-oid4vp/lib/authorization-response/AuthorizationResponse.ts +++ b/packages/siop-oid4vp/lib/authorization-response/AuthorizationResponse.ts @@ -130,7 +130,7 @@ export class AuthorizationResponse { if (responseOpts.presentationExchange) { const wrappedPresentations = response.payload.vp_token - ? await extractPresentationsFromVpToken(response.payload.vp_token, { + ? extractPresentationsFromVpToken(response.payload.vp_token, { hasher: verifyOpts.hasher, }) : [] @@ -144,14 +144,12 @@ export class AuthorizationResponse { hasher: verifyOpts.hasher, }, }) - } else { - const dcqlQuery = verifiedAuthorizationRequest.dcqlQuery - if (!dcqlQuery) { - throw new Error('vp_token is present, but no presentation definitions or dcql query provided') - } - assertValidDcqlPresentationResult(responseOpts.dcqlQuery.dcqlPresentation as DcqlPresentation, dcqlQuery, { + } else if (verifiedAuthorizationRequest.dcqlQuery) { + assertValidDcqlPresentationResult(responseOpts.dcqlQuery.dcqlPresentation as DcqlPresentation, verifiedAuthorizationRequest.dcqlQuery, { hasher: verifyOpts.hasher, }) + } else { + throw new Error('vp_token is present, but no presentation definitions or dcql query provided') } return response diff --git a/packages/siop-oid4vp/lib/authorization-response/Dcql.ts b/packages/siop-oid4vp/lib/authorization-response/Dcql.ts index 77ab1da5..7740fdb3 100644 --- a/packages/siop-oid4vp/lib/authorization-response/Dcql.ts +++ b/packages/siop-oid4vp/lib/authorization-response/Dcql.ts @@ -43,10 +43,10 @@ export const getDcqlPresentationResult = (record: DcqlPresentation | string, dcq if (p.format === 'mso_mdoc') { return [ queryId, - { credentialFormat: 'mso_mdoc', doctype: p.vcs[0].credential.toJson().docType, namespaces: p.vcs[0].decoded } satisfies DcqlMdocCredential, + { credential_format: 'mso_mdoc', doctype: p.vcs[0].credential.toJson().docType, namespaces: p.vcs[0].decoded } satisfies DcqlMdocCredential, ] } else if (p.format === 'vc+sd-jwt') { - return [queryId, { credentialFormat: 'vc+sd-jwt', vct: p.vcs[0].decoded.vct, claims: p.vcs[0].decoded } satisfies DcqlSdJwtVcCredential] + return [queryId, { credential_format: 'vc+sd-jwt', vct: p.vcs[0].decoded.vct, claims: p.vcs[0].decoded } satisfies DcqlSdJwtVcCredential] } else { throw new Error('DcqlPresentation atm only supports mso_mdoc and vc+sd-jwt') } diff --git a/packages/siop-oid4vp/lib/authorization-response/OpenID4VP.ts b/packages/siop-oid4vp/lib/authorization-response/OpenID4VP.ts index d1177ef4..d4fe8b9d 100644 --- a/packages/siop-oid4vp/lib/authorization-response/OpenID4VP.ts +++ b/packages/siop-oid4vp/lib/authorization-response/OpenID4VP.ts @@ -113,7 +113,7 @@ export const verifyPresentations = async ( } } else { const presentations = authorizationResponse.payload.vp_token - ? await extractPresentationsFromVpToken(authorizationResponse.payload.vp_token, { hasher: verifyOpts.hasher }) + ? extractPresentationsFromVpToken(authorizationResponse.payload.vp_token, { hasher: verifyOpts.hasher }) : [] wrappedPresentations = Array.isArray(presentations) ? presentations : [presentations] diff --git a/packages/siop-oid4vp/package.json b/packages/siop-oid4vp/package.json index 937ce385..acb5d152 100644 --- a/packages/siop-oid4vp/package.json +++ b/packages/siop-oid4vp/package.json @@ -18,7 +18,7 @@ "@sphereon/jarm": "workspace:*", "@sphereon/oid4vc-common": "workspace:*", "@sphereon/pex": "5.0.0-unstable.24", - "dcql": "^0.2.13", + "dcql": "^0.2.15", "@sphereon/pex-models": "^2.3.1", "@sphereon/ssi-types": "0.30.2-next.279", "cross-fetch": "^4.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f4e47bc6..4b704bde 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -461,8 +461,8 @@ importers: specifier: ^4.0.0 version: 4.0.0(encoding@0.1.13) dcql: - specifier: ^0.2.13 - version: 0.2.13(typescript@5.4.5) + specifier: ^0.2.15 + version: 0.2.16(typescript@5.4.5) debug: specifier: ^4.3.5 version: 4.3.7 @@ -1867,7 +1867,7 @@ packages: '@expo/bunyan@4.0.1': resolution: {integrity: sha512-+Lla7nYSiHZirgK+U/uYzsLv/X+HaJienbD5AKX1UQZHYfWaP+9uuQluRB4GrEVWF0GZ7vEVp/jzaOT9k/SQlg==} - engines: {node: '>=0.10.0'} + engines: {'0': node >=0.10.0} '@expo/cli@0.7.3': resolution: {integrity: sha512-uMGHbAhApqXR2sd1KPhgvpbOhBBnspad8msEqHleT2PHXwKIwTUDzBGO9+jdOAWwCx2MJfw3+asYjzoD3DN9Bg==} @@ -4015,8 +4015,8 @@ packages: dayjs@1.11.13: resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} - dcql@0.2.13: - resolution: {integrity: sha512-XfePsSz9ULj9HH3VFNguzK/xlFnliKDX2iUDb1tIrn97S+TfftcFo+jipw16m9jPlWLhhBx48QniF0D8KotIWA==} + dcql@0.2.16: + resolution: {integrity: sha512-sZpx8QZYc/vdPsOy0PyFWPNmTrDTPyLGsV0wPJJ6u/F+RFkQlw5LHV4fIfzPefdDKUF5YvGm3oDiBt3qzz9/zg==} debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} @@ -13835,7 +13835,7 @@ snapshots: dayjs@1.11.13: {} - dcql@0.2.13(typescript@5.4.5): + dcql@0.2.16(typescript@5.4.5): dependencies: valibot: 0.37.0(typescript@5.4.5) transitivePeerDependencies: From d7cc1e7342280370e9a4bef023e1b88ac735f412 Mon Sep 17 00:00:00 2001 From: Martin Auer Date: Tue, 26 Nov 2024 09:59:27 +0100 Subject: [PATCH 10/11] feat: update dcql --- packages/siop-oid4vp/package.json | 2 +- pnpm-lock.yaml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/siop-oid4vp/package.json b/packages/siop-oid4vp/package.json index acb5d152..fb3df1a0 100644 --- a/packages/siop-oid4vp/package.json +++ b/packages/siop-oid4vp/package.json @@ -18,7 +18,7 @@ "@sphereon/jarm": "workspace:*", "@sphereon/oid4vc-common": "workspace:*", "@sphereon/pex": "5.0.0-unstable.24", - "dcql": "^0.2.15", + "dcql": "0.2.16", "@sphereon/pex-models": "^2.3.1", "@sphereon/ssi-types": "0.30.2-next.279", "cross-fetch": "^4.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4b704bde..ef85fa2a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -461,7 +461,7 @@ importers: specifier: ^4.0.0 version: 4.0.0(encoding@0.1.13) dcql: - specifier: ^0.2.15 + specifier: 0.2.16 version: 0.2.16(typescript@5.4.5) debug: specifier: ^4.3.5 @@ -2535,6 +2535,7 @@ packages: '@sphereon/kmp-mdl-mdoc@0.2.0-SNAPSHOT.22': resolution: {integrity: sha512-uAZZExVy+ug9JLircejWa5eLtAZ7bnBP6xb7DO2+86LRsHNLh2k2jMWJYxp+iWtGHTsh6RYsZl14ScQLvjiQ/A==} + bundledDependencies: [] '@sphereon/pex-models@2.3.1': resolution: {integrity: sha512-SByU4cJ0XYA6VZQ/L6lsSiRcFtBPHbFioCeQ4GP7/W/jQ+PSBD7uK2oTnKQ9/0iEiMK/6JYqhKgLs4a9UX3UTQ==} From ad19797805d68855d902afcf586d521927654adc Mon Sep 17 00:00:00 2001 From: Martin Auer Date: Tue, 26 Nov 2024 10:00:10 +0100 Subject: [PATCH 11/11] feat: update dcql --- packages/siop-oid4vp/package.json | 2 +- pnpm-lock.yaml | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/siop-oid4vp/package.json b/packages/siop-oid4vp/package.json index fb3df1a0..dbe0097d 100644 --- a/packages/siop-oid4vp/package.json +++ b/packages/siop-oid4vp/package.json @@ -18,7 +18,7 @@ "@sphereon/jarm": "workspace:*", "@sphereon/oid4vc-common": "workspace:*", "@sphereon/pex": "5.0.0-unstable.24", - "dcql": "0.2.16", + "dcql": "0.2.17", "@sphereon/pex-models": "^2.3.1", "@sphereon/ssi-types": "0.30.2-next.279", "cross-fetch": "^4.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ef85fa2a..82bfd10d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -461,8 +461,8 @@ importers: specifier: ^4.0.0 version: 4.0.0(encoding@0.1.13) dcql: - specifier: 0.2.16 - version: 0.2.16(typescript@5.4.5) + specifier: 0.2.17 + version: 0.2.17(typescript@5.4.5) debug: specifier: ^4.3.5 version: 4.3.7 @@ -4016,8 +4016,8 @@ packages: dayjs@1.11.13: resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} - dcql@0.2.16: - resolution: {integrity: sha512-sZpx8QZYc/vdPsOy0PyFWPNmTrDTPyLGsV0wPJJ6u/F+RFkQlw5LHV4fIfzPefdDKUF5YvGm3oDiBt3qzz9/zg==} + dcql@0.2.17: + resolution: {integrity: sha512-YKNJR2anEiWooUCg7cJt/QmSFxpBS+SJQurcsNA60+8qUrjOuroh1Wd+lka/yOAV2VUdRzdNY6ISouxTV6SUaQ==} debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} @@ -8322,16 +8322,16 @@ packages: resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} engines: {node: '>=10.12.0'} - valibot@0.37.0: - resolution: {integrity: sha512-FQz52I8RXgFgOHym3XHYSREbNtkgSjF9prvMFH1nBsRyfL6SfCzoT1GuSDTlbsuPubM7/6Kbw0ZMQb8A+V+VsQ==} + valibot@0.42.1: + resolution: {integrity: sha512-3keXV29Ar5b//Hqi4MbSdV7lfVp6zuYLZuA9V1PvQUsXqogr+u5lvLPLk3A4f74VUXDnf/JfWMN6sB+koJ/FFw==} peerDependencies: typescript: '>=5' peerDependenciesMeta: typescript: optional: true - valibot@0.42.1: - resolution: {integrity: sha512-3keXV29Ar5b//Hqi4MbSdV7lfVp6zuYLZuA9V1PvQUsXqogr+u5lvLPLk3A4f74VUXDnf/JfWMN6sB+koJ/FFw==} + valibot@1.0.0-beta.8: + resolution: {integrity: sha512-OPAwJZtowb0j91b+bd77+ny7D1VVzsCzD7Jl9waLUlMprTsfI9Y3HHbW3hAQD7wKDKHsmGEesuiYWaYvcZL2wg==} peerDependencies: typescript: '>=5' peerDependenciesMeta: @@ -13836,9 +13836,9 @@ snapshots: dayjs@1.11.13: {} - dcql@0.2.16(typescript@5.4.5): + dcql@0.2.17(typescript@5.4.5): dependencies: - valibot: 0.37.0(typescript@5.4.5) + valibot: 1.0.0-beta.8(typescript@5.4.5) transitivePeerDependencies: - typescript @@ -19309,14 +19309,14 @@ snapshots: '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 - valibot@0.37.0(typescript@5.4.5): - optionalDependencies: - typescript: 5.4.5 - valibot@0.42.1(typescript@5.5.3): optionalDependencies: typescript: 5.5.3 + valibot@1.0.0-beta.8(typescript@5.4.5): + optionalDependencies: + typescript: 5.4.5 + valid-url@1.0.9: {} validate-npm-package-license@3.0.4: