@sphereon/ssi-sdk.sd-jwt 0.24.1-next.112

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,254 @@
1
+ {
2
+ "ISDJwtPlugin": {
3
+ "components": {
4
+ "schemas": {
5
+ "ICreateSdJwtPresentationArgs": {
6
+ "type": "object",
7
+ "properties": {
8
+ "presentation": {
9
+ "type": "string",
10
+ "description": "Encoded SD-JWT credential"
11
+ },
12
+ "presentationFrame": {
13
+ "$ref": "#/components/schemas/IPresentationFrame"
14
+ },
15
+ "kb": {
16
+ "type": "object",
17
+ "properties": {
18
+ "payload": {
19
+ "type": "object",
20
+ "properties": {
21
+ "iat": {
22
+ "type": "number"
23
+ },
24
+ "aud": {
25
+ "type": "string"
26
+ },
27
+ "nonce": {
28
+ "type": "string"
29
+ }
30
+ },
31
+ "required": [
32
+ "iat",
33
+ "aud",
34
+ "nonce"
35
+ ]
36
+ }
37
+ },
38
+ "required": [
39
+ "payload"
40
+ ],
41
+ "description": "Information to include to add key binding."
42
+ }
43
+ },
44
+ "required": [
45
+ "presentation"
46
+ ]
47
+ },
48
+ "IPresentationFrame": {
49
+ "type": "object",
50
+ "additionalProperties": {
51
+ "anyOf": [
52
+ {
53
+ "type": "boolean"
54
+ },
55
+ {
56
+ "$ref": "#/components/schemas/IPresentationFrame"
57
+ }
58
+ ]
59
+ }
60
+ },
61
+ "ICreateSdJwtPresentationResult": {
62
+ "type": "object",
63
+ "properties": {
64
+ "presentation": {
65
+ "type": "string",
66
+ "description": "Encoded presentation."
67
+ }
68
+ },
69
+ "required": [
70
+ "presentation"
71
+ ],
72
+ "description": "Created presentation"
73
+ },
74
+ "ICreateSdJwtVcArgs": {
75
+ "type": "object",
76
+ "properties": {
77
+ "credentialPayload": {
78
+ "type": "object",
79
+ "properties": {
80
+ "iss": {
81
+ "type": "string"
82
+ },
83
+ "nbf": {
84
+ "type": "number"
85
+ },
86
+ "exp": {
87
+ "type": "number"
88
+ },
89
+ "cnf": {},
90
+ "vct": {
91
+ "type": "string"
92
+ },
93
+ "status": {},
94
+ "sub": {
95
+ "type": "string"
96
+ },
97
+ "iat": {
98
+ "type": "number"
99
+ }
100
+ },
101
+ "required": [
102
+ "iss",
103
+ "vct"
104
+ ]
105
+ },
106
+ "disclosureFrame": {
107
+ "$ref": "#/components/schemas/IDisclosureFrame"
108
+ }
109
+ },
110
+ "required": [
111
+ "credentialPayload"
112
+ ],
113
+ "description": "ICreateSdJwtVcArgs"
114
+ },
115
+ "IDisclosureFrame": {
116
+ "type": "object",
117
+ "properties": {
118
+ "_sd": {
119
+ "type": "array",
120
+ "items": {
121
+ "type": "string"
122
+ }
123
+ },
124
+ "_sd_decoy": {
125
+ "type": "number"
126
+ }
127
+ },
128
+ "additionalProperties": {
129
+ "anyOf": [
130
+ {
131
+ "type": "array",
132
+ "items": {
133
+ "type": "string"
134
+ }
135
+ },
136
+ {
137
+ "type": "number"
138
+ },
139
+ {
140
+ "$ref": "#/components/schemas/IDisclosureFrame"
141
+ },
142
+ {
143
+ "not": {}
144
+ }
145
+ ]
146
+ }
147
+ },
148
+ "ICreateSdJwtVcResult": {
149
+ "type": "object",
150
+ "properties": {
151
+ "credential": {
152
+ "type": "string",
153
+ "description": "the encoded sd-jwt credential"
154
+ }
155
+ },
156
+ "required": [
157
+ "credential"
158
+ ],
159
+ "description": "ICreateSdJwtVcResult"
160
+ },
161
+ "IVerifySdJwtPresentationArgs": {
162
+ "type": "object",
163
+ "properties": {
164
+ "presentation": {
165
+ "type": "string"
166
+ },
167
+ "requiredClaimKeys": {
168
+ "type": "array",
169
+ "items": {
170
+ "type": "string"
171
+ }
172
+ },
173
+ "kb": {
174
+ "type": "boolean"
175
+ }
176
+ },
177
+ "required": [
178
+ "presentation"
179
+ ]
180
+ },
181
+ "IVerifySdJwtPresentationResult": {
182
+ "type": "object",
183
+ "properties": {
184
+ "verifiedPayloads": {
185
+ "type": "object",
186
+ "additionalProperties": {}
187
+ }
188
+ },
189
+ "required": [
190
+ "verifiedPayloads"
191
+ ]
192
+ },
193
+ "IVerifySdJwtVcArgs": {
194
+ "type": "object",
195
+ "properties": {
196
+ "credential": {
197
+ "type": "string"
198
+ }
199
+ },
200
+ "required": [
201
+ "credential"
202
+ ]
203
+ },
204
+ "IVerifySdJwtVcResult": {
205
+ "type": "object",
206
+ "properties": {
207
+ "verifiedPayloads": {}
208
+ },
209
+ "required": [
210
+ "verifiedPayloads"
211
+ ]
212
+ }
213
+ },
214
+ "methods": {
215
+ "createSdJwtPresentation": {
216
+ "description": "Create a signed SD-JWT presentation.",
217
+ "arguments": {
218
+ "$ref": "#/components/schemas/ICreateSdJwtPresentationArgs"
219
+ },
220
+ "returnType": {
221
+ "$ref": "#/components/schemas/ICreateSdJwtPresentationResult"
222
+ }
223
+ },
224
+ "createSdJwtVc": {
225
+ "description": "Create a signed SD-JWT credential.",
226
+ "arguments": {
227
+ "$ref": "#/components/schemas/ICreateSdJwtVcArgs"
228
+ },
229
+ "returnType": {
230
+ "$ref": "#/components/schemas/ICreateSdJwtVcResult"
231
+ }
232
+ },
233
+ "verifySdJwtPresentation": {
234
+ "description": "Verify a signed SD-JWT presentation.",
235
+ "arguments": {
236
+ "$ref": "#/components/schemas/IVerifySdJwtPresentationArgs"
237
+ },
238
+ "returnType": {
239
+ "$ref": "#/components/schemas/IVerifySdJwtPresentationResult"
240
+ }
241
+ },
242
+ "verifySdJwtVc": {
243
+ "description": "Verify a signed SD-JWT credential.",
244
+ "arguments": {
245
+ "$ref": "#/components/schemas/IVerifySdJwtVcArgs"
246
+ },
247
+ "returnType": {
248
+ "$ref": "#/components/schemas/IVerifySdJwtVcResult"
249
+ }
250
+ }
251
+ }
252
+ }
253
+ }
254
+ }
@@ -0,0 +1,340 @@
1
+ import { DisclosureFrame, kbPayload } from '@sd-jwt/types'
2
+ import { createAgent, IDIDManager, IKeyManager, IResolver, TAgent } from '@veramo/core'
3
+ import { DIDManager, MemoryDIDStore } from '@veramo/did-manager'
4
+ import { JwkDIDProvider } from '@sphereon/ssi-sdk-ext.did-provider-jwk'
5
+ import { getDidJwkResolver } from '@sphereon/ssi-sdk-ext.did-resolver-jwk'
6
+ import { DIDResolverPlugin } from '@veramo/did-resolver'
7
+ import { DIDDocument, Resolver, VerificationMethod } from 'did-resolver'
8
+ import { SdJwtVcPayload } from '@sd-jwt/sd-jwt-vc'
9
+ import { decodeSdJwt } from '@sd-jwt/decode'
10
+ import { KBJwt } from '@sd-jwt/core'
11
+ import { ISDJwtPlugin, SDJwtPlugin } from '../index'
12
+ import { createHash, randomBytes, subtle } from 'crypto'
13
+ import {MemoryKeyStore, MemoryPrivateKeyStore, SphereonKeyManager} from '@sphereon/ssi-sdk-ext.key-manager'
14
+ import {SphereonKeyManagementSystem} from '@sphereon/ssi-sdk-ext.kms-local'
15
+
16
+ const generateDigest = (data: string, algorithm: string) => {
17
+ return createHash(algorithm).update(data).digest()
18
+ }
19
+
20
+ const generateSalt = (): string => {
21
+ return randomBytes(16).toString('hex');
22
+ }
23
+
24
+ async function verifySignature<T>(data: string, signature: string, key: JsonWebKey) {
25
+ let { alg, crv } = key
26
+ if (alg === 'ES256') alg = 'ECDSA'
27
+ const publicKey = await subtle.importKey(
28
+ 'jwk',
29
+ key,
30
+ { name: alg, namedCurve: crv } as EcKeyImportParams,
31
+ true,
32
+ ['verify'],
33
+ )
34
+ return Promise.resolve(
35
+ subtle.verify(
36
+ { name: alg as string, hash: 'SHA-256' },
37
+ publicKey,
38
+ Buffer.from(signature, 'base64'),
39
+ Buffer.from(data),
40
+ ),
41
+ )
42
+ }
43
+
44
+ type AgentType = IDIDManager & IKeyManager & IResolver & ISDJwtPlugin
45
+
46
+ describe('Agent plugin', () => {
47
+ let agent: TAgent<AgentType>
48
+
49
+ let issuer: string
50
+
51
+ let holder: string
52
+
53
+ // Issuer Define the claims object with the user's information
54
+ const claims = {
55
+ sub: '',
56
+ given_name: 'John',
57
+ family_name: 'Deo',
58
+ email: 'johndeo@example.com',
59
+ phone: '+1-202-555-0101',
60
+ address: {
61
+ street_address: '123 Main St',
62
+ locality: 'Anytown',
63
+ region: 'Anystate',
64
+ country: 'US',
65
+ },
66
+ birthdate: '1940-01-01',
67
+ }
68
+
69
+ // Issuer Define the disclosure frame to specify which claims can be disclosed
70
+ const disclosureFrame: DisclosureFrame<typeof claims> = {
71
+ _sd: ['given_name', 'family_name', 'email', 'phone', 'address', 'birthdate'],
72
+ }
73
+
74
+ beforeAll(async () => {
75
+ agent = createAgent<AgentType>({
76
+ plugins: [
77
+ new SDJwtPlugin({
78
+ hasher: generateDigest,
79
+ saltGenerator: generateSalt,
80
+ verifySignature
81
+ }),
82
+ new SphereonKeyManager({
83
+ store: new MemoryKeyStore(),
84
+ kms: {
85
+ local: new SphereonKeyManagementSystem(new MemoryPrivateKeyStore()),
86
+ },
87
+ }),
88
+ new DIDResolverPlugin({
89
+ resolver: new Resolver({
90
+ ...getDidJwkResolver(),
91
+ }),
92
+ }),
93
+ new DIDManager({
94
+ store: new MemoryDIDStore(),
95
+ defaultProvider: 'did:jwk',
96
+ providers: {
97
+ 'did:jwk': new JwkDIDProvider({
98
+ defaultKms: 'local',
99
+ }),
100
+ },
101
+ }),
102
+ ],
103
+ })
104
+ issuer = await agent
105
+ .didManagerCreate({
106
+ kms: 'local',
107
+ provider: 'did:jwk',
108
+ alias: 'issuer',
109
+ //we use this curve since nodejs does not support ES256k which is the default one.
110
+ options: { keyType: 'Secp256r1' }
111
+ })
112
+ .then((did) => {
113
+ // we add a key reference
114
+ return `${did.did}#0`
115
+ })
116
+ holder = await agent
117
+ .didManagerCreate({
118
+ kms: 'local',
119
+ provider: 'did:jwk',
120
+ alias: 'holder',
121
+ //we use this curve since nodejs does not support ES256k which is the default one.
122
+ options: { keyType: 'Secp256r1' }
123
+ })
124
+ .then((did) => `${did.did}#0`)
125
+ claims.sub = holder
126
+ })
127
+
128
+ it('create a sd-jwt', async () => {
129
+ const credentialPayload: SdJwtVcPayload = {
130
+ ...claims,
131
+ iss: issuer,
132
+ iat: new Date().getTime() / 1000,
133
+ vct: '',
134
+ }
135
+ const credential = await agent.createSdJwtVc({
136
+ credentialPayload,
137
+ disclosureFrame,
138
+ })
139
+ expect(credential).toBeDefined()
140
+ })
141
+
142
+ it('create sd without an issuer', async () => {
143
+ const credentialPayload = {
144
+ ...claims,
145
+ iat: new Date().getTime() / 1000,
146
+ vct: '',
147
+ }
148
+ expect(
149
+ agent.createSdJwtVc({
150
+ credentialPayload: credentialPayload as unknown as SdJwtVcPayload,
151
+ disclosureFrame,
152
+ }),
153
+ ).rejects.toThrow('credential.issuer must not be empty')
154
+ })
155
+
156
+ it('verify a sd-jwt', async () => {
157
+ const credentialPayload: SdJwtVcPayload = {
158
+ ...claims,
159
+ iss: issuer,
160
+ iat: new Date().getTime() / 1000,
161
+ vct: '',
162
+ }
163
+ const credential = await agent.createSdJwtVc({
164
+ credentialPayload,
165
+ disclosureFrame: disclosureFrame,
166
+ })
167
+ await agent.verifySdJwtVc({
168
+ credential: credential.credential,
169
+ })
170
+ }, 5000)
171
+
172
+ it('create a presentation', async () => {
173
+ const credentialPayload: SdJwtVcPayload = {
174
+ ...claims,
175
+ iss: issuer,
176
+ iat: new Date().getTime() / 1000,
177
+ vct: '',
178
+ }
179
+ const credential = await agent.createSdJwtVc({
180
+ credentialPayload,
181
+ disclosureFrame,
182
+ })
183
+ const presentation = await agent.createSdJwtPresentation({
184
+ presentation: credential.credential,
185
+ presentationFrame: { given_name: true },
186
+ kb: {
187
+ payload: {
188
+ aud: '1',
189
+ iat: 1,
190
+ nonce: '342',
191
+ },
192
+ },
193
+ })
194
+ expect(presentation).toBeDefined()
195
+ const decoded = await decodeSdJwt(presentation.presentation, generateDigest)
196
+ expect(decoded.kbJwt).toBeDefined()
197
+ expect(((decoded.kbJwt as KBJwt).payload as kbPayload).aud).toBe('1')
198
+ })
199
+
200
+ it('create presentation with cnf', async () => {
201
+ const did = await agent.didManagerFind({ alias: 'holder' }).then((dids) => dids[0])
202
+ const resolvedDid = await agent.resolveDid({ didUrl: `${did.did}#0` })
203
+ const jwk: JsonWebKey = (
204
+ (resolvedDid.didDocument as DIDDocument).verificationMethod as VerificationMethod[]
205
+ )[0].publicKeyJwk as JsonWebKey
206
+ const credentialPayload: SdJwtVcPayload = {
207
+ ...claims,
208
+ cnf: {
209
+ jwk,
210
+ },
211
+ iss: issuer,
212
+ iat: new Date().getTime() / 1000,
213
+ vct: '',
214
+ }
215
+ const credential = await agent.createSdJwtVc({
216
+ credentialPayload,
217
+ disclosureFrame,
218
+ })
219
+ const presentation = await agent.createSdJwtPresentation({
220
+ presentation: credential.credential,
221
+ presentationFrame: { given_name: true },
222
+ kb: {
223
+ payload: {
224
+ aud: '1',
225
+ iat: 1,
226
+ nonce: '342',
227
+ },
228
+ },
229
+ })
230
+ expect(presentation).toBeDefined()
231
+ const decoded = await decodeSdJwt(presentation.presentation, generateDigest)
232
+ expect(decoded.kbJwt).toBeDefined()
233
+ expect(((decoded.kbJwt as KBJwt).payload as kbPayload).aud).toBe('1')
234
+ })
235
+
236
+ it('includes no holder reference', async () => {
237
+ const newClaims = JSON.parse(JSON.stringify(claims))
238
+ newClaims.sub = undefined
239
+ const credentialPayload: SdJwtVcPayload = {
240
+ ...newClaims,
241
+ iss: issuer,
242
+ iat: new Date().getTime() / 1000,
243
+ vct: '',
244
+ }
245
+ const credential = await agent.createSdJwtVc({
246
+ credentialPayload,
247
+ disclosureFrame,
248
+ })
249
+ const presentation = agent.createSdJwtPresentation({
250
+ presentation: credential.credential,
251
+ presentationFrame: { given_name: true },
252
+ kb: {
253
+ payload: {
254
+ aud: '1',
255
+ iat: 1,
256
+ nonce: '342',
257
+ },
258
+ },
259
+ })
260
+ expect(presentation).rejects.toThrow('credential does not include a holder reference')
261
+ })
262
+
263
+ it('verify a presentation', async () => {
264
+ const holderDId = await agent.resolveDid({ didUrl: holder })
265
+ const jwk: JsonWebKey = (
266
+ (holderDId.didDocument as DIDDocument).verificationMethod as VerificationMethod[]
267
+ )[0].publicKeyJwk as JsonWebKey
268
+ const credentialPayload: SdJwtVcPayload = {
269
+ ...claims,
270
+ iss: issuer,
271
+ iat: new Date().getTime() / 1000,
272
+ vct: '',
273
+ cnf: {
274
+ jwk,
275
+ },
276
+ }
277
+ const credential = await agent.createSdJwtVc({
278
+ credentialPayload,
279
+ disclosureFrame,
280
+ })
281
+ const presentation = await agent.createSdJwtPresentation({
282
+ presentation: credential.credential,
283
+ presentationFrame: { given_name: true },
284
+ kb: {
285
+ payload: {
286
+ aud: '1',
287
+ iat: 1,
288
+ nonce: '342',
289
+ },
290
+ },
291
+ })
292
+ const result = await agent.verifySdJwtPresentation({
293
+ presentation: presentation.presentation,
294
+ requiredClaimKeys: ['given_name'],
295
+ // we are not able to verify the kb yet since we have no reference to the public key of the holder.
296
+ kb: true,
297
+ })
298
+ expect(result).toBeDefined()
299
+ expect((result.verifiedPayloads.payload as typeof claims).given_name).toBe('John')
300
+ })
301
+
302
+ it('verify a presentation with sub set', async () => {
303
+ const holderDId = await agent.resolveDid({ didUrl: holder })
304
+ const jwk: JsonWebKey = (
305
+ (holderDId.didDocument as DIDDocument).verificationMethod as VerificationMethod[]
306
+ )[0].publicKeyJwk as JsonWebKey
307
+ const credentialPayload: SdJwtVcPayload = {
308
+ ...claims,
309
+ iss: issuer,
310
+ iat: new Date().getTime() / 1000,
311
+ vct: '',
312
+ cnf: {
313
+ jwk,
314
+ },
315
+ }
316
+ const credential = await agent.createSdJwtVc({
317
+ credentialPayload,
318
+ disclosureFrame,
319
+ })
320
+ const presentation = await agent.createSdJwtPresentation({
321
+ presentation: credential.credential,
322
+ presentationFrame: { given_name: true },
323
+ kb: {
324
+ payload: {
325
+ aud: '1',
326
+ iat: 1,
327
+ nonce: '342',
328
+ },
329
+ },
330
+ })
331
+ const result = await agent.verifySdJwtPresentation({
332
+ presentation: presentation.presentation,
333
+ requiredClaimKeys: ['given_name'],
334
+ // we are not able to verify the kb yet since we have no reference to the public key of the holder.
335
+ kb: true,
336
+ })
337
+ expect(result).toBeDefined()
338
+ expect((result.verifiedPayloads.payload as typeof claims).given_name).toBe('John')
339
+ })
340
+ })