@sphereon/ssi-sdk.siopv2-oid4vp-op-auth 0.32.1-fix.15 → 0.32.1-next.113
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 +99 -9
- package/dist/services/Siopv2MachineService.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 +6 -1
- 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 +22 -19
- package/src/agent/DidAuthSiopOpAuthenticator.ts +133 -55
- package/src/services/Siopv2MachineService.ts +119 -13
- package/src/session/OpSession.ts +5 -3
- package/src/types/IDidAuthSiopOpAuthenticator.ts +14 -0
- package/src/types/machine/index.ts +1 -0
- package/src/types/siop-service/index.ts +24 -5
- 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 { Loggers } from '@sphereon/ssi-types'
|
|
13
|
+
import { Hasher, 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,32 +28,31 @@ 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 {
|
|
36
|
+
AddIdentityArgs,
|
|
37
|
+
CreateConfigArgs,
|
|
38
|
+
CreateConfigResult,
|
|
39
|
+
GetSiopRequestArgs,
|
|
29
40
|
IDidAuthSiopOpAuthenticator,
|
|
30
41
|
IGetSiopSessionArgs,
|
|
31
42
|
IRegisterCustomApprovalForSiopArgs,
|
|
32
43
|
IRemoveCustomApprovalForSiopArgs,
|
|
33
44
|
IRemoveSiopSessionArgs,
|
|
34
45
|
IRequiredContext,
|
|
35
|
-
} from '../types/IDidAuthSiopOpAuthenticator'
|
|
36
|
-
import { Siopv2Machine as Siopv2MachineId, Siopv2MachineInstanceOpts } from '../types/machine'
|
|
37
|
-
|
|
38
|
-
import {
|
|
39
|
-
AddIdentityArgs,
|
|
40
|
-
CreateConfigArgs,
|
|
41
|
-
CreateConfigResult,
|
|
42
|
-
GetSiopRequestArgs,
|
|
43
46
|
OnContactIdentityCreatedArgs,
|
|
44
47
|
OnIdentifierCreatedArgs,
|
|
45
48
|
RetrieveContactArgs,
|
|
46
49
|
SendResponseArgs,
|
|
47
50
|
Siopv2AuthorizationRequestData,
|
|
48
51
|
Siopv2HolderEvent,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
import {
|
|
53
|
-
import { EventEmitter } from 'events'
|
|
52
|
+
Siopv2Machine as Siopv2MachineId,
|
|
53
|
+
Siopv2MachineInstanceOpts,
|
|
54
|
+
} from '../types'
|
|
55
|
+
import { DcqlCredential, DcqlPresentation, DcqlQuery, DcqlSdJwtVcCredential } from 'dcql'
|
|
54
56
|
|
|
55
57
|
const logger = Loggers.DEFAULT.options(LOGGER_NAMESPACE, {}).get(LOGGER_NAMESPACE)
|
|
56
58
|
|
|
@@ -89,23 +91,20 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin {
|
|
|
89
91
|
private readonly sessions: Map<string, OpSession>
|
|
90
92
|
private readonly customApprovals: Record<string, (verifiedAuthorizationRequest: VerifiedAuthorizationRequest, sessionId: string) => Promise<void>>
|
|
91
93
|
private readonly presentationSignCallback?: PresentationSignCallback
|
|
92
|
-
|
|
93
94
|
private readonly onContactIdentityCreated?: (args: OnContactIdentityCreatedArgs) => Promise<void>
|
|
94
95
|
private readonly onIdentifierCreated?: (args: OnIdentifierCreatedArgs) => Promise<void>
|
|
95
96
|
private readonly eventEmitter?: EventEmitter
|
|
97
|
+
private readonly hasher?: Hasher
|
|
96
98
|
|
|
97
|
-
constructor(
|
|
98
|
-
presentationSignCallback
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
) {
|
|
102
|
-
const { onContactIdentityCreated, onIdentifierCreated } = options ?? {}
|
|
99
|
+
constructor(options?: DidAuthSiopOpAuthenticatorOptions) {
|
|
100
|
+
const { onContactIdentityCreated, onIdentifierCreated, hasher, customApprovals = {}, presentationSignCallback } = { ...options }
|
|
101
|
+
|
|
102
|
+
this.hasher = hasher
|
|
103
103
|
this.onContactIdentityCreated = onContactIdentityCreated
|
|
104
104
|
this.onIdentifierCreated = onIdentifierCreated
|
|
105
|
-
|
|
106
|
-
this.sessions = new Map<string, OpSession>()
|
|
107
|
-
this.customApprovals = customApprovals || {}
|
|
108
105
|
this.presentationSignCallback = presentationSignCallback
|
|
106
|
+
this.sessions = new Map<string, OpSession>()
|
|
107
|
+
this.customApprovals = customApprovals
|
|
109
108
|
}
|
|
110
109
|
|
|
111
110
|
public async onEvent(event: any, context: RequiredContext): Promise<void> {
|
|
@@ -185,8 +184,8 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin {
|
|
|
185
184
|
return Siopv2Machine.newInstance(siopv2MachineOpts)
|
|
186
185
|
}
|
|
187
186
|
|
|
188
|
-
private async siopCreateConfig(
|
|
189
|
-
const { url } =
|
|
187
|
+
private async siopCreateConfig<TContext extends CreateConfigArgs>(context: TContext): Promise<CreateConfigResult> {
|
|
188
|
+
const { url } = context
|
|
190
189
|
|
|
191
190
|
if (!url) {
|
|
192
191
|
return Promise.reject(Error('Missing request uri in context'))
|
|
@@ -213,9 +212,14 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin {
|
|
|
213
212
|
}
|
|
214
213
|
const { sessionId, redirectUrl } = didAuthConfig
|
|
215
214
|
|
|
216
|
-
const session: OpSession = await agent
|
|
217
|
-
|
|
218
|
-
|
|
215
|
+
const session: OpSession = await agent.siopGetOPSession({ sessionId }).catch(
|
|
216
|
+
async () =>
|
|
217
|
+
await agent.siopRegisterOPSession({
|
|
218
|
+
requestJwtOrUri: redirectUrl,
|
|
219
|
+
sessionId,
|
|
220
|
+
op: { eventEmitter: this.eventEmitter, hasher: this.hasher },
|
|
221
|
+
}),
|
|
222
|
+
)
|
|
219
223
|
|
|
220
224
|
logger.debug(`session: ${JSON.stringify(session.id, null, 2)}`)
|
|
221
225
|
const verifiedAuthorizationRequest = await session.getAuthorizationRequest()
|
|
@@ -244,6 +248,7 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin {
|
|
|
244
248
|
verifiedAuthorizationRequest.presentationDefinitions.length > 0)
|
|
245
249
|
? verifiedAuthorizationRequest.presentationDefinitions
|
|
246
250
|
: undefined,
|
|
251
|
+
dcqlQuery: verifiedAuthorizationRequest.dcqlQuery,
|
|
247
252
|
}
|
|
248
253
|
}
|
|
249
254
|
|
|
@@ -332,7 +337,7 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin {
|
|
|
332
337
|
}
|
|
333
338
|
|
|
334
339
|
private async siopSendResponse(args: SendResponseArgs, context: RequiredContext): Promise<Siopv2AuthorizationResponseData> {
|
|
335
|
-
const { didAuthConfig, authorizationRequestData, selectedCredentials } = args
|
|
340
|
+
const { didAuthConfig, authorizationRequestData, selectedCredentials, isFirstParty } = args
|
|
336
341
|
|
|
337
342
|
if (didAuthConfig === undefined) {
|
|
338
343
|
return Promise.reject(Error('Missing config in context'))
|
|
@@ -342,34 +347,75 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin {
|
|
|
342
347
|
return Promise.reject(Error('Missing authorization request data in context'))
|
|
343
348
|
}
|
|
344
349
|
|
|
345
|
-
const pex = new PEX()
|
|
350
|
+
const pex = new PEX({ hasher: this.hasher })
|
|
346
351
|
const verifiableCredentialsWithDefinition: Array<VerifiableCredentialsWithDefinition> = []
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
352
|
+
const dcqlCredentialsWithCredentials: Map<DcqlCredential, UniqueDigitalCredential> = new Map()
|
|
353
|
+
|
|
354
|
+
if (Array.isArray(authorizationRequestData.presentationDefinitions) && authorizationRequestData?.presentationDefinitions.length > 0) {
|
|
355
|
+
try {
|
|
356
|
+
authorizationRequestData.presentationDefinitions?.forEach((presentationDefinition) => {
|
|
357
|
+
const { areRequiredCredentialsPresent, verifiableCredential: verifiableCredentials } = pex.selectFrom(
|
|
358
|
+
presentationDefinition.definition,
|
|
359
|
+
selectedCredentials.map((udc) => udc.originalVerifiableCredential!),
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
if (areRequiredCredentialsPresent !== Status.ERROR && verifiableCredentials) {
|
|
363
|
+
let uniqueDigitalCredentials: UniqueDigitalCredential[] = []
|
|
364
|
+
uniqueDigitalCredentials = verifiableCredentials.map((vc) => {
|
|
365
|
+
// @ts-ignore FIXME Funke
|
|
366
|
+
const hash = computeEntryHash(vc)
|
|
367
|
+
const udc = selectedCredentials.find((udc) => udc.hash == hash)
|
|
368
|
+
|
|
369
|
+
if (!udc) {
|
|
370
|
+
throw Error('UniqueDigitalCredential could not be found')
|
|
371
|
+
}
|
|
372
|
+
return udc
|
|
373
|
+
})
|
|
374
|
+
verifiableCredentialsWithDefinition.push({
|
|
375
|
+
definition: presentationDefinition,
|
|
376
|
+
credentials: uniqueDigitalCredentials,
|
|
377
|
+
})
|
|
361
378
|
}
|
|
362
|
-
return udc
|
|
363
|
-
})
|
|
364
|
-
verifiableCredentialsWithDefinition.push({
|
|
365
|
-
definition: presentationDefinition,
|
|
366
|
-
credentials: uniqueDigitalCredentials,
|
|
367
379
|
})
|
|
380
|
+
} catch (e) {
|
|
381
|
+
return Promise.reject(e)
|
|
368
382
|
}
|
|
369
|
-
})
|
|
370
383
|
|
|
371
|
-
|
|
372
|
-
|
|
384
|
+
if (verifiableCredentialsWithDefinition.length === 0) {
|
|
385
|
+
return Promise.reject(Error('None of the selected credentials match any of the presentation definitions.'))
|
|
386
|
+
}
|
|
387
|
+
} else if (authorizationRequestData.dcqlQuery) {
|
|
388
|
+
//TODO Only SD-JWT and MSO MDOC are supported at the moment
|
|
389
|
+
if (this.hasMDocCredentials(selectedCredentials) || this.hasSdJwtCredentials(selectedCredentials)) {
|
|
390
|
+
try {
|
|
391
|
+
selectedCredentials.forEach((vc) => {
|
|
392
|
+
if (this.isSdJwtCredential(vc)) {
|
|
393
|
+
const payload = (vc.originalVerifiableCredential as SdJwtDecodedVerifiableCredential).decodedPayload
|
|
394
|
+
const result: DcqlSdJwtVcCredential = {
|
|
395
|
+
claims: payload as { [x: string]: Json },
|
|
396
|
+
vct: payload.vct,
|
|
397
|
+
credential_format: 'vc+sd-jwt',
|
|
398
|
+
}
|
|
399
|
+
dcqlCredentialsWithCredentials.set(result, vc)
|
|
400
|
+
//FIXME MDoc namespaces are incompatible: array of strings vs complex object - https://sphereon.atlassian.net/browse/SPRIND-143
|
|
401
|
+
} else {
|
|
402
|
+
throw Error(`Invalid credential format: ${vc.digitalCredential.documentFormat}`)
|
|
403
|
+
}
|
|
404
|
+
})
|
|
405
|
+
} catch (e) {
|
|
406
|
+
return Promise.reject(e)
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const dcqlPresentationRecord: DcqlPresentation.Output = {}
|
|
410
|
+
const queryResult = DcqlQuery.query(authorizationRequestData.dcqlQuery, Array.from(dcqlCredentialsWithCredentials.keys()))
|
|
411
|
+
for (const [key, value] of Object.entries(queryResult.credential_matches)) {
|
|
412
|
+
if (value.success) {
|
|
413
|
+
dcqlPresentationRecord[key] = this.retrieveEncodedCredential(dcqlCredentialsWithCredentials.get(value.output)!) as
|
|
414
|
+
| string
|
|
415
|
+
| { [x: string]: Json }
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
373
419
|
}
|
|
374
420
|
|
|
375
421
|
const response = await siopSendAuthorizationResponse(
|
|
@@ -378,6 +424,8 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin {
|
|
|
378
424
|
sessionId: didAuthConfig.sessionId,
|
|
379
425
|
...(args.idOpts && { idOpts: args.idOpts }),
|
|
380
426
|
...(authorizationRequestData.presentationDefinitions !== undefined && { verifiableCredentialsWithDefinition }),
|
|
427
|
+
isFirstParty,
|
|
428
|
+
hasher: this.hasher,
|
|
381
429
|
},
|
|
382
430
|
context,
|
|
383
431
|
)
|
|
@@ -392,11 +440,41 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin {
|
|
|
392
440
|
|
|
393
441
|
return {
|
|
394
442
|
body: responseBody,
|
|
395
|
-
url: response
|
|
396
|
-
queryParams: decodeUriAsJson(response
|
|
443
|
+
url: response?.url,
|
|
444
|
+
queryParams: decodeUriAsJson(response?.url),
|
|
397
445
|
}
|
|
398
446
|
}
|
|
399
447
|
|
|
448
|
+
private hasMDocCredentials = (credentials: UniqueDigitalCredential[]): boolean => {
|
|
449
|
+
return credentials.some(this.isMDocCredential)
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
private isMDocCredential = (credential: UniqueDigitalCredential) => {
|
|
453
|
+
return (
|
|
454
|
+
credential.digitalCredential.documentFormat === CredentialDocumentFormat.MSO_MDOC &&
|
|
455
|
+
credential.digitalCredential.documentType === DocumentType.VC
|
|
456
|
+
)
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
private hasSdJwtCredentials = (credentials: UniqueDigitalCredential[]): boolean => {
|
|
460
|
+
return credentials.some(this.isSdJwtCredential)
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
private isSdJwtCredential = (credential: UniqueDigitalCredential) => {
|
|
464
|
+
return (
|
|
465
|
+
credential.digitalCredential.documentFormat === CredentialDocumentFormat.SD_JWT && credential.digitalCredential.documentType === DocumentType.VC
|
|
466
|
+
)
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
private retrieveEncodedCredential = (credential: UniqueDigitalCredential) => {
|
|
470
|
+
return credential.originalVerifiableCredential !== undefined &&
|
|
471
|
+
credential.originalVerifiableCredential !== null &&
|
|
472
|
+
(credential?.originalVerifiableCredential as SdJwtDecodedVerifiableCredential)?.compactSdJwtVc !== undefined &&
|
|
473
|
+
(credential?.originalVerifiableCredential as SdJwtDecodedVerifiableCredential)?.compactSdJwtVc !== null
|
|
474
|
+
? (credential.originalVerifiableCredential as SdJwtDecodedVerifiableCredential).compactSdJwtVc
|
|
475
|
+
: credential.originalVerifiableCredential
|
|
476
|
+
}
|
|
477
|
+
|
|
400
478
|
private async siopGetSelectableCredentials(args: GetSelectableCredentialsArgs, context: RequiredContext): Promise<SelectableCredentialsMap> {
|
|
401
479
|
const { authorizationRequestData } = args
|
|
402
480
|
|
|
@@ -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, 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
|
|
|
@@ -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?: Hasher
|
|
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 } = 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
|
|
@@ -153,16 +159,116 @@ export const siopSendAuthorizationResponse = async (
|
|
|
153
159
|
|
|
154
160
|
idOpts = presentationsAndDefs[0].idOpts
|
|
155
161
|
presentationSubmission = presentationsAndDefs[0].presentationSubmission
|
|
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!,
|
|
171
|
+
isFirstParty,
|
|
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
|
+
}
|
|
156
270
|
}
|
|
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
|
-
})
|
|
271
|
+
throw Error('Presentation Definition or DCQL is required')
|
|
166
272
|
}
|
|
167
273
|
|
|
168
274
|
function buildPartialPD(
|
package/src/session/OpSession.ts
CHANGED
|
@@ -240,7 +240,7 @@ export class OpSession {
|
|
|
240
240
|
private createPresentationVerificationCallback(context: IRequiredContext) {
|
|
241
241
|
async function presentationVerificationCallback(
|
|
242
242
|
args: W3CVerifiablePresentation | CompactSdJwtVc,
|
|
243
|
-
presentationSubmission
|
|
243
|
+
presentationSubmission?: PresentationSubmission,
|
|
244
244
|
): Promise<PresentationVerificationResult> {
|
|
245
245
|
let result: IVerifyResult
|
|
246
246
|
if (CredentialMapper.isSdJwtEncoded(args)) {
|
|
@@ -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,
|
|
@@ -360,12 +360,14 @@ export class OpSession {
|
|
|
360
360
|
const responseOpts = {
|
|
361
361
|
verification,
|
|
362
362
|
issuer,
|
|
363
|
+
...(args.isFirstParty && { isFirstParty: args.isFirstParty }),
|
|
363
364
|
...(args.verifiablePresentations && {
|
|
364
365
|
presentationExchange: {
|
|
365
366
|
verifiablePresentations,
|
|
366
367
|
presentationSubmission: args.presentationSubmission,
|
|
367
368
|
} as PresentationExchangeResponseOpts,
|
|
368
369
|
}),
|
|
370
|
+
dcqlQuery: args.dcqlResponse,
|
|
369
371
|
}
|
|
370
372
|
|
|
371
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,7 +123,9 @@ export interface IOpsSendSiopAuthorizationResponseArgs {
|
|
|
122
123
|
// verifiedAuthorizationRequest: VerifiedAuthorizationRequest
|
|
123
124
|
presentationSubmission?: PresentationSubmission
|
|
124
125
|
verifiablePresentations?: W3CVerifiablePresentation[]
|
|
126
|
+
dcqlResponse?: DcqlResponseOpts
|
|
125
127
|
hasher?: Hasher
|
|
128
|
+
isFirstParty?: boolean
|
|
126
129
|
}
|
|
127
130
|
|
|
128
131
|
export enum events {
|
|
@@ -197,4 +200,15 @@ export interface IGetPresentationExchangeArgs {
|
|
|
197
200
|
hasher?: Hasher
|
|
198
201
|
}
|
|
199
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
|
+
|
|
200
214
|
export const DEFAULT_JWT_PROOF_TYPE = 'JwtProof2020'
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
PresentationDefinitionWithLocation,
|
|
3
|
+
PresentationSignCallback,
|
|
4
|
+
RPRegistrationMetadataPayload, VerifiedAuthorizationRequest
|
|
5
|
+
} from '@sphereon/did-auth-siop'
|
|
2
6
|
import { IIdentifierResolution, ManagedIdentifierOptsOrResult } from '@sphereon/ssi-sdk-ext.identifier-resolution'
|
|
3
7
|
import { IContactManager } from '@sphereon/ssi-sdk.contact-manager'
|
|
4
8
|
import { ICredentialStore, UniqueDigitalCredential } from '@sphereon/ssi-sdk.credential-store'
|
|
@@ -7,10 +11,15 @@ import { IIssuanceBranding } from '@sphereon/ssi-sdk.issuance-branding'
|
|
|
7
11
|
import { IAgentContext, IDIDManager, IIdentifier, IResolver } from '@veramo/core'
|
|
8
12
|
import { IDidAuthSiopOpAuthenticator } from '../IDidAuthSiopOpAuthenticator'
|
|
9
13
|
import { Siopv2MachineContext, Siopv2MachineInterpreter, Siopv2MachineState } from '../machine'
|
|
14
|
+
import { DcqlQuery } from 'dcql'
|
|
15
|
+
import { Hasher } from '@sphereon/ssi-types'
|
|
10
16
|
|
|
11
17
|
export type DidAuthSiopOpAuthenticatorOptions = {
|
|
18
|
+
presentationSignCallback?: PresentationSignCallback
|
|
19
|
+
customApprovals?: Record<string, (verifiedAuthorizationRequest: VerifiedAuthorizationRequest, sessionId: string) => Promise<void>>
|
|
12
20
|
onContactIdentityCreated?: (args: OnContactIdentityCreatedArgs) => Promise<void>
|
|
13
21
|
onIdentifierCreated?: (args: OnIdentifierCreatedArgs) => Promise<void>
|
|
22
|
+
hasher?: Hasher
|
|
14
23
|
}
|
|
15
24
|
|
|
16
25
|
export type GetMachineArgs = {
|
|
@@ -19,12 +28,21 @@ export type GetMachineArgs = {
|
|
|
19
28
|
stateNavigationListener?: (siopv2Machine: Siopv2MachineInterpreter, state: Siopv2MachineState, navigation?: any) => Promise<void>
|
|
20
29
|
}
|
|
21
30
|
|
|
22
|
-
export type CreateConfigArgs =
|
|
31
|
+
export type CreateConfigArgs = { url: string }
|
|
23
32
|
export type CreateConfigResult = Omit<DidAuthConfig, 'stateId' | 'idOpts'>
|
|
24
|
-
export type GetSiopRequestArgs =
|
|
33
|
+
export type GetSiopRequestArgs = { didAuthConfig?: Omit<DidAuthConfig, 'identifier'>, url: string }
|
|
34
|
+
// FIXME it would be nicer if these function are not tied to a certain machine so that we can start calling them for anywhere
|
|
25
35
|
export type RetrieveContactArgs = Pick<Siopv2MachineContext, 'url' | 'authorizationRequestData'>
|
|
36
|
+
// FIXME it would be nicer if these function are not tied to a certain machine so that we can start calling them for anywhere
|
|
26
37
|
export type AddIdentityArgs = Pick<Siopv2MachineContext, 'contact' | 'authorizationRequestData'>
|
|
27
|
-
export type SendResponseArgs =
|
|
38
|
+
export type SendResponseArgs = {
|
|
39
|
+
didAuthConfig?: Omit<DidAuthConfig, 'identifier'>,
|
|
40
|
+
authorizationRequestData?: Siopv2AuthorizationRequestData,
|
|
41
|
+
selectedCredentials: Array<UniqueDigitalCredential>
|
|
42
|
+
idOpts?: ManagedIdentifierOptsOrResult
|
|
43
|
+
isFirstParty?: boolean
|
|
44
|
+
}
|
|
45
|
+
// FIXME it would be nicer if these function are not tied to a certain machine so that we can start calling them for anywhere
|
|
28
46
|
export type GetSelectableCredentialsArgs = Pick<Siopv2MachineContext, 'authorizationRequestData'>
|
|
29
47
|
|
|
30
48
|
export enum Siopv2HolderEvent {
|
|
@@ -38,7 +56,7 @@ export enum SupportedLanguage {
|
|
|
38
56
|
}
|
|
39
57
|
|
|
40
58
|
export type Siopv2AuthorizationResponseData = {
|
|
41
|
-
body?: string
|
|
59
|
+
body?: string | Record<string, any>
|
|
42
60
|
url?: string
|
|
43
61
|
queryParams?: Record<string, any>
|
|
44
62
|
}
|
|
@@ -51,6 +69,7 @@ export type Siopv2AuthorizationRequestData = {
|
|
|
51
69
|
uri?: URL
|
|
52
70
|
clientId?: string
|
|
53
71
|
presentationDefinitions?: PresentationDefinitionWithLocation[]
|
|
72
|
+
dcqlQuery?: DcqlQuery
|
|
54
73
|
}
|
|
55
74
|
|
|
56
75
|
export type SelectableCredentialsMap = Map<string, Array<SelectableCredential>>
|