@sphereon/ssi-sdk.siopv2-oid4vp-op-auth 0.32.1-feature.SPRIND.89.53 → 0.32.1-feature.SSISDK.5.credential.offer.uri.204

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 (33) 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 +91 -22
  4. package/dist/agent/DidAuthSiopOpAuthenticator.js.map +1 -1
  5. package/dist/services/Siopv2MachineService.d.ts +2 -0
  6. package/dist/services/Siopv2MachineService.d.ts.map +1 -1
  7. package/dist/services/Siopv2MachineService.js +97 -7
  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 +2 -2
  11. package/dist/session/OpSession.js.map +1 -1
  12. package/dist/types/IDidAuthSiopOpAuthenticator.d.ts +5 -1
  13. package/dist/types/IDidAuthSiopOpAuthenticator.d.ts.map +1 -1
  14. package/dist/types/IDidAuthSiopOpAuthenticator.js.map +1 -1
  15. package/dist/types/siop-service/index.d.ts +2 -0
  16. package/dist/types/siop-service/index.d.ts.map +1 -1
  17. package/dist/types/siop-service/index.js.map +1 -1
  18. package/dist/utils/CredentialUtils.d.ts +23 -0
  19. package/dist/utils/CredentialUtils.d.ts.map +1 -0
  20. package/dist/utils/CredentialUtils.js +65 -0
  21. package/dist/utils/CredentialUtils.js.map +1 -0
  22. package/dist/utils/dcql.d.ts +5 -0
  23. package/dist/utils/dcql.d.ts.map +1 -0
  24. package/dist/utils/dcql.js +37 -0
  25. package/dist/utils/dcql.js.map +1 -0
  26. package/package.json +22 -19
  27. package/src/agent/DidAuthSiopOpAuthenticator.ts +113 -38
  28. package/src/services/Siopv2MachineService.ts +115 -12
  29. package/src/session/OpSession.ts +3 -2
  30. package/src/types/IDidAuthSiopOpAuthenticator.ts +13 -0
  31. package/src/types/siop-service/index.ts +2 -0
  32. package/src/utils/CredentialUtils.ts +71 -0
  33. 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, 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
 
@@ -50,6 +53,7 @@ export const siopSendAuthorizationResponse = async (
50
53
  idOpts?: ManagedIdentifierOptsOrResult
51
54
  isFirstParty?: boolean
52
55
  hasher?: Hasher
56
+ dcqlQuery?: DcqlQuery
53
57
  },
54
58
  context: RequiredContext,
55
59
  ) => {
@@ -155,17 +159,116 @@ export const siopSendAuthorizationResponse = async (
155
159
 
156
160
  idOpts = presentationsAndDefs[0].idOpts
157
161
  presentationSubmission = presentationsAndDefs[0].presentationSubmission
158
- }
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!,
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!,
167
171
  isFirstParty,
168
- })
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
+ }
270
+ }
271
+ throw Error('Presentation Definition or DCQL is required')
169
272
  }
170
273
 
171
274
  function buildPartialPD(
@@ -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)
@@ -1,4 +1,5 @@
1
1
  import {
2
+ DcqlResponseOpts,
2
3
  PresentationDefinitionWithLocation,
3
4
  PresentationSignCallback,
4
5
  ResponseMode,
@@ -122,6 +123,7 @@ export interface IOpsSendSiopAuthorizationResponseArgs {
122
123
  // verifiedAuthorizationRequest: VerifiedAuthorizationRequest
123
124
  presentationSubmission?: PresentationSubmission
124
125
  verifiablePresentations?: W3CVerifiablePresentation[]
126
+ dcqlResponse?: DcqlResponseOpts
125
127
  hasher?: Hasher
126
128
  isFirstParty?: boolean
127
129
  }
@@ -198,4 +200,15 @@ export interface IGetPresentationExchangeArgs {
198
200
  hasher?: Hasher
199
201
  }
200
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
+
201
214
  export const DEFAULT_JWT_PROOF_TYPE = 'JwtProof2020'
@@ -11,6 +11,7 @@ import { IIssuanceBranding } from '@sphereon/ssi-sdk.issuance-branding'
11
11
  import { IAgentContext, IDIDManager, IIdentifier, IResolver } from '@veramo/core'
12
12
  import { IDidAuthSiopOpAuthenticator } from '../IDidAuthSiopOpAuthenticator'
13
13
  import { Siopv2MachineContext, Siopv2MachineInterpreter, Siopv2MachineState } from '../machine'
14
+ import { DcqlQuery } from 'dcql'
14
15
  import { Hasher } from '@sphereon/ssi-types'
15
16
 
16
17
  export type DidAuthSiopOpAuthenticatorOptions = {
@@ -68,6 +69,7 @@ export type Siopv2AuthorizationRequestData = {
68
69
  uri?: URL
69
70
  clientId?: string
70
71
  presentationDefinitions?: PresentationDefinitionWithLocation[]
72
+ dcqlQuery?: DcqlQuery
71
73
  }
72
74
 
73
75
  export type SelectableCredentialsMap = Map<string, Array<SelectableCredential>>
@@ -0,0 +1,71 @@
1
+ import { CredentialMapper, Hasher, 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?: Hasher): 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, Hasher, OriginalVerifiableCredential } from '@sphereon/ssi-types'
4
+ import { isUniqueDigitalCredential } from './CredentialUtils'
5
+
6
+ export function convertToDcqlCredentials(credential: UniqueDigitalCredential | OriginalVerifiableCredential, hasher?: Hasher): 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
+ }