@sphereon/ssi-sdk.siopv2-oid4vp-op-auth 0.32.1-next.54 → 0.33.1-feature.vcdm2.4

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 (63) hide show
  1. package/dist/agent/DidAuthSiopOpAuthenticator.d.ts +6 -1
  2. package/dist/agent/DidAuthSiopOpAuthenticator.d.ts.map +1 -1
  3. package/dist/agent/DidAuthSiopOpAuthenticator.js +325 -277
  4. package/dist/agent/DidAuthSiopOpAuthenticator.js.map +1 -1
  5. package/dist/index.js +7 -27
  6. package/dist/index.js.map +1 -1
  7. package/dist/link-handler/index.js +35 -47
  8. package/dist/link-handler/index.js.map +1 -1
  9. package/dist/localization/Localization.js +38 -43
  10. package/dist/localization/Localization.js.map +1 -1
  11. package/dist/machine/CallbackStateListener.js +9 -22
  12. package/dist/machine/CallbackStateListener.js.map +1 -1
  13. package/dist/machine/Siopv2Machine.js +129 -131
  14. package/dist/machine/Siopv2Machine.js.map +1 -1
  15. package/dist/services/IdentifierService.js +11 -24
  16. package/dist/services/IdentifierService.js.map +1 -1
  17. package/dist/services/Siopv2MachineService.d.ts +4 -2
  18. package/dist/services/Siopv2MachineService.d.ts.map +1 -1
  19. package/dist/services/Siopv2MachineService.js +193 -98
  20. package/dist/services/Siopv2MachineService.js.map +1 -1
  21. package/dist/session/OID4VP.d.ts +4 -4
  22. package/dist/session/OID4VP.d.ts.map +1 -1
  23. package/dist/session/OID4VP.js +184 -194
  24. package/dist/session/OID4VP.js.map +1 -1
  25. package/dist/session/OpSession.d.ts.map +1 -1
  26. package/dist/session/OpSession.js +252 -288
  27. package/dist/session/OpSession.js.map +1 -1
  28. package/dist/session/functions.js +95 -111
  29. package/dist/session/functions.js.map +1 -1
  30. package/dist/session/index.js +3 -19
  31. package/dist/session/index.js.map +1 -1
  32. package/dist/types/IDidAuthSiopOpAuthenticator.d.ts +11 -7
  33. package/dist/types/IDidAuthSiopOpAuthenticator.d.ts.map +1 -1
  34. package/dist/types/IDidAuthSiopOpAuthenticator.js +4 -7
  35. package/dist/types/IDidAuthSiopOpAuthenticator.js.map +1 -1
  36. package/dist/types/error/index.js +1 -2
  37. package/dist/types/identifier/index.js +1 -4
  38. package/dist/types/identifier/index.js.map +1 -1
  39. package/dist/types/index.js +5 -21
  40. package/dist/types/index.js.map +1 -1
  41. package/dist/types/machine/index.js +10 -13
  42. package/dist/types/machine/index.js.map +1 -1
  43. package/dist/types/siop-service/index.d.ts +4 -2
  44. package/dist/types/siop-service/index.d.ts.map +1 -1
  45. package/dist/types/siop-service/index.js +4 -7
  46. package/dist/types/siop-service/index.js.map +1 -1
  47. package/dist/utils/CredentialUtils.d.ts +23 -0
  48. package/dist/utils/CredentialUtils.d.ts.map +1 -0
  49. package/dist/utils/CredentialUtils.js +55 -0
  50. package/dist/utils/CredentialUtils.js.map +1 -0
  51. package/dist/utils/dcql.d.ts +5 -0
  52. package/dist/utils/dcql.d.ts.map +1 -0
  53. package/dist/utils/dcql.js +34 -0
  54. package/dist/utils/dcql.js.map +1 -0
  55. package/package.json +26 -23
  56. package/src/agent/DidAuthSiopOpAuthenticator.ts +122 -42
  57. package/src/services/Siopv2MachineService.ts +130 -20
  58. package/src/session/OID4VP.ts +8 -8
  59. package/src/session/OpSession.ts +5 -4
  60. package/src/types/IDidAuthSiopOpAuthenticator.ts +20 -7
  61. package/src/types/siop-service/index.ts +9 -6
  62. package/src/utils/CredentialUtils.ts +71 -0
  63. package/src/utils/dcql.ts +36 -0
@@ -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, Hasher, 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
 
@@ -49,13 +52,14 @@ export const siopSendAuthorizationResponse = async (
49
52
  verifiableCredentialsWithDefinition?: VerifiableCredentialsWithDefinition[]
50
53
  idOpts?: ManagedIdentifierOptsOrResult
51
54
  isFirstParty?: boolean
52
- hasher?: Hasher
55
+ hasher?: HasherSync
56
+ dcqlQuery?: DcqlQuery
53
57
  },
54
58
  context: RequiredContext,
55
59
  ) => {
56
60
  const { agent } = context
57
61
  const agentContext = { ...context, agent: context.agent as DidAgents }
58
- let { idOpts, isFirstParty, hasher } = args
62
+ let { idOpts, isFirstParty, hasher = defaultHasher } = args
59
63
 
60
64
  if (connectionType !== ConnectionType.SIOPv2_OpenID4VP) {
61
65
  return Promise.reject(Error(`No supported authentication provider for type: ${connectionType}`))
@@ -125,11 +129,18 @@ export const siopSendAuthorizationResponse = async (
125
129
  break
126
130
  // TODO other implementations?
127
131
  default:
128
- // Since we are using the kmsKeyRef we will find the KID regardless of the identifier. We set it for later access though
129
- identifier = await session.context.agent.identifierManagedGetByKid({
130
- identifier: digitalCredential.subjectCorrelationId ?? holder ?? digitalCredential.kmsKeyRef,
131
- kmsKeyRef: digitalCredential.kmsKeyRef,
132
- })
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
+ }
133
144
  }
134
145
  }
135
146
 
@@ -155,17 +166,116 @@ export const siopSendAuthorizationResponse = async (
155
166
 
156
167
  idOpts = presentationsAndDefs[0].idOpts
157
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
+ }
158
277
  }
159
- logger.log(`Definitions and locations:`, JSON.stringify(presentationsAndDefs?.[0]?.verifiablePresentations, null, 2))
160
- logger.log(`Presentation Submission:`, JSON.stringify(presentationSubmission, null, 2))
161
- const mergedVerifiablePresentations = presentationsAndDefs?.flatMap((pd) => pd.verifiablePresentations) || []
162
- return await session.sendAuthorizationResponse({
163
- ...(presentationsAndDefs && { verifiablePresentations: mergedVerifiablePresentations }),
164
- ...(presentationSubmission && { presentationSubmission }),
165
- // todo: Change issuer value in case we do not use identifier. Use key.meta.jwkThumbprint then
166
- responseSignerOpts: idOpts!,
167
- isFirstParty,
168
- })
278
+ throw Error('Presentation Definition or DCQL is required')
169
279
  }
170
280
 
171
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 }
@@ -20,7 +20,7 @@ import { encodeBase64url } from '@sphereon/ssi-sdk.core'
20
20
  import {
21
21
  CompactSdJwtVc,
22
22
  CredentialMapper,
23
- Hasher,
23
+ HasherSync,
24
24
  OriginalVerifiableCredential,
25
25
  parseDid,
26
26
  PresentationSubmission,
@@ -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,
@@ -367,6 +367,7 @@ export class OpSession {
367
367
  presentationSubmission: args.presentationSubmission,
368
368
  } as PresentationExchangeResponseOpts,
369
369
  }),
370
+ dcqlQuery: args.dcqlResponse,
370
371
  }
371
372
 
372
373
  const authResponse = await op.createAuthorizationResponse(request, responseOpts)
@@ -379,7 +380,7 @@ export class OpSession {
379
380
  }
380
381
  }
381
382
 
382
- private countVCsInAllVPs(verifiablePresentations: W3CVerifiablePresentation[], hasher?: Hasher) {
383
+ private countVCsInAllVPs(verifiablePresentations: W3CVerifiablePresentation[], hasher?: HasherSync) {
383
384
  return verifiablePresentations.reduce((sum, vp) => {
384
385
  if (CredentialMapper.isMsoMdocDecodedPresentation(vp) || CredentialMapper.isMsoMdocOid4VPEncoded(vp)) {
385
386
  return sum + 1
@@ -1,4 +1,5 @@
1
1
  import {
2
+ DcqlResponseOpts,
2
3
  PresentationDefinitionWithLocation,
3
4
  PresentationSignCallback,
4
5
  ResponseMode,
@@ -18,7 +19,7 @@ import { ICredentialStore, UniqueDigitalCredential } from '@sphereon/ssi-sdk.cre
18
19
  import { Party } from '@sphereon/ssi-sdk.data-store'
19
20
  import { IPDManager } from '@sphereon/ssi-sdk.pd-manager'
20
21
  import { ISDJwtPlugin } from '@sphereon/ssi-sdk.sd-jwt'
21
- import { Hasher, OriginalVerifiableCredential, PresentationSubmission, W3CVerifiablePresentation } from '@sphereon/ssi-types'
22
+ import { HasherSync, OriginalVerifiableCredential, PresentationSubmission, W3CVerifiablePresentation } from '@sphereon/ssi-types'
22
23
  import { VerifyCallback } from '@sphereon/wellknown-dids-client'
23
24
  import {
24
25
  IAgentContext,
@@ -122,7 +123,8 @@ export interface IOpsSendSiopAuthorizationResponseArgs {
122
123
  // verifiedAuthorizationRequest: VerifiedAuthorizationRequest
123
124
  presentationSubmission?: PresentationSubmission
124
125
  verifiablePresentations?: W3CVerifiablePresentation[]
125
- hasher?: Hasher
126
+ dcqlResponse?: DcqlResponseOpts
127
+ hasher?: HasherSync
126
128
  isFirstParty?: boolean
127
129
  }
128
130
 
@@ -160,7 +162,7 @@ export interface IOPOptions {
160
162
  presentationSignCallback?: PresentationSignCallback
161
163
 
162
164
  resolveOpts?: ResolveOpts
163
- hasher?: Hasher
165
+ hasher?: HasherSync
164
166
  }
165
167
 
166
168
  /*
@@ -183,19 +185,30 @@ export interface VerifiablePresentationWithDefinition extends VerifiablePresenta
183
185
 
184
186
  export interface IOpSessionGetOID4VPArgs {
185
187
  allIdentifiers?: string[]
186
- hasher?: Hasher
188
+ hasher?: HasherSync
187
189
  }
188
190
 
189
191
  export interface IOID4VPArgs {
190
192
  session: OpSession
191
193
  allIdentifiers?: string[]
192
- hasher?: Hasher
194
+ hasher?: HasherSync
193
195
  }
194
196
 
195
197
  export interface IGetPresentationExchangeArgs {
196
198
  verifiableCredentials: OriginalVerifiableCredential[]
197
199
  allIdentifiers?: string[]
198
- hasher?: Hasher
199
- }
200
+ hasher?: HasherSync
201
+ }
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[]
200
213
 
201
214
  export const DEFAULT_JWT_PROOF_TYPE = 'JwtProof2020'
@@ -1,7 +1,8 @@
1
1
  import {
2
2
  PresentationDefinitionWithLocation,
3
3
  PresentationSignCallback,
4
- RPRegistrationMetadataPayload, VerifiedAuthorizationRequest
4
+ RPRegistrationMetadataPayload,
5
+ VerifiedAuthorizationRequest,
5
6
  } from '@sphereon/did-auth-siop'
6
7
  import { IIdentifierResolution, ManagedIdentifierOptsOrResult } from '@sphereon/ssi-sdk-ext.identifier-resolution'
7
8
  import { IContactManager } from '@sphereon/ssi-sdk.contact-manager'
@@ -11,14 +12,15 @@ import { IIssuanceBranding } from '@sphereon/ssi-sdk.issuance-branding'
11
12
  import { IAgentContext, IDIDManager, IIdentifier, IResolver } from '@veramo/core'
12
13
  import { IDidAuthSiopOpAuthenticator } from '../IDidAuthSiopOpAuthenticator'
13
14
  import { Siopv2MachineContext, Siopv2MachineInterpreter, Siopv2MachineState } from '../machine'
14
- import { Hasher } from '@sphereon/ssi-types'
15
+ import { DcqlQuery } from 'dcql'
16
+ import { HasherSync } from '@sphereon/ssi-types'
15
17
 
16
18
  export type DidAuthSiopOpAuthenticatorOptions = {
17
19
  presentationSignCallback?: PresentationSignCallback
18
20
  customApprovals?: Record<string, (verifiedAuthorizationRequest: VerifiedAuthorizationRequest, sessionId: string) => Promise<void>>
19
21
  onContactIdentityCreated?: (args: OnContactIdentityCreatedArgs) => Promise<void>
20
22
  onIdentifierCreated?: (args: OnIdentifierCreatedArgs) => Promise<void>
21
- hasher?: Hasher
23
+ hasher?: HasherSync
22
24
  }
23
25
 
24
26
  export type GetMachineArgs = {
@@ -29,14 +31,14 @@ export type GetMachineArgs = {
29
31
 
30
32
  export type CreateConfigArgs = { url: string }
31
33
  export type CreateConfigResult = Omit<DidAuthConfig, 'stateId' | 'idOpts'>
32
- export type GetSiopRequestArgs = { didAuthConfig?: Omit<DidAuthConfig, 'identifier'>, url: string }
34
+ export type GetSiopRequestArgs = { didAuthConfig?: Omit<DidAuthConfig, 'identifier'>; url: string }
33
35
  // FIXME it would be nicer if these function are not tied to a certain machine so that we can start calling them for anywhere
34
36
  export type RetrieveContactArgs = Pick<Siopv2MachineContext, 'url' | 'authorizationRequestData'>
35
37
  // FIXME it would be nicer if these function are not tied to a certain machine so that we can start calling them for anywhere
36
38
  export type AddIdentityArgs = Pick<Siopv2MachineContext, 'contact' | 'authorizationRequestData'>
37
39
  export type SendResponseArgs = {
38
- didAuthConfig?: Omit<DidAuthConfig, 'identifier'>,
39
- authorizationRequestData?: Siopv2AuthorizationRequestData,
40
+ didAuthConfig?: Omit<DidAuthConfig, 'identifier'>
41
+ authorizationRequestData?: Siopv2AuthorizationRequestData
40
42
  selectedCredentials: Array<UniqueDigitalCredential>
41
43
  idOpts?: ManagedIdentifierOptsOrResult
42
44
  isFirstParty?: boolean
@@ -68,6 +70,7 @@ export type Siopv2AuthorizationRequestData = {
68
70
  uri?: URL
69
71
  clientId?: string
70
72
  presentationDefinitions?: PresentationDefinitionWithLocation[]
73
+ dcqlQuery?: DcqlQuery
71
74
  }
72
75
 
73
76
  export type SelectableCredentialsMap = Map<string, Array<SelectableCredential>>
@@ -0,0 +1,71 @@
1
+ import { CredentialMapper, HasherSync, ICredential, IVerifiableCredential, OriginalVerifiableCredential } from '@sphereon/ssi-types'
2
+ import { VerifiableCredential } from '@veramo/core'
3
+ import { UniqueDigitalCredential } from '@sphereon/ssi-sdk.credential-store'
4
+
5
+ /**
6
+ * Return the type(s) of a VC minus the VerifiableCredential type which should always be present
7
+ * @param credential The input credential
8
+ */
9
+ export const getCredentialTypeAsString = (credential: ICredential | VerifiableCredential): string => {
10
+ if (!credential.type) {
11
+ return 'Verifiable Credential'
12
+ } else if (typeof credential.type === 'string') {
13
+ return credential.type
14
+ }
15
+ return credential.type.filter((type: string): boolean => type !== 'VerifiableCredential').join(', ')
16
+ }
17
+
18
+ /**
19
+ * Returns a Unique Verifiable Credential (with hash) as stored in Veramo, based upon matching the id of the input VC or the proof value of the input VC
20
+ * @param uniqueVCs The Unique VCs to search in
21
+ * @param searchVC The VC to search for in the unique VCs array
22
+ */
23
+ export const getMatchingUniqueDigitalCredential = (
24
+ uniqueVCs: UniqueDigitalCredential[],
25
+ searchVC: OriginalVerifiableCredential,
26
+ ): UniqueDigitalCredential | undefined => {
27
+ // Since an ID is optional in a VC according to VCDM, and we really need the matches, we have a fallback match on something which is guaranteed to be unique for any VC (the proof(s))
28
+ return uniqueVCs.find(
29
+ (uniqueVC: UniqueDigitalCredential) =>
30
+ (typeof searchVC !== 'string' &&
31
+ (uniqueVC.id === (<IVerifiableCredential>searchVC).id ||
32
+ (uniqueVC.originalVerifiableCredential as VerifiableCredential).proof === (<IVerifiableCredential>searchVC).proof)) ||
33
+ (typeof searchVC === 'string' && (uniqueVC.uniformVerifiableCredential as VerifiableCredential)?.proof?.jwt === searchVC) ||
34
+ // We are ignoring the signature of the sd-jwt as PEX signs the vc again and it will not match anymore with the jwt in the proof of the stored jsonld vc
35
+ (typeof searchVC === 'string' &&
36
+ CredentialMapper.isSdJwtEncoded(searchVC) &&
37
+ uniqueVC.uniformVerifiableCredential?.proof &&
38
+ 'jwt' in uniqueVC.uniformVerifiableCredential.proof &&
39
+ uniqueVC.uniformVerifiableCredential.proof.jwt?.split('.')?.slice(0, 2)?.join('.') === searchVC.split('.')?.slice(0, 2)?.join('.')),
40
+ )
41
+ }
42
+
43
+ type InputCredential = UniqueDigitalCredential | VerifiableCredential | ICredential | OriginalVerifiableCredential
44
+
45
+ /**
46
+ * Get an original verifiable credential. Maps to wrapped Verifiable Credential first, to get an original JWT as Veramo stores these with a special proof value
47
+ * @param credential The input VC
48
+ */
49
+
50
+ export const getOriginalVerifiableCredential = (credential: InputCredential): OriginalVerifiableCredential => {
51
+ if (isUniqueDigitalCredential(credential)) {
52
+ if (!credential.originalVerifiableCredential) {
53
+ throw new Error('originalVerifiableCredential is not defined in UniqueDigitalCredential')
54
+ }
55
+ return getCredentialFromProofOrWrapped(credential.originalVerifiableCredential)
56
+ }
57
+
58
+ return getCredentialFromProofOrWrapped(credential)
59
+ }
60
+
61
+ const getCredentialFromProofOrWrapped = (cred: any, hasher?: HasherSync): OriginalVerifiableCredential => {
62
+ if (typeof cred === 'object' && 'proof' in cred && 'jwt' in cred.proof && CredentialMapper.isSdJwtEncoded(cred.proof.jwt)) {
63
+ return cred.proof.jwt
64
+ }
65
+
66
+ return CredentialMapper.toWrappedVerifiableCredential(cred as OriginalVerifiableCredential, { hasher }).original
67
+ }
68
+
69
+ export const isUniqueDigitalCredential = (credential: InputCredential): credential is UniqueDigitalCredential => {
70
+ return (credential as UniqueDigitalCredential).digitalCredential !== undefined
71
+ }
@@ -0,0 +1,36 @@
1
+ import { UniqueDigitalCredential } from '@sphereon/ssi-sdk.credential-store'
2
+ import { DcqlCredential, DcqlSdJwtVcCredential, DcqlW3cVcCredential } from 'dcql'
3
+ import { CredentialMapper, HasherSync, OriginalVerifiableCredential } from '@sphereon/ssi-types'
4
+ import { isUniqueDigitalCredential } from './CredentialUtils'
5
+
6
+ export function convertToDcqlCredentials(credential: UniqueDigitalCredential | OriginalVerifiableCredential, hasher?: HasherSync): DcqlCredential {
7
+ let payload
8
+ if (isUniqueDigitalCredential(credential)) {
9
+ if (!credential.originalVerifiableCredential) {
10
+ throw new Error('originalVerifiableCredential is not defined in UniqueDigitalCredential')
11
+ }
12
+ payload = CredentialMapper.decodeVerifiableCredential(credential.originalVerifiableCredential, hasher)
13
+ } else {
14
+ payload = CredentialMapper.decodeVerifiableCredential(credential as OriginalVerifiableCredential, hasher)
15
+ }
16
+
17
+ if (!payload) {
18
+ throw new Error('No payload found')
19
+ }
20
+
21
+ if ('decodedPayload' in payload && payload.decodedPayload) {
22
+ payload = payload.decodedPayload
23
+ }
24
+
25
+ if ('vct' in payload!) {
26
+ return { vct: payload.vct, claims: payload, credential_format: 'vc+sd-jwt' } satisfies DcqlSdJwtVcCredential // TODO dc+sd-jwt support?
27
+ } else if ('docType' in payload! && 'namespaces' in payload) {
28
+ // mdoc
29
+ return { docType: payload.docType, namespaces: payload.namespaces, claims: payload }
30
+ } else {
31
+ return {
32
+ claims: payload,
33
+ credential_format: 'jwt_vc_json', // TODO jwt_vc_json-ld support
34
+ } as DcqlW3cVcCredential
35
+ }
36
+ }