@sd-jwt/core 0.3.0 → 2.0.2-next.26
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/LICENSE +201 -0
- package/dist/index.d.mts +106 -0
- package/dist/index.d.ts +106 -0
- package/dist/index.js +606 -0
- package/dist/index.mjs +586 -0
- package/package.json +60 -48
- package/src/decoy.ts +15 -0
- package/src/index.ts +235 -0
- package/src/jwt.ts +107 -0
- package/src/kbjwt.ts +45 -0
- package/src/sdjwt.ts +318 -0
- package/src/test/decoy.spec.ts +30 -0
- package/src/test/index.spec.ts +379 -0
- package/src/test/jwt.spec.ts +141 -0
- package/src/test/kbjwt.spec.ts +275 -0
- package/src/test/pass.spec.ts +6 -0
- package/src/test/sdjwt.spec.ts +382 -0
- package/test/app-e2e.spec.ts +248 -0
- package/test/array_data_types.json +29 -0
- package/test/array_full_sd.json +21 -0
- package/test/array_in_sd.json +13 -0
- package/test/array_nested_in_plain.json +20 -0
- package/test/array_none_disclosed.json +17 -0
- package/test/array_of_nulls.json +15 -0
- package/test/array_of_objects.json +58 -0
- package/test/array_of_scalars.json +15 -0
- package/test/array_recursive_sd.json +35 -0
- package/test/array_recursive_sd_some_disclosed.json +55 -0
- package/test/complex.json +43 -0
- package/test/header_mod.json +44 -0
- package/test/json_serialization.json +44 -0
- package/test/key_binding.json +44 -0
- package/test/no_sd.json +36 -0
- package/test/object_data_types.json +60 -0
- package/test/recursions.json +98 -0
- package/tsconfig.json +7 -0
- package/vitest.config.mts +4 -0
- package/README.md +0 -97
- package/build/index.d.ts +0 -13
- package/build/index.js +0 -20
- package/build/index.js.map +0 -1
- package/build/jwt/error.d.ts +0 -2
- package/build/jwt/error.js +0 -7
- package/build/jwt/error.js.map +0 -1
- package/build/jwt/index.d.ts +0 -2
- package/build/jwt/index.js +0 -19
- package/build/jwt/index.js.map +0 -1
- package/build/jwt/jwt.d.ts +0 -208
- package/build/jwt/jwt.js +0 -325
- package/build/jwt/jwt.js.map +0 -1
- package/build/keyBinding/index.d.ts +0 -1
- package/build/keyBinding/index.js +0 -18
- package/build/keyBinding/index.js.map +0 -1
- package/build/keyBinding/keyBinding.d.ts +0 -64
- package/build/keyBinding/keyBinding.js +0 -119
- package/build/keyBinding/keyBinding.js.map +0 -1
- package/build/sdJwt/decoys.d.ts +0 -3
- package/build/sdJwt/decoys.js +0 -35
- package/build/sdJwt/decoys.js.map +0 -1
- package/build/sdJwt/disclosureFrame.d.ts +0 -8
- package/build/sdJwt/disclosureFrame.js +0 -87
- package/build/sdJwt/disclosureFrame.js.map +0 -1
- package/build/sdJwt/disclosures.d.ts +0 -33
- package/build/sdJwt/disclosures.js +0 -114
- package/build/sdJwt/disclosures.js.map +0 -1
- package/build/sdJwt/error.d.ts +0 -2
- package/build/sdJwt/error.js +0 -7
- package/build/sdJwt/error.js.map +0 -1
- package/build/sdJwt/index.d.ts +0 -6
- package/build/sdJwt/index.js +0 -23
- package/build/sdJwt/index.js.map +0 -1
- package/build/sdJwt/sdJwt.d.ts +0 -206
- package/build/sdJwt/sdJwt.js +0 -442
- package/build/sdJwt/sdJwt.js.map +0 -1
- package/build/sdJwt/types.d.ts +0 -5
- package/build/sdJwt/types.js +0 -3
- package/build/sdJwt/types.js.map +0 -1
- package/build/sdJwtVc/error.d.ts +0 -2
- package/build/sdJwtVc/error.js +0 -7
- package/build/sdJwtVc/error.js.map +0 -1
- package/build/sdJwtVc/index.d.ts +0 -2
- package/build/sdJwtVc/index.js +0 -19
- package/build/sdJwtVc/index.js.map +0 -1
- package/build/sdJwtVc/sdJwtVc.d.ts +0 -47
- package/build/sdJwtVc/sdJwtVc.js +0 -149
- package/build/sdJwtVc/sdJwtVc.js.map +0 -1
- package/build/signatureAndEncryptionAlgorithm.d.ts +0 -105
- package/build/signatureAndEncryptionAlgorithm.js +0 -110
- package/build/signatureAndEncryptionAlgorithm.js.map +0 -1
- package/build/types/disclosure.d.ts +0 -5
- package/build/types/disclosure.js +0 -3
- package/build/types/disclosure.js.map +0 -1
- package/build/types/index.d.ts +0 -5
- package/build/types/index.js +0 -22
- package/build/types/index.js.map +0 -1
- package/build/types/saltGenerator.d.ts +0 -17
- package/build/types/saltGenerator.js +0 -3
- package/build/types/saltGenerator.js.map +0 -1
- package/build/types/signer.d.ts +0 -2
- package/build/types/signer.js +0 -3
- package/build/types/signer.js.map +0 -1
- package/build/types/utils.d.ts +0 -2
- package/build/types/utils.js +0 -3
- package/build/types/utils.js.map +0 -1
- package/build/types/verifier.d.ts +0 -14
- package/build/types/verifier.js +0 -3
- package/build/types/verifier.js.map +0 -1
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import { Disclosure } from '@sd-jwt/utils';
|
|
2
|
+
import { Jwt } from '../jwt';
|
|
3
|
+
import { SDJwt, listKeys, pack } from '../sdjwt';
|
|
4
|
+
import Crypto from 'node:crypto';
|
|
5
|
+
import { describe, test, expect } from 'vitest';
|
|
6
|
+
import { DisclosureFrame, Signer } from '@sd-jwt/types';
|
|
7
|
+
import { generateSalt, digest as hasher } from '@sd-jwt/crypto-nodejs';
|
|
8
|
+
import { unpack, createHashMapping } from '@sd-jwt/decode';
|
|
9
|
+
|
|
10
|
+
const hash = { alg: 'SHA256', hasher };
|
|
11
|
+
|
|
12
|
+
describe('SD JWT', () => {
|
|
13
|
+
test('create and encode', async () => {
|
|
14
|
+
const { privateKey } = Crypto.generateKeyPairSync('ed25519');
|
|
15
|
+
const testSigner: Signer = async (data: string) => {
|
|
16
|
+
const sig = Crypto.sign(null, Buffer.from(data), privateKey);
|
|
17
|
+
return Buffer.from(sig).toString('base64url');
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const jwt = new Jwt({
|
|
21
|
+
header: { alg: 'EdDSA' },
|
|
22
|
+
payload: { foo: 'bar' },
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
await jwt.sign(testSigner);
|
|
26
|
+
const sdJwt = new SDJwt({
|
|
27
|
+
jwt,
|
|
28
|
+
disclosures: [],
|
|
29
|
+
});
|
|
30
|
+
expect(sdJwt).toBeDefined();
|
|
31
|
+
|
|
32
|
+
const encoded = sdJwt.encodeSDJwt();
|
|
33
|
+
expect(encoded).toBeDefined();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('decode', async () => {
|
|
37
|
+
const { privateKey } = Crypto.generateKeyPairSync('ed25519');
|
|
38
|
+
const testSigner: Signer = async (data: string) => {
|
|
39
|
+
const sig = Crypto.sign(null, Buffer.from(data), privateKey);
|
|
40
|
+
return Buffer.from(sig).toString('base64url');
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const jwt = new Jwt({
|
|
44
|
+
header: { alg: 'EdDSA' },
|
|
45
|
+
payload: { foo: 'bar', _sd_alg: 'sha-256' },
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
await jwt.sign(testSigner);
|
|
49
|
+
const sdJwt = new SDJwt({
|
|
50
|
+
jwt,
|
|
51
|
+
disclosures: [],
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const encoded = sdJwt.encodeSDJwt();
|
|
55
|
+
|
|
56
|
+
const newSdJwt = await SDJwt.fromEncode(encoded, hasher);
|
|
57
|
+
expect(newSdJwt).toBeDefined();
|
|
58
|
+
const newJwt = newSdJwt.jwt;
|
|
59
|
+
expect(newJwt?.header).toEqual(jwt.header);
|
|
60
|
+
expect(newJwt?.payload).toEqual(jwt.payload);
|
|
61
|
+
expect(newJwt?.signature).toEqual(jwt.signature);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('decode compatibilty', async () => {
|
|
65
|
+
const { privateKey } = Crypto.generateKeyPairSync('ed25519');
|
|
66
|
+
const testSigner: Signer = async (data: string) => {
|
|
67
|
+
const sig = Crypto.sign(null, Buffer.from(data), privateKey);
|
|
68
|
+
return Buffer.from(sig).toString('base64url');
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const jwt = new Jwt({
|
|
72
|
+
header: { alg: 'EdDSA' },
|
|
73
|
+
payload: { foo: 'bar' },
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
await jwt.sign(testSigner);
|
|
77
|
+
const sdJwt = new SDJwt({
|
|
78
|
+
jwt,
|
|
79
|
+
disclosures: [],
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const encoded = sdJwt.encodeSDJwt();
|
|
83
|
+
|
|
84
|
+
const newSdJwt = await SDJwt.fromEncode(encoded, hasher);
|
|
85
|
+
expect(newSdJwt).toBeDefined();
|
|
86
|
+
const newJwt = newSdJwt.jwt;
|
|
87
|
+
expect(newJwt?.header).toEqual(jwt.header);
|
|
88
|
+
expect(newJwt?.payload).toEqual(jwt.payload);
|
|
89
|
+
expect(newJwt?.signature).toEqual(jwt.signature);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('keys', async () => {
|
|
93
|
+
const { privateKey } = Crypto.generateKeyPairSync('ed25519');
|
|
94
|
+
const testSigner: Signer = async (data: string) => {
|
|
95
|
+
const sig = Crypto.sign(null, Buffer.from(data), privateKey);
|
|
96
|
+
return Buffer.from(sig).toString('base64url');
|
|
97
|
+
};
|
|
98
|
+
const jwt = new Jwt({
|
|
99
|
+
header: { alg: 'EdDSA' },
|
|
100
|
+
payload: { foo: 'bar' },
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
await jwt.sign(testSigner);
|
|
104
|
+
const sdJwt = new SDJwt({
|
|
105
|
+
jwt,
|
|
106
|
+
disclosures: [],
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const keys = await sdJwt.keys(hasher);
|
|
110
|
+
expect(keys).toBeDefined();
|
|
111
|
+
expect(keys).toEqual(['foo']);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('presentable keys', async () => {
|
|
115
|
+
const { privateKey } = Crypto.generateKeyPairSync('ed25519');
|
|
116
|
+
const testSigner: Signer = async (data: string) => {
|
|
117
|
+
const sig = Crypto.sign(null, Buffer.from(data), privateKey);
|
|
118
|
+
return Buffer.from(sig).toString('base64url');
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const jwt = new Jwt({
|
|
122
|
+
header: { alg: 'EdDSA' },
|
|
123
|
+
payload: { foo: 'bar' },
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
await jwt.sign(testSigner);
|
|
127
|
+
const sdJwt = new SDJwt({
|
|
128
|
+
jwt,
|
|
129
|
+
disclosures: [],
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const keys = await sdJwt.presentableKeys(hasher);
|
|
133
|
+
expect(keys).toBeDefined();
|
|
134
|
+
expect(keys).toEqual([]);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('claims', async () => {
|
|
138
|
+
const { privateKey } = Crypto.generateKeyPairSync('ed25519');
|
|
139
|
+
const testSigner: Signer = async (data: string) => {
|
|
140
|
+
const sig = Crypto.sign(null, Buffer.from(data), privateKey);
|
|
141
|
+
return Buffer.from(sig).toString('base64url');
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const jwt = new Jwt({
|
|
145
|
+
header: { alg: 'EdDSA' },
|
|
146
|
+
payload: { foo: 'bar' },
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
await jwt.sign(testSigner);
|
|
150
|
+
const sdJwt = new SDJwt({
|
|
151
|
+
jwt,
|
|
152
|
+
disclosures: [],
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const claims = await sdJwt.getClaims(hasher);
|
|
156
|
+
expect(claims).toBeDefined();
|
|
157
|
+
expect(claims).toEqual({
|
|
158
|
+
foo: 'bar',
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('pack', async () => {
|
|
163
|
+
const claim = {
|
|
164
|
+
firstname: 'John',
|
|
165
|
+
lastname: 'Doe',
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const { packedClaims, disclosures } = await pack(
|
|
169
|
+
claim,
|
|
170
|
+
{
|
|
171
|
+
_sd: ['firstname'],
|
|
172
|
+
},
|
|
173
|
+
hash,
|
|
174
|
+
generateSalt,
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
expect(disclosures).toBeDefined();
|
|
178
|
+
expect(packedClaims).toBeDefined();
|
|
179
|
+
|
|
180
|
+
expect(disclosures.length).toEqual(1);
|
|
181
|
+
expect(disclosures[0].key).toEqual('firstname');
|
|
182
|
+
expect(disclosures[0].value).toEqual('John');
|
|
183
|
+
|
|
184
|
+
expect(packedClaims._sd).toBeDefined();
|
|
185
|
+
expect(packedClaims._sd.length).toEqual(1);
|
|
186
|
+
expect(packedClaims.lastname).toEqual('Doe');
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test('list keys', () => {
|
|
190
|
+
const data = {
|
|
191
|
+
a: {
|
|
192
|
+
b: {
|
|
193
|
+
c: 1,
|
|
194
|
+
},
|
|
195
|
+
d: [
|
|
196
|
+
{
|
|
197
|
+
e: 'fasfdsa',
|
|
198
|
+
f: 1234,
|
|
199
|
+
},
|
|
200
|
+
3,
|
|
201
|
+
4,
|
|
202
|
+
],
|
|
203
|
+
},
|
|
204
|
+
g: [['h'], 'i'],
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const keys = listKeys(data);
|
|
208
|
+
expect(keys).toEqual([
|
|
209
|
+
'a',
|
|
210
|
+
'a.b',
|
|
211
|
+
'a.b.c',
|
|
212
|
+
'a.d',
|
|
213
|
+
'a.d.0',
|
|
214
|
+
'a.d.0.e',
|
|
215
|
+
'a.d.0.f',
|
|
216
|
+
'a.d.1',
|
|
217
|
+
'a.d.2',
|
|
218
|
+
'g',
|
|
219
|
+
'g.0',
|
|
220
|
+
'g.0.0',
|
|
221
|
+
'g.1',
|
|
222
|
+
]);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test('presentable keys', async () => {
|
|
226
|
+
const claim = {
|
|
227
|
+
firstname: 'John',
|
|
228
|
+
lastname: 'Doe',
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const { packedClaims, disclosures } = await pack(
|
|
232
|
+
claim,
|
|
233
|
+
{
|
|
234
|
+
_sd: ['firstname'],
|
|
235
|
+
},
|
|
236
|
+
hash,
|
|
237
|
+
generateSalt,
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
const { privateKey } = Crypto.generateKeyPairSync('ed25519');
|
|
241
|
+
const testSigner: Signer = async (data: string) => {
|
|
242
|
+
const sig = Crypto.sign(null, Buffer.from(data), privateKey);
|
|
243
|
+
return Buffer.from(sig).toString('base64url');
|
|
244
|
+
};
|
|
245
|
+
const jwt = new Jwt({
|
|
246
|
+
header: { alg: 'EdDSA' },
|
|
247
|
+
payload: packedClaims,
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
await jwt.sign(testSigner);
|
|
251
|
+
const sdJwt = new SDJwt({
|
|
252
|
+
jwt,
|
|
253
|
+
disclosures,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const keys = await sdJwt.presentableKeys(hasher);
|
|
257
|
+
|
|
258
|
+
expect(keys).toBeDefined();
|
|
259
|
+
expect(keys).toEqual(['firstname']);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test('hash map', async () => {
|
|
263
|
+
const claim = {
|
|
264
|
+
firstname: 'John',
|
|
265
|
+
lastname: 'Doe',
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const { disclosures } = await pack(
|
|
269
|
+
claim,
|
|
270
|
+
{
|
|
271
|
+
_sd: ['firstname'],
|
|
272
|
+
},
|
|
273
|
+
hash,
|
|
274
|
+
generateSalt,
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
const mapping = await createHashMapping(disclosures, hash);
|
|
278
|
+
expect(mapping).toBeDefined();
|
|
279
|
+
expect(Object.keys(mapping).length).toEqual(1);
|
|
280
|
+
expect(mapping[Object.keys(mapping)[0]]).toBeInstanceOf(Disclosure);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test('unpack', async () => {
|
|
284
|
+
const claim = {
|
|
285
|
+
firstname: 'John',
|
|
286
|
+
lastname: 'Doe',
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
const { packedClaims, disclosures } = await pack(
|
|
290
|
+
claim,
|
|
291
|
+
{
|
|
292
|
+
_sd: ['firstname'],
|
|
293
|
+
},
|
|
294
|
+
hash,
|
|
295
|
+
generateSalt,
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
const { disclosureKeymap, unpackedObj } = await unpack(
|
|
299
|
+
packedClaims,
|
|
300
|
+
disclosures,
|
|
301
|
+
hasher,
|
|
302
|
+
);
|
|
303
|
+
expect(disclosureKeymap).toBeDefined();
|
|
304
|
+
expect(unpackedObj).toBeDefined();
|
|
305
|
+
expect(unpackedObj).toEqual({
|
|
306
|
+
firstname: 'John',
|
|
307
|
+
lastname: 'Doe',
|
|
308
|
+
});
|
|
309
|
+
expect(disclosureKeymap.firstname).toBeDefined();
|
|
310
|
+
expect(typeof disclosureKeymap.firstname).toEqual('string');
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
test('pack and unpack', async () => {
|
|
314
|
+
const claims = {
|
|
315
|
+
firstname: 'John',
|
|
316
|
+
lastname: 'Doe',
|
|
317
|
+
ssn: '123-45-6789',
|
|
318
|
+
id: '1234',
|
|
319
|
+
data: {
|
|
320
|
+
firstname: 'John',
|
|
321
|
+
lastname: 'Doe',
|
|
322
|
+
ssn: '123-45-6789',
|
|
323
|
+
list: [{ r: '1' }, 'b', 'c'],
|
|
324
|
+
},
|
|
325
|
+
data2: {
|
|
326
|
+
hi: 'bye',
|
|
327
|
+
},
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
const disclosureFrame: DisclosureFrame<typeof claims> = {
|
|
331
|
+
_sd: ['firstname', 'id', 'data2'],
|
|
332
|
+
data: {
|
|
333
|
+
_sd: ['list'],
|
|
334
|
+
_sd_decoy: 2,
|
|
335
|
+
list: {
|
|
336
|
+
_sd: [0, 2],
|
|
337
|
+
_sd_decoy: 1,
|
|
338
|
+
0: {
|
|
339
|
+
_sd: ['r'],
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
data2: {
|
|
344
|
+
_sd: ['hi'],
|
|
345
|
+
},
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
const { packedClaims, disclosures } = await pack(
|
|
349
|
+
claims,
|
|
350
|
+
disclosureFrame,
|
|
351
|
+
hash,
|
|
352
|
+
generateSalt,
|
|
353
|
+
);
|
|
354
|
+
const { unpackedObj } = await unpack(packedClaims, disclosures, hasher);
|
|
355
|
+
|
|
356
|
+
expect(unpackedObj).toEqual(claims);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
test('no disclosures', async () => {
|
|
360
|
+
const { privateKey } = Crypto.generateKeyPairSync('ed25519');
|
|
361
|
+
const testSigner: Signer = async (data: string) => {
|
|
362
|
+
const sig = Crypto.sign(null, Buffer.from(data), privateKey);
|
|
363
|
+
return Buffer.from(sig).toString('base64url');
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
const jwt = new Jwt({
|
|
367
|
+
header: { alg: 'EdDSA' },
|
|
368
|
+
payload: { foo: 'bar' },
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
await jwt.sign(testSigner);
|
|
372
|
+
const sdJwt = new SDJwt({
|
|
373
|
+
jwt,
|
|
374
|
+
disclosures: [],
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
const credential = sdJwt.encodeSDJwt();
|
|
378
|
+
const decoded = await SDJwt.decodeSDJwt(credential, hasher);
|
|
379
|
+
expect(jwt).toEqual(decoded.jwt);
|
|
380
|
+
expect(decoded.disclosures).toEqual([]);
|
|
381
|
+
});
|
|
382
|
+
});
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import Crypto from 'node:crypto';
|
|
2
|
+
import { SDJwtInstance } from '../src';
|
|
3
|
+
import { DisclosureFrame, Signer, Verifier } from '@sd-jwt/types';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { describe, expect, test } from 'vitest';
|
|
7
|
+
import { digest, generateSalt } from '@sd-jwt/crypto-nodejs';
|
|
8
|
+
|
|
9
|
+
export const createSignerVerifier = () => {
|
|
10
|
+
const { privateKey, publicKey } = Crypto.generateKeyPairSync('ed25519');
|
|
11
|
+
const signer: Signer = async (data: string) => {
|
|
12
|
+
const sig = Crypto.sign(null, Buffer.from(data), privateKey);
|
|
13
|
+
return Buffer.from(sig).toString('base64url');
|
|
14
|
+
};
|
|
15
|
+
const verifier: Verifier = async (data: string, sig: string) => {
|
|
16
|
+
return Crypto.verify(
|
|
17
|
+
null,
|
|
18
|
+
Buffer.from(data),
|
|
19
|
+
publicKey,
|
|
20
|
+
Buffer.from(sig, 'base64url'),
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
return { signer, verifier };
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
describe('App', () => {
|
|
27
|
+
test('Example', async () => {
|
|
28
|
+
const { signer, verifier } = createSignerVerifier();
|
|
29
|
+
const sdjwt = new SDJwtInstance({
|
|
30
|
+
signer,
|
|
31
|
+
signAlg: 'EdDSA',
|
|
32
|
+
verifier,
|
|
33
|
+
hasher: digest,
|
|
34
|
+
hashAlg: 'SHA-256',
|
|
35
|
+
saltGenerator: generateSalt,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const claims = {
|
|
39
|
+
firstname: 'John',
|
|
40
|
+
lastname: 'Doe',
|
|
41
|
+
ssn: '123-45-6789',
|
|
42
|
+
id: '1234',
|
|
43
|
+
data: {
|
|
44
|
+
firstname: 'John',
|
|
45
|
+
lastname: 'Doe',
|
|
46
|
+
ssn: '123-45-6789',
|
|
47
|
+
list: [{ r: '1' }, 'b', 'c'],
|
|
48
|
+
},
|
|
49
|
+
data2: {
|
|
50
|
+
hi: 'bye',
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
const disclosureFrame: DisclosureFrame<typeof claims> = {
|
|
54
|
+
_sd: ['firstname', 'id', 'data2'],
|
|
55
|
+
data: {
|
|
56
|
+
_sd: ['list'],
|
|
57
|
+
_sd_decoy: 2,
|
|
58
|
+
list: {
|
|
59
|
+
_sd: [0, 2],
|
|
60
|
+
_sd_decoy: 1,
|
|
61
|
+
0: {
|
|
62
|
+
_sd: ['r'],
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
data2: {
|
|
67
|
+
_sd: ['hi'],
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
const encodedSdjwt = await sdjwt.issue(claims, disclosureFrame);
|
|
71
|
+
expect(encodedSdjwt).toBeDefined();
|
|
72
|
+
const validated = await sdjwt.validate(encodedSdjwt);
|
|
73
|
+
expect(validated).toBeDefined();
|
|
74
|
+
|
|
75
|
+
const decoded = await sdjwt.decode(encodedSdjwt);
|
|
76
|
+
const keys = await decoded.keys(digest);
|
|
77
|
+
expect(keys).toEqual([
|
|
78
|
+
'data',
|
|
79
|
+
'data.firstname',
|
|
80
|
+
'data.lastname',
|
|
81
|
+
'data.list',
|
|
82
|
+
'data.list.0',
|
|
83
|
+
'data.list.0.r',
|
|
84
|
+
'data.list.1',
|
|
85
|
+
'data.list.2',
|
|
86
|
+
'data.ssn',
|
|
87
|
+
'data2',
|
|
88
|
+
'data2.hi',
|
|
89
|
+
'firstname',
|
|
90
|
+
'id',
|
|
91
|
+
'lastname',
|
|
92
|
+
'ssn',
|
|
93
|
+
]);
|
|
94
|
+
const payloads = await decoded.getClaims(digest);
|
|
95
|
+
expect(payloads).toEqual(claims);
|
|
96
|
+
const presentableKeys = await decoded.presentableKeys(digest);
|
|
97
|
+
expect(presentableKeys).toEqual([
|
|
98
|
+
'data.list',
|
|
99
|
+
'data.list.0',
|
|
100
|
+
'data.list.0.r',
|
|
101
|
+
'data.list.2',
|
|
102
|
+
'data2',
|
|
103
|
+
'data2.hi',
|
|
104
|
+
'firstname',
|
|
105
|
+
'id',
|
|
106
|
+
]);
|
|
107
|
+
|
|
108
|
+
const presentationFrame = ['firstname', 'id'];
|
|
109
|
+
const presentedSDJwt = await sdjwt.present(encodedSdjwt, presentationFrame);
|
|
110
|
+
expect(presentedSDJwt).toBeDefined();
|
|
111
|
+
|
|
112
|
+
const presentationClaims = await sdjwt.getClaims(presentedSDJwt);
|
|
113
|
+
expect(presentationClaims).toBeDefined();
|
|
114
|
+
expect(presentationClaims).toEqual({
|
|
115
|
+
lastname: 'Doe',
|
|
116
|
+
ssn: '123-45-6789',
|
|
117
|
+
data: { firstname: 'John', lastname: 'Doe', ssn: '123-45-6789' },
|
|
118
|
+
id: '1234',
|
|
119
|
+
firstname: 'John',
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const requiredClaimKeys = ['firstname', 'id', 'data.ssn'];
|
|
123
|
+
const verified = await sdjwt.verify(encodedSdjwt, requiredClaimKeys);
|
|
124
|
+
expect(verified).toBeDefined();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('From JSON (complex)', async () => {
|
|
128
|
+
await JSONtest('./complex.json');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('From JSON (array_data_types)', async () => {
|
|
132
|
+
await JSONtest('./array_data_types.json');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('From JSON (array_full_sd)', async () => {
|
|
136
|
+
await JSONtest('./array_full_sd.json');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('From JSON (array_in_sd)', async () => {
|
|
140
|
+
await JSONtest('./array_in_sd.json');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('From JSON (array_recursive_sd_some_disclosed)', async () => {
|
|
144
|
+
await JSONtest('./array_recursive_sd_some_disclosed.json');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('From JSON (header_mod)', async () => {
|
|
148
|
+
await JSONtest('./header_mod.json');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('From JSON (json_serialization)', async () => {
|
|
152
|
+
await JSONtest('./json_serialization.json');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test('From JSON (key_binding)', async () => {
|
|
156
|
+
await JSONtest('./key_binding.json');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('From JSON (no_sd)', async () => {
|
|
160
|
+
await JSONtest('./no_sd.json');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('From JSON (object_data_types)', async () => {
|
|
164
|
+
await JSONtest('./object_data_types.json');
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test('From JSON (recursions)', async () => {
|
|
168
|
+
await JSONtest('./recursions.json');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('From JSON (array_recursive_sd)', async () => {
|
|
172
|
+
await JSONtest('./array_recursive_sd.json');
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test('From JSON (array_of_scalars)', async () => {
|
|
176
|
+
await JSONtest('./array_of_scalars.json');
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test('From JSON (array_of_objects)', async () => {
|
|
180
|
+
await JSONtest('./array_of_objects.json');
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test('From JSON (array_of_nulls)', async () => {
|
|
184
|
+
await JSONtest('./array_of_nulls.json');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test('From JSON (array_nested_in_plain)', async () => {
|
|
188
|
+
await JSONtest('./array_nested_in_plain.json');
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
async function JSONtest(filename: string) {
|
|
193
|
+
const test = loadTestJsonFile(filename);
|
|
194
|
+
const { signer, verifier } = createSignerVerifier();
|
|
195
|
+
const sdjwt = new SDJwtInstance({
|
|
196
|
+
signer,
|
|
197
|
+
signAlg: 'EdDSA',
|
|
198
|
+
verifier,
|
|
199
|
+
hasher: digest,
|
|
200
|
+
hashAlg: 'SHA-256',
|
|
201
|
+
saltGenerator: generateSalt,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const encodedSdjwt = await sdjwt.issue(test.claims, test.disclosureFrame);
|
|
205
|
+
|
|
206
|
+
expect(encodedSdjwt).toBeDefined();
|
|
207
|
+
|
|
208
|
+
const validated = await sdjwt.validate(encodedSdjwt);
|
|
209
|
+
|
|
210
|
+
expect(validated).toBeDefined();
|
|
211
|
+
expect(validated).toStrictEqual({
|
|
212
|
+
header: { alg: 'EdDSA', typ: 'sd-jwt' },
|
|
213
|
+
payload: test.claims,
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const presentedSDJwt = await sdjwt.present(
|
|
217
|
+
encodedSdjwt,
|
|
218
|
+
test.presentationKeys,
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
expect(presentedSDJwt).toBeDefined();
|
|
222
|
+
|
|
223
|
+
const presentationClaims = await sdjwt.getClaims(presentedSDJwt);
|
|
224
|
+
|
|
225
|
+
expect(presentationClaims).toEqual(test.presenatedClaims);
|
|
226
|
+
|
|
227
|
+
const verified = await sdjwt.verify(encodedSdjwt, test.requiredClaimKeys);
|
|
228
|
+
|
|
229
|
+
expect(verified).toBeDefined();
|
|
230
|
+
expect(verified).toStrictEqual({
|
|
231
|
+
header: { alg: 'EdDSA', typ: 'sd-jwt' },
|
|
232
|
+
payload: test.claims,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
type TestJson = {
|
|
237
|
+
claims: object;
|
|
238
|
+
disclosureFrame: DisclosureFrame<object>;
|
|
239
|
+
presentationKeys: string[];
|
|
240
|
+
presenatedClaims: object;
|
|
241
|
+
requiredClaimKeys: string[];
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
function loadTestJsonFile(filename: string) {
|
|
245
|
+
const filepath = path.join(__dirname, filename);
|
|
246
|
+
const fileContents = fs.readFileSync(filepath, 'utf8');
|
|
247
|
+
return JSON.parse(fileContents) as TestJson;
|
|
248
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"claims": {
|
|
3
|
+
"data_types": [null, 42, 3.14, "foo", ["Test"], { "foo": "bar" }]
|
|
4
|
+
},
|
|
5
|
+
"disclosureFrame": {
|
|
6
|
+
"data_types": {
|
|
7
|
+
"_sd": [0, 1, 2, 3, 4, 5]
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"presentationKeys": [
|
|
11
|
+
"data_types.0",
|
|
12
|
+
"data_types.1",
|
|
13
|
+
"data_types.2",
|
|
14
|
+
"data_types.3",
|
|
15
|
+
"data_types.4",
|
|
16
|
+
"data_types.5"
|
|
17
|
+
],
|
|
18
|
+
"presenatedClaims": {
|
|
19
|
+
"data_types": [null, 42, 3.14, "foo", ["Test"], { "foo": "bar" }]
|
|
20
|
+
},
|
|
21
|
+
"requiredClaimKeys": [
|
|
22
|
+
"data_types.0",
|
|
23
|
+
"data_types.1",
|
|
24
|
+
"data_types.2",
|
|
25
|
+
"data_types.3",
|
|
26
|
+
"data_types.4",
|
|
27
|
+
"data_types.5"
|
|
28
|
+
]
|
|
29
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"claims": {
|
|
3
|
+
"is_over": {
|
|
4
|
+
"13": true,
|
|
5
|
+
"18": false,
|
|
6
|
+
"21": false
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
"disclosureFrame": {
|
|
10
|
+
"is_over": {
|
|
11
|
+
"_sd": ["13", "18", "21"]
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"presentationKeys": ["is_over.18"],
|
|
15
|
+
"presenatedClaims": {
|
|
16
|
+
"is_over": {
|
|
17
|
+
"18": false
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"requiredClaimKeys": ["is_over.18"]
|
|
21
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"claims": {
|
|
3
|
+
"nested_array": [["foo", "bar"], ["baz", "qux"]]
|
|
4
|
+
},
|
|
5
|
+
"disclosureFrame": {
|
|
6
|
+
"nested_array": {
|
|
7
|
+
"0": {
|
|
8
|
+
"_sd": [0, 1]
|
|
9
|
+
},
|
|
10
|
+
"1": {
|
|
11
|
+
"_sd": [0, 1]
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"presentationKeys": ["nested_array.0.0", "nested_array.1.1"],
|
|
16
|
+
"presenatedClaims": {
|
|
17
|
+
"nested_array": [["foo"], ["qux"]]
|
|
18
|
+
},
|
|
19
|
+
"requiredClaimKeys": ["nested_array.0.0", "nested_array.1.0"]
|
|
20
|
+
}
|