@sphereon/ssi-sdk.siopv2-oid4vp-op-auth 0.32.1-next.20 → 0.32.1-next.291
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 +8 -3
- package/dist/agent/DidAuthSiopOpAuthenticator.d.ts.map +1 -1
- package/dist/agent/DidAuthSiopOpAuthenticator.js +105 -35
- package/dist/agent/DidAuthSiopOpAuthenticator.js.map +1 -1
- package/dist/services/Siopv2MachineService.d.ts +5 -0
- package/dist/services/Siopv2MachineService.d.ts.map +1 -1
- package/dist/services/Siopv2MachineService.js +112 -14
- 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 +3 -3
- package/dist/session/OpSession.js.map +1 -1
- package/dist/types/IDidAuthSiopOpAuthenticator.d.ts +12 -7
- package/dist/types/IDidAuthSiopOpAuthenticator.d.ts.map +1 -1
- package/dist/types/IDidAuthSiopOpAuthenticator.js.map +1 -1
- package/dist/types/machine/index.d.ts +1 -0
- package/dist/types/machine/index.d.ts.map +1 -1
- package/dist/types/machine/index.js.map +1 -1
- package/dist/types/siop-service/index.d.ts +22 -5
- 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 +132 -49
- package/src/services/Siopv2MachineService.ts +132 -19
- package/src/session/OID4VP.ts +8 -8
- package/src/session/OpSession.ts +7 -5
- package/src/types/IDidAuthSiopOpAuthenticator.ts +21 -7
- package/src/types/machine/index.ts +1 -0
- package/src/types/siop-service/index.ts +25 -5
- package/src/utils/CredentialUtils.ts +71 -0
- 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.
|
|
3
|
+
"version": "0.32.1-next.291+bef8bb12",
|
|
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.
|
|
18
|
-
"@sphereon/did-auth-siop-adapter": "0.
|
|
19
|
-
"@sphereon/oid4vc-common": "0.
|
|
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.
|
|
23
|
-
"@sphereon/ssi-sdk-ext.identifier-resolution": "0.
|
|
24
|
-
"@sphereon/ssi-sdk-ext.jwt-service": "0.
|
|
25
|
-
"@sphereon/ssi-sdk.contact-manager": "0.32.1-next.
|
|
26
|
-
"@sphereon/ssi-sdk.core": "0.32.1-next.
|
|
27
|
-
"@sphereon/ssi-sdk.credential-store": "0.32.1-next.
|
|
28
|
-
"@sphereon/ssi-sdk.credential-validation": "0.32.1-next.
|
|
29
|
-
"@sphereon/ssi-sdk.data-store": "0.32.1-next.
|
|
30
|
-
"@sphereon/ssi-sdk.issuance-branding": "0.32.1-next.
|
|
31
|
-
"@sphereon/ssi-sdk.pd-manager": "0.32.1-next.
|
|
32
|
-
"@sphereon/ssi-sdk.presentation-exchange": "0.32.1-next.
|
|
33
|
-
"@sphereon/ssi-sdk.sd-jwt": "0.32.1-next.
|
|
34
|
-
"@sphereon/ssi-sdk.siopv2-oid4vp-common": "0.32.1-next.
|
|
35
|
-
"@sphereon/ssi-sdk.xstate-machine-persistence": "0.32.1-next.
|
|
36
|
-
"@sphereon/ssi-types": "0.32.1-next.
|
|
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.291+bef8bb12",
|
|
26
|
+
"@sphereon/ssi-sdk.core": "0.32.1-next.291+bef8bb12",
|
|
27
|
+
"@sphereon/ssi-sdk.credential-store": "0.32.1-next.291+bef8bb12",
|
|
28
|
+
"@sphereon/ssi-sdk.credential-validation": "0.32.1-next.291+bef8bb12",
|
|
29
|
+
"@sphereon/ssi-sdk.data-store": "0.32.1-next.291+bef8bb12",
|
|
30
|
+
"@sphereon/ssi-sdk.issuance-branding": "0.32.1-next.291+bef8bb12",
|
|
31
|
+
"@sphereon/ssi-sdk.pd-manager": "0.32.1-next.291+bef8bb12",
|
|
32
|
+
"@sphereon/ssi-sdk.presentation-exchange": "0.32.1-next.291+bef8bb12",
|
|
33
|
+
"@sphereon/ssi-sdk.sd-jwt": "0.32.1-next.291+bef8bb12",
|
|
34
|
+
"@sphereon/ssi-sdk.siopv2-oid4vp-common": "0.32.1-next.291+bef8bb12",
|
|
35
|
+
"@sphereon/ssi-sdk.xstate-machine-persistence": "0.32.1-next.291+bef8bb12",
|
|
36
|
+
"@sphereon/ssi-types": "0.32.1-next.291+bef8bb12",
|
|
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.
|
|
50
|
-
"@sphereon/ssi-sdk.agent-config": "0.32.1-next.
|
|
50
|
+
"@sphereon/ssi-sdk-ext.did-resolver-jwk": "0.28.0",
|
|
51
|
+
"@sphereon/ssi-sdk.agent-config": "0.32.1-next.291+bef8bb12",
|
|
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": "
|
|
94
|
+
"gitHead": "bef8bb123f197ecb66da7edff9d2cb55492af7ef"
|
|
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
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
import {
|
|
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
|
-
|
|
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(
|
|
189
|
-
const { url } =
|
|
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
|
-
|
|
218
|
-
|
|
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
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
-
|
|
372
|
-
|
|
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
|
|
396
|
-
queryParams: decodeUriAsJson(response
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
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(
|
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 }
|