@sd-jwt/core 0.20.1 → 0.21.0-alpha-20260916100413

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.
Files changed (68) hide show
  1. package/LICENSE +2 -2
  2. package/README.md +82 -80
  3. package/dist/index.cjs +1526 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +696 -0
  6. package/dist/index.d.mts +537 -527
  7. package/dist/index.mjs +1422 -1998
  8. package/dist/index.mjs.map +1 -0
  9. package/package.json +29 -49
  10. package/CHANGELOG.md +0 -39
  11. package/dist/index.d.ts +0 -686
  12. package/dist/index.js +0 -2100
  13. package/src/decode/decode.ts +0 -407
  14. package/src/decode/index.ts +0 -1
  15. package/src/decoy.ts +0 -15
  16. package/src/flattenJSON.ts +0 -99
  17. package/src/generalJSON.ts +0 -157
  18. package/src/index.ts +0 -1000
  19. package/src/jwt.ts +0 -286
  20. package/src/kbjwt.ts +0 -101
  21. package/src/present/index.ts +0 -1
  22. package/src/present/present.ts +0 -216
  23. package/src/sdjwt.ts +0 -384
  24. package/src/test/decode/decode.spec.ts +0 -197
  25. package/src/test/decoy.spec.ts +0 -30
  26. package/src/test/flattenJSON.spec.ts +0 -122
  27. package/src/test/generalJSON.spec.ts +0 -198
  28. package/src/test/index.spec.ts +0 -986
  29. package/src/test/jwt.spec.ts +0 -301
  30. package/src/test/kbjwt.spec.ts +0 -555
  31. package/src/test/pass.spec.ts +0 -7
  32. package/src/test/present/present.spec.ts +0 -305
  33. package/src/test/sdjwt.spec.ts +0 -384
  34. package/src/test/types/type.spec.ts +0 -88
  35. package/src/test/utils/base64url.spec.ts +0 -33
  36. package/src/test/utils/disclosure.spec.ts +0 -170
  37. package/src/test/utils/error.spec.ts +0 -15
  38. package/src/types/index.ts +0 -2
  39. package/src/types/type.ts +0 -270
  40. package/src/types/verification-error.ts +0 -55
  41. package/src/utils/base64url.ts +0 -6
  42. package/src/utils/disclosure.ts +0 -121
  43. package/src/utils/error.ts +0 -25
  44. package/src/utils/index.ts +0 -3
  45. package/src/utils/strict-json.ts +0 -17
  46. package/test/app-e2e.spec.ts +0 -292
  47. package/test/array_data_types.json +0 -31
  48. package/test/array_full_sd.json +0 -21
  49. package/test/array_in_sd.json +0 -13
  50. package/test/array_nested_in_plain.json +0 -29
  51. package/test/array_none_disclosed.json +0 -17
  52. package/test/array_of_nulls.json +0 -15
  53. package/test/array_of_objects.json +0 -65
  54. package/test/array_of_scalars.json +0 -19
  55. package/test/array_recursive_sd.json +0 -35
  56. package/test/array_recursive_sd_some_disclosed.json +0 -63
  57. package/test/complex.json +0 -43
  58. package/test/header_mod.json +0 -48
  59. package/test/json_serialization.json +0 -48
  60. package/test/key_binding.json +0 -48
  61. package/test/no_sd.json +0 -36
  62. package/test/object_data_types.json +0 -62
  63. package/test/recursions.json +0 -117
  64. package/test/rfc9901-audit-fixes.spec.ts +0 -252
  65. package/test/rfc9901-validation.spec.ts +0 -150
  66. package/test/tsconfig.json +0 -4
  67. package/tsconfig.json +0 -7
  68. package/vitest.config.mts +0 -4
package/src/sdjwt.ts DELETED
@@ -1,384 +0,0 @@
1
- import { createHashMapping, getSDAlgAndPayload, unpack } from './decode';
2
- import { createDecoy } from './decoy';
3
- import { Jwt } from './jwt';
4
- import { KBJwt } from './kbjwt';
5
- import { transformPresentationFrame } from './present';
6
- import {
7
- type DisclosureFrame,
8
- encodePathSegment,
9
- type Hasher,
10
- type HasherAndAlg,
11
- type kbHeader,
12
- type kbPayload,
13
- type PresentationFrame,
14
- type SaltGenerator,
15
- SD_DECOY,
16
- SD_DIGEST,
17
- SD_LIST_KEY,
18
- SD_SEPARATOR,
19
- type SDJWTCompact,
20
- } from './types';
21
- import { Disclosure, SDJWTException } from './utils';
22
-
23
- const createDisclosureSalt = async (
24
- saltGenerator: SaltGenerator,
25
- seenSalts: Set<string>,
26
- ) => {
27
- const salt = await saltGenerator(16);
28
- if (typeof salt !== 'string') {
29
- throw new SDJWTException('SaltGenerator must return a string');
30
- }
31
- if (seenSalts.has(salt)) {
32
- throw new SDJWTException('Duplicate disclosure salt detected');
33
- }
34
- seenSalts.add(salt);
35
- return salt;
36
- };
37
-
38
- const addDisclosureDigest = (digest: string, seenDigests: Set<string>) => {
39
- if (seenDigests.has(digest)) {
40
- throw new SDJWTException('Duplicate disclosure digest detected');
41
- }
42
- seenDigests.add(digest);
43
- };
44
-
45
- export type SDJwtData<
46
- Header extends Record<string, unknown>,
47
- Payload extends Record<string, unknown>,
48
- KBHeader extends kbHeader = kbHeader,
49
- KBPayload extends kbPayload = kbPayload,
50
- > = {
51
- jwt?: Jwt<Header, Payload>;
52
- disclosures?: Array<Disclosure>;
53
- kbJwt?: KBJwt<KBHeader, KBPayload>;
54
- };
55
-
56
- export class SDJwt<
57
- Header extends Record<string, unknown> = Record<string, unknown>,
58
- Payload extends Record<string, unknown> = Record<string, unknown>,
59
- KBHeader extends kbHeader = kbHeader,
60
- KBPayload extends kbPayload = kbPayload,
61
- > {
62
- public jwt?: Jwt<Header, Payload>;
63
- public disclosures?: Array<Disclosure>;
64
- public kbJwt?: KBJwt<KBHeader, KBPayload>;
65
-
66
- constructor(data?: SDJwtData<Header, Payload, KBHeader, KBPayload>) {
67
- this.jwt = data?.jwt;
68
- this.disclosures = data?.disclosures;
69
- this.kbJwt = data?.kbJwt;
70
- }
71
-
72
- public static async decodeSDJwt<
73
- Header extends Record<string, unknown> = Record<string, unknown>,
74
- Payload extends Record<string, unknown> = Record<string, unknown>,
75
- KBHeader extends kbHeader = kbHeader,
76
- KBPayload extends kbPayload = kbPayload,
77
- >(
78
- sdjwt: SDJWTCompact,
79
- hasher: Hasher,
80
- ): Promise<{
81
- jwt: Jwt<Header, Payload>;
82
- disclosures: Array<Disclosure>;
83
- kbJwt?: KBJwt<KBHeader, KBPayload>;
84
- }> {
85
- const [encodedJwt, ...encodedDisclosures] = sdjwt.split(SD_SEPARATOR);
86
- if (encodedDisclosures.length === 0) {
87
- throw new SDJWTException('Invalid SD-JWT: missing SD-JWT separator');
88
- }
89
- const jwt = Jwt.fromEncode<Header, Payload>(encodedJwt);
90
-
91
- if (!jwt.payload) {
92
- throw new Error('Payload is undefined on the JWT. Invalid state reached');
93
- }
94
-
95
- const encodedKeyBindingJwt = encodedDisclosures.pop();
96
- const kbJwt = encodedKeyBindingJwt
97
- ? KBJwt.fromKBEncode<KBHeader, KBPayload>(encodedKeyBindingJwt)
98
- : undefined;
99
-
100
- const { _sd_alg } = getSDAlgAndPayload(jwt.payload);
101
-
102
- const disclosures = await Promise.all(
103
- encodedDisclosures.map((ed) =>
104
- Disclosure.fromEncode(ed, { alg: _sd_alg, hasher }),
105
- ),
106
- );
107
-
108
- return {
109
- jwt,
110
- disclosures,
111
- kbJwt,
112
- };
113
- }
114
-
115
- public static async extractJwt<
116
- Header extends Record<string, unknown> = Record<string, unknown>,
117
- Payload extends Record<string, unknown> = Record<string, unknown>,
118
- >(encodedSdJwt: SDJWTCompact): Promise<Jwt<Header, Payload>> {
119
- const [encodedJwt, ..._encodedDisclosures] =
120
- encodedSdJwt.split(SD_SEPARATOR);
121
-
122
- return Jwt.fromEncode(encodedJwt);
123
- }
124
-
125
- public static async fromEncode<
126
- Header extends Record<string, unknown> = Record<string, unknown>,
127
- Payload extends Record<string, unknown> = Record<string, unknown>,
128
- KBHeader extends kbHeader = kbHeader,
129
- KBPayload extends kbPayload = kbPayload,
130
- >(
131
- encodedSdJwt: SDJWTCompact,
132
- hasher: Hasher,
133
- ): Promise<SDJwt<Header, Payload>> {
134
- const { jwt, disclosures, kbJwt } = await SDJwt.decodeSDJwt<
135
- Header,
136
- Payload,
137
- KBHeader,
138
- KBPayload
139
- >(encodedSdJwt, hasher);
140
-
141
- return new SDJwt<Header, Payload, KBHeader, KBPayload>({
142
- jwt,
143
- disclosures,
144
- kbJwt,
145
- });
146
- }
147
-
148
- public async present<T extends Record<string, unknown>>(
149
- presentFrame: PresentationFrame<T> | undefined,
150
- hasher: Hasher,
151
- ): Promise<SDJWTCompact> {
152
- const disclosures = await this.getPresentDisclosures(presentFrame, hasher);
153
- const presentSDJwt = new SDJwt({
154
- jwt: this.jwt,
155
- disclosures,
156
- kbJwt: this.kbJwt,
157
- });
158
- return presentSDJwt.encodeSDJwt();
159
- }
160
-
161
- public async getPresentDisclosures<T extends Record<string, unknown>>(
162
- presentFrame: PresentationFrame<T> | undefined,
163
- hasher: Hasher,
164
- ): Promise<Disclosure<unknown>[]> {
165
- if (!this.jwt?.payload || !this.disclosures) {
166
- throw new SDJWTException('Invalid sd-jwt: jwt or disclosures is missing');
167
- }
168
- const { _sd_alg: alg } = getSDAlgAndPayload(this.jwt.payload);
169
- const hash = { alg, hasher };
170
- const hashmap = await createHashMapping(this.disclosures, hash);
171
- const { disclosureKeymap } = await unpack(
172
- this.jwt.payload,
173
- this.disclosures,
174
- hasher,
175
- );
176
-
177
- const keys = presentFrame
178
- ? transformPresentationFrame(presentFrame)
179
- : await this.presentableKeys(hasher);
180
- const disclosures = keys
181
- .map((k) => hashmap[disclosureKeymap[k]])
182
- .filter((d) => d !== undefined);
183
- return disclosures;
184
- }
185
-
186
- public encodeSDJwt(): SDJWTCompact {
187
- const data: string[] = [];
188
-
189
- if (!this.jwt) {
190
- throw new SDJWTException('Invalid sd-jwt: jwt is missing');
191
- }
192
-
193
- const encodedJwt = this.jwt.encodeJwt();
194
- data.push(encodedJwt);
195
-
196
- if (this.disclosures && this.disclosures.length > 0) {
197
- const encodeddisclosures = this.disclosures
198
- .map((dc) => dc.encode())
199
- .join(SD_SEPARATOR);
200
- data.push(encodeddisclosures);
201
- }
202
-
203
- data.push(this.kbJwt ? this.kbJwt.encodeJwt() : '');
204
- return data.join(SD_SEPARATOR);
205
- }
206
-
207
- public async keys(hasher: Hasher): Promise<string[]> {
208
- return listKeys(await this.getClaims(hasher)).sort();
209
- }
210
-
211
- public async presentableKeys(hasher: Hasher): Promise<string[]> {
212
- if (!this.jwt?.payload || !this.disclosures) {
213
- throw new SDJWTException('Invalid sd-jwt: jwt or disclosures is missing');
214
- }
215
- const { disclosureKeymap } = await unpack(
216
- this.jwt?.payload,
217
- this.disclosures,
218
- hasher,
219
- );
220
- return Object.keys(disclosureKeymap).sort();
221
- }
222
-
223
- public async getClaims<T>(hasher: Hasher): Promise<T> {
224
- if (!this.jwt?.payload || !this.disclosures) {
225
- throw new SDJWTException('Invalid sd-jwt: jwt or disclosures is missing');
226
- }
227
- const { unpackedObj } = await unpack(
228
- this.jwt.payload,
229
- this.disclosures,
230
- hasher,
231
- );
232
- return unpackedObj as T;
233
- }
234
- }
235
-
236
- export const listKeys = (obj: Record<string, unknown>, prefix = '') => {
237
- const keys: string[] = [];
238
- for (const key in obj) {
239
- if (obj[key] === undefined) continue;
240
- const escapedKey = encodePathSegment(key);
241
- const newKey = prefix ? `${prefix}.${escapedKey}` : escapedKey;
242
- keys.push(newKey);
243
-
244
- const value = obj[key];
245
- if (value && typeof value === 'object') {
246
- keys.push(...listKeys(value as Record<string, unknown>, newKey));
247
- }
248
- }
249
- return keys;
250
- };
251
-
252
- export const pack = async <T extends Record<string, unknown>>(
253
- claims: T,
254
- disclosureFrame: DisclosureFrame<T> | undefined,
255
- hash: HasherAndAlg,
256
- saltGenerator: SaltGenerator,
257
- seenSalts = new Set<string>(),
258
- seenDigests = new Set<string>(),
259
- ): Promise<{
260
- packedClaims: Record<string, unknown> | Array<Record<string, unknown>>;
261
- disclosures: Array<Disclosure>;
262
- }> => {
263
- if (!disclosureFrame) {
264
- return {
265
- packedClaims: claims,
266
- disclosures: [],
267
- };
268
- }
269
-
270
- const sd = disclosureFrame[SD_DIGEST] ?? [];
271
- const decoyCount = disclosureFrame[SD_DECOY] ?? 0;
272
-
273
- if (Array.isArray(claims)) {
274
- const packedClaims: Array<Record<typeof SD_LIST_KEY, string>> = [];
275
- const disclosures: Array<Disclosure> = [];
276
- const recursivePackedClaims: Record<number, unknown> = {};
277
-
278
- for (const key in disclosureFrame) {
279
- if (key !== SD_DIGEST) {
280
- const idx = Number.parseInt(key, 10);
281
- const packed = await pack(
282
- claims[idx],
283
- disclosureFrame[idx],
284
- hash,
285
- saltGenerator,
286
- seenSalts,
287
- seenDigests,
288
- );
289
- recursivePackedClaims[idx] = packed.packedClaims;
290
- disclosures.push(...packed.disclosures);
291
- }
292
- }
293
-
294
- for (let i = 0; i < claims.length; i++) {
295
- const claim = recursivePackedClaims[i]
296
- ? recursivePackedClaims[i]
297
- : claims[i];
298
- /** This part is set discloure for array items.
299
- * The example of disclosureFrame of an Array is
300
- *
301
- * const claims = {
302
- * array: ['a', 'b', 'c']
303
- * }
304
- *
305
- * diclosureFrame: DisclosureFrame<typeof claims> = {
306
- * array: {
307
- * _sd: [0, 2]
308
- * }
309
- * }
310
- *
311
- * It means that we want to disclose the first and the third item of the array
312
- *
313
- * So If the index `i` is in the disclosure list(sd), then we create a disclosure for the claim
314
- */
315
- // @ts-expect-error
316
- if (sd.includes(i)) {
317
- const salt = await createDisclosureSalt(saltGenerator, seenSalts);
318
- const disclosure = new Disclosure([salt, claim]);
319
- const digest = await disclosure.digest(hash);
320
- addDisclosureDigest(digest, seenDigests);
321
- packedClaims.push({ [SD_LIST_KEY]: digest });
322
- disclosures.push(disclosure);
323
- } else {
324
- packedClaims.push(claim);
325
- }
326
- }
327
- for (let j = 0; j < decoyCount; j++) {
328
- const decoyDigest = await createDecoy(hash, saltGenerator);
329
- addDisclosureDigest(decoyDigest, seenDigests);
330
- packedClaims.push({ [SD_LIST_KEY]: decoyDigest });
331
- }
332
- return { packedClaims, disclosures };
333
- }
334
-
335
- const packedClaims: Record<string, unknown> = {};
336
- const disclosures: Array<Disclosure> = [];
337
- const recursivePackedClaims: Record<string, unknown> = {};
338
-
339
- for (const key in disclosureFrame) {
340
- if (key !== SD_DIGEST) {
341
- const packed = await pack(
342
- // @ts-expect-error
343
- claims[key],
344
- disclosureFrame[key],
345
- hash,
346
- saltGenerator,
347
- seenSalts,
348
- seenDigests,
349
- );
350
- recursivePackedClaims[key] = packed.packedClaims;
351
- disclosures.push(...packed.disclosures);
352
- }
353
- }
354
-
355
- const _sd: string[] = [];
356
-
357
- for (const key in claims) {
358
- const claim = recursivePackedClaims[key]
359
- ? recursivePackedClaims[key]
360
- : claims[key];
361
- if (sd.includes(key)) {
362
- const salt = await createDisclosureSalt(saltGenerator, seenSalts);
363
- const disclosure = new Disclosure([salt, key, claim]);
364
- const digest = await disclosure.digest(hash);
365
- addDisclosureDigest(digest, seenDigests);
366
-
367
- _sd.push(digest);
368
- disclosures.push(disclosure);
369
- } else {
370
- packedClaims[key] = claim;
371
- }
372
- }
373
-
374
- for (let j = 0; j < decoyCount; j++) {
375
- const decoyDigest = await createDecoy(hash, saltGenerator);
376
- addDisclosureDigest(decoyDigest, seenDigests);
377
- _sd.push(decoyDigest);
378
- }
379
-
380
- if (_sd.length > 0) {
381
- packedClaims[SD_DIGEST] = _sd.sort();
382
- }
383
- return { packedClaims, disclosures };
384
- };
@@ -1,197 +0,0 @@
1
- import { hasher as digest } from '@owf/crypto';
2
- import { describe, expect, test } from 'vitest';
3
- import {
4
- createHashMapping,
5
- decodeJwt,
6
- decodeSdJwt,
7
- decodeSdJwtSync,
8
- getClaims,
9
- getClaimsSync,
10
- getSDAlgAndPayload,
11
- splitSdJwt,
12
- unpackObj,
13
- } from '../../decode';
14
-
15
- describe('decode tests', () => {
16
- test('decode jwt', () => {
17
- const jwt =
18
- 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
19
- const { header, payload, signature } = decodeJwt(jwt);
20
- expect(signature).toBeDefined();
21
- expect(header).toStrictEqual({ alg: 'HS256', typ: 'JWT' });
22
- expect(payload).toStrictEqual({
23
- sub: '1234567890',
24
- name: 'John Doe',
25
- iat: 1516239022,
26
- });
27
- });
28
-
29
- test('decode jwt with invalid input', () => {
30
- const jwt = 'invalid.invalid';
31
- expect(() => decodeJwt(jwt)).toThrow('Invalid JWT as input');
32
- });
33
-
34
- test('split sdjwt', () => {
35
- const sdjwt = 'h.p.s~d1~d2~';
36
- const { jwt, disclosures, kbJwt } = splitSdJwt(sdjwt);
37
- expect(jwt).toBe('h.p.s');
38
- expect(disclosures).toStrictEqual(['d1', 'd2']);
39
- expect(kbJwt).toBeUndefined();
40
- });
41
-
42
- test('split rejects bare jwt without SD-JWT separator', () => {
43
- const sdjwt = 'h.p.s';
44
- expect(() => splitSdJwt(sdjwt)).toThrow(
45
- 'Invalid SD-JWT: missing SD-JWT separator',
46
- );
47
- });
48
-
49
- test('split sdjwt with kbjwt', () => {
50
- const sdjwt = 'h.p.s~d1~d2~kbh.kbp.kbs';
51
- const { jwt, disclosures, kbJwt } = splitSdJwt(sdjwt);
52
- expect(jwt).toBe('h.p.s');
53
- expect(disclosures).toStrictEqual(['d1', 'd2']);
54
- expect(kbJwt).toBe('kbh.kbp.kbs');
55
- });
56
-
57
- test('decode sdjwt', async () => {
58
- const sdjwt =
59
- 'eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJfc2QiOlsiaWQ1azZ1ZVplVTY4bExaMlU2YjJJbF9QR3ZKb1RDMlpkMkpwY0RwMzFIWSJdLCJfc2RfYWxnIjoic2hhLTI1NiJ9.GiLF_HhacrstqCJ223VvWOoJJWU8qk4dYQHklSMwxv36pPF_7ER53Wbty1qYRlQ6NeMUdBRRdj9JQLLCzz1gCQ~WyI2NTMxNDA2ZmVhZmU0YjBmIiwiZm9vIiwiYmFyIl0~';
60
- const decodedSdJwt = await decodeSdJwt(sdjwt, digest);
61
- expect(decodedSdJwt).toBeDefined();
62
- expect(decodedSdJwt.kbJwt).toBeUndefined();
63
- expect(decodedSdJwt.disclosures.length).toEqual(1);
64
- expect(decodedSdJwt.jwt).toBeDefined();
65
- });
66
-
67
- test('decode rejects bare jwt without SD-JWT separator', async () => {
68
- const jwt =
69
- 'eyJhbGciOiJIUzI1NiIsInR5cCI6InNkK2p3dCJ9.eyJsYXN0bmFtZSI6IkRvZSIsInNzbiI6IjEyMy00NS02Nzg5IiwiX3NkIjpbIk4yUXhZV1UxTlRnME1qQmpOR1JpWVRCaU1tRmtaamN5WXpSbFpXUmhaRGd5WkRCbE1qaGhZVGcwTnpJMU9XSXpZek5qWkdNNE1qZG1NVGN6TmpZd05RIiwiWlRSalkyUTVOemRoWkRVM05tWTFZV0UyTmpka01XVmpNRE16WXpOak5qQmtNak5pT0dZelpHSTBOelV4TURsak9EWTRNREEzWm1JeFpUY3daREZqTmciXSwiX3NkX2FsZyI6InNoYS0yNTYifQ.mX14Sw86xy8NFQta7tCfNmhVCqzfaJ_K3VEIhTjbLDY';
70
- await expect(decodeSdJwt(jwt, digest)).rejects.toThrow(
71
- 'Invalid SD-JWT: missing SD-JWT separator',
72
- );
73
- });
74
-
75
- test('decode sdjwt sync', () => {
76
- const sdjwt =
77
- 'eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJfc2QiOlsiaWQ1azZ1ZVplVTY4bExaMlU2YjJJbF9QR3ZKb1RDMlpkMkpwY0RwMzFIWSJdLCJfc2RfYWxnIjoic2hhLTI1NiJ9.GiLF_HhacrstqCJ223VvWOoJJWU8qk4dYQHklSMwxv36pPF_7ER53Wbty1qYRlQ6NeMUdBRRdj9JQLLCzz1gCQ~WyI2NTMxNDA2ZmVhZmU0YjBmIiwiZm9vIiwiYmFyIl0~';
78
- const decodedSdJwt = decodeSdJwtSync(sdjwt, digest);
79
- expect(decodedSdJwt).toBeDefined();
80
- expect(decodedSdJwt.kbJwt).toBeUndefined();
81
- expect(decodedSdJwt.disclosures.length).toEqual(1);
82
- expect(decodedSdJwt.jwt).toBeDefined();
83
- });
84
-
85
- test('decode sync rejects bare jwt without SD-JWT separator', () => {
86
- const jwt =
87
- 'eyJhbGciOiJIUzI1NiIsInR5cCI6InNkK2p3dCJ9.eyJsYXN0bmFtZSI6IkRvZSIsInNzbiI6IjEyMy00NS02Nzg5IiwiX3NkIjpbIk4yUXhZV1UxTlRnME1qQmpOR1JpWVRCaU1tRmtaamN5WXpSbFpXUmhaRGd5WkRCbE1qaGhZVGcwTnpJMU9XSXpZek5qWkdNNE1qZG1NVGN6TmpZd05RIiwiWlRSalkyUTVOemRoWkRVM05tWTFZV0UyTmpka01XVmpNRE16WXpOak5qQmtNak5pT0dZelpHSTBOelV4TURsak9EWTRNREEzWm1JeFpUY3daREZqTmciXSwiX3NkX2FsZyI6InNoYS0yNTYifQ.mX14Sw86xy8NFQta7tCfNmhVCqzfaJ_K3VEIhTjbLDY';
88
- expect(() => decodeSdJwtSync(jwt, digest)).toThrow(
89
- 'Invalid SD-JWT: missing SD-JWT separator',
90
- );
91
- });
92
-
93
- test('decode sdjwt sync (with KB)', () => {
94
- const sdjwt =
95
- 'eyJ0eXAiOiJzZCtqd3QiLCJhbGciOiJFUzI1NiJ9.eyJpZCI6IjEyMzQiLCJfc2QiOlsiYkRUUnZtNS1Zbi1IRzdjcXBWUjVPVlJJWHNTYUJrNTdKZ2lPcV9qMVZJNCIsImV0M1VmUnlsd1ZyZlhkUEt6Zzc5aGNqRDFJdHpvUTlvQm9YUkd0TW9zRmsiLCJ6V2ZaTlMxOUF0YlJTVGJvN3NKUm4wQlpRdldSZGNob0M3VVphYkZyalk4Il0sIl9zZF9hbGciOiJzaGEtMjU2In0.n27NCtnuwytlBYtUNjgkesDP_7gN7bhaLhWNL4SWT6MaHsOjZ2ZMp987GgQRL6ZkLbJ7Cd3hlePHS84GBXPuvg~WyI1ZWI4Yzg2MjM0MDJjZjJlIiwiZmlyc3RuYW1lIiwiSm9obiJd~WyJjNWMzMWY2ZWYzNTg4MWJjIiwibGFzdG5hbWUiLCJEb2UiXQ~WyJmYTlkYTUzZWJjOTk3OThlIiwic3NuIiwiMTIzLTQ1LTY3ODkiXQ~eyJ0eXAiOiJrYitqd3QiLCJhbGciOiJFUzI1NiJ9.eyJpYXQiOjE3MTAwNjk3MjIsImF1ZCI6ImRpZDpleGFtcGxlOjEyMyIsIm5vbmNlIjoiazh2ZGYwbmQ2Iiwic2RfaGFzaCI6Il8tTmJWSzNmczl3VzNHaDNOUktSNEt1NmZDMUwzN0R2MFFfalBXd0ppRkUifQ.pqw2OB5IA5ya9Mxf60hE3nr2gsJEIoIlnuCa4qIisijHbwg3WzTDFmW2SuNvK_ORN0WU6RoGbJx5uYZh8k4EbA';
96
- const decodedSdJwt = decodeSdJwtSync(sdjwt, digest);
97
- expect(decodedSdJwt).toBeDefined();
98
- expect(decodedSdJwt.kbJwt).toBeDefined();
99
- expect(decodedSdJwt.disclosures.length).toEqual(3);
100
- expect(decodedSdJwt.jwt).toBeDefined();
101
- });
102
-
103
- test('decode sdjwt (with KB)', async () => {
104
- const sdjwt =
105
- 'eyJ0eXAiOiJzZCtqd3QiLCJhbGciOiJFUzI1NiJ9.eyJpZCI6IjEyMzQiLCJfc2QiOlsiYkRUUnZtNS1Zbi1IRzdjcXBWUjVPVlJJWHNTYUJrNTdKZ2lPcV9qMVZJNCIsImV0M1VmUnlsd1ZyZlhkUEt6Zzc5aGNqRDFJdHpvUTlvQm9YUkd0TW9zRmsiLCJ6V2ZaTlMxOUF0YlJTVGJvN3NKUm4wQlpRdldSZGNob0M3VVphYkZyalk4Il0sIl9zZF9hbGciOiJzaGEtMjU2In0.n27NCtnuwytlBYtUNjgkesDP_7gN7bhaLhWNL4SWT6MaHsOjZ2ZMp987GgQRL6ZkLbJ7Cd3hlePHS84GBXPuvg~WyI1ZWI4Yzg2MjM0MDJjZjJlIiwiZmlyc3RuYW1lIiwiSm9obiJd~WyJjNWMzMWY2ZWYzNTg4MWJjIiwibGFzdG5hbWUiLCJEb2UiXQ~WyJmYTlkYTUzZWJjOTk3OThlIiwic3NuIiwiMTIzLTQ1LTY3ODkiXQ~eyJ0eXAiOiJrYitqd3QiLCJhbGciOiJFUzI1NiJ9.eyJpYXQiOjE3MTAwNjk3MjIsImF1ZCI6ImRpZDpleGFtcGxlOjEyMyIsIm5vbmNlIjoiazh2ZGYwbmQ2Iiwic2RfaGFzaCI6Il8tTmJWSzNmczl3VzNHaDNOUktSNEt1NmZDMUwzN0R2MFFfalBXd0ppRkUifQ.pqw2OB5IA5ya9Mxf60hE3nr2gsJEIoIlnuCa4qIisijHbwg3WzTDFmW2SuNvK_ORN0WU6RoGbJx5uYZh8k4EbA';
106
- const decodedSdJwt = await decodeSdJwt(sdjwt, digest);
107
- expect(decodedSdJwt).toBeDefined();
108
- expect(decodedSdJwt.kbJwt).toBeDefined();
109
- expect(decodedSdJwt.disclosures.length).toEqual(3);
110
- expect(decodedSdJwt.jwt).toBeDefined();
111
- });
112
-
113
- test('get claims', async () => {
114
- const sdjwt =
115
- 'eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJfc2QiOlsiaWQ1azZ1ZVplVTY4bExaMlU2YjJJbF9QR3ZKb1RDMlpkMkpwY0RwMzFIWSJdLCJfc2RfYWxnIjoic2hhLTI1NiJ9.GiLF_HhacrstqCJ223VvWOoJJWU8qk4dYQHklSMwxv36pPF_7ER53Wbty1qYRlQ6NeMUdBRRdj9JQLLCzz1gCQ~WyI2NTMxNDA2ZmVhZmU0YjBmIiwiZm9vIiwiYmFyIl0~';
116
- const decodedSdJwt = await decodeSdJwt(sdjwt, digest);
117
- const claims = await getClaims(
118
- decodedSdJwt.jwt.payload,
119
- decodedSdJwt.disclosures,
120
- digest,
121
- );
122
- expect(claims).toStrictEqual({
123
- foo: 'bar',
124
- });
125
- });
126
-
127
- test('getClaims #2', async () => {
128
- const sdjwt =
129
- 'eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJ0ZXN0Ijp7Il9zZCI6WyJqVEszMHNleDZhYV9kUk1KSWZDR056Q0FwbVB5MzRRNjNBa3QzS3hhSktzIl19LCJfc2QiOlsiME9nMi1ReG95eW1UOGNnVzZZUjVSSFpQLUJuR2tHUi1NM2otLV92RWlzSSIsIkcwZ3lHNnExVFMyUlQxMkZ3X2RRRDVVcjlZc1AwZlVWOXVtQWdGMC1jQ1EiXSwiX3NkX2FsZyI6InNoYS0yNTYifQ.ggEyE4SeDO2Hu3tol3VLmi7NQj56yKzKQDaafocgkLrUBdivghohtzrfcbrMN7CRufJ_Cnh0EL54kymXLGTdDQ~WyIwNGU0MjAzOWU4ZWFiOWRjIiwiYSIsIjEiXQ~WyIwOGE1Yjc5MjMyYjAzYzBhIiwiMSJd~WyJiNWE2YjUzZGQwYTFmMGIwIiwienp6IiwieHh4Il0~WyIxYzdmOTE4ZTE0MjA2NzZiIiwiZm9vIiwiYmFyIl0~WyJmZjYxYzQ5ZGU2NjFiYzMxIiwiYXJyIixbeyIuLi4iOiJTSG96VW5KNUpkd0ZtTjVCbXB5dXZCWGZfZWRjckVvcExPYThTVlBFUmg0In0sIjIiLHsiX3NkIjpbIkpuODNhZkp0OGx4NG1FMzZpRkZyS2U2R2VnN0dlVUQ4Z3UwdVo3NnRZcW8iXX1dXQ~';
130
- const decodedSdJwt = await decodeSdJwt(sdjwt, digest);
131
- const claims = await getClaims(
132
- decodedSdJwt.jwt.payload,
133
- decodedSdJwt.disclosures,
134
- digest,
135
- );
136
- expect(claims).toStrictEqual({
137
- foo: 'bar',
138
- arr: ['1', '2', { a: '1' }],
139
- test: {
140
- zzz: 'xxx',
141
- },
142
- });
143
- });
144
-
145
- test('get claims sync', () => {
146
- const sdjwt =
147
- 'eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJfc2QiOlsiaWQ1azZ1ZVplVTY4bExaMlU2YjJJbF9QR3ZKb1RDMlpkMkpwY0RwMzFIWSJdLCJfc2RfYWxnIjoic2hhLTI1NiJ9.GiLF_HhacrstqCJ223VvWOoJJWU8qk4dYQHklSMwxv36pPF_7ER53Wbty1qYRlQ6NeMUdBRRdj9JQLLCzz1gCQ~WyI2NTMxNDA2ZmVhZmU0YjBmIiwiZm9vIiwiYmFyIl0~';
148
- const decodedSdJwt = decodeSdJwtSync(sdjwt, digest);
149
- const claims = getClaimsSync(
150
- decodedSdJwt.jwt.payload,
151
- decodedSdJwt.disclosures,
152
- digest,
153
- );
154
- expect(claims).toStrictEqual({
155
- foo: 'bar',
156
- });
157
- });
158
-
159
- test('getClaims sync #2', () => {
160
- const sdjwt =
161
- 'eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJ0ZXN0Ijp7Il9zZCI6WyJqVEszMHNleDZhYV9kUk1KSWZDR056Q0FwbVB5MzRRNjNBa3QzS3hhSktzIl19LCJfc2QiOlsiME9nMi1ReG95eW1UOGNnVzZZUjVSSFpQLUJuR2tHUi1NM2otLV92RWlzSSIsIkcwZ3lHNnExVFMyUlQxMkZ3X2RRRDVVcjlZc1AwZlVWOXVtQWdGMC1jQ1EiXSwiX3NkX2FsZyI6InNoYS0yNTYifQ.ggEyE4SeDO2Hu3tol3VLmi7NQj56yKzKQDaafocgkLrUBdivghohtzrfcbrMN7CRufJ_Cnh0EL54kymXLGTdDQ~WyIwNGU0MjAzOWU4ZWFiOWRjIiwiYSIsIjEiXQ~WyIwOGE1Yjc5MjMyYjAzYzBhIiwiMSJd~WyJiNWE2YjUzZGQwYTFmMGIwIiwienp6IiwieHh4Il0~WyIxYzdmOTE4ZTE0MjA2NzZiIiwiZm9vIiwiYmFyIl0~WyJmZjYxYzQ5ZGU2NjFiYzMxIiwiYXJyIixbeyIuLi4iOiJTSG96VW5KNUpkd0ZtTjVCbXB5dXZCWGZfZWRjckVvcExPYThTVlBFUmg0In0sIjIiLHsiX3NkIjpbIkpuODNhZkp0OGx4NG1FMzZpRkZyS2U2R2VnN0dlVUQ4Z3UwdVo3NnRZcW8iXX1dXQ~';
162
- const decodedSdJwt = decodeSdJwtSync(sdjwt, digest);
163
- const claims = getClaimsSync(
164
- decodedSdJwt.jwt.payload,
165
- decodedSdJwt.disclosures,
166
- digest,
167
- );
168
- expect(claims).toStrictEqual({
169
- foo: 'bar',
170
- arr: ['1', '2', { a: '1' }],
171
- test: {
172
- zzz: 'xxx',
173
- },
174
- });
175
- });
176
-
177
- test('Test default sd hash algorithm', () => {
178
- const { _sd_alg } = getSDAlgAndPayload({});
179
- expect(_sd_alg).toBe('sha-256');
180
- });
181
-
182
- test('unpackObj clone', async () => {
183
- const sdjwt =
184
- 'eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJ0ZXN0Ijp7Il9zZCI6WyJqVEszMHNleDZhYV9kUk1KSWZDR056Q0FwbVB5MzRRNjNBa3QzS3hhSktzIl19LCJfc2QiOlsiME9nMi1ReG95eW1UOGNnVzZZUjVSSFpQLUJuR2tHUi1NM2otLV92RWlzSSIsIkcwZ3lHNnExVFMyUlQxMkZ3X2RRRDVVcjlZc1AwZlVWOXVtQWdGMC1jQ1EiXSwiX3NkX2FsZyI6InNoYS0yNTYifQ.ggEyE4SeDO2Hu3tol3VLmi7NQj56yKzKQDaafocgkLrUBdivghohtzrfcbrMN7CRufJ_Cnh0EL54kymXLGTdDQ~WyIwNGU0MjAzOWU4ZWFiOWRjIiwiYSIsIjEiXQ~WyIwOGE1Yjc5MjMyYjAzYzBhIiwiMSJd~WyJiNWE2YjUzZGQwYTFmMGIwIiwienp6IiwieHh4Il0~WyIxYzdmOTE4ZTE0MjA2NzZiIiwiZm9vIiwiYmFyIl0~WyJmZjYxYzQ5ZGU2NjFiYzMxIiwiYXJyIixbeyIuLi4iOiJTSG96VW5KNUpkd0ZtTjVCbXB5dXZCWGZfZWRjckVvcExPYThTVlBFUmg0In0sIjIiLHsiX3NkIjpbIkpuODNhZkp0OGx4NG1FMzZpRkZyS2U2R2VnN0dlVUQ4Z3UwdVo3NnRZcW8iXX1dXQ~';
185
- const decodedSdJwt = await decodeSdJwt(sdjwt, digest);
186
- const jwtPayload = JSON.parse(JSON.stringify(decodedSdJwt.jwt.payload));
187
-
188
- const { _sd_alg } = getSDAlgAndPayload(decodedSdJwt.jwt.payload);
189
- const hash = { hasher: digest, alg: _sd_alg };
190
- const map = await createHashMapping(decodedSdJwt.disclosures, hash);
191
-
192
- const { unpackedObj } = unpackObj(decodedSdJwt.jwt.payload, map);
193
-
194
- expect(unpackedObj).toBeDefined();
195
- expect(jwtPayload).toStrictEqual(decodedSdJwt.jwt.payload);
196
- });
197
- });
@@ -1,30 +0,0 @@
1
- import { hasher as digest, generateSalt } from '@owf/crypto';
2
- import { describe, expect, test } from 'vitest';
3
- import { createDecoy } from '../decoy';
4
- import { base64urlEncode } from '../utils';
5
-
6
- const hash = {
7
- hasher: digest,
8
- alg: 'SHA256',
9
- };
10
-
11
- describe('Decoy', () => {
12
- test('decoy', async () => {
13
- const decoyValue = await createDecoy(hash, generateSalt);
14
- expect(decoyValue.length).toBe(43);
15
- });
16
-
17
- // ref https://datatracker.ietf.org/doc/draft-ietf-oauth-selective-disclosure-jwt/07/
18
- // *Claim email*:
19
- // * SHA-256 Hash: JzYjH4svliH0R3PyEMfeZu6Jt69u5qehZo7F7EPYlSE
20
- // * Disclosure: WyI2SWo3dE0tYTVpVlBHYm9TNXRtdlZBIiwgImVtYWlsIiwgImpvaG5kb2VAZXhhbXBsZS5jb20iXQ
21
- // * Contents: ["6Ij7tM-a5iVPGboS5tmvVA", "email", "johndoe@example.com"]
22
- test('apply hasher and saltGenerator', async () => {
23
- const decoyValue = await createDecoy(hash, () =>
24
- base64urlEncode(
25
- '["6Ij7tM-a5iVPGboS5tmvVA", "email", "johndoe@example.com"]',
26
- ),
27
- );
28
- expect(decoyValue).toBe('JzYjH4svliH0R3PyEMfeZu6Jt69u5qehZo7F7EPYlSE');
29
- });
30
- });