@sphereon/ssi-sdk.sd-jwt 0.34.1-next.7 → 0.34.1-next.86
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 +254 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +66 -8
- package/dist/index.d.ts +66 -8
- package/dist/index.js +252 -41
- package/dist/index.js.map +1 -1
- package/package.json +21 -20
- package/src/__tests__/{sd-jwt.test.ts → sd-jwt-vc.test.ts} +6 -4
- package/src/__tests__/sd-jwt-vcdm2.test.ts +316 -0
- package/src/action-handler.ts +83 -36
- package/src/sdJwtVcdm2Instance.ts +155 -0
- package/src/types.ts +40 -6
- package/src/utils.ts +32 -1
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { beforeAll, describe, expect, it } from 'vitest'
|
|
2
|
+
import { KBJwt } from '@sd-jwt/core'
|
|
3
|
+
import { decodeSdJwt } from '@sd-jwt/decode'
|
|
4
|
+
import { SdJwtVcPayload } from '@sd-jwt/sd-jwt-vc'
|
|
5
|
+
import { DisclosureFrame, kbPayload } from '@sd-jwt/types'
|
|
6
|
+
import { JwkDIDProvider } from '@sphereon/ssi-sdk-ext.did-provider-jwk'
|
|
7
|
+
import { getDidJwkResolver } from '@sphereon/ssi-sdk-ext.did-resolver-jwk'
|
|
8
|
+
import { IdentifierResolution, IIdentifierResolution } from '@sphereon/ssi-sdk-ext.identifier-resolution'
|
|
9
|
+
import { IJwtService, JwtService } from '@sphereon/ssi-sdk-ext.jwt-service'
|
|
10
|
+
import { MemoryKeyStore, MemoryPrivateKeyStore, SphereonKeyManager } from '@sphereon/ssi-sdk-ext.key-manager'
|
|
11
|
+
import { SphereonKeyManagementSystem } from '@sphereon/ssi-sdk-ext.kms-local'
|
|
12
|
+
import { ImDLMdoc, MDLMdoc } from '@sphereon/ssi-sdk.mdl-mdoc'
|
|
13
|
+
import { createAgent, IDIDManager, IKeyManager, IResolver, TAgent } from '@veramo/core'
|
|
14
|
+
import { DIDManager, MemoryDIDStore } from '@veramo/did-manager'
|
|
15
|
+
import { DIDResolverPlugin } from '@veramo/did-resolver'
|
|
16
|
+
import { DIDDocument, Resolver, VerificationMethod } from 'did-resolver'
|
|
17
|
+
import { defaultGenerateDigest } from '../defaultCallbacks'
|
|
18
|
+
import { ISDJwtPlugin, SDJwtPlugin } from '../index'
|
|
19
|
+
import { SdJwtVcdm2Payload } from '@sphereon/ssi-types'
|
|
20
|
+
|
|
21
|
+
type AgentType = IDIDManager & IKeyManager & IIdentifierResolution & IJwtService & IResolver & ISDJwtPlugin & ImDLMdoc
|
|
22
|
+
|
|
23
|
+
describe('Agent plugin', () => {
|
|
24
|
+
let agent: TAgent<AgentType>
|
|
25
|
+
|
|
26
|
+
let issuer: string
|
|
27
|
+
|
|
28
|
+
let holder: string
|
|
29
|
+
|
|
30
|
+
// Issuer Define the claims object with the user's information
|
|
31
|
+
const claims = {
|
|
32
|
+
sub: '',
|
|
33
|
+
credentialSubject: {
|
|
34
|
+
given_name: 'John',
|
|
35
|
+
family_name: 'Deo',
|
|
36
|
+
email: 'johndeo@example.com',
|
|
37
|
+
phone: '+1-202-555-0101',
|
|
38
|
+
address: {
|
|
39
|
+
street_address: '123 Main St',
|
|
40
|
+
locality: 'Anytown',
|
|
41
|
+
region: 'Anystate',
|
|
42
|
+
country: 'US',
|
|
43
|
+
},
|
|
44
|
+
birthdate: '1940-01-01',
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Issuer Define the disclosure frame to specify which claims can be disclosed
|
|
49
|
+
const disclosureFrame: DisclosureFrame<typeof claims> = {
|
|
50
|
+
credentialSubject: {
|
|
51
|
+
_sd: ['given_name', 'family_name', 'email', 'phone', 'address', 'birthdate'],
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
let testVcdm2SdJwtCredentialPayload: SdJwtVcdm2Payload
|
|
55
|
+
|
|
56
|
+
beforeAll(async () => {
|
|
57
|
+
agent = createAgent<AgentType>({
|
|
58
|
+
plugins: [
|
|
59
|
+
new SDJwtPlugin(),
|
|
60
|
+
new IdentifierResolution(),
|
|
61
|
+
new JwtService(),
|
|
62
|
+
new SphereonKeyManager({
|
|
63
|
+
store: new MemoryKeyStore(),
|
|
64
|
+
kms: {
|
|
65
|
+
local: new SphereonKeyManagementSystem(new MemoryPrivateKeyStore()),
|
|
66
|
+
},
|
|
67
|
+
}),
|
|
68
|
+
new DIDResolverPlugin({
|
|
69
|
+
resolver: new Resolver({
|
|
70
|
+
...getDidJwkResolver(),
|
|
71
|
+
}),
|
|
72
|
+
}),
|
|
73
|
+
new DIDManager({
|
|
74
|
+
store: new MemoryDIDStore(),
|
|
75
|
+
defaultProvider: 'did:jwk',
|
|
76
|
+
providers: {
|
|
77
|
+
'did:jwk': new JwkDIDProvider({
|
|
78
|
+
defaultKms: 'local',
|
|
79
|
+
}),
|
|
80
|
+
},
|
|
81
|
+
}),
|
|
82
|
+
],
|
|
83
|
+
})
|
|
84
|
+
issuer = await agent
|
|
85
|
+
.didManagerCreate({
|
|
86
|
+
kms: 'local',
|
|
87
|
+
provider: 'did:jwk',
|
|
88
|
+
alias: 'issuer',
|
|
89
|
+
//we use this curve since nodejs does not support ES256k which is the default one.
|
|
90
|
+
options: { keyType: 'Secp256r1' },
|
|
91
|
+
})
|
|
92
|
+
.then((did) => {
|
|
93
|
+
// we add a key reference
|
|
94
|
+
return `${did.did}#0`
|
|
95
|
+
})
|
|
96
|
+
holder = await agent
|
|
97
|
+
.didManagerCreate({
|
|
98
|
+
kms: 'local',
|
|
99
|
+
provider: 'did:jwk',
|
|
100
|
+
alias: 'holder',
|
|
101
|
+
//we use this curve since nodejs does not support ES256k which is the default one.
|
|
102
|
+
options: { keyType: 'Secp256r1' },
|
|
103
|
+
})
|
|
104
|
+
.then((did) => `${did.did}#0`)
|
|
105
|
+
claims.sub = holder
|
|
106
|
+
|
|
107
|
+
testVcdm2SdJwtCredentialPayload = {
|
|
108
|
+
...claims,
|
|
109
|
+
'@context': ['https://www.w3.org/ns/credentials/v2'],
|
|
110
|
+
type: ['VerifiableCredential'],
|
|
111
|
+
issuer,
|
|
112
|
+
validFrom: '2021-01-01T00:00:00Z',
|
|
113
|
+
// iat: Math.floor(new Date().getTime() / 1000),
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('create a sd-jwt vcdm2', async () => {
|
|
118
|
+
const credentialPayload: SdJwtVcdm2Payload = {
|
|
119
|
+
...claims,
|
|
120
|
+
'@context': ['https://www.w3.org/ns/credentials/v2'],
|
|
121
|
+
type: ['VerifiableCredential'],
|
|
122
|
+
issuer,
|
|
123
|
+
validFrom: '2021-01-01T00:00:00Z',
|
|
124
|
+
iat: Math.floor(new Date().getTime() / 1000),
|
|
125
|
+
}
|
|
126
|
+
const credential = await agent.createSdJwtVc({
|
|
127
|
+
credentialPayload,
|
|
128
|
+
disclosureFrame,
|
|
129
|
+
})
|
|
130
|
+
expect(credential).toBeDefined()
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('create sd-jwt vcdm2 without an issuer', async () => {
|
|
134
|
+
const credentialPayload = {
|
|
135
|
+
...claims,
|
|
136
|
+
'@context': ['https://www.w3.org/ns/credentials/v2'],
|
|
137
|
+
type: ['VerifiableCredential'],
|
|
138
|
+
validFrom: '2021-01-01T00:00:00Z',
|
|
139
|
+
// iat: Math.floor(new Date().getTime() / 1000),
|
|
140
|
+
}
|
|
141
|
+
await expect(
|
|
142
|
+
agent.createSdJwtVc({
|
|
143
|
+
credentialPayload: credentialPayload as unknown as SdJwtVcPayload,
|
|
144
|
+
disclosureFrame,
|
|
145
|
+
}),
|
|
146
|
+
).rejects.toThrow('No issuer (iss or VCDM 2 issuer) found in SD-JWT or no VCDM2 SD-JWT or SD-JWT VC')
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('verify a sd-jwt vcdm2', async () => {
|
|
150
|
+
const credential = await agent.createSdJwtVc({
|
|
151
|
+
credentialPayload: testVcdm2SdJwtCredentialPayload,
|
|
152
|
+
disclosureFrame: disclosureFrame,
|
|
153
|
+
})
|
|
154
|
+
const result = await agent.verifySdJwtVc({
|
|
155
|
+
credential: credential.credential,
|
|
156
|
+
})
|
|
157
|
+
expect(result.payload).toBeDefined()
|
|
158
|
+
}, 5000)
|
|
159
|
+
|
|
160
|
+
it('create a presentation', async () => {
|
|
161
|
+
const credentialPayload = testVcdm2SdJwtCredentialPayload
|
|
162
|
+
const credential = await agent.createSdJwtVc({
|
|
163
|
+
credentialPayload,
|
|
164
|
+
disclosureFrame,
|
|
165
|
+
})
|
|
166
|
+
const presentation = await agent.createSdJwtPresentation({
|
|
167
|
+
presentation: credential.credential,
|
|
168
|
+
presentationFrame: { given_name: true },
|
|
169
|
+
kb: {
|
|
170
|
+
payload: {
|
|
171
|
+
aud: '1',
|
|
172
|
+
iat: 1,
|
|
173
|
+
nonce: '342',
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
})
|
|
177
|
+
expect(presentation).toBeDefined()
|
|
178
|
+
const decoded = await decodeSdJwt(presentation.presentation, defaultGenerateDigest)
|
|
179
|
+
expect(decoded.kbJwt).toBeDefined()
|
|
180
|
+
expect(((decoded.kbJwt as KBJwt).payload as kbPayload).aud).toBe('1')
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('create presentation with cnf', async () => {
|
|
184
|
+
const did = await agent.didManagerFind({ alias: 'holder' }).then((dids) => dids[0])
|
|
185
|
+
const resolvedDid = await agent.resolveDid({ didUrl: `${did.did}#0` })
|
|
186
|
+
const jwk: JsonWebKey = ((resolvedDid.didDocument as DIDDocument).verificationMethod as VerificationMethod[])[0].publicKeyJwk as JsonWebKey
|
|
187
|
+
const credentialPayload = {
|
|
188
|
+
...testVcdm2SdJwtCredentialPayload,
|
|
189
|
+
cnf: {
|
|
190
|
+
jwk,
|
|
191
|
+
},
|
|
192
|
+
}
|
|
193
|
+
const credential = await agent.createSdJwtVc({
|
|
194
|
+
credentialPayload,
|
|
195
|
+
disclosureFrame,
|
|
196
|
+
})
|
|
197
|
+
const presentation = await agent.createSdJwtPresentation({
|
|
198
|
+
presentation: credential.credential,
|
|
199
|
+
presentationFrame: { given_name: true },
|
|
200
|
+
kb: {
|
|
201
|
+
payload: {
|
|
202
|
+
aud: '1',
|
|
203
|
+
iat: 1,
|
|
204
|
+
nonce: '342',
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
})
|
|
208
|
+
expect(presentation).toBeDefined()
|
|
209
|
+
const decoded = await decodeSdJwt(presentation.presentation, defaultGenerateDigest)
|
|
210
|
+
expect(decoded.kbJwt).toBeDefined()
|
|
211
|
+
expect(((decoded.kbJwt as KBJwt).payload as kbPayload).aud).toBe('1')
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
it('includes no holder reference', async () => {
|
|
215
|
+
const newClaims = JSON.parse(JSON.stringify(claims))
|
|
216
|
+
newClaims.sub = undefined
|
|
217
|
+
const credentialPayload: SdJwtVcPayload = {
|
|
218
|
+
...newClaims,
|
|
219
|
+
iss: issuer,
|
|
220
|
+
iat: Math.floor(new Date().getTime() / 1000),
|
|
221
|
+
vct: '',
|
|
222
|
+
}
|
|
223
|
+
const credential = await agent.createSdJwtVc({
|
|
224
|
+
credentialPayload,
|
|
225
|
+
disclosureFrame,
|
|
226
|
+
})
|
|
227
|
+
const presentation = agent.createSdJwtPresentation({
|
|
228
|
+
presentation: credential.credential,
|
|
229
|
+
presentationFrame: { given_name: true },
|
|
230
|
+
kb: {
|
|
231
|
+
payload: {
|
|
232
|
+
aud: '1',
|
|
233
|
+
iat: 1,
|
|
234
|
+
nonce: '342',
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
})
|
|
238
|
+
await expect(presentation).rejects.toThrow('credential does not include a holder reference')
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
it('verify a presentation', async () => {
|
|
242
|
+
const holderDId = await agent.resolveDid({ didUrl: holder })
|
|
243
|
+
const jwk: JsonWebKey = ((holderDId.didDocument as DIDDocument).verificationMethod as VerificationMethod[])[0].publicKeyJwk as JsonWebKey
|
|
244
|
+
const credentialPayload: SdJwtVcPayload = {
|
|
245
|
+
...claims,
|
|
246
|
+
iss: issuer,
|
|
247
|
+
iat: Math.floor(new Date().getTime() / 1000),
|
|
248
|
+
vct: '',
|
|
249
|
+
cnf: {
|
|
250
|
+
jwk,
|
|
251
|
+
},
|
|
252
|
+
}
|
|
253
|
+
const credential = await agent.createSdJwtVc({
|
|
254
|
+
credentialPayload,
|
|
255
|
+
disclosureFrame,
|
|
256
|
+
})
|
|
257
|
+
const presentation = await agent.createSdJwtPresentation({
|
|
258
|
+
presentation: credential.credential,
|
|
259
|
+
presentationFrame: { credentialSubject: {given_name: true} },
|
|
260
|
+
kb: {
|
|
261
|
+
payload: {
|
|
262
|
+
aud: '1',
|
|
263
|
+
iat: 1,
|
|
264
|
+
nonce: '342',
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
})
|
|
268
|
+
const result = await agent.verifySdJwtPresentation({
|
|
269
|
+
presentation: presentation.presentation,
|
|
270
|
+
requiredClaimKeys: ['credentialSubject.given_name'],
|
|
271
|
+
// we are not able to verify the kb yet since we have no reference to the public key of the holder.
|
|
272
|
+
keyBindingAud: '1',
|
|
273
|
+
keyBindingNonce: '342',
|
|
274
|
+
})
|
|
275
|
+
expect(result).toBeDefined()
|
|
276
|
+
expect((result.payload as typeof claims).credentialSubject.given_name).toBe('John')
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
it('verify a presentation with sub set', async () => {
|
|
280
|
+
const holderDId = await agent.resolveDid({ didUrl: holder })
|
|
281
|
+
const jwk: JsonWebKey = ((holderDId.didDocument as DIDDocument).verificationMethod as VerificationMethod[])[0].publicKeyJwk as JsonWebKey
|
|
282
|
+
const credentialPayload: SdJwtVcPayload = {
|
|
283
|
+
...claims,
|
|
284
|
+
iss: issuer,
|
|
285
|
+
iat: Math.floor(new Date().getTime() / 1000),
|
|
286
|
+
vct: '',
|
|
287
|
+
cnf: {
|
|
288
|
+
jwk,
|
|
289
|
+
},
|
|
290
|
+
}
|
|
291
|
+
const credential = await agent.createSdJwtVc({
|
|
292
|
+
credentialPayload,
|
|
293
|
+
disclosureFrame,
|
|
294
|
+
})
|
|
295
|
+
const presentation = await agent.createSdJwtPresentation({
|
|
296
|
+
presentation: credential.credential,
|
|
297
|
+
presentationFrame: { credentialSubject: {given_name: true} },
|
|
298
|
+
kb: {
|
|
299
|
+
payload: {
|
|
300
|
+
aud: '1',
|
|
301
|
+
iat: 1,
|
|
302
|
+
nonce: '342',
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
})
|
|
306
|
+
const result = await agent.verifySdJwtPresentation({
|
|
307
|
+
presentation: presentation.presentation,
|
|
308
|
+
requiredClaimKeys: ['credentialSubject.given_name'],
|
|
309
|
+
// we are not able to verify the kb yet since we have no reference to the public key of the holder.
|
|
310
|
+
keyBindingAud: '1',
|
|
311
|
+
keyBindingNonce: '342',
|
|
312
|
+
})
|
|
313
|
+
expect(result).toBeDefined()
|
|
314
|
+
expect((result.payload as typeof claims).credentialSubject.given_name).toBe('John')
|
|
315
|
+
})
|
|
316
|
+
})
|
package/src/action-handler.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { Jwt, SDJwt } from '@sd-jwt/core'
|
|
2
|
-
import { SDJwtVcInstance, SdJwtVcPayload } from '@sd-jwt/sd-jwt-vc'
|
|
3
|
-
import { DisclosureFrame, Hasher, JwtPayload, KbVerifier, PresentationFrame, Signer, Verifier } from '@sd-jwt/types'
|
|
1
|
+
import { Jwt, SDJwt, type SdJwtPayload, type VerifierOptions } from '@sd-jwt/core'
|
|
2
|
+
import { SDJwtVcInstance, type SdJwtVcPayload } from '@sd-jwt/sd-jwt-vc'
|
|
3
|
+
import type { DisclosureFrame, HashAlgorithm, Hasher, JwtPayload, KbVerifier, PresentationFrame, Signer, Verifier } from '@sd-jwt/types'
|
|
4
4
|
import { calculateJwkThumbprint, signatureAlgorithmFromKey } from '@sphereon/ssi-sdk-ext.key-utils'
|
|
5
|
-
import { X509CertificateChainValidationOpts } from '@sphereon/ssi-sdk-ext.x509-utils'
|
|
6
|
-
import { HasherSync, JsonWebKey, JWK, SdJwtTypeMetadata } from '@sphereon/ssi-types'
|
|
7
|
-
import { IAgentPlugin } from '@veramo/core'
|
|
8
|
-
import { decodeBase64url } from '@veramo/utils'
|
|
5
|
+
import type { X509CertificateChainValidationOpts } from '@sphereon/ssi-sdk-ext.x509-utils'
|
|
6
|
+
import type { HasherSync, JsonWebKey, JWK, SdJwtTypeMetadata } from '@sphereon/ssi-types'
|
|
7
|
+
import type { IAgentPlugin } from '@veramo/core'
|
|
8
|
+
// import { decodeBase64url } from '@veramo/utils'
|
|
9
9
|
import Debug from 'debug'
|
|
10
10
|
import { defaultGenerateDigest, defaultGenerateSalt, defaultVerifySignature } from './defaultCallbacks'
|
|
11
11
|
import { funkeTestCA, sphereonCA } from './trustAnchors'
|
|
12
|
-
import { assertValidTypeMetadata, fetchUrlWithErrorHandling, validateIntegrity } from './utils'
|
|
13
|
-
import {
|
|
12
|
+
import { assertValidTypeMetadata, fetchUrlWithErrorHandling, getIssuerFromSdJwt, isSdjwtVcPayload, isVcdm2SdJwtPayload, validateIntegrity } from './utils'
|
|
13
|
+
import type {
|
|
14
14
|
Claims,
|
|
15
15
|
FetchSdJwtTypeMetadataFromVctUrlArgs,
|
|
16
16
|
GetSignerForIdentifierArgs,
|
|
@@ -30,6 +30,10 @@ import {
|
|
|
30
30
|
SignKeyArgs,
|
|
31
31
|
SignKeyResult,
|
|
32
32
|
} from './types'
|
|
33
|
+
import { SDJwtVcdm2Instance, SDJwtVcdmInstanceFactory } from './sdJwtVcdm2Instance'
|
|
34
|
+
|
|
35
|
+
// @ts-ignore
|
|
36
|
+
import * as u8a from 'uint8arrays'
|
|
33
37
|
|
|
34
38
|
const debug = Debug('@sphereon/ssi-sdk.sd-jwt')
|
|
35
39
|
|
|
@@ -101,27 +105,47 @@ export class SDJwtPlugin implements IAgentPlugin {
|
|
|
101
105
|
* @returns A signed SD-JWT credential.
|
|
102
106
|
*/
|
|
103
107
|
async createSdJwtVc(args: ICreateSdJwtVcArgs, context: IRequiredContext): Promise<ICreateSdJwtVcResult> {
|
|
104
|
-
const
|
|
108
|
+
const payload = args.credentialPayload
|
|
109
|
+
const isVcdm2 = isVcdm2SdJwtPayload(payload)
|
|
110
|
+
const isSdJwtVc = isSdjwtVcPayload(payload)
|
|
111
|
+
const type = args.type ?? (isVcdm2 ? 'vc+sd-jwt' : 'dc+sd-jwt')
|
|
112
|
+
|
|
113
|
+
const issuer = getIssuerFromSdJwt(args.credentialPayload)
|
|
105
114
|
if (!issuer) {
|
|
106
115
|
throw new Error('credential.issuer must not be empty')
|
|
107
116
|
}
|
|
108
117
|
const { alg, signer, signingKey } = await this.getSignerForIdentifier({ identifier: issuer, resolution: args.resolution }, context)
|
|
109
|
-
const
|
|
118
|
+
const signAlg = alg ?? signingKey?.alg ?? 'ES256'
|
|
119
|
+
const hashAlg: HashAlgorithm = /(\d{3})$/.test(signAlg) ? (`sha-${signAlg.slice(-3)}` as HashAlgorithm) : 'sha-256'
|
|
120
|
+
const sdjwt = SDJwtVcdmInstanceFactory.create(type, {
|
|
121
|
+
omitTyp: true,
|
|
110
122
|
signer,
|
|
111
123
|
hasher: this.registeredImplementations.hasher,
|
|
112
124
|
saltGenerator: this.registeredImplementations.saltGenerator,
|
|
113
|
-
signAlg
|
|
114
|
-
hashAlg
|
|
125
|
+
signAlg,
|
|
126
|
+
hashAlg,
|
|
115
127
|
})
|
|
116
128
|
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
129
|
+
const header = {
|
|
130
|
+
...(signingKey?.key.kid !== undefined && { kid: signingKey.key.kid }),
|
|
131
|
+
...(signingKey?.key.x5c !== undefined && { x5c: signingKey.key.x5c }),
|
|
132
|
+
...(type && { typ: type }),
|
|
133
|
+
}
|
|
134
|
+
let credential: string
|
|
135
|
+
if (isVcdm2) {
|
|
136
|
+
credential = await (sdjwt as SDJwtVcdm2Instance).issue(
|
|
137
|
+
payload,
|
|
138
|
+
// @ts-ignore
|
|
139
|
+
args.disclosureFrame as DisclosureFrame<typeof payload>,
|
|
140
|
+
{ header },
|
|
141
|
+
)
|
|
142
|
+
} else if (isSdJwtVc) {
|
|
143
|
+
credential = await (sdjwt as SDJwtVcInstance).issue(payload, args.disclosureFrame as DisclosureFrame<typeof payload>, { header })
|
|
144
|
+
} else {
|
|
145
|
+
return Promise.reject(new Error(`invalid_argument: credential '${type}' type is not supported`))
|
|
146
|
+
}
|
|
123
147
|
|
|
124
|
-
return { credential }
|
|
148
|
+
return { type, credential }
|
|
125
149
|
}
|
|
126
150
|
|
|
127
151
|
/**
|
|
@@ -183,10 +207,13 @@ export class SDJwtPlugin implements IAgentPlugin {
|
|
|
183
207
|
* @returns A signed SD-JWT presentation.
|
|
184
208
|
*/
|
|
185
209
|
async createSdJwtPresentation(args: ICreateSdJwtPresentationArgs, context: IRequiredContext): Promise<ICreateSdJwtPresentationResult> {
|
|
210
|
+
const type = args.type ?? 'dc+sd-jwt'
|
|
211
|
+
|
|
186
212
|
const cred = await SDJwt.fromEncode(args.presentation, this.registeredImplementations.hasher!)
|
|
213
|
+
|
|
187
214
|
const claims = await cred.getClaims<Claims>(this.registeredImplementations.hasher!)
|
|
188
215
|
let holder: string
|
|
189
|
-
// we
|
|
216
|
+
// we primarily look for a cnf field, if it's not there, we look for a sub field. If this is also not given, we throw an error since we can not sign it.
|
|
190
217
|
if (args.holder) {
|
|
191
218
|
holder = args.holder
|
|
192
219
|
} else if (claims.cnf?.jwk) {
|
|
@@ -201,15 +228,17 @@ export class SDJwtPlugin implements IAgentPlugin {
|
|
|
201
228
|
}
|
|
202
229
|
const { alg, signer } = await this.getSignerForIdentifier({ identifier: holder }, context)
|
|
203
230
|
|
|
204
|
-
const sdjwt =
|
|
205
|
-
|
|
231
|
+
const sdjwt = SDJwtVcdmInstanceFactory.create(type, {
|
|
232
|
+
omitTyp: true,
|
|
233
|
+
hasher: this.registeredImplementations.hasher,
|
|
206
234
|
saltGenerator: this.registeredImplementations.saltGenerator,
|
|
207
235
|
kbSigner: signer,
|
|
208
236
|
kbSignAlg: alg ?? 'ES256',
|
|
209
237
|
})
|
|
238
|
+
|
|
210
239
|
const presentation = await sdjwt.present(args.presentation, args.presentationFrame as PresentationFrame<SdJwtVcPayload>, { kb: args.kb })
|
|
211
240
|
|
|
212
|
-
return { presentation }
|
|
241
|
+
return { type, presentation }
|
|
213
242
|
}
|
|
214
243
|
|
|
215
244
|
/**
|
|
@@ -220,11 +249,19 @@ export class SDJwtPlugin implements IAgentPlugin {
|
|
|
220
249
|
*/
|
|
221
250
|
async verifySdJwtVc(args: IVerifySdJwtVcArgs, context: IRequiredContext): Promise<IVerifySdJwtVcResult> {
|
|
222
251
|
// callback
|
|
223
|
-
const verifier: Verifier = async (data: string, signature: string) => this.
|
|
224
|
-
const sdjwt = new SDJwtVcInstance({ verifier, hasher: this.registeredImplementations.hasher ?? defaultGenerateDigest })
|
|
225
|
-
const { header = {}, payload, kb } = await sdjwt.verify(args.credential)
|
|
252
|
+
const verifier: Verifier = async (data: string, signature: string) => this.verifyCallbackImpl(sdjwt, context, data, signature)
|
|
226
253
|
|
|
227
|
-
|
|
254
|
+
const cred = await SDJwt.fromEncode(args.credential, this.registeredImplementations.hasher!)
|
|
255
|
+
const type = isVcdm2SdJwtPayload(cred.jwt?.payload as SdJwtPayload) ? 'vc+sd-jwt' : 'dc+sd-jwt'
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
const sdjwt = SDJwtVcdmInstanceFactory.create(type, {verifier, hasher: this.registeredImplementations.hasher ?? defaultGenerateDigest })
|
|
259
|
+
// FIXME: Findynet. Issuer returns expired status lists, and low level lib throws errors on these. We need to fix this in our implementation by wrapping the verification function
|
|
260
|
+
// For now a workaround is to ad 5 days of skew seconds, yuck
|
|
261
|
+
const { header = {}, payload, kb } = await sdjwt.verify(args.credential, {skewSeconds: 60*60*24*5})
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
return { type, header, payload, kb }
|
|
228
265
|
}
|
|
229
266
|
|
|
230
267
|
/**
|
|
@@ -236,10 +273,14 @@ export class SDJwtPlugin implements IAgentPlugin {
|
|
|
236
273
|
* @param payload - The payload of the SD-JWT
|
|
237
274
|
* @returns
|
|
238
275
|
*/
|
|
239
|
-
private verifyKb(
|
|
276
|
+
private verifyKb(context: IRequiredContext, data: string, signature: string, payload: JwtPayload): Promise<boolean> {
|
|
240
277
|
if (!payload.cnf) {
|
|
241
278
|
throw Error('other method than cnf is not supported yet')
|
|
242
279
|
}
|
|
280
|
+
|
|
281
|
+
// TODO add aud verification
|
|
282
|
+
|
|
283
|
+
|
|
243
284
|
return this.verifySignatureCallback(context)(data, signature, this.getJwk(payload))
|
|
244
285
|
}
|
|
245
286
|
|
|
@@ -251,15 +292,16 @@ export class SDJwtPlugin implements IAgentPlugin {
|
|
|
251
292
|
* @param signature - The signature
|
|
252
293
|
* @returns
|
|
253
294
|
*/
|
|
254
|
-
async
|
|
255
|
-
sdjwt: SDJwtVcInstance,
|
|
295
|
+
async verifyCallbackImpl(
|
|
296
|
+
sdjwt: SDJwtVcInstance | SDJwtVcdm2Instance,
|
|
256
297
|
context: IRequiredContext,
|
|
257
298
|
data: string,
|
|
258
299
|
signature: string,
|
|
259
300
|
opts?: { x5cValidation?: X509CertificateChainValidationOpts },
|
|
260
301
|
): Promise<boolean> {
|
|
261
302
|
const decodedVC = await sdjwt.decode(`${data}.${signature}`)
|
|
262
|
-
const
|
|
303
|
+
const payload: SdJwtPayload = (decodedVC.jwt as Jwt).payload as SdJwtPayload
|
|
304
|
+
const issuer: string = getIssuerFromSdJwt(payload)
|
|
263
305
|
const header = (decodedVC.jwt as Jwt).header as Record<string, any>
|
|
264
306
|
const x5c: string[] | undefined = header?.x5c as string[]
|
|
265
307
|
let jwk: JWK | JsonWebKey | undefined = header.jwk
|
|
@@ -329,16 +371,21 @@ export class SDJwtPlugin implements IAgentPlugin {
|
|
|
329
371
|
*/
|
|
330
372
|
async verifySdJwtPresentation(args: IVerifySdJwtPresentationArgs, context: IRequiredContext): Promise<IVerifySdJwtPresentationResult> {
|
|
331
373
|
let sdjwt: SDJwtVcInstance
|
|
332
|
-
const verifier: Verifier = async (data: string, signature: string) => this.
|
|
374
|
+
const verifier: Verifier = async (data: string, signature: string) => this.verifyCallbackImpl(sdjwt, context, data, signature)
|
|
333
375
|
const verifierKb: KbVerifier = async (data: string, signature: string, payload: JwtPayload) =>
|
|
334
|
-
this.verifyKb(
|
|
376
|
+
this.verifyKb(context, data, signature, payload)
|
|
335
377
|
sdjwt = new SDJwtVcInstance({
|
|
336
378
|
verifier,
|
|
337
379
|
hasher: this.registeredImplementations.hasher,
|
|
338
380
|
kbVerifier: verifierKb,
|
|
339
381
|
})
|
|
340
382
|
|
|
341
|
-
|
|
383
|
+
const verifierOpts: VerifierOptions = {
|
|
384
|
+
requiredClaimKeys: args.requiredClaimKeys,
|
|
385
|
+
keyBindingNonce: args.keyBindingNonce,
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return sdjwt.verify(args.presentation, verifierOpts)
|
|
342
389
|
}
|
|
343
390
|
|
|
344
391
|
/**
|
|
@@ -411,7 +458,7 @@ export class SDJwtPlugin implements IAgentPlugin {
|
|
|
411
458
|
// extract JWK from kid FIXME isn't there a did function for this already? Otherwise create one
|
|
412
459
|
// FIXME this is a quick-fix to make verification but we need a real solution
|
|
413
460
|
const encoded = this.extractBase64FromDIDJwk(payload.cnf.kid)
|
|
414
|
-
const decoded =
|
|
461
|
+
const decoded = u8a.toString(u8a.fromString(encoded, 'base64url'), 'utf-8')
|
|
415
462
|
const jwt = JSON.parse(decoded)
|
|
416
463
|
return jwt as JsonWebKey
|
|
417
464
|
}
|