@sphereon/ssi-sdk.siopv2-oid4vp-op-auth 0.32.1-next.18 → 0.32.1-next.287

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 (42) 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 +112 -14
  8. package/dist/services/Siopv2MachineService.js.map +1 -1
  9. package/dist/session/OID4VP.d.ts +4 -4
  10. package/dist/session/OID4VP.d.ts.map +1 -1
  11. package/dist/session/OID4VP.js +2 -1
  12. package/dist/session/OID4VP.js.map +1 -1
  13. package/dist/session/OpSession.d.ts.map +1 -1
  14. package/dist/session/OpSession.js +3 -3
  15. package/dist/session/OpSession.js.map +1 -1
  16. package/dist/types/IDidAuthSiopOpAuthenticator.d.ts +12 -7
  17. package/dist/types/IDidAuthSiopOpAuthenticator.d.ts.map +1 -1
  18. package/dist/types/IDidAuthSiopOpAuthenticator.js.map +1 -1
  19. package/dist/types/machine/index.d.ts +1 -0
  20. package/dist/types/machine/index.d.ts.map +1 -1
  21. package/dist/types/machine/index.js.map +1 -1
  22. package/dist/types/siop-service/index.d.ts +22 -5
  23. package/dist/types/siop-service/index.d.ts.map +1 -1
  24. package/dist/types/siop-service/index.js.map +1 -1
  25. package/dist/utils/CredentialUtils.d.ts +23 -0
  26. package/dist/utils/CredentialUtils.d.ts.map +1 -0
  27. package/dist/utils/CredentialUtils.js +65 -0
  28. package/dist/utils/CredentialUtils.js.map +1 -0
  29. package/dist/utils/dcql.d.ts +5 -0
  30. package/dist/utils/dcql.d.ts.map +1 -0
  31. package/dist/utils/dcql.js +37 -0
  32. package/dist/utils/dcql.js.map +1 -0
  33. package/package.json +26 -23
  34. package/src/agent/DidAuthSiopOpAuthenticator.ts +132 -49
  35. package/src/services/Siopv2MachineService.ts +132 -19
  36. package/src/session/OID4VP.ts +8 -8
  37. package/src/session/OpSession.ts +7 -5
  38. package/src/types/IDidAuthSiopOpAuthenticator.ts +21 -7
  39. package/src/types/machine/index.ts +1 -0
  40. package/src/types/siop-service/index.ts +25 -5
  41. package/src/utils/CredentialUtils.ts +71 -0
  42. package/src/utils/dcql.ts +36 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sphereon/ssi-sdk.siopv2-oid4vp-op-auth",
3
- "version": "0.32.1-next.18+7d08055e",
3
+ "version": "0.32.1-next.287+5f85ee8f",
4
4
  "source": "src/index.ts",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -14,30 +14,31 @@
14
14
  "build:clean": "tsc --build --clean && tsc --build"
15
15
  },
16
16
  "dependencies": {
17
- "@sphereon/did-auth-siop": "0.16.1-next.233",
18
- "@sphereon/did-auth-siop-adapter": "0.16.1-next.233",
19
- "@sphereon/oid4vc-common": "0.16.1-next.233",
17
+ "@sphereon/did-auth-siop": "0.17.0",
18
+ "@sphereon/did-auth-siop-adapter": "0.17.0",
19
+ "@sphereon/oid4vc-common": "0.17.0",
20
20
  "@sphereon/pex": "5.0.0-unstable.28",
21
21
  "@sphereon/pex-models": "^2.3.2",
22
- "@sphereon/ssi-sdk-ext.did-utils": "0.27.0",
23
- "@sphereon/ssi-sdk-ext.identifier-resolution": "0.27.0",
24
- "@sphereon/ssi-sdk-ext.jwt-service": "0.27.0",
25
- "@sphereon/ssi-sdk.contact-manager": "0.32.1-next.18+7d08055e",
26
- "@sphereon/ssi-sdk.core": "0.32.1-next.18+7d08055e",
27
- "@sphereon/ssi-sdk.credential-store": "0.32.1-next.18+7d08055e",
28
- "@sphereon/ssi-sdk.credential-validation": "0.32.1-next.18+7d08055e",
29
- "@sphereon/ssi-sdk.data-store": "0.32.1-next.18+7d08055e",
30
- "@sphereon/ssi-sdk.issuance-branding": "0.32.1-next.18+7d08055e",
31
- "@sphereon/ssi-sdk.pd-manager": "0.32.1-next.18+7d08055e",
32
- "@sphereon/ssi-sdk.presentation-exchange": "0.32.1-next.18+7d08055e",
33
- "@sphereon/ssi-sdk.sd-jwt": "0.32.1-next.18+7d08055e",
34
- "@sphereon/ssi-sdk.siopv2-oid4vp-common": "0.32.1-next.18+7d08055e",
35
- "@sphereon/ssi-sdk.xstate-machine-persistence": "0.32.1-next.18+7d08055e",
36
- "@sphereon/ssi-types": "0.32.1-next.18+7d08055e",
22
+ "@sphereon/ssi-sdk-ext.did-utils": "0.28.0",
23
+ "@sphereon/ssi-sdk-ext.identifier-resolution": "0.28.0",
24
+ "@sphereon/ssi-sdk-ext.jwt-service": "0.28.0",
25
+ "@sphereon/ssi-sdk.contact-manager": "0.32.1-next.287+5f85ee8f",
26
+ "@sphereon/ssi-sdk.core": "0.32.1-next.287+5f85ee8f",
27
+ "@sphereon/ssi-sdk.credential-store": "0.32.1-next.287+5f85ee8f",
28
+ "@sphereon/ssi-sdk.credential-validation": "0.32.1-next.287+5f85ee8f",
29
+ "@sphereon/ssi-sdk.data-store": "0.32.1-next.287+5f85ee8f",
30
+ "@sphereon/ssi-sdk.issuance-branding": "0.32.1-next.287+5f85ee8f",
31
+ "@sphereon/ssi-sdk.pd-manager": "0.32.1-next.287+5f85ee8f",
32
+ "@sphereon/ssi-sdk.presentation-exchange": "0.32.1-next.287+5f85ee8f",
33
+ "@sphereon/ssi-sdk.sd-jwt": "0.32.1-next.287+5f85ee8f",
34
+ "@sphereon/ssi-sdk.siopv2-oid4vp-common": "0.32.1-next.287+5f85ee8f",
35
+ "@sphereon/ssi-sdk.xstate-machine-persistence": "0.32.1-next.287+5f85ee8f",
36
+ "@sphereon/ssi-types": "0.32.1-next.287+5f85ee8f",
37
37
  "@sphereon/wellknown-dids-client": "^0.1.3",
38
38
  "@veramo/core": "4.2.0",
39
39
  "@veramo/credential-w3c": "4.2.0",
40
40
  "cross-fetch": "^3.1.8",
41
+ "dcql": "0.2.19",
41
42
  "did-jwt-vc": "3.1.3",
42
43
  "i18n-js": "^3.9.2",
43
44
  "lodash.memoize": "^4.1.2",
@@ -46,19 +47,21 @@
46
47
  },
47
48
  "devDependencies": {
48
49
  "@sphereon/did-uni-client": "^0.6.3",
49
- "@sphereon/ssi-sdk-ext.did-resolver-jwk": "0.27.0",
50
- "@sphereon/ssi-sdk.agent-config": "0.32.1-next.18+7d08055e",
50
+ "@sphereon/ssi-sdk-ext.did-resolver-jwk": "0.28.0",
51
+ "@sphereon/ssi-sdk.agent-config": "0.32.1-next.287+5f85ee8f",
51
52
  "@types/i18n-js": "^3.8.9",
52
53
  "@types/lodash.memoize": "^4.1.9",
53
54
  "@types/sha.js": "^2.4.4",
54
55
  "@types/uuid": "^9.0.8",
56
+ "@veramo/data-store": "4.2.0",
55
57
  "@veramo/did-provider-key": "4.2.0",
56
58
  "@veramo/did-resolver": "4.2.0",
57
59
  "@veramo/remote-client": "4.2.0",
58
60
  "@veramo/remote-server": "4.2.0",
59
61
  "@veramo/utils": "4.2.0",
60
62
  "did-resolver": "^4.1.0",
61
- "nock": "^13.5.4"
63
+ "nock": "^13.5.4",
64
+ "typeorm": "^0.3.21"
62
65
  },
63
66
  "files": [
64
67
  "dist/**/*",
@@ -88,5 +91,5 @@
88
91
  "Authenticator"
89
92
  ],
90
93
  "nx": {},
91
- "gitHead": "7d08055e7c148eff0a031196ea1007519f6398b9"
94
+ "gitHead": "5f85ee8fef1e0fce3d20d150f187b00c7c70f093"
92
95
  }
@@ -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 { HasherSync, 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,6 +28,10 @@ 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 {
29
36
  IDidAuthSiopOpAuthenticator,
30
37
  IGetSiopSessionArgs,
@@ -32,8 +39,7 @@ import {
32
39
  IRemoveCustomApprovalForSiopArgs,
33
40
  IRemoveSiopSessionArgs,
34
41
  IRequiredContext,
35
- } from '../types/IDidAuthSiopOpAuthenticator'
36
- import { Siopv2Machine as Siopv2MachineId, Siopv2MachineInstanceOpts } from '../types/machine'
42
+ } from '../types'
37
43
 
38
44
  import {
39
45
  AddIdentityArgs,
@@ -46,11 +52,10 @@ import {
46
52
  SendResponseArgs,
47
53
  Siopv2AuthorizationRequestData,
48
54
  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'
55
+ Siopv2Machine as Siopv2MachineId,
56
+ Siopv2MachineInstanceOpts,
57
+ } from '../types'
58
+ import { DcqlCredential, DcqlPresentation, DcqlQuery, DcqlSdJwtVcCredential } from 'dcql'
54
59
 
55
60
  const logger = Loggers.DEFAULT.options(LOGGER_NAMESPACE, {}).get(LOGGER_NAMESPACE)
56
61
 
@@ -89,23 +94,20 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin {
89
94
  private readonly sessions: Map<string, OpSession>
90
95
  private readonly customApprovals: Record<string, (verifiedAuthorizationRequest: VerifiedAuthorizationRequest, sessionId: string) => Promise<void>>
91
96
  private readonly presentationSignCallback?: PresentationSignCallback
92
-
93
97
  private readonly onContactIdentityCreated?: (args: OnContactIdentityCreatedArgs) => Promise<void>
94
98
  private readonly onIdentifierCreated?: (args: OnIdentifierCreatedArgs) => Promise<void>
95
99
  private readonly eventEmitter?: EventEmitter
100
+ private readonly hasher?: HasherSync
101
+
102
+ constructor(options?: DidAuthSiopOpAuthenticatorOptions) {
103
+ const { onContactIdentityCreated, onIdentifierCreated, hasher, customApprovals = {}, presentationSignCallback } = { ...options }
96
104
 
97
- constructor(
98
- presentationSignCallback?: PresentationSignCallback,
99
- customApprovals?: Record<string, (verifiedAuthorizationRequest: VerifiedAuthorizationRequest, sessionId: string) => Promise<void>>,
100
- options?: DidAuthSiopOpAuthenticatorOptions,
101
- ) {
102
- const { onContactIdentityCreated, onIdentifierCreated } = options ?? {}
105
+ this.hasher = hasher
103
106
  this.onContactIdentityCreated = onContactIdentityCreated
104
107
  this.onIdentifierCreated = onIdentifierCreated
105
-
106
- this.sessions = new Map<string, OpSession>()
107
- this.customApprovals = customApprovals || {}
108
108
  this.presentationSignCallback = presentationSignCallback
109
+ this.sessions = new Map<string, OpSession>()
110
+ this.customApprovals = customApprovals
109
111
  }
110
112
 
111
113
  public async onEvent(event: any, context: RequiredContext): Promise<void> {
@@ -185,8 +187,8 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin {
185
187
  return Siopv2Machine.newInstance(siopv2MachineOpts)
186
188
  }
187
189
 
188
- private async siopCreateConfig(args: CreateConfigArgs): Promise<CreateConfigResult> {
189
- const { url } = args
190
+ private async siopCreateConfig<TContext extends CreateConfigArgs>(context: TContext): Promise<CreateConfigResult> {
191
+ const { url } = context
190
192
 
191
193
  if (!url) {
192
194
  return Promise.reject(Error('Missing request uri in context'))
@@ -213,9 +215,14 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin {
213
215
  }
214
216
  const { sessionId, redirectUrl } = didAuthConfig
215
217
 
216
- const session: OpSession = await agent
217
- .siopGetOPSession({ sessionId })
218
- .catch(async () => await agent.siopRegisterOPSession({ requestJwtOrUri: redirectUrl, sessionId, op: { eventEmitter: this.eventEmitter } }))
218
+ const session: OpSession = await agent.siopGetOPSession({ sessionId }).catch(
219
+ async () =>
220
+ await agent.siopRegisterOPSession({
221
+ requestJwtOrUri: redirectUrl,
222
+ sessionId,
223
+ op: { eventEmitter: this.eventEmitter, hasher: this.hasher },
224
+ }),
225
+ )
219
226
 
220
227
  logger.debug(`session: ${JSON.stringify(session.id, null, 2)}`)
221
228
  const verifiedAuthorizationRequest = await session.getAuthorizationRequest()
@@ -244,6 +251,7 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin {
244
251
  verifiedAuthorizationRequest.presentationDefinitions.length > 0)
245
252
  ? verifiedAuthorizationRequest.presentationDefinitions
246
253
  : undefined,
254
+ dcqlQuery: verifiedAuthorizationRequest.dcqlQuery,
247
255
  }
248
256
  }
249
257
 
@@ -332,7 +340,7 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin {
332
340
  }
333
341
 
334
342
  private async siopSendResponse(args: SendResponseArgs, context: RequiredContext): Promise<Siopv2AuthorizationResponseData> {
335
- const { didAuthConfig, authorizationRequestData, selectedCredentials } = args
343
+ const { didAuthConfig, authorizationRequestData, selectedCredentials, isFirstParty } = args
336
344
 
337
345
  if (didAuthConfig === undefined) {
338
346
  return Promise.reject(Error('Missing config in context'))
@@ -342,34 +350,77 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin {
342
350
  return Promise.reject(Error('Missing authorization request data in context'))
343
351
  }
344
352
 
345
- const pex = new PEX()
353
+ const pex = new PEX({ hasher: this.hasher })
346
354
  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')
355
+ const dcqlCredentialsWithCredentials: Map<DcqlCredential, UniqueDigitalCredential> = new Map()
356
+
357
+ if (Array.isArray(authorizationRequestData.presentationDefinitions) && authorizationRequestData?.presentationDefinitions.length > 0) {
358
+ try {
359
+ authorizationRequestData.presentationDefinitions?.forEach((presentationDefinition) => {
360
+ const { areRequiredCredentialsPresent, verifiableCredential: verifiableCredentials } = pex.selectFrom(
361
+ presentationDefinition.definition,
362
+ selectedCredentials.map((udc) => udc.originalVerifiableCredential!),
363
+ )
364
+
365
+ if (areRequiredCredentialsPresent !== Status.ERROR && verifiableCredentials) {
366
+ let uniqueDigitalCredentials: UniqueDigitalCredential[] = []
367
+ uniqueDigitalCredentials = verifiableCredentials.map((vc) => {
368
+ // @ts-ignore FIXME Funke
369
+ const hash = typeof vc === 'string' ? computeEntryHash(vc.split('~'[0])) : computeEntryHash(vc)
370
+ const udc = selectedCredentials.find((udc) => udc.hash == hash || udc.originalVerifiableCredential == vc)
371
+
372
+ if (!udc) {
373
+ throw Error(
374
+ `UniqueDigitalCredential could not be found in store. Either the credential is not present in the store or the hash is not correct.`,
375
+ )
376
+ }
377
+ return udc
378
+ })
379
+ verifiableCredentialsWithDefinition.push({
380
+ definition: presentationDefinition,
381
+ credentials: uniqueDigitalCredentials,
382
+ })
361
383
  }
362
- return udc
363
- })
364
- verifiableCredentialsWithDefinition.push({
365
- definition: presentationDefinition,
366
- credentials: uniqueDigitalCredentials,
367
384
  })
385
+ } catch (e) {
386
+ return Promise.reject(e)
368
387
  }
369
- })
370
388
 
371
- if (verifiableCredentialsWithDefinition.length === 0) {
372
- return Promise.reject(Error('None of the selected credentials match any of the presentation definitions.'))
389
+ if (verifiableCredentialsWithDefinition.length === 0) {
390
+ return Promise.reject(Error('None of the selected credentials match any of the presentation definitions.'))
391
+ }
392
+ } else if (authorizationRequestData.dcqlQuery) {
393
+ //TODO Only SD-JWT and MSO MDOC are supported at the moment
394
+ if (this.hasMDocCredentials(selectedCredentials) || this.hasSdJwtCredentials(selectedCredentials)) {
395
+ try {
396
+ selectedCredentials.forEach((vc) => {
397
+ if (this.isSdJwtCredential(vc)) {
398
+ const payload = (vc.originalVerifiableCredential as SdJwtDecodedVerifiableCredential).decodedPayload
399
+ const result: DcqlSdJwtVcCredential = {
400
+ claims: payload as { [x: string]: Json },
401
+ vct: payload.vct,
402
+ credential_format: 'vc+sd-jwt',
403
+ }
404
+ dcqlCredentialsWithCredentials.set(result, vc)
405
+ //FIXME MDoc namespaces are incompatible: array of strings vs complex object - https://sphereon.atlassian.net/browse/SPRIND-143
406
+ } else {
407
+ throw Error(`Invalid credential format: ${vc.digitalCredential.documentFormat}`)
408
+ }
409
+ })
410
+ } catch (e) {
411
+ return Promise.reject(e)
412
+ }
413
+
414
+ const dcqlPresentationRecord: DcqlPresentation.Output = {}
415
+ const queryResult = DcqlQuery.query(authorizationRequestData.dcqlQuery, Array.from(dcqlCredentialsWithCredentials.keys()))
416
+ for (const [key, value] of Object.entries(queryResult.credential_matches)) {
417
+ if (value.success) {
418
+ dcqlPresentationRecord[key] = this.retrieveEncodedCredential(dcqlCredentialsWithCredentials.get(value.output)!) as
419
+ | string
420
+ | { [x: string]: Json }
421
+ }
422
+ }
423
+ }
373
424
  }
374
425
 
375
426
  const response = await siopSendAuthorizationResponse(
@@ -378,6 +429,8 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin {
378
429
  sessionId: didAuthConfig.sessionId,
379
430
  ...(args.idOpts && { idOpts: args.idOpts }),
380
431
  ...(authorizationRequestData.presentationDefinitions !== undefined && { verifiableCredentialsWithDefinition }),
432
+ isFirstParty,
433
+ hasher: this.hasher,
381
434
  },
382
435
  context,
383
436
  )
@@ -392,11 +445,41 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin {
392
445
 
393
446
  return {
394
447
  body: responseBody,
395
- url: response.url,
396
- queryParams: decodeUriAsJson(response.url),
448
+ url: response?.url,
449
+ queryParams: decodeUriAsJson(response?.url),
397
450
  }
398
451
  }
399
452
 
453
+ private hasMDocCredentials = (credentials: UniqueDigitalCredential[]): boolean => {
454
+ return credentials.some(this.isMDocCredential)
455
+ }
456
+
457
+ private isMDocCredential = (credential: UniqueDigitalCredential) => {
458
+ return (
459
+ credential.digitalCredential.documentFormat === CredentialDocumentFormat.MSO_MDOC &&
460
+ credential.digitalCredential.documentType === DocumentType.VC
461
+ )
462
+ }
463
+
464
+ private hasSdJwtCredentials = (credentials: UniqueDigitalCredential[]): boolean => {
465
+ return credentials.some(this.isSdJwtCredential)
466
+ }
467
+
468
+ private isSdJwtCredential = (credential: UniqueDigitalCredential) => {
469
+ return (
470
+ credential.digitalCredential.documentFormat === CredentialDocumentFormat.SD_JWT && credential.digitalCredential.documentType === DocumentType.VC
471
+ )
472
+ }
473
+
474
+ private retrieveEncodedCredential = (credential: UniqueDigitalCredential) => {
475
+ return credential.originalVerifiableCredential !== undefined &&
476
+ credential.originalVerifiableCredential !== null &&
477
+ (credential?.originalVerifiableCredential as SdJwtDecodedVerifiableCredential)?.compactSdJwtVc !== undefined &&
478
+ (credential?.originalVerifiableCredential as SdJwtDecodedVerifiableCredential)?.compactSdJwtVc !== null
479
+ ? (credential.originalVerifiableCredential as SdJwtDecodedVerifiableCredential).compactSdJwtVc
480
+ : credential.originalVerifiableCredential
481
+ }
482
+
400
483
  private async siopGetSelectableCredentials(args: GetSelectableCredentialsArgs, context: RequiredContext): Promise<SelectableCredentialsMap> {
401
484
  const { authorizationRequestData } = args
402
485
 
@@ -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, HasherSync, Loggers, OriginalVerifiableCredential, PresentationSubmission } from '@sphereon/ssi-types'
8
8
  import { OID4VP, OpSession } from '../session'
9
9
  import {
10
10
  DidAgents,
@@ -19,7 +19,10 @@ import {
19
19
  } from '../types'
20
20
  import { IAgentContext, IDIDManager } from '@veramo/core'
21
21
  import { getOrCreatePrimaryIdentifier, SupportedDidMethodEnum } from '@sphereon/ssi-sdk-ext.did-utils'
22
- import { encodeJoseBlob } from '@sphereon/ssi-sdk.core'
22
+ import { defaultHasher, encodeJoseBlob } from '@sphereon/ssi-sdk.core'
23
+ import { DcqlCredential, DcqlCredentialPresentation, DcqlPresentation, DcqlQuery } 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?: HasherSync
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 = defaultHasher } = 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
@@ -123,11 +129,18 @@ export const siopSendAuthorizationResponse = async (
123
129
  break
124
130
  // TODO other implementations?
125
131
  default:
126
- // Since we are using the kmsKeyRef we will find the KID regardless of the identifier. We set it for later access though
127
- identifier = await session.context.agent.identifierManagedGetByKid({
128
- identifier: digitalCredential.subjectCorrelationId ?? holder ?? digitalCredential.kmsKeyRef,
129
- kmsKeyRef: digitalCredential.kmsKeyRef,
130
- })
132
+ if (digitalCredential.subjectCorrelationId?.startsWith('did:') || holder?.startsWith('did:')) {
133
+ identifier = await session.context.agent.identifierManagedGetByDid({
134
+ identifier: digitalCredential.subjectCorrelationId ?? holder,
135
+ kmsKeyRef: digitalCredential.kmsKeyRef,
136
+ })
137
+ } else {
138
+ // Since we are using the kmsKeyRef we will find the KID regardless of the identifier. We set it for later access though
139
+ identifier = await session.context.agent.identifierManagedGetByKid({
140
+ identifier: digitalCredential.subjectCorrelationId ?? holder ?? digitalCredential.kmsKeyRef,
141
+ kmsKeyRef: digitalCredential.kmsKeyRef,
142
+ })
143
+ }
131
144
  }
132
145
  }
133
146
 
@@ -153,16 +166,116 @@ export const siopSendAuthorizationResponse = async (
153
166
 
154
167
  idOpts = presentationsAndDefs[0].idOpts
155
168
  presentationSubmission = presentationsAndDefs[0].presentationSubmission
169
+
170
+ logger.log(`Definitions and locations:`, JSON.stringify(presentationsAndDefs?.[0]?.verifiablePresentations, null, 2))
171
+ logger.log(`Presentation Submission:`, JSON.stringify(presentationSubmission, null, 2))
172
+ const mergedVerifiablePresentations = presentationsAndDefs?.flatMap((pd) => pd.verifiablePresentations) || []
173
+ return await session.sendAuthorizationResponse({
174
+ ...(presentationsAndDefs && { verifiablePresentations: mergedVerifiablePresentations }),
175
+ ...(presentationSubmission && { presentationSubmission }),
176
+ // todo: Change issuer value in case we do not use identifier. Use key.meta.jwkThumbprint then
177
+ responseSignerOpts: idOpts!,
178
+ isFirstParty,
179
+ })
180
+ } else if (request.dcqlQuery) {
181
+ if (args.verifiableCredentialsWithDefinition !== undefined && args.verifiableCredentialsWithDefinition !== null) {
182
+ const vcs = args.verifiableCredentialsWithDefinition.flatMap((vcd) => vcd.credentials)
183
+ const domain =
184
+ ((await request.authorizationRequest.getMergedProperty('client_id')) as string) ??
185
+ request.issuer ??
186
+ (request.versions.includes(SupportedVersion.JWT_VC_PRESENTATION_PROFILE_v1)
187
+ ? 'https://self-issued.me/v2/openid-vc'
188
+ : 'https://self-issued.me/v2')
189
+ logger.debug(`NONCE: ${session.nonce}, domain: ${domain}`)
190
+
191
+ const firstUniqueDC = vcs[0]
192
+ if (typeof firstUniqueDC !== 'object' || !('digitalCredential' in firstUniqueDC)) {
193
+ return Promise.reject(Error('SiopMachine only supports UniqueDigitalCredentials for now'))
194
+ }
195
+
196
+ let identifier: ManagedIdentifierOptsOrResult
197
+ const digitalCredential = firstUniqueDC.digitalCredential
198
+ const firstVC = firstUniqueDC.uniformVerifiableCredential
199
+ const holder = CredentialMapper.isSdJwtDecodedCredential(firstVC)
200
+ ? firstVC.decodedPayload.cnf?.jwk
201
+ ? //TODO SDK-19: convert the JWK to hex and search for the appropriate key and associated DID
202
+ //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
203
+ `did:jwk:${encodeJoseBlob(firstVC.decodedPayload.cnf?.jwk)}#0`
204
+ : firstVC.decodedPayload.sub
205
+ : Array.isArray(firstVC.credentialSubject)
206
+ ? firstVC.credentialSubject[0].id
207
+ : firstVC.credentialSubject.id
208
+ if (!digitalCredential.kmsKeyRef) {
209
+ // In case the store does not have the kmsKeyRef lets search for the holder
210
+
211
+ if (!holder) {
212
+ return Promise.reject(`No holder found and no kmsKeyRef in DB. Cannot determine identifier to use`)
213
+ }
214
+ try {
215
+ identifier = await session.context.agent.identifierManagedGet({ identifier: holder })
216
+ } catch (e) {
217
+ logger.debug(`Holder DID not found: ${holder}`)
218
+ throw e
219
+ }
220
+ } else if (isOID4VCIssuerIdentifier(digitalCredential.kmsKeyRef)) {
221
+ identifier = await session.context.agent.identifierManagedGetByOID4VCIssuer({
222
+ identifier: firstUniqueDC.digitalCredential.kmsKeyRef,
223
+ })
224
+ } else {
225
+ switch (digitalCredential.subjectCorrelationType) {
226
+ case 'DID':
227
+ identifier = await session.context.agent.identifierManagedGetByDid({
228
+ identifier: digitalCredential.subjectCorrelationId ?? holder,
229
+ kmsKeyRef: digitalCredential.kmsKeyRef,
230
+ })
231
+ break
232
+ // TODO other implementations?
233
+ default:
234
+ // Since we are using the kmsKeyRef we will find the KID regardless of the identifier. We set it for later access though
235
+ identifier = await session.context.agent.identifierManagedGetByKid({
236
+ identifier: digitalCredential.subjectCorrelationId ?? holder ?? digitalCredential.kmsKeyRef,
237
+ kmsKeyRef: digitalCredential.kmsKeyRef,
238
+ })
239
+ }
240
+ }
241
+ console.log(`Identifier`, identifier)
242
+
243
+ const dcqlRepresentations: DcqlCredential[] = []
244
+ vcs.forEach((vc: UniqueDigitalCredential | OriginalVerifiableCredential) => {
245
+ const rep = convertToDcqlCredentials(vc, args.hasher)
246
+ if (rep) {
247
+ dcqlRepresentations.push(rep)
248
+ }
249
+ })
250
+
251
+ const queryResult = DcqlQuery.query(request.dcqlQuery, dcqlRepresentations)
252
+ const presentation: Record<string, DcqlCredentialPresentation> = {}
253
+
254
+ for (const [key, value] of Object.entries(queryResult.credential_matches)) {
255
+ const allMatches = Array.isArray(value) ? value : [value]
256
+ allMatches.forEach((match) => {
257
+ if (match.success) {
258
+ const originalCredential = getOriginalVerifiableCredential(vcs[match.input_credential_index])
259
+ if (!originalCredential) {
260
+ throw new Error(`Index ${match.input_credential_index} out of range in credentials array`)
261
+ }
262
+ presentation[key] =
263
+ (originalCredential as any)['compactSdJwtVc'] !== undefined ? (originalCredential as any).compactSdJwtVc : originalCredential
264
+ }
265
+ })
266
+ }
267
+
268
+ const response = session.sendAuthorizationResponse({
269
+ responseSignerOpts: identifier,
270
+ ...{ dcqlQuery: { dcqlPresentation: DcqlPresentation.parse(presentation) } },
271
+ })
272
+
273
+ logger.debug(`Response: `, response)
274
+
275
+ return response
276
+ }
156
277
  }
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
- })
278
+ throw Error('Presentation Definition or DCQL is required')
166
279
  }
167
280
 
168
281
  function buildPartialPD(
@@ -2,15 +2,15 @@ import { PresentationDefinitionWithLocation, PresentationExchange } from '@spher
2
2
  import { SelectResults, Status, SubmissionRequirementMatch } from '@sphereon/pex'
3
3
  import { Format } from '@sphereon/pex-models'
4
4
  import {
5
- isOID4VCIssuerIdentifier,
6
5
  isManagedIdentifierDidResult,
6
+ isOID4VCIssuerIdentifier,
7
7
  ManagedIdentifierOptsOrResult,
8
8
  ManagedIdentifierResult,
9
9
  } from '@sphereon/ssi-sdk-ext.identifier-resolution'
10
- import { ProofOptions } from '@sphereon/ssi-sdk.core'
10
+ import { defaultHasher, ProofOptions } from '@sphereon/ssi-sdk.core'
11
11
  import { UniqueDigitalCredential, verifiableCredentialForRoleFilter } from '@sphereon/ssi-sdk.credential-store'
12
12
  import { CredentialRole, FindDigitalCredentialArgs } from '@sphereon/ssi-sdk.data-store'
13
- import { CompactJWT, Hasher, IProof, OriginalVerifiableCredential } from '@sphereon/ssi-types'
13
+ import { CompactJWT, HasherSync, IProof, OriginalVerifiableCredential } from '@sphereon/ssi-types'
14
14
  import {
15
15
  DEFAULT_JWT_PROOF_TYPE,
16
16
  IGetPresentationExchangeArgs,
@@ -24,17 +24,17 @@ import { OpSession } from './OpSession'
24
24
  export class OID4VP {
25
25
  private readonly session: OpSession
26
26
  private readonly allIdentifiers: string[]
27
- private readonly hasher?: Hasher
27
+ private readonly hasher?: HasherSync
28
28
 
29
29
  private constructor(args: IOID4VPArgs) {
30
- const { session, allIdentifiers, hasher } = args
30
+ const { session, allIdentifiers, hasher = defaultHasher } = args
31
31
 
32
32
  this.session = session
33
33
  this.allIdentifiers = allIdentifiers ?? []
34
34
  this.hasher = hasher
35
35
  }
36
36
 
37
- public static async init(session: OpSession, allIdentifiers: string[], hasher?: Hasher): Promise<OID4VP> {
37
+ public static async init(session: OpSession, allIdentifiers: string[], hasher?: HasherSync): Promise<OID4VP> {
38
38
  return new OID4VP({ session, allIdentifiers: allIdentifiers ?? (await session.getSupportedDIDs()), hasher })
39
39
  }
40
40
 
@@ -68,7 +68,7 @@ export class OID4VP {
68
68
  skipDidResolution?: boolean
69
69
  holderDID?: string
70
70
  subjectIsHolder?: boolean
71
- hasher?: Hasher
71
+ hasher?: HasherSync
72
72
  applyFilter?: boolean
73
73
  },
74
74
  ): Promise<VerifiablePresentationWithDefinition[]> {
@@ -88,7 +88,7 @@ export class OID4VP {
88
88
  holder?: string
89
89
  subjectIsHolder?: boolean
90
90
  applyFilter?: boolean
91
- hasher?: Hasher
91
+ hasher?: HasherSync
92
92
  },
93
93
  ): Promise<VerifiablePresentationWithDefinition> {
94
94
  const { subjectIsHolder, holder, forceNoCredentialsInVP = false } = { ...opts }