@sphereon/ssi-sdk.credential-vcdm2-jose-provider 0.33.1-next.68 → 0.33.1-next.73
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 +108 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +102 -21
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/src/__tests__/issue-verify-flow-vcdm2-jose.test.ts +11 -2
- package/src/agent/CredentialProviderVcdm2Jose.ts +161 -61
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
} from '@veramo/core'
|
|
1
|
+
import {
|
|
2
|
+
type ExternalIdentifierDidOpts,
|
|
3
|
+
ExternalIdentifierResult,
|
|
4
|
+
type IIdentifierResolution,
|
|
5
|
+
isDidIdentifier
|
|
6
|
+
} from '@sphereon/ssi-sdk-ext.identifier-resolution'
|
|
7
|
+
import type { IJwtService, JwsHeader, JwsPayload } from '@sphereon/ssi-sdk-ext.jwt-service'
|
|
8
|
+
import { signatureAlgorithmFromKey } from '@sphereon/ssi-sdk-ext.key-utils'
|
|
9
|
+
import { contextHasPlugin } from '@sphereon/ssi-sdk.agent-config'
|
|
10
|
+
import { asArray, intersect, type VerifiableCredentialSP, type VerifiablePresentationSP } from '@sphereon/ssi-sdk.core'
|
|
12
11
|
import {
|
|
13
12
|
type ICanIssueCredentialTypeArgs,
|
|
14
13
|
type ICanVerifyDocumentTypeArgs,
|
|
@@ -17,29 +16,39 @@ import {
|
|
|
17
16
|
type IVcdmCredentialProvider,
|
|
18
17
|
type IVcdmIssuerAgentContext,
|
|
19
18
|
IVcdmVerifierAgentContext,
|
|
20
|
-
|
|
19
|
+
IVerifyCredentialVcdmArgs,
|
|
21
20
|
IVerifyPresentationLDArgs,
|
|
22
21
|
pickSigningKey,
|
|
23
22
|
preProcessCredentialPayload,
|
|
24
|
-
preProcessPresentation
|
|
23
|
+
preProcessPresentation
|
|
25
24
|
} from '@sphereon/ssi-sdk.credential-vcdm'
|
|
25
|
+
import {
|
|
26
|
+
CredentialMapper,
|
|
27
|
+
isVcdm2Credential,
|
|
28
|
+
type IVerifyResult,
|
|
29
|
+
type OriginalVerifiableCredential
|
|
30
|
+
} from '@sphereon/ssi-types'
|
|
31
|
+
import type {
|
|
32
|
+
IAgentContext,
|
|
33
|
+
IDIDManager,
|
|
34
|
+
IIdentifier,
|
|
35
|
+
IKey,
|
|
36
|
+
IKeyManager,
|
|
37
|
+
IResolver,
|
|
38
|
+
VerifiableCredential, VerificationPolicies,
|
|
39
|
+
VerifierAgentContext
|
|
40
|
+
} from '@veramo/core'
|
|
41
|
+
|
|
42
|
+
import Debug from 'debug'
|
|
43
|
+
|
|
44
|
+
import { decodeJWT, JWT_ERROR } from 'did-jwt'
|
|
26
45
|
|
|
27
46
|
// @ts-ignore
|
|
28
47
|
import { normalizeCredential, normalizePresentation, verifyPresentation as verifyPresentationJWT } from 'did-jwt-vc'
|
|
29
48
|
|
|
30
49
|
import { type Resolvable } from 'did-resolver'
|
|
31
50
|
|
|
32
|
-
import { decodeJWT, JWT_ERROR } from 'did-jwt'
|
|
33
|
-
|
|
34
|
-
import Debug from 'debug'
|
|
35
|
-
import { asArray, intersect, VerifiableCredentialSP, VerifiablePresentationSP } from '@sphereon/ssi-sdk.core'
|
|
36
|
-
import { contextHasPlugin } from '@sphereon/ssi-sdk.agent-config'
|
|
37
|
-
import { IJwtService, JwsHeader, JwsPayload } from '@sphereon/ssi-sdk-ext.jwt-service'
|
|
38
|
-
import { ExternalIdentifierDidOpts, IIdentifierResolution, isDidIdentifier } from '@sphereon/ssi-sdk-ext.identifier-resolution'
|
|
39
|
-
import { CredentialMapper, isVcdm2Credential, OriginalVerifiableCredential } from '@sphereon/ssi-types'
|
|
40
|
-
|
|
41
51
|
import { SELF_ISSUED_V0_1, SELF_ISSUED_V2, SELF_ISSUED_V2_VC_INTEROP } from '../did-jwt/JWT'
|
|
42
|
-
import { signatureAlgorithmFromKey } from '@sphereon/ssi-sdk-ext.key-utils'
|
|
43
52
|
// import {validateCredentialPayload} from "did-jwt-vc/src";
|
|
44
53
|
|
|
45
54
|
const debug = Debug('sphereon:ssi-sdk:credential-jwt')
|
|
@@ -96,13 +105,13 @@ export class CredentialProviderVcdm2Jose implements IVcdmCredentialProvider {
|
|
|
96
105
|
const key = await pickSigningKey({ identifier, kmsKeyRef: keyRef }, context)
|
|
97
106
|
|
|
98
107
|
// TODO: Probably wise to give control to caller as well, as some key types allow multiple signature algos
|
|
99
|
-
const alg = await signatureAlgorithmFromKey({key}) as string
|
|
108
|
+
const alg = (await signatureAlgorithmFromKey({ key })) as string
|
|
100
109
|
debug('Signing VC with', identifier.did, alg)
|
|
101
110
|
const header: JwsHeader = {
|
|
102
111
|
kid: key.meta?.verificationMethod?.id ?? key.kid,
|
|
103
112
|
alg,
|
|
104
113
|
typ: 'vc+jwt',
|
|
105
|
-
cty: 'vc'
|
|
114
|
+
cty: 'vc'
|
|
106
115
|
}
|
|
107
116
|
|
|
108
117
|
const jwt = await context.agent.jwtCreateJwsCompactSignature({
|
|
@@ -110,7 +119,7 @@ export class CredentialProviderVcdm2Jose implements IVcdmCredentialProvider {
|
|
|
110
119
|
issuer: managedIdentifier,
|
|
111
120
|
payload: credential,
|
|
112
121
|
protectedHeader: header,
|
|
113
|
-
clientIdScheme: 'did'
|
|
122
|
+
clientIdScheme: 'did'
|
|
114
123
|
})
|
|
115
124
|
|
|
116
125
|
// debug(jwt)
|
|
@@ -118,19 +127,26 @@ export class CredentialProviderVcdm2Jose implements IVcdmCredentialProvider {
|
|
|
118
127
|
}
|
|
119
128
|
|
|
120
129
|
/** {@inheritdoc ICredentialVerifier.verifyCredential} */
|
|
121
|
-
async verifyCredential(args:
|
|
122
|
-
let { credential
|
|
130
|
+
async verifyCredential(args: IVerifyCredentialVcdmArgs, context: VerifierAgentContext): Promise<IVerifyResult> {
|
|
131
|
+
let { credential, policies, /*...otherOptions*/ } = args
|
|
123
132
|
const uniform = CredentialMapper.toUniformCredential(credential as OriginalVerifiableCredential)
|
|
124
133
|
// let verifiedCredential: VerifiableCredential
|
|
125
134
|
if (!isVcdm2Credential(uniform)) {
|
|
126
|
-
return Promise.reject(new Error('invalid_argument: credential must be a VCDM2 credential. Context: ' +
|
|
135
|
+
return Promise.reject(new Error('invalid_argument: credential must be a VCDM2 credential. Context: ' + uniform['@context']))
|
|
127
136
|
}
|
|
128
137
|
let verificationResult: IVerifyResult = { verified: false }
|
|
129
138
|
let jwt: string | undefined = typeof credential === 'string' ? credential : asArray(uniform.proof)?.[0]?.jwt
|
|
130
139
|
if (!jwt) {
|
|
131
140
|
return Promise.reject(new Error('invalid_argument: credential must be a VCDM2 credential in JOSE format (string)'))
|
|
132
141
|
}
|
|
133
|
-
|
|
142
|
+
policies = {
|
|
143
|
+
...policies,
|
|
144
|
+
nbf: policies?.nbf ?? policies?.issuanceDate ?? policies?.validFrom,
|
|
145
|
+
iat: policies?.iat ?? policies?.issuanceDate ?? policies?.validFrom,
|
|
146
|
+
exp: policies?.exp ?? policies?.expirationDate ?? policies?.validUntil,
|
|
147
|
+
aud: policies?.aud ?? policies?.audience
|
|
148
|
+
}
|
|
149
|
+
verificationResult = await verifierSignature({ jwt, policies }, context)
|
|
134
150
|
return verificationResult
|
|
135
151
|
/* let errorCode, message
|
|
136
152
|
const resolver = {
|
|
@@ -153,25 +169,25 @@ export class CredentialProviderVcdm2Jose implements IVcdmCredentialProvider {
|
|
|
153
169
|
},
|
|
154
170
|
})
|
|
155
171
|
verifiedCredential = verificationResult.verifiableCredential
|
|
156
|
-
|
|
172
|
+
|
|
157
173
|
const nbf = policies?.issuanceDate === false ? false : undefined
|
|
158
174
|
const exp = policies?.expirationDate === false ? false : undefined
|
|
159
175
|
const options = { ...otherOptions, policies: { ...policies, nbf, exp, iat: nbf, format: policies?.format ?? true } }
|
|
160
|
-
|
|
176
|
+
|
|
161
177
|
const verified: Partial<VerifiedCredential> = await verifyDIDJWT(asArray(uniform.proof)[0].jwt, { resolver, ...options }, context)
|
|
162
178
|
verified.verifiableCredential = normalizeCredential(verified.jwt as string, true)
|
|
163
179
|
if (options?.policies?.format !== false) {
|
|
164
180
|
validateCredentialPayload(verified.verifiableCredential)
|
|
165
181
|
}
|
|
166
|
-
|
|
182
|
+
|
|
167
183
|
// if credential was presented with other fields, make sure those fields match what's in the JWT
|
|
168
184
|
if (typeof credential !== 'string' && asArray(credential.proof)[0].type === 'JwtProof2020') {
|
|
169
185
|
const credentialCopy = JSON.parse(JSON.stringify(credential))
|
|
170
186
|
delete credentialCopy.proof.jwt
|
|
171
|
-
|
|
187
|
+
|
|
172
188
|
const verifiedCopy = JSON.parse(JSON.stringify(verifiedCredential))
|
|
173
189
|
delete verifiedCopy.proof.jwt
|
|
174
|
-
|
|
190
|
+
|
|
175
191
|
if (canonicalize(credentialCopy) !== canonicalize(verifiedCopy)) {
|
|
176
192
|
verificationResult.verified = false
|
|
177
193
|
verificationResult.error = new Error('invalid_credential: Credential JSON does not match JWT payload')
|
|
@@ -202,7 +218,13 @@ export class CredentialProviderVcdm2Jose implements IVcdmCredentialProvider {
|
|
|
202
218
|
|
|
203
219
|
const managedIdentifier = await agent.identifierManagedGetByDid({ identifier: holder, kmsKeyRef: keyRef })
|
|
204
220
|
const identifier = managedIdentifier.identifier
|
|
205
|
-
const key = await pickSigningKey(
|
|
221
|
+
const key = await pickSigningKey(
|
|
222
|
+
{
|
|
223
|
+
identifier: managedIdentifier.identifier,
|
|
224
|
+
kmsKeyRef: managedIdentifier.kmsKeyRef
|
|
225
|
+
},
|
|
226
|
+
context
|
|
227
|
+
)
|
|
206
228
|
|
|
207
229
|
debug('Signing VC with', identifier.did)
|
|
208
230
|
let alg = 'ES256'
|
|
@@ -216,12 +238,12 @@ export class CredentialProviderVcdm2Jose implements IVcdmCredentialProvider {
|
|
|
216
238
|
kid: key.meta.verificationMethod.id ?? key.kid,
|
|
217
239
|
alg,
|
|
218
240
|
typ: 'vp+jwt',
|
|
219
|
-
cty: 'vp'
|
|
241
|
+
cty: 'vp'
|
|
220
242
|
}
|
|
221
243
|
const payload: JwsPayload = {
|
|
222
244
|
...presentation,
|
|
223
245
|
...(domain && { aud: domain }),
|
|
224
|
-
...(challenge && { nonce: challenge })
|
|
246
|
+
...(challenge && { nonce: challenge })
|
|
225
247
|
}
|
|
226
248
|
|
|
227
249
|
const jwt = await agent.jwtCreateJwsCompactSignature({
|
|
@@ -229,7 +251,7 @@ export class CredentialProviderVcdm2Jose implements IVcdmCredentialProvider {
|
|
|
229
251
|
issuer: managedIdentifier,
|
|
230
252
|
payload,
|
|
231
253
|
protectedHeader: header,
|
|
232
|
-
clientIdScheme: 'did'
|
|
254
|
+
clientIdScheme: 'did'
|
|
233
255
|
})
|
|
234
256
|
|
|
235
257
|
debug(jwt)
|
|
@@ -249,8 +271,8 @@ export class CredentialProviderVcdm2Jose implements IVcdmCredentialProvider {
|
|
|
249
271
|
resolve: (didUrl: string) =>
|
|
250
272
|
context.agent.resolveDid({
|
|
251
273
|
didUrl,
|
|
252
|
-
options: otherOptions?.resolutionOptions
|
|
253
|
-
})
|
|
274
|
+
options: otherOptions?.resolutionOptions
|
|
275
|
+
})
|
|
254
276
|
} as Resolvable
|
|
255
277
|
|
|
256
278
|
let audience = domain
|
|
@@ -278,15 +300,33 @@ export class CredentialProviderVcdm2Jose implements IVcdmCredentialProvider {
|
|
|
278
300
|
nbf: policies?.nbf ?? policies?.issuanceDate,
|
|
279
301
|
iat: policies?.iat ?? policies?.issuanceDate,
|
|
280
302
|
exp: policies?.exp ?? policies?.expirationDate,
|
|
281
|
-
aud: policies?.aud ?? policies?.audience
|
|
303
|
+
aud: policies?.aud ?? policies?.audience
|
|
282
304
|
},
|
|
283
|
-
...otherOptions
|
|
305
|
+
...otherOptions
|
|
284
306
|
})
|
|
285
307
|
if (result) {
|
|
308
|
+
/**
|
|
309
|
+
* {id: 'valid_signature', valid: true},
|
|
310
|
+
* // {id: 'issuer_did_resolves', valid: true},
|
|
311
|
+
* // {id: 'expiration', valid: true},
|
|
312
|
+
* // {id: 'revocation_status', valid: true},
|
|
313
|
+
* // {id: 'suspension_status', valid: true}
|
|
314
|
+
*/
|
|
286
315
|
return {
|
|
287
316
|
verified: true,
|
|
288
|
-
|
|
289
|
-
|
|
317
|
+
results: [
|
|
318
|
+
{
|
|
319
|
+
verified: true,
|
|
320
|
+
presentation: result.verifiablePresentation,
|
|
321
|
+
log: [
|
|
322
|
+
{
|
|
323
|
+
id: 'valid_signature',
|
|
324
|
+
valid: true
|
|
325
|
+
}
|
|
326
|
+
]
|
|
327
|
+
}
|
|
328
|
+
]
|
|
329
|
+
} satisfies IVerifyResult
|
|
290
330
|
}
|
|
291
331
|
} catch (e: any) {
|
|
292
332
|
message = e.message
|
|
@@ -296,8 +336,8 @@ export class CredentialProviderVcdm2Jose implements IVcdmCredentialProvider {
|
|
|
296
336
|
verified: false,
|
|
297
337
|
error: {
|
|
298
338
|
message,
|
|
299
|
-
errorCode: errorCode ? errorCode : message?.split(':')[0]
|
|
300
|
-
}
|
|
339
|
+
errorCode: errorCode ? errorCode : message?.split(':')[0]
|
|
340
|
+
}
|
|
301
341
|
}
|
|
302
342
|
}
|
|
303
343
|
|
|
@@ -329,8 +369,8 @@ export class CredentialProviderVcdm2Jose implements IVcdmCredentialProvider {
|
|
|
329
369
|
}
|
|
330
370
|
|
|
331
371
|
export async function verifierSignature(
|
|
332
|
-
{ jwt }: { jwt: string /*resolver: Resolvable*/ },
|
|
333
|
-
verifierContext: VerifierAgentContext
|
|
372
|
+
{ jwt, policies }: { jwt: string, policies: VerificationPolicies /*resolver: Resolvable*/ },
|
|
373
|
+
verifierContext: VerifierAgentContext
|
|
334
374
|
): Promise<IVerifyResult> {
|
|
335
375
|
let credIssuer: string | undefined = undefined
|
|
336
376
|
const context = assertContext(verifierContext)
|
|
@@ -374,26 +414,86 @@ export async function verifierSignature(
|
|
|
374
414
|
if (!credIssuer) {
|
|
375
415
|
throw new Error(`${JWT_ERROR.INVALID_JWT}: No DID has been found in the JWT`)
|
|
376
416
|
}
|
|
377
|
-
|
|
417
|
+
let resolution: ExternalIdentifierResult | undefined = undefined
|
|
418
|
+
try {
|
|
419
|
+
resolution = await agent.identifierExternalResolve({ identifier: credIssuer })
|
|
420
|
+
} catch (e: any) {
|
|
421
|
+
}
|
|
422
|
+
const credential = CredentialMapper.toUniformCredential(jwt)
|
|
423
|
+
|
|
424
|
+
const validFromError = (policies.nbf !== false && policies.iat !== false) && 'validFrom' in credential && !!credential.validFrom && Date.parse(credential.validFrom) > new Date().getTime()
|
|
425
|
+
const expired = policies.exp !== false && 'validUntil' in credential && !!credential.validUntil && Date.parse(credential.validUntil) < new Date().getTime()
|
|
378
426
|
|
|
379
427
|
const didOpts = { method: 'did', identifier: credIssuer } satisfies ExternalIdentifierDidOpts
|
|
380
428
|
const jwtResult = await agent.jwtVerifyJwsSignature({
|
|
381
429
|
jws: jwt,
|
|
382
430
|
// @ts-ignore
|
|
383
|
-
jwk: resolution
|
|
384
|
-
opts: { ...(isDidIdentifier(credIssuer) && { did: didOpts }) }
|
|
431
|
+
jwk: resolution?.jwks[0].jwk,
|
|
432
|
+
opts: { ...(isDidIdentifier(credIssuer) && { did: didOpts }) }
|
|
385
433
|
})
|
|
386
|
-
|
|
387
|
-
|
|
434
|
+
const error = jwtResult.error || expired || !resolution
|
|
435
|
+
const errorMessage = expired ? 'Credential is expired' : validFromError ? 'Credential is not valid yet' : !resolution ? `Issuer ${credIssuer} could not be resolved` : jwtResult.message
|
|
436
|
+
|
|
437
|
+
if (error) {
|
|
438
|
+
const log = [
|
|
439
|
+
{
|
|
440
|
+
id: 'valid_signature',
|
|
441
|
+
valid: !jwtResult.error
|
|
442
|
+
},
|
|
443
|
+
{ id: 'issuer_did_resolves', valid: resolution != undefined },
|
|
444
|
+
{ id: 'validFrom', valid: policies.nbf !== false && !validFromError },
|
|
445
|
+
{ id: 'expiration', valid: policies.exp !== false && !expired }
|
|
446
|
+
]
|
|
388
447
|
return {
|
|
389
448
|
verified: false,
|
|
390
|
-
error: { message:
|
|
449
|
+
error: { message: errorMessage, errorCode: jwtResult.name },
|
|
450
|
+
log,
|
|
451
|
+
results: [
|
|
452
|
+
{
|
|
453
|
+
verified: false,
|
|
454
|
+
credential: jwt,
|
|
455
|
+
log,
|
|
456
|
+
error: { message: errorMessage, errorCode: jwtResult.name }
|
|
457
|
+
}
|
|
458
|
+
],
|
|
391
459
|
payload,
|
|
392
460
|
didResolutionResult: resolution,
|
|
393
|
-
jwt
|
|
394
|
-
}
|
|
461
|
+
jwt
|
|
462
|
+
} satisfies IVerifyResult
|
|
395
463
|
}
|
|
396
|
-
|
|
464
|
+
|
|
465
|
+
const log = [
|
|
466
|
+
{
|
|
467
|
+
id: 'valid_signature',
|
|
468
|
+
valid: true
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
id: 'issuer_did_resolves',
|
|
472
|
+
valid: true
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
id: 'validFrom',
|
|
476
|
+
valid: true
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
id: 'expiration',
|
|
480
|
+
valid: true
|
|
481
|
+
}
|
|
482
|
+
]
|
|
483
|
+
return {
|
|
484
|
+
verified: true,
|
|
485
|
+
log,
|
|
486
|
+
results: [
|
|
487
|
+
{
|
|
488
|
+
verified: true,
|
|
489
|
+
credential,
|
|
490
|
+
log
|
|
491
|
+
}
|
|
492
|
+
],
|
|
493
|
+
payload,
|
|
494
|
+
didResolutionResult: resolution,
|
|
495
|
+
jwt
|
|
496
|
+
} satisfies IVerifyResult
|
|
397
497
|
}
|
|
398
498
|
|
|
399
499
|
/*
|
|
@@ -535,17 +635,17 @@ export function validateContext(value: string | string[]): void {
|
|
|
535
635
|
}
|
|
536
636
|
*/
|
|
537
637
|
function assertContext(
|
|
538
|
-
context: IVcdmIssuerAgentContext | IVcdmVerifierAgentContext
|
|
638
|
+
context: IVcdmIssuerAgentContext | IVcdmVerifierAgentContext
|
|
539
639
|
): IAgentContext<
|
|
540
640
|
IResolver & IDIDManager & Pick<IKeyManager, 'keyManagerGet' | 'keyManagerSign' | 'keyManagerVerify'> & IJwtService & IIdentifierResolution
|
|
541
641
|
> {
|
|
542
642
|
if (!contextHasPlugin<IJwtService>(context, 'jwtPrepareJws')) {
|
|
543
643
|
throw Error(
|
|
544
|
-
'JwtService plugin not found, which is required for JWT signing in the VCDM2 Jose credential provider. Please add the JwtService plugin to your agent configuration.'
|
|
644
|
+
'JwtService plugin not found, which is required for JWT signing in the VCDM2 Jose credential provider. Please add the JwtService plugin to your agent configuration.'
|
|
545
645
|
)
|
|
546
646
|
} else if (!contextHasPlugin<IIdentifierResolution>(context, 'identifierManagedGet')) {
|
|
547
647
|
throw Error(
|
|
548
|
-
'Identifier resolution plugin not found, which is required for JWT signing in the VCDM2 Jose credential provider. Please add the JwtService plugin to your agent configuration.'
|
|
648
|
+
'Identifier resolution plugin not found, which is required for JWT signing in the VCDM2 Jose credential provider. Please add the JwtService plugin to your agent configuration.'
|
|
549
649
|
)
|
|
550
650
|
}
|
|
551
651
|
return context as IAgentContext<
|