@sphereon/ssi-sdk.credential-vcdm2-jose-provider 0.33.1-feature.jose.vcdm.66 → 0.33.1-feature.vcdm.verification.69
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 +88 -21
- 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 +82 -15
- package/dist/index.js.map +1 -1
- package/package.json +13 -13
- package/src/__tests__/issue-verify-flow-vcdm2-jose.test.ts +11 -2
- package/src/agent/CredentialProviderVcdm2Jose.ts +127 -41
|
@@ -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,
|
|
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,7 +105,7 @@ 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,
|
|
@@ -118,12 +127,12 @@ export class CredentialProviderVcdm2Jose implements IVcdmCredentialProvider {
|
|
|
118
127
|
}
|
|
119
128
|
|
|
120
129
|
/** {@inheritdoc ICredentialVerifier.verifyCredential} */
|
|
121
|
-
async verifyCredential(args:
|
|
130
|
+
async verifyCredential(args: IVerifyCredentialVcdmArgs, context: VerifierAgentContext): Promise<IVerifyResult> {
|
|
122
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
|
|
@@ -153,25 +162,25 @@ export class CredentialProviderVcdm2Jose implements IVcdmCredentialProvider {
|
|
|
153
162
|
},
|
|
154
163
|
})
|
|
155
164
|
verifiedCredential = verificationResult.verifiableCredential
|
|
156
|
-
|
|
165
|
+
|
|
157
166
|
const nbf = policies?.issuanceDate === false ? false : undefined
|
|
158
167
|
const exp = policies?.expirationDate === false ? false : undefined
|
|
159
168
|
const options = { ...otherOptions, policies: { ...policies, nbf, exp, iat: nbf, format: policies?.format ?? true } }
|
|
160
|
-
|
|
169
|
+
|
|
161
170
|
const verified: Partial<VerifiedCredential> = await verifyDIDJWT(asArray(uniform.proof)[0].jwt, { resolver, ...options }, context)
|
|
162
171
|
verified.verifiableCredential = normalizeCredential(verified.jwt as string, true)
|
|
163
172
|
if (options?.policies?.format !== false) {
|
|
164
173
|
validateCredentialPayload(verified.verifiableCredential)
|
|
165
174
|
}
|
|
166
|
-
|
|
175
|
+
|
|
167
176
|
// if credential was presented with other fields, make sure those fields match what's in the JWT
|
|
168
177
|
if (typeof credential !== 'string' && asArray(credential.proof)[0].type === 'JwtProof2020') {
|
|
169
178
|
const credentialCopy = JSON.parse(JSON.stringify(credential))
|
|
170
179
|
delete credentialCopy.proof.jwt
|
|
171
|
-
|
|
180
|
+
|
|
172
181
|
const verifiedCopy = JSON.parse(JSON.stringify(verifiedCredential))
|
|
173
182
|
delete verifiedCopy.proof.jwt
|
|
174
|
-
|
|
183
|
+
|
|
175
184
|
if (canonicalize(credentialCopy) !== canonicalize(verifiedCopy)) {
|
|
176
185
|
verificationResult.verified = false
|
|
177
186
|
verificationResult.error = new Error('invalid_credential: Credential JSON does not match JWT payload')
|
|
@@ -202,7 +211,13 @@ export class CredentialProviderVcdm2Jose implements IVcdmCredentialProvider {
|
|
|
202
211
|
|
|
203
212
|
const managedIdentifier = await agent.identifierManagedGetByDid({ identifier: holder, kmsKeyRef: keyRef })
|
|
204
213
|
const identifier = managedIdentifier.identifier
|
|
205
|
-
const key = await pickSigningKey(
|
|
214
|
+
const key = await pickSigningKey(
|
|
215
|
+
{
|
|
216
|
+
identifier: managedIdentifier.identifier,
|
|
217
|
+
kmsKeyRef: managedIdentifier.kmsKeyRef,
|
|
218
|
+
},
|
|
219
|
+
context,
|
|
220
|
+
)
|
|
206
221
|
|
|
207
222
|
debug('Signing VC with', identifier.did)
|
|
208
223
|
let alg = 'ES256'
|
|
@@ -283,10 +298,28 @@ export class CredentialProviderVcdm2Jose implements IVcdmCredentialProvider {
|
|
|
283
298
|
...otherOptions,
|
|
284
299
|
})
|
|
285
300
|
if (result) {
|
|
301
|
+
/**
|
|
302
|
+
* {id: 'valid_signature', valid: true},
|
|
303
|
+
* // {id: 'issuer_did_resolves', valid: true},
|
|
304
|
+
* // {id: 'expiration', valid: true},
|
|
305
|
+
* // {id: 'revocation_status', valid: true},
|
|
306
|
+
* // {id: 'suspension_status', valid: true}
|
|
307
|
+
*/
|
|
286
308
|
return {
|
|
287
309
|
verified: true,
|
|
288
|
-
|
|
289
|
-
|
|
310
|
+
results: [
|
|
311
|
+
{
|
|
312
|
+
verified: true,
|
|
313
|
+
presentation: result.verifiablePresentation,
|
|
314
|
+
log: [
|
|
315
|
+
{
|
|
316
|
+
id: 'valid_signature',
|
|
317
|
+
valid: true,
|
|
318
|
+
},
|
|
319
|
+
],
|
|
320
|
+
},
|
|
321
|
+
],
|
|
322
|
+
} satisfies IVerifyResult
|
|
290
323
|
}
|
|
291
324
|
} catch (e: any) {
|
|
292
325
|
message = e.message
|
|
@@ -374,26 +407,79 @@ export async function verifierSignature(
|
|
|
374
407
|
if (!credIssuer) {
|
|
375
408
|
throw new Error(`${JWT_ERROR.INVALID_JWT}: No DID has been found in the JWT`)
|
|
376
409
|
}
|
|
377
|
-
|
|
410
|
+
let resolution: ExternalIdentifierResult | undefined = undefined
|
|
411
|
+
try {
|
|
412
|
+
resolution = await agent.identifierExternalResolve({ identifier: credIssuer })
|
|
413
|
+
} catch (e: any) {}
|
|
414
|
+
const credential = CredentialMapper.toUniformCredential(jwt)
|
|
415
|
+
|
|
416
|
+
const expired = 'validUntil' in credential && !!credential.validUntil && Date.parse(credential.validUntil) < new Date().getTime() / 1000
|
|
378
417
|
|
|
379
418
|
const didOpts = { method: 'did', identifier: credIssuer } satisfies ExternalIdentifierDidOpts
|
|
380
419
|
const jwtResult = await agent.jwtVerifyJwsSignature({
|
|
381
420
|
jws: jwt,
|
|
382
421
|
// @ts-ignore
|
|
383
|
-
jwk: resolution
|
|
422
|
+
jwk: resolution?.jwks[0].jwk,
|
|
384
423
|
opts: { ...(isDidIdentifier(credIssuer) && { did: didOpts }) },
|
|
385
424
|
})
|
|
386
|
-
|
|
387
|
-
|
|
425
|
+
const error = jwtResult.error || expired || !resolution
|
|
426
|
+
const errorMessage = expired ? 'Credential is expired' : !resolution ? `Issuer ${credIssuer} could not be resolved` : jwtResult.message
|
|
427
|
+
|
|
428
|
+
if (error) {
|
|
429
|
+
const log = [
|
|
430
|
+
{
|
|
431
|
+
id: 'valid_signature',
|
|
432
|
+
valid: false,
|
|
433
|
+
},
|
|
434
|
+
{ id: 'issuer_did_resolves', valid: resolution != undefined },
|
|
435
|
+
{ id: 'expiration', valid: !expired },
|
|
436
|
+
]
|
|
388
437
|
return {
|
|
389
438
|
verified: false,
|
|
390
|
-
error: { message:
|
|
439
|
+
error: { message: errorMessage, errorCode: jwtResult.name },
|
|
440
|
+
log,
|
|
441
|
+
results: [
|
|
442
|
+
{
|
|
443
|
+
verified: false,
|
|
444
|
+
credential: jwt,
|
|
445
|
+
log,
|
|
446
|
+
error: { message: errorMessage, errorCode: jwtResult.name },
|
|
447
|
+
},
|
|
448
|
+
],
|
|
391
449
|
payload,
|
|
392
450
|
didResolutionResult: resolution,
|
|
393
451
|
jwt,
|
|
394
|
-
}
|
|
452
|
+
} satisfies IVerifyResult
|
|
395
453
|
}
|
|
396
|
-
|
|
454
|
+
|
|
455
|
+
const log = [
|
|
456
|
+
{
|
|
457
|
+
id: 'valid_signature',
|
|
458
|
+
valid: true,
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
id: 'issuer_did_resolves',
|
|
462
|
+
valid: true,
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
id: 'expiration',
|
|
466
|
+
valid: true,
|
|
467
|
+
},
|
|
468
|
+
]
|
|
469
|
+
return {
|
|
470
|
+
verified: true,
|
|
471
|
+
log,
|
|
472
|
+
results: [
|
|
473
|
+
{
|
|
474
|
+
verified: true,
|
|
475
|
+
credential,
|
|
476
|
+
log,
|
|
477
|
+
},
|
|
478
|
+
],
|
|
479
|
+
payload,
|
|
480
|
+
didResolutionResult: resolution,
|
|
481
|
+
jwt,
|
|
482
|
+
} satisfies IVerifyResult
|
|
397
483
|
}
|
|
398
484
|
|
|
399
485
|
/*
|