@sphereon/ssi-sdk.siopv2-oid4vp-op-auth 0.34.1-next.29 → 0.34.1-next.299
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/index.cjs +629 -1112
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +710 -112
- package/dist/index.d.ts +710 -112
- package/dist/index.js +563 -1046
- package/dist/index.js.map +1 -1
- package/package.json +24 -24
- package/src/agent/DidAuthSiopOpAuthenticator.ts +10 -145
- package/src/index.ts +2 -1
- package/src/machine/Siopv2Machine.ts +5 -5
- package/src/services/Siopv2MachineService.ts +189 -265
- package/src/session/OID4VP.ts +310 -300
- package/src/session/OpSession.ts +22 -114
- package/src/session/functions.ts +1 -8
- package/src/types/IDidAuthSiopOpAuthenticator.ts +6 -59
- package/src/types/identifier/index.ts +0 -4
- package/src/types/machine/index.ts +1 -1
- package/src/types/siop-service/index.ts +12 -10
- package/src/utils/CredentialUtils.ts +2 -40
- package/src/utils/dcql.ts +22 -19
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
import { AuthorizationRequest
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { AuthorizationRequest } from '@sphereon/did-auth-siop'
|
|
2
|
+
import type { PartialSdJwtDecodedVerifiableCredential, PartialSdJwtKbJwt } from '@sphereon/pex/dist/main/lib/index.js'
|
|
3
|
+
import { calculateSdHash } from '@sphereon/pex/dist/main/lib/utils/index.js'
|
|
4
|
+
import { getOrCreatePrimaryIdentifier, SupportedDidMethodEnum } from '@sphereon/ssi-sdk-ext.did-utils'
|
|
4
5
|
import { isOID4VCIssuerIdentifier, ManagedIdentifierOptsOrResult } from '@sphereon/ssi-sdk-ext.identifier-resolution'
|
|
6
|
+
import { encodeJoseBlob } from '@sphereon/ssi-sdk.core'
|
|
5
7
|
import { UniqueDigitalCredential, verifiableCredentialForRoleFilter } from '@sphereon/ssi-sdk.credential-store'
|
|
6
|
-
import { ConnectionType
|
|
7
|
-
import {
|
|
8
|
-
import { OID4VP, OpSession } from '../session'
|
|
8
|
+
import { ConnectionType } from '@sphereon/ssi-sdk.data-store-types'
|
|
9
|
+
import { defaultGenerateDigest } from '@sphereon/ssi-sdk.sd-jwt'
|
|
9
10
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
VerifiableCredentialsWithDefinition,
|
|
18
|
-
VerifiablePresentationWithDefinition,
|
|
19
|
-
} from '../types'
|
|
11
|
+
CredentialMapper,
|
|
12
|
+
CredentialRole,
|
|
13
|
+
HasherSync,
|
|
14
|
+
Loggers,
|
|
15
|
+
OriginalVerifiableCredential,
|
|
16
|
+
SdJwtDecodedVerifiableCredential,
|
|
17
|
+
} from '@sphereon/ssi-types'
|
|
20
18
|
import { IAgentContext, IDIDManager } from '@veramo/core'
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
19
|
+
import { DcqlPresentation, DcqlQuery } from 'dcql'
|
|
20
|
+
import { OpSession } from '../session'
|
|
21
|
+
import { LOGGER_NAMESPACE, RequiredContext, SelectableCredential, SelectableCredentialsMap, Siopv2HolderEvent } from '../types'
|
|
24
22
|
import { convertToDcqlCredentials } from '../utils/dcql'
|
|
25
|
-
|
|
23
|
+
|
|
24
|
+
const CLOCK_SKEW = 120
|
|
26
25
|
|
|
27
26
|
export const logger = Loggers.DEFAULT.get(LOGGER_NAMESPACE)
|
|
28
27
|
|
|
28
|
+
// @ts-ignore
|
|
29
29
|
const createEbsiIdentifier = async (agentContext: IAgentContext<IDIDManager>): Promise<ManagedIdentifierOptsOrResult> => {
|
|
30
30
|
logger.log(`No EBSI key present yet. Creating a new one...`)
|
|
31
31
|
const { result: newIdentifier, created } = await getOrCreatePrimaryIdentifier(agentContext, {
|
|
@@ -39,9 +39,10 @@ const createEbsiIdentifier = async (agentContext: IAgentContext<IDIDManager>): P
|
|
|
39
39
|
return await agentContext.agent.identifierManagedGetByDid({ identifier: newIdentifier.did })
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
// @ts-ignore
|
|
42
43
|
const hasEbsiClient = async (authorizationRequest: AuthorizationRequest) => {
|
|
43
|
-
const clientId =
|
|
44
|
-
const redirectUri =
|
|
44
|
+
const clientId = authorizationRequest.getMergedProperty<string>('client_id')
|
|
45
|
+
const redirectUri = authorizationRequest.getMergedProperty<string>('redirect_uri')
|
|
45
46
|
return clientId?.toLowerCase().includes('.ebsi.eu') || redirectUri?.toLowerCase().includes('.ebsi.eu')
|
|
46
47
|
}
|
|
47
48
|
|
|
@@ -49,293 +50,186 @@ export const siopSendAuthorizationResponse = async (
|
|
|
49
50
|
connectionType: ConnectionType,
|
|
50
51
|
args: {
|
|
51
52
|
sessionId: string
|
|
52
|
-
|
|
53
|
+
credentials: Array<UniqueDigitalCredential | OriginalVerifiableCredential>
|
|
53
54
|
idOpts?: ManagedIdentifierOptsOrResult
|
|
54
55
|
isFirstParty?: boolean
|
|
55
56
|
hasher?: HasherSync
|
|
56
|
-
dcqlQuery?: DcqlQuery
|
|
57
57
|
},
|
|
58
58
|
context: RequiredContext,
|
|
59
59
|
) => {
|
|
60
60
|
const { agent } = context
|
|
61
|
-
const
|
|
62
|
-
let { idOpts, isFirstParty, hasher = defaultHasher } = args
|
|
63
|
-
|
|
61
|
+
const { credentials } = args
|
|
64
62
|
if (connectionType !== ConnectionType.SIOPv2_OpenID4VP) {
|
|
65
63
|
return Promise.reject(Error(`No supported authentication provider for type: ${connectionType}`))
|
|
66
64
|
}
|
|
65
|
+
|
|
67
66
|
const session: OpSession = await agent.siopGetOPSession({ sessionId: args.sessionId })
|
|
68
67
|
const request = await session.getAuthorizationRequest()
|
|
69
|
-
const aud =
|
|
68
|
+
const aud = request.authorizationRequest.getMergedProperty<string>('aud')
|
|
70
69
|
logger.debug(`AUD: ${aud}`)
|
|
71
70
|
logger.debug(JSON.stringify(request.authorizationRequest))
|
|
72
71
|
|
|
73
|
-
|
|
74
|
-
let presentationSubmission: PresentationSubmission | undefined
|
|
75
|
-
if (await session.hasPresentationDefinitions()) {
|
|
76
|
-
const oid4vp: OID4VP = await session.getOID4VP({ hasher })
|
|
77
|
-
|
|
78
|
-
const credentialsAndDefinitions = args.verifiableCredentialsWithDefinition
|
|
79
|
-
? args.verifiableCredentialsWithDefinition
|
|
80
|
-
: await oid4vp.filterCredentialsAgainstAllDefinitions(CredentialRole.HOLDER)
|
|
81
|
-
const domain =
|
|
82
|
-
((await request.authorizationRequest.getMergedProperty('client_id')) as string) ??
|
|
83
|
-
request.issuer ??
|
|
84
|
-
(request.versions.includes(SupportedVersion.JWT_VC_PRESENTATION_PROFILE_v1)
|
|
85
|
-
? 'https://self-issued.me/v2/openid-vc'
|
|
86
|
-
: 'https://self-issued.me/v2')
|
|
87
|
-
logger.log(`NONCE: ${session.nonce}, domain: ${domain}`)
|
|
88
|
-
|
|
89
|
-
const firstUniqueDC = credentialsAndDefinitions[0].credentials[0]
|
|
90
|
-
if (typeof firstUniqueDC !== 'object' || !('digitalCredential' in firstUniqueDC)) {
|
|
91
|
-
return Promise.reject(Error('SiopMachine only supports UniqueDigitalCredentials for now'))
|
|
92
|
-
}
|
|
72
|
+
const domain = ((await request.authorizationRequest.getMergedProperty('client_id')) as string) ?? request.issuer ?? 'https://self-issued.me/v2'
|
|
93
73
|
|
|
94
|
-
|
|
95
|
-
const digitalCredential = firstUniqueDC.digitalCredential
|
|
96
|
-
const firstVC = firstUniqueDC.uniformVerifiableCredential
|
|
97
|
-
const holder = CredentialMapper.isSdJwtDecodedCredential(firstVC)
|
|
98
|
-
? firstVC.decodedPayload.cnf?.jwk
|
|
99
|
-
? //TODO SDK-19: convert the JWK to hex and search for the appropriate key and associated DID
|
|
100
|
-
//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
|
|
101
|
-
`did:jwk:${encodeJoseBlob(firstVC.decodedPayload.cnf?.jwk)}#0`
|
|
102
|
-
: firstVC.decodedPayload.sub
|
|
103
|
-
: Array.isArray(firstVC.credentialSubject)
|
|
104
|
-
? firstVC.credentialSubject[0].id
|
|
105
|
-
: firstVC.credentialSubject.id
|
|
106
|
-
if (!digitalCredential.kmsKeyRef) {
|
|
107
|
-
// In case the store does not have the kmsKeyRef lets search for the holder
|
|
108
|
-
|
|
109
|
-
if (!holder) {
|
|
110
|
-
return Promise.reject(`No holder found and no kmsKeyRef in DB. Cannot determine identifier to use`)
|
|
111
|
-
}
|
|
112
|
-
try {
|
|
113
|
-
identifier = await session.context.agent.identifierManagedGet({ identifier: holder })
|
|
114
|
-
} catch (e) {
|
|
115
|
-
logger.debug(`Holder DID not found: ${holder}`)
|
|
116
|
-
throw e
|
|
117
|
-
}
|
|
118
|
-
} else if (isOID4VCIssuerIdentifier(digitalCredential.kmsKeyRef)) {
|
|
119
|
-
identifier = await session.context.agent.identifierManagedGetByOID4VCIssuer({
|
|
120
|
-
identifier: firstUniqueDC.digitalCredential.kmsKeyRef,
|
|
121
|
-
})
|
|
122
|
-
} else {
|
|
123
|
-
switch (digitalCredential.subjectCorrelationType) {
|
|
124
|
-
case 'DID':
|
|
125
|
-
identifier = await session.context.agent.identifierManagedGetByDid({
|
|
126
|
-
identifier: digitalCredential.subjectCorrelationId ?? holder,
|
|
127
|
-
kmsKeyRef: digitalCredential.kmsKeyRef,
|
|
128
|
-
})
|
|
129
|
-
break
|
|
130
|
-
// TODO other implementations?
|
|
131
|
-
default:
|
|
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
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (identifier === undefined && idOpts !== undefined && (await hasEbsiClient(request.authorizationRequest))) {
|
|
148
|
-
identifier = await createEbsiIdentifier(agentContext)
|
|
149
|
-
}
|
|
150
|
-
logger.debug(`Identifier`, identifier)
|
|
74
|
+
logger.debug(`NONCE: ${session.nonce}, domain: ${domain}`)
|
|
151
75
|
|
|
152
|
-
|
|
76
|
+
const firstUniqueDC = credentials[0]
|
|
77
|
+
if (typeof firstUniqueDC !== 'object' || !('digitalCredential' in firstUniqueDC)) {
|
|
78
|
+
return Promise.reject(Error('SiopMachine only supports UniqueDigitalCredentials for now'))
|
|
79
|
+
}
|
|
153
80
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
81
|
+
let identifier: ManagedIdentifierOptsOrResult
|
|
82
|
+
const digitalCredential = firstUniqueDC.digitalCredential
|
|
83
|
+
const firstVC = firstUniqueDC.uniformVerifiableCredential
|
|
84
|
+
const holder = CredentialMapper.isSdJwtDecodedCredential(firstVC)
|
|
85
|
+
? firstVC.decodedPayload.cnf?.jwk
|
|
86
|
+
? //TODO SDK-19: convert the JWK to hex and search for the appropriate key and associated DID
|
|
87
|
+
//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
|
|
88
|
+
`did:jwk:${encodeJoseBlob(firstVC.decodedPayload.cnf?.jwk)}#0`
|
|
89
|
+
: firstVC.decodedPayload.sub
|
|
90
|
+
: Array.isArray(firstVC.credentialSubject)
|
|
91
|
+
? firstVC.credentialSubject[0].id
|
|
92
|
+
: firstVC.credentialSubject.id
|
|
93
|
+
if (!digitalCredential.kmsKeyRef) {
|
|
94
|
+
// In case the store does not have the kmsKeyRef lets search for the holder
|
|
95
|
+
|
|
96
|
+
if (!holder) {
|
|
97
|
+
return Promise.reject(`No holder found and no kmsKeyRef in DB. Cannot determine identifier to use`)
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
identifier = await session.context.agent.identifierManagedGet({ identifier: holder })
|
|
101
|
+
} catch (e) {
|
|
102
|
+
logger.debug(`Holder DID not found: ${holder}`)
|
|
103
|
+
throw e
|
|
104
|
+
}
|
|
105
|
+
} else if (isOID4VCIssuerIdentifier(digitalCredential.kmsKeyRef)) {
|
|
106
|
+
identifier = await session.context.agent.identifierManagedGetByOID4VCIssuer({
|
|
107
|
+
identifier: firstUniqueDC.digitalCredential.kmsKeyRef,
|
|
160
108
|
})
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
109
|
+
} else {
|
|
110
|
+
switch (digitalCredential.subjectCorrelationType) {
|
|
111
|
+
case 'DID':
|
|
112
|
+
identifier = await session.context.agent.identifierManagedGetByDid({
|
|
113
|
+
identifier: digitalCredential.subjectCorrelationId ?? holder,
|
|
114
|
+
kmsKeyRef: digitalCredential.kmsKeyRef,
|
|
115
|
+
})
|
|
116
|
+
break
|
|
117
|
+
// TODO other implementations?
|
|
118
|
+
default:
|
|
119
|
+
// Since we are using the kmsKeyRef we will find the KID regardless of the identifier. We set it for later access though
|
|
120
|
+
identifier = await session.context.agent.identifierManagedGetByKid({
|
|
121
|
+
identifier: digitalCredential.subjectCorrelationId ?? holder ?? digitalCredential.kmsKeyRef,
|
|
122
|
+
kmsKeyRef: digitalCredential.kmsKeyRef,
|
|
123
|
+
})
|
|
165
124
|
}
|
|
125
|
+
}
|
|
166
126
|
|
|
167
|
-
|
|
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
|
-
}
|
|
127
|
+
const dcqlCredentialsWithCredentials = new Map(credentials.map((vc) => [convertToDcqlCredentials(vc), vc]))
|
|
195
128
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
if (!
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
}
|
|
129
|
+
const queryResult = DcqlQuery.query(request.dcqlQuery, Array.from(dcqlCredentialsWithCredentials.keys()))
|
|
130
|
+
|
|
131
|
+
if (!queryResult.can_be_satisfied) {
|
|
132
|
+
return Promise.reject(Error('Credentials do not match required query request'))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const presentation: DcqlPresentation.Output = {}
|
|
136
|
+
const uniqueCredentials = Array.from(dcqlCredentialsWithCredentials.values())
|
|
137
|
+
for (const [key, value] of Object.entries(queryResult.credential_matches)) {
|
|
138
|
+
if (value.success) {
|
|
139
|
+
const matchedCredentials = value.valid_credentials.map((cred) => uniqueCredentials[cred.input_credential_index])
|
|
140
|
+
const vc = matchedCredentials[0] // taking the first match for now //uniqueCredentials[value.input_credential_index]
|
|
141
|
+
if (!vc) {
|
|
142
|
+
continue
|
|
143
|
+
}
|
|
144
|
+
const originalVc = retrieveEncodedCredential(vc as UniqueDigitalCredential) // TODO this is not nice // also always a UniqueDigitalCredential
|
|
145
|
+
if (!originalVc) {
|
|
146
|
+
continue
|
|
240
147
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
148
|
+
// FIXME SSISDK-44
|
|
149
|
+
const decodedSdJwt = await CredentialMapper.decodeSdJwtVcAsync(originalVc as string, defaultGenerateDigest)
|
|
150
|
+
const updatedSdJwt = updateSdJwtCredential(decodedSdJwt, request.requestObject?.getPayload()?.nonce, domain)
|
|
151
|
+
|
|
152
|
+
const presentationResult = await context.agent.createSdJwtPresentation({
|
|
153
|
+
presentation: updatedSdJwt.compactSdJwtVc,
|
|
154
|
+
kb: {
|
|
155
|
+
payload: {
|
|
156
|
+
...updatedSdJwt.kbJwt?.payload,
|
|
157
|
+
// FIXME SSISDK-44
|
|
158
|
+
nonce: updatedSdJwt.kbJwt?.payload.nonce ?? request.requestObject!.getPayload()!.nonce,
|
|
159
|
+
// FIXME SSISDK-44
|
|
160
|
+
aud: updatedSdJwt.kbJwt?.payload.aud ?? domain,
|
|
161
|
+
iat: updatedSdJwt.kbJwt?.payload?.iat ?? Math.floor(Date.now() / 1000 - CLOCK_SKEW),
|
|
162
|
+
},
|
|
163
|
+
},
|
|
249
164
|
})
|
|
250
165
|
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
})
|
|
166
|
+
if (originalVc) {
|
|
167
|
+
presentation[key] = presentationResult.presentation
|
|
266
168
|
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
267
171
|
|
|
268
|
-
|
|
269
|
-
responseSignerOpts: identifier,
|
|
270
|
-
...{ dcqlQuery: { dcqlPresentation: DcqlPresentation.parse(presentation) } },
|
|
271
|
-
})
|
|
172
|
+
const dcqlPresentation = DcqlPresentation.parse(presentation)
|
|
272
173
|
|
|
273
|
-
|
|
174
|
+
const response = session.sendAuthorizationResponse({
|
|
175
|
+
responseSignerOpts: identifier,
|
|
176
|
+
dcqlResponse: {
|
|
177
|
+
dcqlPresentation,
|
|
178
|
+
},
|
|
179
|
+
})
|
|
274
180
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
throw Error('Presentation Definition or DCQL is required')
|
|
181
|
+
logger.debug(`Response: `, response)
|
|
182
|
+
return response
|
|
279
183
|
}
|
|
280
184
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
)
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
} as IPresentationDefinition
|
|
185
|
+
const retrieveEncodedCredential = (credential: UniqueDigitalCredential): OriginalVerifiableCredential | undefined => {
|
|
186
|
+
return credential.originalVerifiableCredential !== undefined &&
|
|
187
|
+
credential.originalVerifiableCredential !== null &&
|
|
188
|
+
(credential?.originalVerifiableCredential as SdJwtDecodedVerifiableCredential)?.compactSdJwtVc !== undefined &&
|
|
189
|
+
(credential?.originalVerifiableCredential as SdJwtDecodedVerifiableCredential)?.compactSdJwtVc !== null
|
|
190
|
+
? (credential.originalVerifiableCredential as SdJwtDecodedVerifiableCredential).compactSdJwtVc
|
|
191
|
+
: credential.originalVerifiableCredential
|
|
289
192
|
}
|
|
290
193
|
|
|
291
|
-
export const getSelectableCredentials = async (
|
|
292
|
-
|
|
293
|
-
context: RequiredContext,
|
|
294
|
-
): Promise<SelectableCredentialsMap> => {
|
|
295
|
-
const agentContext = { ...context, agent: context.agent as SuitableCredentialAgents }
|
|
194
|
+
export const getSelectableCredentials = async (dcqlQuery: DcqlQuery, context: RequiredContext): Promise<SelectableCredentialsMap> => {
|
|
195
|
+
const agentContext = { ...context, agent: context.agent }
|
|
296
196
|
const { agent } = agentContext
|
|
297
|
-
const pex = new PEX()
|
|
298
|
-
|
|
299
197
|
const uniqueVerifiableCredentials = await agent.crsGetUniqueCredentials({
|
|
300
198
|
filter: verifiableCredentialForRoleFilter(CredentialRole.HOLDER),
|
|
301
199
|
})
|
|
302
|
-
const
|
|
303
|
-
|
|
200
|
+
const branding = await agent.ibGetCredentialBranding()
|
|
201
|
+
const dcqlCredentialsWithCredentials = new Map(uniqueVerifiableCredentials.map((vc) => [convertToDcqlCredentials(vc), vc]))
|
|
202
|
+
const queryResult = DcqlQuery.query(dcqlQuery, Array.from(dcqlCredentialsWithCredentials.keys()))
|
|
203
|
+
const uniqueCredentials = Array.from(dcqlCredentialsWithCredentials.values())
|
|
304
204
|
const selectableCredentialsMap: SelectableCredentialsMap = new Map()
|
|
305
205
|
|
|
306
|
-
for (const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
})
|
|
311
|
-
const selectionResults = pex.selectFrom(partialPD, originalCredentials)
|
|
206
|
+
for (const [key, value] of Object.entries(queryResult.credential_matches)) {
|
|
207
|
+
if (!value.valid_credentials) {
|
|
208
|
+
continue
|
|
209
|
+
}
|
|
312
210
|
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
211
|
+
const mapSelectableCredentialPromises = value.valid_credentials.map(async (cred) => {
|
|
212
|
+
const matchedCredential = uniqueCredentials[cred.input_credential_index]
|
|
213
|
+
const credentialBranding = branding.filter((cb) => cb.vcHash === matchedCredential.hash)
|
|
214
|
+
const issuerPartyIdentity = await agent.cmGetContacts({
|
|
215
|
+
filter: [{ identities: { identifier: { correlationId: matchedCredential.uniformVerifiableCredential!.issuerDid } } }],
|
|
216
|
+
})
|
|
217
|
+
const subjectPartyIdentity = await agent.cmGetContacts({
|
|
218
|
+
filter: [{ identities: { identifier: { correlationId: matchedCredential.uniformVerifiableCredential!.subjectDid } } }],
|
|
318
219
|
})
|
|
319
220
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
const subjectPartyIdentity = await agent.cmGetContacts({
|
|
326
|
-
filter: [{ identities: { identifier: { correlationId: filteredUniqueVC.uniformVerifiableCredential!.subjectDid } } }],
|
|
327
|
-
})
|
|
328
|
-
|
|
329
|
-
selectableCredentials.push({
|
|
330
|
-
credential: filteredUniqueVC,
|
|
331
|
-
credentialBranding: filteredCredentialBrandings[0]?.localeBranding,
|
|
332
|
-
issuerParty: issuerPartyIdentity?.[0],
|
|
333
|
-
subjectParty: subjectPartyIdentity?.[0],
|
|
334
|
-
})
|
|
221
|
+
return {
|
|
222
|
+
credential: matchedCredential,
|
|
223
|
+
credentialBranding: credentialBranding[0]?.localeBranding,
|
|
224
|
+
issuerParty: issuerPartyIdentity?.[0],
|
|
225
|
+
subjectParty: subjectPartyIdentity?.[0],
|
|
335
226
|
}
|
|
336
|
-
}
|
|
337
|
-
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
const selectableCredentials: Array<SelectableCredential> = await Promise.all(mapSelectableCredentialPromises)
|
|
230
|
+
selectableCredentialsMap.set(key, selectableCredentials)
|
|
338
231
|
}
|
|
232
|
+
|
|
339
233
|
return selectableCredentialsMap
|
|
340
234
|
}
|
|
341
235
|
|
|
@@ -352,3 +246,33 @@ export const translateCorrelationIdToName = async (correlationId: string, contex
|
|
|
352
246
|
|
|
353
247
|
return contacts[0].contact.displayName
|
|
354
248
|
}
|
|
249
|
+
|
|
250
|
+
const updateSdJwtCredential = (
|
|
251
|
+
credential: SdJwtDecodedVerifiableCredential,
|
|
252
|
+
nonce?: string,
|
|
253
|
+
aud?: string,
|
|
254
|
+
): PartialSdJwtDecodedVerifiableCredential => {
|
|
255
|
+
const sdJwtCredential = credential as SdJwtDecodedVerifiableCredential
|
|
256
|
+
|
|
257
|
+
// extract sd_alg or default to sha-256
|
|
258
|
+
const hashAlg = sdJwtCredential.signedPayload._sd_alg ?? 'sha-256'
|
|
259
|
+
const sdHash = calculateSdHash(sdJwtCredential.compactSdJwtVc, hashAlg, defaultGenerateDigest)
|
|
260
|
+
|
|
261
|
+
const kbJwt = {
|
|
262
|
+
// alg MUST be set by the signer
|
|
263
|
+
header: {
|
|
264
|
+
typ: 'kb+jwt',
|
|
265
|
+
},
|
|
266
|
+
payload: {
|
|
267
|
+
iat: Math.floor(new Date().getTime() / 1000),
|
|
268
|
+
sd_hash: sdHash,
|
|
269
|
+
...(nonce && { nonce }),
|
|
270
|
+
...(aud && { aud }),
|
|
271
|
+
},
|
|
272
|
+
} satisfies PartialSdJwtKbJwt
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
...sdJwtCredential,
|
|
276
|
+
kbJwt,
|
|
277
|
+
} satisfies PartialSdJwtDecodedVerifiableCredential
|
|
278
|
+
}
|