@sphereon/ssi-sdk.credential-vcdm2-jose-provider 0.33.1-next.68 → 0.34.0

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.
@@ -1,14 +1,13 @@
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'
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
- IVerifyCredentialLDArgs,
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: IVerifyCredentialLDArgs, context: VerifierAgentContext): Promise<IVerifyResult> {
122
- let { credential /*policies, ...otherOptions*/ } = args
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: ' + 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
- verificationResult = await verifierSignature({ jwt }, context)
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({ identifier: managedIdentifier.identifier, kmsKeyRef: managedIdentifier.kmsKeyRef }, context)
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
- verifiablePresentation: result,
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
- const resolution = await agent.identifierExternalResolve({ identifier: credIssuer })
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.jwks[0].jwk,
384
- opts: { ...(isDidIdentifier(credIssuer) && { did: didOpts }) },
431
+ jwk: resolution?.jwks[0].jwk,
432
+ opts: { ...(isDidIdentifier(credIssuer) && { did: didOpts }) }
385
433
  })
386
-
387
- if (jwtResult.error) {
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: jwtResult.message, errorCode: jwtResult.name },
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
- } as IVerifyResult
461
+ jwt
462
+ } satisfies IVerifyResult
395
463
  }
396
- return { verified: true, payload, didResolutionResult: resolution, jwt } as IVerifyResult
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<