@sphereon/ssi-sdk.credential-vcdm2-jose-provider 0.33.1-feature.jose.vcdm.59

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