@sphereon/ssi-sdk.credential-vcdm2-sdjwt-provider 0.34.1-next.85

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.
@@ -0,0 +1,641 @@
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'
11
+ import {
12
+ type ICanIssueCredentialTypeArgs,
13
+ type ICanVerifyDocumentTypeArgs,
14
+ type ICreateVerifiableCredentialLDArgs,
15
+ type ICreateVerifiablePresentationLDArgs,
16
+ type IVcdmCredentialProvider,
17
+ type IVcdmIssuerAgentContext,
18
+ IVcdmVerifierAgentContext,
19
+ IVerifyCredentialVcdmArgs,
20
+ IVerifyPresentationLDArgs,
21
+ pickSigningKey,
22
+ preProcessCredentialPayload,
23
+ preProcessPresentation,
24
+ } from '@sphereon/ssi-sdk.credential-vcdm'
25
+ import { CredentialMapper, isVcdm2Credential, type IVerifyResult, type OriginalVerifiableCredential } from '@sphereon/ssi-types'
26
+ import type {
27
+ IAgentContext,
28
+ IDIDManager,
29
+ IIdentifier,
30
+ IKey,
31
+ IKeyManager,
32
+ IResolver,
33
+ VerifiableCredential,
34
+ VerificationPolicies,
35
+ VerifierAgentContext,
36
+ } from '@veramo/core'
37
+
38
+ import Debug from 'debug'
39
+
40
+ import { decodeJWT, JWT_ERROR } from 'did-jwt'
41
+
42
+ // @ts-ignore
43
+ import { normalizeCredential, normalizePresentation, verifyPresentation as verifyPresentationJWT } from 'did-jwt-vc'
44
+
45
+ import { type Resolvable } from 'did-resolver'
46
+
47
+ import { SELF_ISSUED_V0_1, SELF_ISSUED_V2, SELF_ISSUED_V2_VC_INTEROP } from '../did-jwt/JWT'
48
+ import { getIssuerFromSdJwt, ISDJwtPlugin } from '@sphereon/ssi-sdk.sd-jwt'
49
+ // import {validateCredentialPayload} from "did-jwt-vc/src";
50
+
51
+ const debug = Debug('sphereon:ssi-sdk:credential-vcdm2-sdjwt')
52
+
53
+ /**
54
+ * A handler that implements the {@link IVcdmCredentialProvider} methods.
55
+ *
56
+ * @beta This API may change without a BREAKING CHANGE notice.
57
+ */
58
+ export class CredentialProviderVcdm2SdJwt implements IVcdmCredentialProvider {
59
+ /** {@inheritdoc @veramo/credential-w3c#AbstractCredentialProvider.matchKeyForType} */
60
+ matchKeyForType(key: IKey): boolean {
61
+ return this.matchKeyForJWT(key)
62
+ }
63
+
64
+ /** {@inheritdoc @veramo/credential-w3c#AbstractCredentialProvider.getTypeProofFormat} */
65
+ getTypeProofFormat(): string {
66
+ return 'vc+sd-jwt'
67
+ }
68
+
69
+ /** {@inheritdoc @veramo/credential-w3c#AbstractCredentialProvider.canIssueCredentialType} */
70
+ canIssueCredentialType(args: ICanIssueCredentialTypeArgs): boolean {
71
+ const format = args.proofFormat.toLowerCase()
72
+ // TODO: Create type
73
+ return format === 'vc+sd-jwt' || format === 'vcdm2_sdjwt'
74
+ }
75
+
76
+ /** {@inheritdoc @veramo/credential-w3c#AbstractCredentialProvider.canVerifyDocumentType */
77
+ canVerifyDocumentType(args: ICanVerifyDocumentTypeArgs): boolean {
78
+ const { document } = args
79
+ const jwt = typeof document === 'string' ? document : (<VerifiableCredential>document)?.proof?.jwt
80
+ if (!jwt) {
81
+ return false
82
+ }
83
+ const { payload } = decodeJWT(jwt.split('~')[0])
84
+ return isVcdm2Credential(payload)
85
+ }
86
+
87
+ /** {@inheritdoc @veramo/credential-w3c#AbstractCredentialProvider.createVerifiableCredential} */
88
+ async createVerifiableCredential(args: ICreateVerifiableCredentialLDArgs, context: IVcdmIssuerAgentContext): Promise<VerifiableCredentialSP> {
89
+ const { keyRef } = args
90
+ const agent = assertContext(context).agent
91
+ const { credential, issuer } = preProcessCredentialPayload(args)
92
+ if (!isVcdm2Credential(credential)) {
93
+ return Promise.reject(new Error('invalid_argument: credential must be a VCDM2 credential. Context: ' + credential['@context']))
94
+ } else if (!contextHasPlugin<ISDJwtPlugin>(context, 'createSdJwtVc')) {
95
+ return Promise.reject(
96
+ new Error('invalid_argument: SD-JWT plugin not available. Please install @sphereon/ssi-sdk.sd-jwt and configure agent for VCDM2 SD-JWT'),
97
+ )
98
+ }
99
+ let identifier: IIdentifier
100
+ try {
101
+ identifier = await agent.didManagerGet({ did: issuer })
102
+ } catch (e) {
103
+ return Promise.reject(new Error(`invalid_argument: ${credential.issuer} must be a DID managed by this agent. ${e}`))
104
+ }
105
+ const managedIdentifier = await agent.identifierManagedGetByDid({ identifier: identifier.did, kmsKeyRef: keyRef })
106
+ const key = await pickSigningKey({ identifier, kmsKeyRef: keyRef }, context)
107
+
108
+ // TODO: Probably wise to give control to caller as well, as some key types allow multiple signature algos
109
+ const alg = (await signatureAlgorithmFromKey({ key })) as string
110
+ debug('Signing VC with', identifier.did, alg)
111
+ credential.issuer = { id: identifier.did }
112
+
113
+ const result = await context.agent.createSdJwtVc({
114
+ type: 'vc+sd-jwt',
115
+ credentialPayload: credential,
116
+ resolution: managedIdentifier,
117
+ disclosureFrame: args.opts?.disclosureFrame,
118
+ })
119
+
120
+ const jwt = result.credential.split('~')[0]
121
+
122
+ // debug(jwt)
123
+ const normalized = normalizeCredential(jwt)
124
+ normalized.proof.jwt = result.credential
125
+ return normalized
126
+ }
127
+
128
+ /** {@inheritdoc ICredentialVerifier.verifyCredential} */
129
+ async verifyCredential(args: IVerifyCredentialVcdmArgs, context: VerifierAgentContext): Promise<IVerifyResult> {
130
+ let { credential, policies /*...otherOptions*/ } = args
131
+ const uniform = CredentialMapper.toUniformCredential(credential as OriginalVerifiableCredential)
132
+ // let verifiedCredential: VerifiableCredential
133
+ if (!isVcdm2Credential(uniform)) {
134
+ return Promise.reject(new Error('invalid_argument: credential must be a VCDM2 credential. Context: ' + uniform['@context']))
135
+ } else if (!contextHasPlugin<ISDJwtPlugin>(context, 'createSdJwtVc')) {
136
+ return Promise.reject(
137
+ new Error('invalid_argument: SD-JWT plugin not available. Please install @sphereon/ssi-sdk.sd-jwt and configure agent for VCDM2 SD-JWT'),
138
+ )
139
+ }
140
+ let verificationResult: IVerifyResult = { verified: false }
141
+ let jwt: string | undefined = typeof credential === 'string' ? credential : asArray(uniform.proof)?.[0]?.jwt
142
+ if (!jwt) {
143
+ return Promise.reject(new Error('invalid_argument: credential must be a VCDM2 credential in JOSE format (string)'))
144
+ }
145
+
146
+ try {
147
+ const result = await context.agent.verifySdJwtVc({ credential: jwt })
148
+ if (result.payload) {
149
+ verificationResult = {
150
+ verified: true,
151
+ results: [
152
+ {
153
+ credential: credential as OriginalVerifiableCredential,
154
+ verified: true,
155
+ log: [
156
+ {
157
+ id: 'valid_signature',
158
+ valid: true,
159
+ },
160
+ {
161
+ id: 'issuer_did_resolves',
162
+ valid: true,
163
+ },
164
+ ],
165
+ },
166
+ ],
167
+ }
168
+ }
169
+ } catch (e) {
170
+ verificationResult = { verified: false, error: { message: e.message, errorCode: e.name } }
171
+ }
172
+
173
+ policies = {
174
+ ...policies,
175
+ nbf: policies?.nbf ?? policies?.issuanceDate ?? policies?.validFrom,
176
+ iat: policies?.iat ?? policies?.issuanceDate ?? policies?.validFrom,
177
+ exp: policies?.exp ?? policies?.expirationDate ?? policies?.validUntil,
178
+ aud: policies?.aud ?? policies?.audience,
179
+ }
180
+ verificationResult = await verifierSignature({ jwt: jwt.split('~')[0], policies }, context)
181
+ return verificationResult
182
+ }
183
+
184
+ /** {@inheritdoc @veramo/credential-w3c#AbstractCredentialProvider.createVerifiablePresentation} */
185
+ async createVerifiablePresentation(args: ICreateVerifiablePresentationLDArgs, context: IVcdmIssuerAgentContext): Promise<VerifiablePresentationSP> {
186
+ const { presentation, holder } = preProcessPresentation(args)
187
+ let { domain, challenge, keyRef /* removeOriginalFields, keyRef, now, ...otherOptions*/ } = args
188
+
189
+ const agent = assertContext(context).agent
190
+
191
+ const managedIdentifier = await agent.identifierManagedGetByDid({ identifier: holder, kmsKeyRef: keyRef })
192
+ const identifier = managedIdentifier.identifier
193
+ const key = await pickSigningKey(
194
+ {
195
+ identifier: managedIdentifier.identifier,
196
+ kmsKeyRef: managedIdentifier.kmsKeyRef,
197
+ },
198
+ context,
199
+ )
200
+
201
+ debug('Signing VC with', identifier.did)
202
+ let alg = 'ES256'
203
+ if (key.type === 'Ed25519') {
204
+ alg = 'EdDSA'
205
+ } else if (key.type === 'Secp256k1') {
206
+ alg = 'ES256K'
207
+ }
208
+
209
+ const header: JwsHeader = {
210
+ kid: key.meta.verificationMethod.id ?? key.kid,
211
+ alg,
212
+ typ: 'vp+jwt',
213
+ cty: 'vp',
214
+ }
215
+ const payload: JwsPayload = {
216
+ ...presentation,
217
+ ...(domain && { aud: domain }),
218
+ ...(challenge && { nonce: challenge }),
219
+ }
220
+
221
+ const jwt = await agent.jwtCreateJwsCompactSignature({
222
+ mode: 'did',
223
+ issuer: managedIdentifier,
224
+ payload,
225
+ protectedHeader: header,
226
+ clientIdScheme: 'did',
227
+ })
228
+
229
+ debug(jwt)
230
+ return normalizePresentation(jwt.jwt)
231
+ }
232
+
233
+ /** {@inheritdoc @veramo/credential-w3c#AbstractCredentialProvider.verifyPresentation} */
234
+ async verifyPresentation(args: IVerifyPresentationLDArgs, context: VerifierAgentContext): Promise<IVerifyResult> {
235
+ let { presentation, domain, challenge, fetchRemoteContexts, policies, ...otherOptions } = args
236
+ let jwt: string
237
+ if (typeof presentation === 'string') {
238
+ jwt = presentation
239
+ } else {
240
+ jwt = asArray(presentation.proof)[0].jwt
241
+ }
242
+ const resolver = {
243
+ resolve: (didUrl: string) =>
244
+ context.agent.resolveDid({
245
+ didUrl,
246
+ options: otherOptions?.resolutionOptions,
247
+ }),
248
+ } as Resolvable
249
+
250
+ let audience = domain
251
+ if (!audience) {
252
+ const { payload } = await decodeJWT(jwt)
253
+ if (payload.aud) {
254
+ // automatically add a managed DID as audience if one is found
255
+ const intendedAudience = asArray(payload.aud)
256
+ const managedDids = await context.agent.didManagerFind()
257
+ const filtered = managedDids.filter((identifier) => intendedAudience.includes(identifier.did))
258
+ if (filtered.length > 0) {
259
+ audience = filtered[0].did
260
+ }
261
+ }
262
+ }
263
+
264
+ let message, errorCode
265
+ try {
266
+ const result = await verifyPresentationJWT(jwt, resolver, {
267
+ challenge,
268
+ domain,
269
+ audience,
270
+ policies: {
271
+ ...policies,
272
+ nbf: policies?.nbf ?? policies?.issuanceDate,
273
+ iat: policies?.iat ?? policies?.issuanceDate,
274
+ exp: policies?.exp ?? policies?.expirationDate,
275
+ aud: policies?.aud ?? policies?.audience,
276
+ },
277
+ ...otherOptions,
278
+ })
279
+ if (result) {
280
+ /**
281
+ * {id: 'valid_signature', valid: true},
282
+ * // {id: 'issuer_did_resolves', valid: true},
283
+ * // {id: 'expiration', valid: true},
284
+ * // {id: 'revocation_status', valid: true},
285
+ * // {id: 'suspension_status', valid: true}
286
+ */
287
+ return {
288
+ verified: true,
289
+ results: [
290
+ {
291
+ verified: true,
292
+ presentation: result.verifiablePresentation,
293
+ log: [
294
+ {
295
+ id: 'valid_signature',
296
+ valid: true,
297
+ },
298
+ ],
299
+ },
300
+ ],
301
+ } satisfies IVerifyResult
302
+ }
303
+ } catch (e: any) {
304
+ message = e.message
305
+ errorCode = e.errorCode
306
+ }
307
+ return {
308
+ verified: false,
309
+ error: {
310
+ message,
311
+ errorCode: errorCode ? errorCode : message?.split(':')[0],
312
+ },
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Checks if a key is suitable for signing JWT payloads.
318
+ * @param key - the key to check
319
+ * @param context - the Veramo agent context, unused here
320
+ *
321
+ * @beta
322
+ */
323
+ matchKeyForJWT(key: IKey): boolean {
324
+ switch (key.type) {
325
+ case 'Ed25519':
326
+ case 'Secp256r1':
327
+ return true
328
+ case 'Secp256k1':
329
+ return intersect(key.meta?.algorithms ?? [], ['ES256K', 'ES256K-R']).length > 0
330
+ default:
331
+ return false
332
+ }
333
+ }
334
+
335
+ wrapSigner(context: IAgentContext<Pick<IKeyManager, 'keyManagerSign'>>, key: IKey, algorithm?: string) {
336
+ return async (data: string | Uint8Array): Promise<string> => {
337
+ const result = await context.agent.keyManagerSign({ keyRef: key.kid, data: <string>data, algorithm })
338
+ return result
339
+ }
340
+ }
341
+ }
342
+
343
+ export async function verifierSignature(
344
+ { jwt, policies }: { jwt: string; policies: VerificationPolicies /*resolver: Resolvable*/ },
345
+ verifierContext: VerifierAgentContext,
346
+ ): Promise<IVerifyResult> {
347
+ let credIssuer: string | undefined = undefined
348
+ const context = assertContext(verifierContext)
349
+ const agent = context.agent
350
+ const { payload, header /*signature, data*/ } = decodeJWT(jwt)
351
+
352
+
353
+
354
+ if (!payload.issuer) {
355
+ throw new Error(`${JWT_ERROR.INVALID_JWT}: JWT iss or client_id are required`)
356
+ }
357
+ const issuer = getIssuerFromSdJwt(payload)
358
+ if (issuer === SELF_ISSUED_V2 || issuer === SELF_ISSUED_V2_VC_INTEROP) {
359
+ if (!payload.credentialSubject.id) {
360
+ throw new Error(`${JWT_ERROR.INVALID_JWT}: JWT credentialSubject.id is required`)
361
+ }
362
+ if (typeof payload.sub_jwk === 'undefined') {
363
+ credIssuer = payload.sub
364
+ } else {
365
+ credIssuer = (header.kid || '').split('#')[0]
366
+ }
367
+ } else if (issuer === SELF_ISSUED_V0_1) {
368
+ if (!payload.did) {
369
+ throw new Error(`${JWT_ERROR.INVALID_JWT}: JWT did is required`)
370
+ }
371
+ credIssuer = payload.did
372
+ } else if (!issuer && payload.scope === 'openid' && payload.redirect_uri) {
373
+ // SIOP Request payload
374
+ // https://identity.foundation/jwt-vc-presentation-profile/#self-issued-op-request-object
375
+ if (!payload.client_id) {
376
+ throw new Error(`${JWT_ERROR.INVALID_JWT}: JWT client_id is required`)
377
+ }
378
+ credIssuer = payload.client_id
379
+ } else if (issuer?.indexOf('did:') === 0) {
380
+ credIssuer = issuer
381
+ } else if (header.kid?.indexOf('did:') === 0) {
382
+ // OID4VCI expects iss to be the client and kid, to be the DID VM
383
+ credIssuer = (header.kid || '').split('#')[0]
384
+ } else if (typeof payload.issuer === 'string') {
385
+ credIssuer = payload.issuer
386
+ } else if (payload.issuer?.id) {
387
+ credIssuer = payload.issuer.id
388
+ }
389
+
390
+ if (!credIssuer) {
391
+ throw new Error(`${JWT_ERROR.INVALID_JWT}: No DID has been found in the JWT`)
392
+ }
393
+ let resolution: ExternalIdentifierResult | undefined = undefined
394
+ try {
395
+ resolution = await agent.identifierExternalResolve({ identifier: credIssuer })
396
+ } catch (e: any) {}
397
+ const credential = CredentialMapper.toUniformCredential(jwt)
398
+
399
+ const validFromError =
400
+ policies.nbf !== false &&
401
+ policies.iat !== false &&
402
+ 'validFrom' in credential &&
403
+ !!credential.validFrom &&
404
+ Date.parse(credential.validFrom) > new Date().getTime()
405
+ const expired =
406
+ policies.exp !== false && 'validUntil' in credential && !!credential.validUntil && Date.parse(credential.validUntil) < new Date().getTime()
407
+
408
+ const didOpts = { method: 'did', identifier: credIssuer } satisfies ExternalIdentifierDidOpts
409
+ const jwtResult = await agent.jwtVerifyJwsSignature({
410
+ jws: jwt,
411
+ // @ts-ignore
412
+ jwk: resolution?.jwks[0].jwk,
413
+ opts: { ...(isDidIdentifier(credIssuer) && { did: didOpts }) },
414
+ })
415
+ const error = jwtResult.error || expired || !resolution
416
+ const errorMessage = expired
417
+ ? 'Credential is expired'
418
+ : validFromError
419
+ ? 'Credential is not valid yet'
420
+ : !resolution
421
+ ? `Issuer ${credIssuer} could not be resolved`
422
+ : jwtResult.message
423
+
424
+ if (error) {
425
+ const log = [
426
+ {
427
+ id: 'valid_signature',
428
+ valid: !jwtResult.error,
429
+ },
430
+ { id: 'issuer_did_resolves', valid: resolution != undefined },
431
+ { id: 'validFrom', valid: policies.nbf !== false && !validFromError },
432
+ { id: 'expiration', valid: policies.exp !== false && !expired },
433
+ ]
434
+ return {
435
+ verified: false,
436
+ error: { message: errorMessage, errorCode: jwtResult.name },
437
+ log,
438
+ results: [
439
+ {
440
+ verified: false,
441
+ credential: jwt,
442
+ log,
443
+ error: { message: errorMessage, errorCode: jwtResult.name },
444
+ },
445
+ ],
446
+ payload,
447
+ didResolutionResult: resolution,
448
+ jwt,
449
+ } satisfies IVerifyResult
450
+ }
451
+
452
+ const log = [
453
+ {
454
+ id: 'valid_signature',
455
+ valid: true,
456
+ },
457
+ {
458
+ id: 'issuer_did_resolves',
459
+ valid: true,
460
+ },
461
+ {
462
+ id: 'validFrom',
463
+ valid: true,
464
+ },
465
+ {
466
+ id: 'expiration',
467
+ valid: true,
468
+ },
469
+ ]
470
+ return {
471
+ verified: true,
472
+ log,
473
+ results: [
474
+ {
475
+ verified: true,
476
+ credential,
477
+ log,
478
+ },
479
+ ],
480
+ payload,
481
+ didResolutionResult: resolution,
482
+ jwt,
483
+ } satisfies IVerifyResult
484
+ }
485
+
486
+ /*
487
+ export async function verifyDIDJWT(
488
+ jwt: string,
489
+ options: JWTVerifyOptions = {
490
+ resolver: undefined,
491
+ auth: undefined,
492
+ audience: undefined,
493
+ callbackUrl: undefined,
494
+ skewTime: undefined,
495
+ proofPurpose: undefined,
496
+ policies: {},
497
+ },
498
+ verifierContext: VerifierAgentContext,
499
+ ): Promise<JWTVerified> {
500
+ const context = assertContext(verifierContext)
501
+ const agent = context.agent
502
+ if (!options.resolver) throw new Error('missing_resolver: No DID resolver has been configured')
503
+ const { payload, header, signature, data }: JWTDecoded = decodeJWT(jwt)
504
+ const proofPurpose: ProofPurposeTypes | undefined = Object.prototype.hasOwnProperty.call(options, 'auth')
505
+ ? options.auth
506
+ ? 'authentication'
507
+ : undefined
508
+ : options.proofPurpose
509
+
510
+ let credIssuer: string | undefined = undefined
511
+
512
+ if (!payload.iss && !payload.client_id) {
513
+ throw new Error(`${JWT_ERROR.INVALID_JWT}: JWT iss or client_id are required`)
514
+ }
515
+
516
+ if (payload.iss === SELF_ISSUED_V2 || payload.iss === SELF_ISSUED_V2_VC_INTEROP) {
517
+ if (!payload.sub) {
518
+ throw new Error(`${JWT_ERROR.INVALID_JWT}: JWT sub is required`)
519
+ }
520
+ if (typeof payload.sub_jwk === 'undefined') {
521
+ credIssuer = payload.sub
522
+ } else {
523
+ credIssuer = (header.kid || '').split('#')[0]
524
+ }
525
+ } else if (payload.iss === SELF_ISSUED_V0_1) {
526
+ if (!payload.did) {
527
+ throw new Error(`${JWT_ERROR.INVALID_JWT}: JWT did is required`)
528
+ }
529
+ credIssuer = payload.did
530
+ } else if (!payload.iss && payload.scope === 'openid' && payload.redirect_uri) {
531
+ // SIOP Request payload
532
+ // https://identity.foundation/jwt-vc-presentation-profile/#self-issued-op-request-object
533
+ if (!payload.client_id) {
534
+ throw new Error(`${JWT_ERROR.INVALID_JWT}: JWT client_id is required`)
535
+ }
536
+ credIssuer = payload.client_id
537
+ } else if (payload.iss?.indexOf('did:') === 0) {
538
+ credIssuer = payload.iss
539
+ } else if (header.kid?.indexOf('did:') === 0) {
540
+ // OID4VCI expects iss to be the client and kid, to be the DID VM
541
+ credIssuer = (header.kid || '').split('#')[0]
542
+ } else if (payload.iss) {
543
+ credIssuer = payload.iss
544
+ }
545
+
546
+ if (!credIssuer) {
547
+ throw new Error(`${JWT_ERROR.INVALID_JWT}: No DID has been found in the JWT`)
548
+ }
549
+
550
+ const resolution = await agent.identifierExternalResolve({ identifier: credIssuer })
551
+
552
+ const didOpts = { method: 'did', identifier: credIssuer } satisfies ExternalIdentifierDidOpts
553
+ const jwtResult = await agent.jwtVerifyJwsSignature({
554
+ jws: jwt,
555
+ // @ts-ignore
556
+ jwk: resolution.jwks[0],
557
+ opts: { ...(isDidIdentifier(credIssuer) && { did: didOpts }) },
558
+ })
559
+
560
+ if (jwtResult.error) {
561
+ return Promise.reject(Error(`Error validating credential: ${jwtResult.error}`))
562
+ }
563
+ const { didResolutionResult, authenticators, issuer }: DIDAuthenticator = await resolveAuthenticator(
564
+ options.resolver,
565
+ header.alg,
566
+ credIssuer,
567
+ proofPurpose,
568
+ )
569
+ const signer: VerificationMethod = verifyJWSDecoded({ header, data, signature } as JWSDecoded, authenticators)
570
+ const now: number = typeof options.policies?.now === 'number' ? options.policies.now : Math.floor(Date.now() / 1000)
571
+ const skewTime = typeof options.skewTime !== 'undefined' && options.skewTime >= 0 ? options.skewTime : NBF_SKEW
572
+ if (signer) {
573
+ const nowSkewed = now + skewTime
574
+ if (options.policies?.nbf !== false && payload.nbf) {
575
+ if (payload.nbf > nowSkewed) {
576
+ throw new Error(`${JWT_ERROR.INVALID_JWT}: JWT not valid before nbf: ${payload.nbf}`)
577
+ }
578
+ } else if (options.policies?.iat !== false && payload.iat && payload.iat > nowSkewed) {
579
+ throw new Error(`${JWT_ERROR.INVALID_JWT}: JWT not valid yet (issued in the future) iat: ${payload.iat}`)
580
+ }
581
+ if (options.policies?.exp !== false && payload.exp && payload.exp <= now - skewTime) {
582
+ throw new Error(`${JWT_ERROR.INVALID_JWT}: JWT has expired: exp: ${payload.exp} < now: ${now}`)
583
+ }
584
+ if (options.policies?.aud !== false && payload.aud) {
585
+ if (!options.audience && !options.callbackUrl) {
586
+ throw new Error(`${JWT_ERROR.INVALID_AUDIENCE}: JWT audience is required but your app address has not been configured`)
587
+ }
588
+ const audArray = Array.isArray(payload.aud) ? payload.aud : [payload.aud]
589
+ const matchedAudience = audArray.find((item: any) => options.audience === item || options.callbackUrl === item)
590
+
591
+ if (typeof matchedAudience === 'undefined') {
592
+ throw new Error(`${JWT_ERROR.INVALID_AUDIENCE}: JWT audience does not match your DID or callback url`)
593
+ }
594
+ }
595
+ return { verified: true, payload, didResolutionResult, issuer, signer, jwt, policies: options.policies }
596
+ }
597
+ throw new Error(
598
+ `${JWT_ERROR.INVALID_SIGNATURE}: JWT not valid. issuer DID document does not contain a verificationMethod that matches the signature.`,
599
+ )
600
+ }
601
+
602
+ function verifyJWSDecoded({ header, data, signature }: JWSDecoded, pubKeys: VerificationMethod | VerificationMethod[]): VerificationMethod {
603
+ if (!Array.isArray(pubKeys)) pubKeys = [pubKeys]
604
+ const signer: VerificationMethod = VerifierAlgorithm(header.alg)(data, signature, pubKeys)
605
+ return signer
606
+ }
607
+
608
+
609
+ export function validateCredentialPayload(payload: CredentialPayload): void {
610
+ validateContext(asArray(payload['@context']))
611
+ validateVcType(payload.type)
612
+ validateCredentialSubject(payload.credentialSubject)
613
+ if (payload.validFrom) validateTimestamp(payload.validFrom)
614
+ if (payload.validUntil) validateTimestamp(payload.validUntil)
615
+ }
616
+
617
+ export function validateContext(value: string | string[]): void {
618
+ const input = asArray(value)
619
+ if (input.length < 1 || input.indexOf(VCDM_CREDENTIAL_CONTEXT_V2) === -1) {
620
+ throw new TypeError(`${VC_ERROR.SCHEMA_ERROR}: @context is missing default context "${VCDM_CREDENTIAL_CONTEXT_V2}"`)
621
+ }
622
+ }
623
+ */
624
+ function assertContext(
625
+ context: IVcdmIssuerAgentContext | IVcdmVerifierAgentContext,
626
+ ): IAgentContext<
627
+ IResolver & IDIDManager & Pick<IKeyManager, 'keyManagerGet' | 'keyManagerSign' | 'keyManagerVerify'> & IJwtService & IIdentifierResolution
628
+ > {
629
+ if (!contextHasPlugin<IJwtService>(context, 'jwtPrepareJws')) {
630
+ throw Error(
631
+ 'JwtService plugin not found, which is required for JWT signing in the VCDM2 SD-JWT credential provider. Please add the JwtService plugin to your agent configuration.',
632
+ )
633
+ } else if (!contextHasPlugin<IIdentifierResolution>(context, 'identifierManagedGet')) {
634
+ throw Error(
635
+ 'Identifier resolution plugin not found, which is required for JWT signing in the VCDM2 SD-JWT credential provider. Please add the JwtService plugin to your agent configuration.',
636
+ )
637
+ }
638
+ return context as IAgentContext<
639
+ IResolver & IDIDManager & Pick<IKeyManager, 'keyManagerGet' | 'keyManagerSign' | 'keyManagerVerify'> & IJwtService & IIdentifierResolution
640
+ >
641
+ }