@sphereon/ssi-sdk.siopv2-oid4vp-op-auth 0.32.1-next.13 → 0.32.1-next.141

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/agent/DidAuthSiopOpAuthenticator.d.ts +8 -3
  2. package/dist/agent/DidAuthSiopOpAuthenticator.d.ts.map +1 -1
  3. package/dist/agent/DidAuthSiopOpAuthenticator.js +105 -35
  4. package/dist/agent/DidAuthSiopOpAuthenticator.js.map +1 -1
  5. package/dist/services/Siopv2MachineService.d.ts +5 -0
  6. package/dist/services/Siopv2MachineService.d.ts.map +1 -1
  7. package/dist/services/Siopv2MachineService.js +99 -9
  8. package/dist/services/Siopv2MachineService.js.map +1 -1
  9. package/dist/session/OpSession.d.ts.map +1 -1
  10. package/dist/session/OpSession.js +3 -3
  11. package/dist/session/OpSession.js.map +1 -1
  12. package/dist/types/IDidAuthSiopOpAuthenticator.d.ts +6 -1
  13. package/dist/types/IDidAuthSiopOpAuthenticator.d.ts.map +1 -1
  14. package/dist/types/IDidAuthSiopOpAuthenticator.js.map +1 -1
  15. package/dist/types/machine/index.d.ts +1 -0
  16. package/dist/types/machine/index.d.ts.map +1 -1
  17. package/dist/types/machine/index.js.map +1 -1
  18. package/dist/types/siop-service/index.d.ts +22 -5
  19. package/dist/types/siop-service/index.d.ts.map +1 -1
  20. package/dist/types/siop-service/index.js.map +1 -1
  21. package/dist/utils/CredentialUtils.d.ts +23 -0
  22. package/dist/utils/CredentialUtils.d.ts.map +1 -0
  23. package/dist/utils/CredentialUtils.js +65 -0
  24. package/dist/utils/CredentialUtils.js.map +1 -0
  25. package/dist/utils/dcql.d.ts +5 -0
  26. package/dist/utils/dcql.d.ts.map +1 -0
  27. package/dist/utils/dcql.js +37 -0
  28. package/dist/utils/dcql.js.map +1 -0
  29. package/package.json +22 -19
  30. package/src/agent/DidAuthSiopOpAuthenticator.ts +133 -55
  31. package/src/services/Siopv2MachineService.ts +119 -13
  32. package/src/session/OpSession.ts +5 -3
  33. package/src/types/IDidAuthSiopOpAuthenticator.ts +14 -0
  34. package/src/types/machine/index.ts +1 -0
  35. package/src/types/siop-service/index.ts +24 -5
  36. package/src/utils/CredentialUtils.ts +71 -0
  37. package/src/utils/dcql.ts +36 -0
@@ -2,19 +2,22 @@ import { decodeUriAsJson, PresentationSignCallback, SupportedVersion, VerifiedAu
2
2
  import {
3
3
  ConnectionType,
4
4
  CorrelationIdentifierType,
5
+ CredentialDocumentFormat,
5
6
  CredentialRole,
7
+ DocumentType,
6
8
  Identity,
7
9
  IdentityOrigin,
8
10
  NonPersistedIdentity,
9
11
  Party,
10
12
  } from '@sphereon/ssi-sdk.data-store'
11
- import { Loggers } from '@sphereon/ssi-types'
13
+ import { Hasher, Loggers, SdJwtDecodedVerifiableCredential } from '@sphereon/ssi-types'
12
14
  import { IAgentPlugin } from '@veramo/core'
13
15
  import { v4 as uuidv4 } from 'uuid'
14
16
  import {
15
17
  DidAuthSiopOpAuthenticatorOptions,
16
18
  GetSelectableCredentialsArgs,
17
19
  IOpSessionArgs,
20
+ Json,
18
21
  LOGGER_NAMESPACE,
19
22
  RequiredContext,
20
23
  schema,
@@ -25,32 +28,31 @@ import {
25
28
  import { Siopv2Machine } from '../machine/Siopv2Machine'
26
29
  import { getSelectableCredentials, siopSendAuthorizationResponse, translateCorrelationIdToName } from '../services/Siopv2MachineService'
27
30
  import { OpSession } from '../session'
31
+ import { PEX, Status } from '@sphereon/pex'
32
+ import { computeEntryHash } from '@veramo/utils'
33
+ import { UniqueDigitalCredential } from '@sphereon/ssi-sdk.credential-store'
34
+ import { EventEmitter } from 'events'
28
35
  import {
36
+ AddIdentityArgs,
37
+ CreateConfigArgs,
38
+ CreateConfigResult,
39
+ GetSiopRequestArgs,
29
40
  IDidAuthSiopOpAuthenticator,
30
41
  IGetSiopSessionArgs,
31
42
  IRegisterCustomApprovalForSiopArgs,
32
43
  IRemoveCustomApprovalForSiopArgs,
33
44
  IRemoveSiopSessionArgs,
34
45
  IRequiredContext,
35
- } from '../types/IDidAuthSiopOpAuthenticator'
36
- import { Siopv2Machine as Siopv2MachineId, Siopv2MachineInstanceOpts } from '../types/machine'
37
-
38
- import {
39
- AddIdentityArgs,
40
- CreateConfigArgs,
41
- CreateConfigResult,
42
- GetSiopRequestArgs,
43
46
  OnContactIdentityCreatedArgs,
44
47
  OnIdentifierCreatedArgs,
45
48
  RetrieveContactArgs,
46
49
  SendResponseArgs,
47
50
  Siopv2AuthorizationRequestData,
48
51
  Siopv2HolderEvent,
49
- } from '../types/siop-service'
50
- import { PEX, Status } from '@sphereon/pex'
51
- import { computeEntryHash } from '@veramo/utils'
52
- import { UniqueDigitalCredential } from '@sphereon/ssi-sdk.credential-store'
53
- import { EventEmitter } from 'events'
52
+ Siopv2Machine as Siopv2MachineId,
53
+ Siopv2MachineInstanceOpts,
54
+ } from '../types'
55
+ import { DcqlCredential, DcqlPresentation, DcqlQuery, DcqlSdJwtVcCredential } from 'dcql'
54
56
 
55
57
  const logger = Loggers.DEFAULT.options(LOGGER_NAMESPACE, {}).get(LOGGER_NAMESPACE)
56
58
 
@@ -89,23 +91,20 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin {
89
91
  private readonly sessions: Map<string, OpSession>
90
92
  private readonly customApprovals: Record<string, (verifiedAuthorizationRequest: VerifiedAuthorizationRequest, sessionId: string) => Promise<void>>
91
93
  private readonly presentationSignCallback?: PresentationSignCallback
92
-
93
94
  private readonly onContactIdentityCreated?: (args: OnContactIdentityCreatedArgs) => Promise<void>
94
95
  private readonly onIdentifierCreated?: (args: OnIdentifierCreatedArgs) => Promise<void>
95
96
  private readonly eventEmitter?: EventEmitter
97
+ private readonly hasher?: Hasher
96
98
 
97
- constructor(
98
- presentationSignCallback?: PresentationSignCallback,
99
- customApprovals?: Record<string, (verifiedAuthorizationRequest: VerifiedAuthorizationRequest, sessionId: string) => Promise<void>>,
100
- options?: DidAuthSiopOpAuthenticatorOptions,
101
- ) {
102
- const { onContactIdentityCreated, onIdentifierCreated } = options ?? {}
99
+ constructor(options?: DidAuthSiopOpAuthenticatorOptions) {
100
+ const { onContactIdentityCreated, onIdentifierCreated, hasher, customApprovals = {}, presentationSignCallback } = { ...options }
101
+
102
+ this.hasher = hasher
103
103
  this.onContactIdentityCreated = onContactIdentityCreated
104
104
  this.onIdentifierCreated = onIdentifierCreated
105
-
106
- this.sessions = new Map<string, OpSession>()
107
- this.customApprovals = customApprovals || {}
108
105
  this.presentationSignCallback = presentationSignCallback
106
+ this.sessions = new Map<string, OpSession>()
107
+ this.customApprovals = customApprovals
109
108
  }
110
109
 
111
110
  public async onEvent(event: any, context: RequiredContext): Promise<void> {
@@ -185,8 +184,8 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin {
185
184
  return Siopv2Machine.newInstance(siopv2MachineOpts)
186
185
  }
187
186
 
188
- private async siopCreateConfig(args: CreateConfigArgs): Promise<CreateConfigResult> {
189
- const { url } = args
187
+ private async siopCreateConfig<TContext extends CreateConfigArgs>(context: TContext): Promise<CreateConfigResult> {
188
+ const { url } = context
190
189
 
191
190
  if (!url) {
192
191
  return Promise.reject(Error('Missing request uri in context'))
@@ -213,9 +212,14 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin {
213
212
  }
214
213
  const { sessionId, redirectUrl } = didAuthConfig
215
214
 
216
- const session: OpSession = await agent
217
- .siopGetOPSession({ sessionId })
218
- .catch(async () => await agent.siopRegisterOPSession({ requestJwtOrUri: redirectUrl, sessionId, op: { eventEmitter: this.eventEmitter } }))
215
+ const session: OpSession = await agent.siopGetOPSession({ sessionId }).catch(
216
+ async () =>
217
+ await agent.siopRegisterOPSession({
218
+ requestJwtOrUri: redirectUrl,
219
+ sessionId,
220
+ op: { eventEmitter: this.eventEmitter, hasher: this.hasher },
221
+ }),
222
+ )
219
223
 
220
224
  logger.debug(`session: ${JSON.stringify(session.id, null, 2)}`)
221
225
  const verifiedAuthorizationRequest = await session.getAuthorizationRequest()
@@ -244,6 +248,7 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin {
244
248
  verifiedAuthorizationRequest.presentationDefinitions.length > 0)
245
249
  ? verifiedAuthorizationRequest.presentationDefinitions
246
250
  : undefined,
251
+ dcqlQuery: verifiedAuthorizationRequest.dcqlQuery,
247
252
  }
248
253
  }
249
254
 
@@ -332,7 +337,7 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin {
332
337
  }
333
338
 
334
339
  private async siopSendResponse(args: SendResponseArgs, context: RequiredContext): Promise<Siopv2AuthorizationResponseData> {
335
- const { didAuthConfig, authorizationRequestData, selectedCredentials } = args
340
+ const { didAuthConfig, authorizationRequestData, selectedCredentials, isFirstParty } = args
336
341
 
337
342
  if (didAuthConfig === undefined) {
338
343
  return Promise.reject(Error('Missing config in context'))
@@ -342,34 +347,75 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin {
342
347
  return Promise.reject(Error('Missing authorization request data in context'))
343
348
  }
344
349
 
345
- const pex = new PEX()
350
+ const pex = new PEX({ hasher: this.hasher })
346
351
  const verifiableCredentialsWithDefinition: Array<VerifiableCredentialsWithDefinition> = []
347
-
348
- authorizationRequestData.presentationDefinitions?.forEach((presentationDefinition) => {
349
- const { areRequiredCredentialsPresent, verifiableCredential: verifiableCredentials } = pex.selectFrom(
350
- presentationDefinition.definition,
351
- selectedCredentials.map((udc) => udc.originalVerifiableCredential!),
352
- )
353
- if (areRequiredCredentialsPresent !== Status.ERROR && verifiableCredentials) {
354
- const uniqueDigitalCredentials: UniqueDigitalCredential[] = verifiableCredentials.map((vc) => {
355
- // @ts-ignore FIXME Funke
356
- const hash = computeEntryHash(vc)
357
- const udc = selectedCredentials.find((udc) => udc.hash == hash)
358
-
359
- if (!udc) {
360
- throw Error('UniqueDigitalCredential could not be found')
352
+ const dcqlCredentialsWithCredentials: Map<DcqlCredential, UniqueDigitalCredential> = new Map()
353
+
354
+ if (Array.isArray(authorizationRequestData.presentationDefinitions) && authorizationRequestData?.presentationDefinitions.length > 0) {
355
+ try {
356
+ authorizationRequestData.presentationDefinitions?.forEach((presentationDefinition) => {
357
+ const { areRequiredCredentialsPresent, verifiableCredential: verifiableCredentials } = pex.selectFrom(
358
+ presentationDefinition.definition,
359
+ selectedCredentials.map((udc) => udc.originalVerifiableCredential!),
360
+ )
361
+
362
+ if (areRequiredCredentialsPresent !== Status.ERROR && verifiableCredentials) {
363
+ let uniqueDigitalCredentials: UniqueDigitalCredential[] = []
364
+ uniqueDigitalCredentials = verifiableCredentials.map((vc) => {
365
+ // @ts-ignore FIXME Funke
366
+ const hash = computeEntryHash(vc)
367
+ const udc = selectedCredentials.find((udc) => udc.hash == hash)
368
+
369
+ if (!udc) {
370
+ throw Error('UniqueDigitalCredential could not be found')
371
+ }
372
+ return udc
373
+ })
374
+ verifiableCredentialsWithDefinition.push({
375
+ definition: presentationDefinition,
376
+ credentials: uniqueDigitalCredentials,
377
+ })
361
378
  }
362
- return udc
363
- })
364
- verifiableCredentialsWithDefinition.push({
365
- definition: presentationDefinition,
366
- credentials: uniqueDigitalCredentials,
367
379
  })
380
+ } catch (e) {
381
+ return Promise.reject(e)
368
382
  }
369
- })
370
383
 
371
- if (verifiableCredentialsWithDefinition.length === 0) {
372
- return Promise.reject(Error('None of the selected credentials match any of the presentation definitions.'))
384
+ if (verifiableCredentialsWithDefinition.length === 0) {
385
+ return Promise.reject(Error('None of the selected credentials match any of the presentation definitions.'))
386
+ }
387
+ } else if (authorizationRequestData.dcqlQuery) {
388
+ //TODO Only SD-JWT and MSO MDOC are supported at the moment
389
+ if (this.hasMDocCredentials(selectedCredentials) || this.hasSdJwtCredentials(selectedCredentials)) {
390
+ try {
391
+ selectedCredentials.forEach((vc) => {
392
+ if (this.isSdJwtCredential(vc)) {
393
+ const payload = (vc.originalVerifiableCredential as SdJwtDecodedVerifiableCredential).decodedPayload
394
+ const result: DcqlSdJwtVcCredential = {
395
+ claims: payload as { [x: string]: Json },
396
+ vct: payload.vct,
397
+ credential_format: 'vc+sd-jwt',
398
+ }
399
+ dcqlCredentialsWithCredentials.set(result, vc)
400
+ //FIXME MDoc namespaces are incompatible: array of strings vs complex object - https://sphereon.atlassian.net/browse/SPRIND-143
401
+ } else {
402
+ throw Error(`Invalid credential format: ${vc.digitalCredential.documentFormat}`)
403
+ }
404
+ })
405
+ } catch (e) {
406
+ return Promise.reject(e)
407
+ }
408
+
409
+ const dcqlPresentationRecord: DcqlPresentation.Output = {}
410
+ const queryResult = DcqlQuery.query(authorizationRequestData.dcqlQuery, Array.from(dcqlCredentialsWithCredentials.keys()))
411
+ for (const [key, value] of Object.entries(queryResult.credential_matches)) {
412
+ if (value.success) {
413
+ dcqlPresentationRecord[key] = this.retrieveEncodedCredential(dcqlCredentialsWithCredentials.get(value.output)!) as
414
+ | string
415
+ | { [x: string]: Json }
416
+ }
417
+ }
418
+ }
373
419
  }
374
420
 
375
421
  const response = await siopSendAuthorizationResponse(
@@ -378,6 +424,8 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin {
378
424
  sessionId: didAuthConfig.sessionId,
379
425
  ...(args.idOpts && { idOpts: args.idOpts }),
380
426
  ...(authorizationRequestData.presentationDefinitions !== undefined && { verifiableCredentialsWithDefinition }),
427
+ isFirstParty,
428
+ hasher: this.hasher,
381
429
  },
382
430
  context,
383
431
  )
@@ -392,11 +440,41 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin {
392
440
 
393
441
  return {
394
442
  body: responseBody,
395
- url: response.url,
396
- queryParams: decodeUriAsJson(response.url),
443
+ url: response?.url,
444
+ queryParams: decodeUriAsJson(response?.url),
397
445
  }
398
446
  }
399
447
 
448
+ private hasMDocCredentials = (credentials: UniqueDigitalCredential[]): boolean => {
449
+ return credentials.some(this.isMDocCredential)
450
+ }
451
+
452
+ private isMDocCredential = (credential: UniqueDigitalCredential) => {
453
+ return (
454
+ credential.digitalCredential.documentFormat === CredentialDocumentFormat.MSO_MDOC &&
455
+ credential.digitalCredential.documentType === DocumentType.VC
456
+ )
457
+ }
458
+
459
+ private hasSdJwtCredentials = (credentials: UniqueDigitalCredential[]): boolean => {
460
+ return credentials.some(this.isSdJwtCredential)
461
+ }
462
+
463
+ private isSdJwtCredential = (credential: UniqueDigitalCredential) => {
464
+ return (
465
+ credential.digitalCredential.documentFormat === CredentialDocumentFormat.SD_JWT && credential.digitalCredential.documentType === DocumentType.VC
466
+ )
467
+ }
468
+
469
+ private retrieveEncodedCredential = (credential: UniqueDigitalCredential) => {
470
+ return credential.originalVerifiableCredential !== undefined &&
471
+ credential.originalVerifiableCredential !== null &&
472
+ (credential?.originalVerifiableCredential as SdJwtDecodedVerifiableCredential)?.compactSdJwtVc !== undefined &&
473
+ (credential?.originalVerifiableCredential as SdJwtDecodedVerifiableCredential)?.compactSdJwtVc !== null
474
+ ? (credential.originalVerifiableCredential as SdJwtDecodedVerifiableCredential).compactSdJwtVc
475
+ : credential.originalVerifiableCredential
476
+ }
477
+
400
478
  private async siopGetSelectableCredentials(args: GetSelectableCredentialsArgs, context: RequiredContext): Promise<SelectableCredentialsMap> {
401
479
  const { authorizationRequestData } = args
402
480
 
@@ -2,9 +2,9 @@ import { AuthorizationRequest, SupportedVersion } from '@sphereon/did-auth-siop'
2
2
  import { IPresentationDefinition, PEX } from '@sphereon/pex'
3
3
  import { InputDescriptorV1, InputDescriptorV2, PresentationDefinitionV1, PresentationDefinitionV2 } from '@sphereon/pex-models'
4
4
  import { isOID4VCIssuerIdentifier, ManagedIdentifierOptsOrResult } from '@sphereon/ssi-sdk-ext.identifier-resolution'
5
- import { verifiableCredentialForRoleFilter } from '@sphereon/ssi-sdk.credential-store'
5
+ import { UniqueDigitalCredential, verifiableCredentialForRoleFilter } from '@sphereon/ssi-sdk.credential-store'
6
6
  import { ConnectionType, CredentialRole } from '@sphereon/ssi-sdk.data-store'
7
- import { CredentialMapper, Loggers, PresentationSubmission } from '@sphereon/ssi-types'
7
+ import { CredentialMapper, Hasher, Loggers, OriginalVerifiableCredential, PresentationSubmission } from '@sphereon/ssi-types'
8
8
  import { OID4VP, OpSession } from '../session'
9
9
  import {
10
10
  DidAgents,
@@ -20,6 +20,9 @@ import {
20
20
  import { IAgentContext, IDIDManager } from '@veramo/core'
21
21
  import { getOrCreatePrimaryIdentifier, SupportedDidMethodEnum } from '@sphereon/ssi-sdk-ext.did-utils'
22
22
  import { encodeJoseBlob } from '@sphereon/ssi-sdk.core'
23
+ import { DcqlCredential, DcqlQuery, DcqlCredentialPresentation, DcqlPresentation } from 'dcql'
24
+ import { convertToDcqlCredentials } from '../utils/dcql'
25
+ import { getOriginalVerifiableCredential } from '../utils/CredentialUtils'
23
26
 
24
27
  export const logger = Loggers.DEFAULT.get(LOGGER_NAMESPACE)
25
28
 
@@ -48,12 +51,15 @@ export const siopSendAuthorizationResponse = async (
48
51
  sessionId: string
49
52
  verifiableCredentialsWithDefinition?: VerifiableCredentialsWithDefinition[]
50
53
  idOpts?: ManagedIdentifierOptsOrResult
54
+ isFirstParty?: boolean
55
+ hasher?: Hasher
56
+ dcqlQuery?: DcqlQuery
51
57
  },
52
58
  context: RequiredContext,
53
59
  ) => {
54
60
  const { agent } = context
55
61
  const agentContext = { ...context, agent: context.agent as DidAgents }
56
- let { idOpts } = args
62
+ let { idOpts, isFirstParty, hasher } = args
57
63
 
58
64
  if (connectionType !== ConnectionType.SIOPv2_OpenID4VP) {
59
65
  return Promise.reject(Error(`No supported authentication provider for type: ${connectionType}`))
@@ -67,7 +73,7 @@ export const siopSendAuthorizationResponse = async (
67
73
  let presentationsAndDefs: VerifiablePresentationWithDefinition[] | undefined
68
74
  let presentationSubmission: PresentationSubmission | undefined
69
75
  if (await session.hasPresentationDefinitions()) {
70
- const oid4vp: OID4VP = await session.getOID4VP({})
76
+ const oid4vp: OID4VP = await session.getOID4VP({ hasher })
71
77
 
72
78
  const credentialsAndDefinitions = args.verifiableCredentialsWithDefinition
73
79
  ? args.verifiableCredentialsWithDefinition
@@ -153,16 +159,116 @@ export const siopSendAuthorizationResponse = async (
153
159
 
154
160
  idOpts = presentationsAndDefs[0].idOpts
155
161
  presentationSubmission = presentationsAndDefs[0].presentationSubmission
162
+
163
+ logger.log(`Definitions and locations:`, JSON.stringify(presentationsAndDefs?.[0]?.verifiablePresentations, null, 2))
164
+ logger.log(`Presentation Submission:`, JSON.stringify(presentationSubmission, null, 2))
165
+ const mergedVerifiablePresentations = presentationsAndDefs?.flatMap((pd) => pd.verifiablePresentations) || []
166
+ return await session.sendAuthorizationResponse({
167
+ ...(presentationsAndDefs && { verifiablePresentations: mergedVerifiablePresentations }),
168
+ ...(presentationSubmission && { presentationSubmission }),
169
+ // todo: Change issuer value in case we do not use identifier. Use key.meta.jwkThumbprint then
170
+ responseSignerOpts: idOpts!,
171
+ isFirstParty,
172
+ })
173
+ } else if (request.dcqlQuery) {
174
+ if (args.verifiableCredentialsWithDefinition !== undefined && args.verifiableCredentialsWithDefinition !== null) {
175
+ const vcs = args.verifiableCredentialsWithDefinition.flatMap((vcd) => vcd.credentials)
176
+ const domain =
177
+ ((await request.authorizationRequest.getMergedProperty('client_id')) as string) ??
178
+ request.issuer ??
179
+ (request.versions.includes(SupportedVersion.JWT_VC_PRESENTATION_PROFILE_v1)
180
+ ? 'https://self-issued.me/v2/openid-vc'
181
+ : 'https://self-issued.me/v2')
182
+ logger.debug(`NONCE: ${session.nonce}, domain: ${domain}`)
183
+
184
+ const firstUniqueDC = vcs[0]
185
+ if (typeof firstUniqueDC !== 'object' || !('digitalCredential' in firstUniqueDC)) {
186
+ return Promise.reject(Error('SiopMachine only supports UniqueDigitalCredentials for now'))
187
+ }
188
+
189
+ let identifier: ManagedIdentifierOptsOrResult
190
+ const digitalCredential = firstUniqueDC.digitalCredential
191
+ const firstVC = firstUniqueDC.uniformVerifiableCredential
192
+ const holder = CredentialMapper.isSdJwtDecodedCredential(firstVC)
193
+ ? firstVC.decodedPayload.cnf?.jwk
194
+ ? //TODO SDK-19: convert the JWK to hex and search for the appropriate key and associated DID
195
+ //doesn't apply to did:jwk only, as you can represent any DID key as a JWK. So whenever you encounter a JWK it doesn't mean it had to come from a did:jwk in the system. It just can always be represented as a did:jwk
196
+ `did:jwk:${encodeJoseBlob(firstVC.decodedPayload.cnf?.jwk)}#0`
197
+ : firstVC.decodedPayload.sub
198
+ : Array.isArray(firstVC.credentialSubject)
199
+ ? firstVC.credentialSubject[0].id
200
+ : firstVC.credentialSubject.id
201
+ if (!digitalCredential.kmsKeyRef) {
202
+ // In case the store does not have the kmsKeyRef lets search for the holder
203
+
204
+ if (!holder) {
205
+ return Promise.reject(`No holder found and no kmsKeyRef in DB. Cannot determine identifier to use`)
206
+ }
207
+ try {
208
+ identifier = await session.context.agent.identifierManagedGet({ identifier: holder })
209
+ } catch (e) {
210
+ logger.debug(`Holder DID not found: ${holder}`)
211
+ throw e
212
+ }
213
+ } else if (isOID4VCIssuerIdentifier(digitalCredential.kmsKeyRef)) {
214
+ identifier = await session.context.agent.identifierManagedGetByOID4VCIssuer({
215
+ identifier: firstUniqueDC.digitalCredential.kmsKeyRef,
216
+ })
217
+ } else {
218
+ switch (digitalCredential.subjectCorrelationType) {
219
+ case 'DID':
220
+ identifier = await session.context.agent.identifierManagedGetByDid({
221
+ identifier: digitalCredential.subjectCorrelationId ?? holder,
222
+ kmsKeyRef: digitalCredential.kmsKeyRef,
223
+ })
224
+ break
225
+ // TODO other implementations?
226
+ default:
227
+ // Since we are using the kmsKeyRef we will find the KID regardless of the identifier. We set it for later access though
228
+ identifier = await session.context.agent.identifierManagedGetByKid({
229
+ identifier: digitalCredential.subjectCorrelationId ?? holder ?? digitalCredential.kmsKeyRef,
230
+ kmsKeyRef: digitalCredential.kmsKeyRef,
231
+ })
232
+ }
233
+ }
234
+ console.log(`Identifier`, identifier)
235
+
236
+ const dcqlRepresentations: DcqlCredential[] = []
237
+ vcs.forEach((vc: UniqueDigitalCredential | OriginalVerifiableCredential) => {
238
+ const rep = convertToDcqlCredentials(vc, args.hasher)
239
+ if (rep) {
240
+ dcqlRepresentations.push(rep)
241
+ }
242
+ })
243
+
244
+ const queryResult = DcqlQuery.query(request.dcqlQuery, dcqlRepresentations)
245
+ const presentation: Record<string, DcqlCredentialPresentation> = {}
246
+
247
+ for (const [key, value] of Object.entries(queryResult.credential_matches)) {
248
+ const allMatches = Array.isArray(value) ? value : [value]
249
+ allMatches.forEach((match) => {
250
+ if (match.success) {
251
+ const originalCredential = getOriginalVerifiableCredential(vcs[match.input_credential_index])
252
+ if (!originalCredential) {
253
+ throw new Error(`Index ${match.input_credential_index} out of range in credentials array`)
254
+ }
255
+ presentation[key] =
256
+ (originalCredential as any)['compactSdJwtVc'] !== undefined ? (originalCredential as any).compactSdJwtVc : originalCredential
257
+ }
258
+ })
259
+ }
260
+
261
+ const response = session.sendAuthorizationResponse({
262
+ responseSignerOpts: identifier,
263
+ ...{ dcqlQuery: { dcqlPresentation: DcqlPresentation.parse(presentation) } },
264
+ })
265
+
266
+ logger.debug(`Response: `, response)
267
+
268
+ return response
269
+ }
156
270
  }
157
- logger.log(`Definitions and locations:`, JSON.stringify(presentationsAndDefs?.[0]?.verifiablePresentations, null, 2))
158
- logger.log(`Presentation Submission:`, JSON.stringify(presentationSubmission, null, 2))
159
- const mergedVerifiablePresentations = presentationsAndDefs?.flatMap((pd) => pd.verifiablePresentations) || []
160
- return await session.sendAuthorizationResponse({
161
- ...(presentationsAndDefs && { verifiablePresentations: mergedVerifiablePresentations }),
162
- ...(presentationSubmission && { presentationSubmission }),
163
- // todo: Change issuer value in case we do not use identifier. Use key.meta.jwkThumbprint then
164
- responseSignerOpts: idOpts!,
165
- })
271
+ throw Error('Presentation Definition or DCQL is required')
166
272
  }
167
273
 
168
274
  function buildPartialPD(
@@ -240,7 +240,7 @@ export class OpSession {
240
240
  private createPresentationVerificationCallback(context: IRequiredContext) {
241
241
  async function presentationVerificationCallback(
242
242
  args: W3CVerifiablePresentation | CompactSdJwtVc,
243
- presentationSubmission: PresentationSubmission,
243
+ presentationSubmission?: PresentationSubmission,
244
244
  ): Promise<PresentationVerificationResult> {
245
245
  let result: IVerifyResult
246
246
  if (CredentialMapper.isSdJwtEncoded(args)) {
@@ -293,8 +293,8 @@ export class OpSession {
293
293
  .jwtEncryptJweCompactJwt({
294
294
  recipientKey,
295
295
  protectedHeader: {},
296
- alg: requestObjectPayload.client_metadata.authorization_encrypted_response_alg as JweAlg | undefined ?? 'ECDH-ES',
297
- enc: requestObjectPayload.client_metadata.authorization_encrypted_response_enc as JweEnc | undefined ?? 'A256GCM',
296
+ alg: (requestObjectPayload.client_metadata.authorization_encrypted_response_alg as JweAlg | undefined) ?? 'ECDH-ES',
297
+ enc: (requestObjectPayload.client_metadata.authorization_encrypted_response_enc as JweEnc | undefined) ?? 'A256GCM',
298
298
  apv: encodeBase64url(opts.requestObjectPayload.nonce),
299
299
  apu: encodeBase64url(v4()),
300
300
  payload: authResponse,
@@ -360,12 +360,14 @@ export class OpSession {
360
360
  const responseOpts = {
361
361
  verification,
362
362
  issuer,
363
+ ...(args.isFirstParty && { isFirstParty: args.isFirstParty }),
363
364
  ...(args.verifiablePresentations && {
364
365
  presentationExchange: {
365
366
  verifiablePresentations,
366
367
  presentationSubmission: args.presentationSubmission,
367
368
  } as PresentationExchangeResponseOpts,
368
369
  }),
370
+ dcqlQuery: args.dcqlResponse,
369
371
  }
370
372
 
371
373
  const authResponse = await op.createAuthorizationResponse(request, responseOpts)
@@ -1,4 +1,5 @@
1
1
  import {
2
+ DcqlResponseOpts,
2
3
  PresentationDefinitionWithLocation,
3
4
  PresentationSignCallback,
4
5
  ResponseMode,
@@ -122,7 +123,9 @@ export interface IOpsSendSiopAuthorizationResponseArgs {
122
123
  // verifiedAuthorizationRequest: VerifiedAuthorizationRequest
123
124
  presentationSubmission?: PresentationSubmission
124
125
  verifiablePresentations?: W3CVerifiablePresentation[]
126
+ dcqlResponse?: DcqlResponseOpts
125
127
  hasher?: Hasher
128
+ isFirstParty?: boolean
126
129
  }
127
130
 
128
131
  export enum events {
@@ -197,4 +200,15 @@ export interface IGetPresentationExchangeArgs {
197
200
  hasher?: Hasher
198
201
  }
199
202
 
203
+ // It was added here because it's not exported from DCQL anymore
204
+ export type Json =
205
+ | string
206
+ | number
207
+ | boolean
208
+ | null
209
+ | {
210
+ [key: string]: Json
211
+ }
212
+ | Json[]
213
+
200
214
  export const DEFAULT_JWT_PROOF_TYPE = 'JwtProof2020'
@@ -18,6 +18,7 @@ export type Siopv2MachineContext = {
18
18
  contactAlias: string
19
19
  selectableCredentialsMap?: SelectableCredentialsMap
20
20
  selectedCredentials: Array<UniqueDigitalCredential>
21
+ isFirstParty?: boolean
21
22
  error?: ErrorDetails
22
23
  }
23
24
 
@@ -1,4 +1,8 @@
1
- import { PresentationDefinitionWithLocation, RPRegistrationMetadataPayload } from '@sphereon/did-auth-siop'
1
+ import {
2
+ PresentationDefinitionWithLocation,
3
+ PresentationSignCallback,
4
+ RPRegistrationMetadataPayload, VerifiedAuthorizationRequest
5
+ } from '@sphereon/did-auth-siop'
2
6
  import { IIdentifierResolution, ManagedIdentifierOptsOrResult } from '@sphereon/ssi-sdk-ext.identifier-resolution'
3
7
  import { IContactManager } from '@sphereon/ssi-sdk.contact-manager'
4
8
  import { ICredentialStore, UniqueDigitalCredential } from '@sphereon/ssi-sdk.credential-store'
@@ -7,10 +11,15 @@ import { IIssuanceBranding } from '@sphereon/ssi-sdk.issuance-branding'
7
11
  import { IAgentContext, IDIDManager, IIdentifier, IResolver } from '@veramo/core'
8
12
  import { IDidAuthSiopOpAuthenticator } from '../IDidAuthSiopOpAuthenticator'
9
13
  import { Siopv2MachineContext, Siopv2MachineInterpreter, Siopv2MachineState } from '../machine'
14
+ import { DcqlQuery } from 'dcql'
15
+ import { Hasher } from '@sphereon/ssi-types'
10
16
 
11
17
  export type DidAuthSiopOpAuthenticatorOptions = {
18
+ presentationSignCallback?: PresentationSignCallback
19
+ customApprovals?: Record<string, (verifiedAuthorizationRequest: VerifiedAuthorizationRequest, sessionId: string) => Promise<void>>
12
20
  onContactIdentityCreated?: (args: OnContactIdentityCreatedArgs) => Promise<void>
13
21
  onIdentifierCreated?: (args: OnIdentifierCreatedArgs) => Promise<void>
22
+ hasher?: Hasher
14
23
  }
15
24
 
16
25
  export type GetMachineArgs = {
@@ -19,12 +28,21 @@ export type GetMachineArgs = {
19
28
  stateNavigationListener?: (siopv2Machine: Siopv2MachineInterpreter, state: Siopv2MachineState, navigation?: any) => Promise<void>
20
29
  }
21
30
 
22
- export type CreateConfigArgs = Pick<Siopv2MachineContext, 'url'>
31
+ export type CreateConfigArgs = { url: string }
23
32
  export type CreateConfigResult = Omit<DidAuthConfig, 'stateId' | 'idOpts'>
24
- export type GetSiopRequestArgs = Pick<Siopv2MachineContext, 'didAuthConfig' | 'url'>
33
+ export type GetSiopRequestArgs = { didAuthConfig?: Omit<DidAuthConfig, 'identifier'>, url: string }
34
+ // FIXME it would be nicer if these function are not tied to a certain machine so that we can start calling them for anywhere
25
35
  export type RetrieveContactArgs = Pick<Siopv2MachineContext, 'url' | 'authorizationRequestData'>
36
+ // FIXME it would be nicer if these function are not tied to a certain machine so that we can start calling them for anywhere
26
37
  export type AddIdentityArgs = Pick<Siopv2MachineContext, 'contact' | 'authorizationRequestData'>
27
- export type SendResponseArgs = Pick<Siopv2MachineContext, 'didAuthConfig' | 'authorizationRequestData' | 'selectedCredentials' | 'idOpts'>
38
+ export type SendResponseArgs = {
39
+ didAuthConfig?: Omit<DidAuthConfig, 'identifier'>,
40
+ authorizationRequestData?: Siopv2AuthorizationRequestData,
41
+ selectedCredentials: Array<UniqueDigitalCredential>
42
+ idOpts?: ManagedIdentifierOptsOrResult
43
+ isFirstParty?: boolean
44
+ }
45
+ // FIXME it would be nicer if these function are not tied to a certain machine so that we can start calling them for anywhere
28
46
  export type GetSelectableCredentialsArgs = Pick<Siopv2MachineContext, 'authorizationRequestData'>
29
47
 
30
48
  export enum Siopv2HolderEvent {
@@ -38,7 +56,7 @@ export enum SupportedLanguage {
38
56
  }
39
57
 
40
58
  export type Siopv2AuthorizationResponseData = {
41
- body?: string
59
+ body?: string | Record<string, any>
42
60
  url?: string
43
61
  queryParams?: Record<string, any>
44
62
  }
@@ -51,6 +69,7 @@ export type Siopv2AuthorizationRequestData = {
51
69
  uri?: URL
52
70
  clientId?: string
53
71
  presentationDefinitions?: PresentationDefinitionWithLocation[]
72
+ dcqlQuery?: DcqlQuery
54
73
  }
55
74
 
56
75
  export type SelectableCredentialsMap = Map<string, Array<SelectableCredential>>