@sphereon/ssi-sdk.siopv2-oid4vp-op-auth 0.32.1-next.54 → 0.33.0
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.
- package/dist/agent/DidAuthSiopOpAuthenticator.d.ts +6 -1
- package/dist/agent/DidAuthSiopOpAuthenticator.d.ts.map +1 -1
- package/dist/agent/DidAuthSiopOpAuthenticator.js +91 -22
- package/dist/agent/DidAuthSiopOpAuthenticator.js.map +1 -1
- package/dist/services/Siopv2MachineService.d.ts +4 -2
- package/dist/services/Siopv2MachineService.d.ts.map +1 -1
- package/dist/services/Siopv2MachineService.js +111 -13
- package/dist/services/Siopv2MachineService.js.map +1 -1
- package/dist/session/OID4VP.d.ts +4 -4
- package/dist/session/OID4VP.d.ts.map +1 -1
- package/dist/session/OID4VP.js +2 -1
- package/dist/session/OID4VP.js.map +1 -1
- package/dist/session/OpSession.d.ts.map +1 -1
- package/dist/session/OpSession.js +2 -2
- package/dist/session/OpSession.js.map +1 -1
- package/dist/types/IDidAuthSiopOpAuthenticator.d.ts +11 -7
- package/dist/types/IDidAuthSiopOpAuthenticator.d.ts.map +1 -1
- package/dist/types/IDidAuthSiopOpAuthenticator.js.map +1 -1
- package/dist/types/siop-service/index.d.ts +4 -2
- package/dist/types/siop-service/index.d.ts.map +1 -1
- package/dist/types/siop-service/index.js.map +1 -1
- package/dist/utils/CredentialUtils.d.ts +23 -0
- package/dist/utils/CredentialUtils.d.ts.map +1 -0
- package/dist/utils/CredentialUtils.js +65 -0
- package/dist/utils/CredentialUtils.js.map +1 -0
- package/dist/utils/dcql.d.ts +5 -0
- package/dist/utils/dcql.d.ts.map +1 -0
- package/dist/utils/dcql.js +37 -0
- package/dist/utils/dcql.js.map +1 -0
- package/package.json +26 -23
- package/src/agent/DidAuthSiopOpAuthenticator.ts +122 -42
- package/src/services/Siopv2MachineService.ts +130 -20
- package/src/session/OID4VP.ts +8 -8
- package/src/session/OpSession.ts +5 -4
- package/src/types/IDidAuthSiopOpAuthenticator.ts +20 -7
- package/src/types/siop-service/index.ts +9 -6
- package/src/utils/CredentialUtils.ts +71 -0
- 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 {
|
|
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,
|
|
@@ -30,16 +33,19 @@ import { computeEntryHash } from '@veramo/utils'
|
|
|
30
33
|
import { UniqueDigitalCredential } from '@sphereon/ssi-sdk.credential-store'
|
|
31
34
|
import { EventEmitter } from 'events'
|
|
32
35
|
import {
|
|
33
|
-
AddIdentityArgs,
|
|
34
|
-
CreateConfigArgs,
|
|
35
|
-
CreateConfigResult,
|
|
36
|
-
GetSiopRequestArgs,
|
|
37
36
|
IDidAuthSiopOpAuthenticator,
|
|
38
37
|
IGetSiopSessionArgs,
|
|
39
38
|
IRegisterCustomApprovalForSiopArgs,
|
|
40
39
|
IRemoveCustomApprovalForSiopArgs,
|
|
41
40
|
IRemoveSiopSessionArgs,
|
|
42
41
|
IRequiredContext,
|
|
42
|
+
} from '../types'
|
|
43
|
+
|
|
44
|
+
import {
|
|
45
|
+
AddIdentityArgs,
|
|
46
|
+
CreateConfigArgs,
|
|
47
|
+
CreateConfigResult,
|
|
48
|
+
GetSiopRequestArgs,
|
|
43
49
|
OnContactIdentityCreatedArgs,
|
|
44
50
|
OnIdentifierCreatedArgs,
|
|
45
51
|
RetrieveContactArgs,
|
|
@@ -47,8 +53,9 @@ import {
|
|
|
47
53
|
Siopv2AuthorizationRequestData,
|
|
48
54
|
Siopv2HolderEvent,
|
|
49
55
|
Siopv2Machine as Siopv2MachineId,
|
|
50
|
-
Siopv2MachineInstanceOpts
|
|
56
|
+
Siopv2MachineInstanceOpts,
|
|
51
57
|
} from '../types'
|
|
58
|
+
import { DcqlCredential, DcqlPresentation, DcqlQuery, DcqlSdJwtVcCredential } from 'dcql'
|
|
52
59
|
|
|
53
60
|
const logger = Loggers.DEFAULT.options(LOGGER_NAMESPACE, {}).get(LOGGER_NAMESPACE)
|
|
54
61
|
|
|
@@ -84,22 +91,16 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin {
|
|
|
84
91
|
siopGetSelectableCredentials: this.siopGetSelectableCredentials.bind(this),
|
|
85
92
|
}
|
|
86
93
|
|
|
87
|
-
private readonly hasher?: Hasher
|
|
88
94
|
private readonly sessions: Map<string, OpSession>
|
|
89
95
|
private readonly customApprovals: Record<string, (verifiedAuthorizationRequest: VerifiedAuthorizationRequest, sessionId: string) => Promise<void>>
|
|
90
96
|
private readonly presentationSignCallback?: PresentationSignCallback
|
|
91
97
|
private readonly onContactIdentityCreated?: (args: OnContactIdentityCreatedArgs) => Promise<void>
|
|
92
98
|
private readonly onIdentifierCreated?: (args: OnIdentifierCreatedArgs) => Promise<void>
|
|
93
99
|
private readonly eventEmitter?: EventEmitter
|
|
100
|
+
private readonly hasher?: HasherSync
|
|
94
101
|
|
|
95
102
|
constructor(options?: DidAuthSiopOpAuthenticatorOptions) {
|
|
96
|
-
const {
|
|
97
|
-
onContactIdentityCreated,
|
|
98
|
-
onIdentifierCreated,
|
|
99
|
-
hasher,
|
|
100
|
-
customApprovals = {},
|
|
101
|
-
presentationSignCallback
|
|
102
|
-
} = { ...options }
|
|
103
|
+
const { onContactIdentityCreated, onIdentifierCreated, hasher, customApprovals = {}, presentationSignCallback } = { ...options }
|
|
103
104
|
|
|
104
105
|
this.hasher = hasher
|
|
105
106
|
this.onContactIdentityCreated = onContactIdentityCreated
|
|
@@ -214,9 +215,14 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin {
|
|
|
214
215
|
}
|
|
215
216
|
const { sessionId, redirectUrl } = didAuthConfig
|
|
216
217
|
|
|
217
|
-
const session: OpSession = await agent
|
|
218
|
-
|
|
219
|
-
|
|
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
|
+
)
|
|
220
226
|
|
|
221
227
|
logger.debug(`session: ${JSON.stringify(session.id, null, 2)}`)
|
|
222
228
|
const verifiedAuthorizationRequest = await session.getAuthorizationRequest()
|
|
@@ -245,6 +251,7 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin {
|
|
|
245
251
|
verifiedAuthorizationRequest.presentationDefinitions.length > 0)
|
|
246
252
|
? verifiedAuthorizationRequest.presentationDefinitions
|
|
247
253
|
: undefined,
|
|
254
|
+
dcqlQuery: verifiedAuthorizationRequest.dcqlQuery,
|
|
248
255
|
}
|
|
249
256
|
}
|
|
250
257
|
|
|
@@ -345,32 +352,75 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin {
|
|
|
345
352
|
|
|
346
353
|
const pex = new PEX({ hasher: this.hasher })
|
|
347
354
|
const verifiableCredentialsWithDefinition: Array<VerifiableCredentialsWithDefinition> = []
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
+
})
|
|
362
383
|
}
|
|
363
|
-
return udc
|
|
364
|
-
})
|
|
365
|
-
verifiableCredentialsWithDefinition.push({
|
|
366
|
-
definition: presentationDefinition,
|
|
367
|
-
credentials: uniqueDigitalCredentials,
|
|
368
384
|
})
|
|
385
|
+
} catch (e) {
|
|
386
|
+
return Promise.reject(e)
|
|
369
387
|
}
|
|
370
|
-
})
|
|
371
388
|
|
|
372
|
-
|
|
373
|
-
|
|
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
|
+
}
|
|
374
424
|
}
|
|
375
425
|
|
|
376
426
|
const response = await siopSendAuthorizationResponse(
|
|
@@ -380,7 +430,7 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin {
|
|
|
380
430
|
...(args.idOpts && { idOpts: args.idOpts }),
|
|
381
431
|
...(authorizationRequestData.presentationDefinitions !== undefined && { verifiableCredentialsWithDefinition }),
|
|
382
432
|
isFirstParty,
|
|
383
|
-
hasher: this.hasher
|
|
433
|
+
hasher: this.hasher,
|
|
384
434
|
},
|
|
385
435
|
context,
|
|
386
436
|
)
|
|
@@ -395,11 +445,41 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin {
|
|
|
395
445
|
|
|
396
446
|
return {
|
|
397
447
|
body: responseBody,
|
|
398
|
-
url: response
|
|
399
|
-
queryParams: decodeUriAsJson(response
|
|
448
|
+
url: response?.url,
|
|
449
|
+
queryParams: decodeUriAsJson(response?.url),
|
|
400
450
|
}
|
|
401
451
|
}
|
|
402
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
|
+
|
|
403
483
|
private async siopGetSelectableCredentials(args: GetSelectableCredentialsArgs, context: RequiredContext): Promise<SelectableCredentialsMap> {
|
|
404
484
|
const { authorizationRequestData } = args
|
|
405
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,
|
|
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?:
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
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(
|
package/src/session/OID4VP.ts
CHANGED
|
@@ -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,
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
91
|
+
hasher?: HasherSync
|
|
92
92
|
},
|
|
93
93
|
): Promise<VerifiablePresentationWithDefinition> {
|
|
94
94
|
const { subjectIsHolder, holder, forceNoCredentialsInVP = false } = { ...opts }
|
package/src/session/OpSession.ts
CHANGED
|
@@ -20,7 +20,7 @@ import { encodeBase64url } from '@sphereon/ssi-sdk.core'
|
|
|
20
20
|
import {
|
|
21
21
|
CompactSdJwtVc,
|
|
22
22
|
CredentialMapper,
|
|
23
|
-
|
|
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?:
|
|
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 {
|
|
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
|
-
|
|
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?:
|
|
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?:
|
|
188
|
+
hasher?: HasherSync
|
|
187
189
|
}
|
|
188
190
|
|
|
189
191
|
export interface IOID4VPArgs {
|
|
190
192
|
session: OpSession
|
|
191
193
|
allIdentifiers?: string[]
|
|
192
|
-
hasher?:
|
|
194
|
+
hasher?: HasherSync
|
|
193
195
|
}
|
|
194
196
|
|
|
195
197
|
export interface IGetPresentationExchangeArgs {
|
|
196
198
|
verifiableCredentials: OriginalVerifiableCredential[]
|
|
197
199
|
allIdentifiers?: string[]
|
|
198
|
-
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,
|
|
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 {
|
|
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?:
|
|
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'
|
|
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>>
|