@sd-jwt/core 0.19.1-next.0 → 0.19.1-next.10
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/README.md +68 -5
- package/dist/index.d.mts +359 -16
- package/dist/index.d.ts +359 -16
- package/dist/index.js +795 -138
- package/dist/index.mjs +736 -123
- package/package.json +4 -7
- package/src/decode/decode.ts +366 -0
- package/src/decode/index.ts +1 -0
- package/src/decoy.ts +2 -2
- package/src/flattenJSON.ts +3 -3
- package/src/generalJSON.ts +3 -3
- package/src/index.ts +227 -14
- package/src/jwt.ts +10 -15
- package/src/kbjwt.ts +5 -6
- package/src/present/index.ts +1 -0
- package/src/present/present.ts +210 -0
- package/src/sdjwt.ts +11 -10
- package/src/test/decode/decode.spec.ts +202 -0
- package/src/test/decoy.spec.ts +2 -2
- package/src/test/generalJSON.spec.ts +1 -1
- package/src/test/index.spec.ts +154 -2
- package/src/test/jwt.spec.ts +7 -13
- package/src/test/kbjwt.spec.ts +5 -5
- package/src/test/present/present.spec.ts +305 -0
- package/src/test/sdjwt.spec.ts +4 -4
- package/src/test/types/type.spec.ts +88 -0
- package/src/test/utils/base64url.spec.ts +33 -0
- package/src/test/utils/disclosure.spec.ts +170 -0
- package/src/test/utils/error.spec.ts +15 -0
- package/src/types/index.ts +2 -0
- package/src/types/type.ts +249 -0
- package/src/types/verification-error.ts +55 -0
- package/src/utils/base64url.ts +6 -0
- package/src/utils/disclosure.ts +98 -0
- package/src/utils/error.ts +25 -0
- package/src/utils/index.ts +3 -0
- package/test/app-e2e.spec.ts +8 -8
- package/test/rfc9901-validation.spec.ts +150 -0
- package/CHANGELOG.md +0 -240
|
@@ -0,0 +1,202 @@
|
|
|
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 sdjwt without disclosures', () => {
|
|
43
|
+
const sdjwt = 'h.p.s';
|
|
44
|
+
const { jwt, disclosures, kbJwt } = splitSdJwt(sdjwt);
|
|
45
|
+
expect(jwt).toBe('h.p.s');
|
|
46
|
+
expect(disclosures).toStrictEqual([]);
|
|
47
|
+
expect(kbJwt).toBeUndefined();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('split sdjwt with kbjwt', () => {
|
|
51
|
+
const sdjwt = 'h.p.s~d1~d2~kbh.kbp.kbs';
|
|
52
|
+
const { jwt, disclosures, kbJwt } = splitSdJwt(sdjwt);
|
|
53
|
+
expect(jwt).toBe('h.p.s');
|
|
54
|
+
expect(disclosures).toStrictEqual(['d1', 'd2']);
|
|
55
|
+
expect(kbJwt).toBe('kbh.kbp.kbs');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('decode sdjwt', async () => {
|
|
59
|
+
const sdjwt =
|
|
60
|
+
'eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJfc2QiOlsiaWQ1azZ1ZVplVTY4bExaMlU2YjJJbF9QR3ZKb1RDMlpkMkpwY0RwMzFIWSJdLCJfc2RfYWxnIjoic2hhLTI1NiJ9.GiLF_HhacrstqCJ223VvWOoJJWU8qk4dYQHklSMwxv36pPF_7ER53Wbty1qYRlQ6NeMUdBRRdj9JQLLCzz1gCQ~WyI2NTMxNDA2ZmVhZmU0YjBmIiwiZm9vIiwiYmFyIl0~';
|
|
61
|
+
const decodedSdJwt = await decodeSdJwt(sdjwt, digest);
|
|
62
|
+
expect(decodedSdJwt).toBeDefined();
|
|
63
|
+
expect(decodedSdJwt.kbJwt).toBeUndefined();
|
|
64
|
+
expect(decodedSdJwt.disclosures.length).toEqual(1);
|
|
65
|
+
expect(decodedSdJwt.jwt).toBeDefined();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('decode jwt', async () => {
|
|
69
|
+
const jwt =
|
|
70
|
+
'eyJhbGciOiJIUzI1NiIsInR5cCI6InNkK2p3dCJ9.eyJsYXN0bmFtZSI6IkRvZSIsInNzbiI6IjEyMy00NS02Nzg5IiwiX3NkIjpbIk4yUXhZV1UxTlRnME1qQmpOR1JpWVRCaU1tRmtaamN5WXpSbFpXUmhaRGd5WkRCbE1qaGhZVGcwTnpJMU9XSXpZek5qWkdNNE1qZG1NVGN6TmpZd05RIiwiWlRSalkyUTVOemRoWkRVM05tWTFZV0UyTmpka01XVmpNRE16WXpOak5qQmtNak5pT0dZelpHSTBOelV4TURsak9EWTRNREEzWm1JeFpUY3daREZqTmciXSwiX3NkX2FsZyI6InNoYS0yNTYifQ.mX14Sw86xy8NFQta7tCfNmhVCqzfaJ_K3VEIhTjbLDY';
|
|
71
|
+
const decodedSdJwt = await decodeSdJwt(jwt, digest);
|
|
72
|
+
expect(decodedSdJwt).toBeDefined();
|
|
73
|
+
expect(decodedSdJwt.kbJwt).toBeUndefined();
|
|
74
|
+
expect(decodedSdJwt.disclosures.length).toEqual(0);
|
|
75
|
+
expect(decodedSdJwt.jwt).toBeDefined();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('decode sdjwt sync', () => {
|
|
79
|
+
const sdjwt =
|
|
80
|
+
'eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJfc2QiOlsiaWQ1azZ1ZVplVTY4bExaMlU2YjJJbF9QR3ZKb1RDMlpkMkpwY0RwMzFIWSJdLCJfc2RfYWxnIjoic2hhLTI1NiJ9.GiLF_HhacrstqCJ223VvWOoJJWU8qk4dYQHklSMwxv36pPF_7ER53Wbty1qYRlQ6NeMUdBRRdj9JQLLCzz1gCQ~WyI2NTMxNDA2ZmVhZmU0YjBmIiwiZm9vIiwiYmFyIl0~';
|
|
81
|
+
const decodedSdJwt = decodeSdJwtSync(sdjwt, digest);
|
|
82
|
+
expect(decodedSdJwt).toBeDefined();
|
|
83
|
+
expect(decodedSdJwt.kbJwt).toBeUndefined();
|
|
84
|
+
expect(decodedSdJwt.disclosures.length).toEqual(1);
|
|
85
|
+
expect(decodedSdJwt.jwt).toBeDefined();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('decode jwt sync', () => {
|
|
89
|
+
const jwt =
|
|
90
|
+
'eyJhbGciOiJIUzI1NiIsInR5cCI6InNkK2p3dCJ9.eyJsYXN0bmFtZSI6IkRvZSIsInNzbiI6IjEyMy00NS02Nzg5IiwiX3NkIjpbIk4yUXhZV1UxTlRnME1qQmpOR1JpWVRCaU1tRmtaamN5WXpSbFpXUmhaRGd5WkRCbE1qaGhZVGcwTnpJMU9XSXpZek5qWkdNNE1qZG1NVGN6TmpZd05RIiwiWlRSalkyUTVOemRoWkRVM05tWTFZV0UyTmpka01XVmpNRE16WXpOak5qQmtNak5pT0dZelpHSTBOelV4TURsak9EWTRNREEzWm1JeFpUY3daREZqTmciXSwiX3NkX2FsZyI6InNoYS0yNTYifQ.mX14Sw86xy8NFQta7tCfNmhVCqzfaJ_K3VEIhTjbLDY';
|
|
91
|
+
const decodedSdJwt = decodeSdJwtSync(jwt, digest);
|
|
92
|
+
expect(decodedSdJwt).toBeDefined();
|
|
93
|
+
expect(decodedSdJwt.kbJwt).toBeUndefined();
|
|
94
|
+
expect(decodedSdJwt.disclosures.length).toEqual(0);
|
|
95
|
+
expect(decodedSdJwt.jwt).toBeDefined();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('decode sdjwt sync (with KB)', () => {
|
|
99
|
+
const sdjwt =
|
|
100
|
+
'eyJ0eXAiOiJzZCtqd3QiLCJhbGciOiJFUzI1NiJ9.eyJpZCI6IjEyMzQiLCJfc2QiOlsiYkRUUnZtNS1Zbi1IRzdjcXBWUjVPVlJJWHNTYUJrNTdKZ2lPcV9qMVZJNCIsImV0M1VmUnlsd1ZyZlhkUEt6Zzc5aGNqRDFJdHpvUTlvQm9YUkd0TW9zRmsiLCJ6V2ZaTlMxOUF0YlJTVGJvN3NKUm4wQlpRdldSZGNob0M3VVphYkZyalk4Il0sIl9zZF9hbGciOiJzaGEtMjU2In0.n27NCtnuwytlBYtUNjgkesDP_7gN7bhaLhWNL4SWT6MaHsOjZ2ZMp987GgQRL6ZkLbJ7Cd3hlePHS84GBXPuvg~WyI1ZWI4Yzg2MjM0MDJjZjJlIiwiZmlyc3RuYW1lIiwiSm9obiJd~WyJjNWMzMWY2ZWYzNTg4MWJjIiwibGFzdG5hbWUiLCJEb2UiXQ~WyJmYTlkYTUzZWJjOTk3OThlIiwic3NuIiwiMTIzLTQ1LTY3ODkiXQ~eyJ0eXAiOiJrYitqd3QiLCJhbGciOiJFUzI1NiJ9.eyJpYXQiOjE3MTAwNjk3MjIsImF1ZCI6ImRpZDpleGFtcGxlOjEyMyIsIm5vbmNlIjoiazh2ZGYwbmQ2Iiwic2RfaGFzaCI6Il8tTmJWSzNmczl3VzNHaDNOUktSNEt1NmZDMUwzN0R2MFFfalBXd0ppRkUifQ.pqw2OB5IA5ya9Mxf60hE3nr2gsJEIoIlnuCa4qIisijHbwg3WzTDFmW2SuNvK_ORN0WU6RoGbJx5uYZh8k4EbA';
|
|
101
|
+
const decodedSdJwt = decodeSdJwtSync(sdjwt, digest);
|
|
102
|
+
expect(decodedSdJwt).toBeDefined();
|
|
103
|
+
expect(decodedSdJwt.kbJwt).toBeDefined();
|
|
104
|
+
expect(decodedSdJwt.disclosures.length).toEqual(3);
|
|
105
|
+
expect(decodedSdJwt.jwt).toBeDefined();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('decode sdjwt (with KB)', async () => {
|
|
109
|
+
const sdjwt =
|
|
110
|
+
'eyJ0eXAiOiJzZCtqd3QiLCJhbGciOiJFUzI1NiJ9.eyJpZCI6IjEyMzQiLCJfc2QiOlsiYkRUUnZtNS1Zbi1IRzdjcXBWUjVPVlJJWHNTYUJrNTdKZ2lPcV9qMVZJNCIsImV0M1VmUnlsd1ZyZlhkUEt6Zzc5aGNqRDFJdHpvUTlvQm9YUkd0TW9zRmsiLCJ6V2ZaTlMxOUF0YlJTVGJvN3NKUm4wQlpRdldSZGNob0M3VVphYkZyalk4Il0sIl9zZF9hbGciOiJzaGEtMjU2In0.n27NCtnuwytlBYtUNjgkesDP_7gN7bhaLhWNL4SWT6MaHsOjZ2ZMp987GgQRL6ZkLbJ7Cd3hlePHS84GBXPuvg~WyI1ZWI4Yzg2MjM0MDJjZjJlIiwiZmlyc3RuYW1lIiwiSm9obiJd~WyJjNWMzMWY2ZWYzNTg4MWJjIiwibGFzdG5hbWUiLCJEb2UiXQ~WyJmYTlkYTUzZWJjOTk3OThlIiwic3NuIiwiMTIzLTQ1LTY3ODkiXQ~eyJ0eXAiOiJrYitqd3QiLCJhbGciOiJFUzI1NiJ9.eyJpYXQiOjE3MTAwNjk3MjIsImF1ZCI6ImRpZDpleGFtcGxlOjEyMyIsIm5vbmNlIjoiazh2ZGYwbmQ2Iiwic2RfaGFzaCI6Il8tTmJWSzNmczl3VzNHaDNOUktSNEt1NmZDMUwzN0R2MFFfalBXd0ppRkUifQ.pqw2OB5IA5ya9Mxf60hE3nr2gsJEIoIlnuCa4qIisijHbwg3WzTDFmW2SuNvK_ORN0WU6RoGbJx5uYZh8k4EbA';
|
|
111
|
+
const decodedSdJwt = await decodeSdJwt(sdjwt, digest);
|
|
112
|
+
expect(decodedSdJwt).toBeDefined();
|
|
113
|
+
expect(decodedSdJwt.kbJwt).toBeDefined();
|
|
114
|
+
expect(decodedSdJwt.disclosures.length).toEqual(3);
|
|
115
|
+
expect(decodedSdJwt.jwt).toBeDefined();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('get claims', async () => {
|
|
119
|
+
const sdjwt =
|
|
120
|
+
'eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJfc2QiOlsiaWQ1azZ1ZVplVTY4bExaMlU2YjJJbF9QR3ZKb1RDMlpkMkpwY0RwMzFIWSJdLCJfc2RfYWxnIjoic2hhLTI1NiJ9.GiLF_HhacrstqCJ223VvWOoJJWU8qk4dYQHklSMwxv36pPF_7ER53Wbty1qYRlQ6NeMUdBRRdj9JQLLCzz1gCQ~WyI2NTMxNDA2ZmVhZmU0YjBmIiwiZm9vIiwiYmFyIl0~';
|
|
121
|
+
const decodedSdJwt = await decodeSdJwt(sdjwt, digest);
|
|
122
|
+
const claims = await getClaims(
|
|
123
|
+
decodedSdJwt.jwt.payload,
|
|
124
|
+
decodedSdJwt.disclosures,
|
|
125
|
+
digest,
|
|
126
|
+
);
|
|
127
|
+
expect(claims).toStrictEqual({
|
|
128
|
+
foo: 'bar',
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('getClaims #2', async () => {
|
|
133
|
+
const sdjwt =
|
|
134
|
+
'eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJ0ZXN0Ijp7Il9zZCI6WyJqVEszMHNleDZhYV9kUk1KSWZDR056Q0FwbVB5MzRRNjNBa3QzS3hhSktzIl19LCJfc2QiOlsiME9nMi1ReG95eW1UOGNnVzZZUjVSSFpQLUJuR2tHUi1NM2otLV92RWlzSSIsIkcwZ3lHNnExVFMyUlQxMkZ3X2RRRDVVcjlZc1AwZlVWOXVtQWdGMC1jQ1EiXSwiX3NkX2FsZyI6InNoYS0yNTYifQ.ggEyE4SeDO2Hu3tol3VLmi7NQj56yKzKQDaafocgkLrUBdivghohtzrfcbrMN7CRufJ_Cnh0EL54kymXLGTdDQ~WyIwNGU0MjAzOWU4ZWFiOWRjIiwiYSIsIjEiXQ~WyIwOGE1Yjc5MjMyYjAzYzBhIiwiMSJd~WyJiNWE2YjUzZGQwYTFmMGIwIiwienp6IiwieHh4Il0~WyIxYzdmOTE4ZTE0MjA2NzZiIiwiZm9vIiwiYmFyIl0~WyJmZjYxYzQ5ZGU2NjFiYzMxIiwiYXJyIixbeyIuLi4iOiJTSG96VW5KNUpkd0ZtTjVCbXB5dXZCWGZfZWRjckVvcExPYThTVlBFUmg0In0sIjIiLHsiX3NkIjpbIkpuODNhZkp0OGx4NG1FMzZpRkZyS2U2R2VnN0dlVUQ4Z3UwdVo3NnRZcW8iXX1dXQ~';
|
|
135
|
+
const decodedSdJwt = await decodeSdJwt(sdjwt, digest);
|
|
136
|
+
const claims = await getClaims(
|
|
137
|
+
decodedSdJwt.jwt.payload,
|
|
138
|
+
decodedSdJwt.disclosures,
|
|
139
|
+
digest,
|
|
140
|
+
);
|
|
141
|
+
expect(claims).toStrictEqual({
|
|
142
|
+
foo: 'bar',
|
|
143
|
+
arr: ['1', '2', { a: '1' }],
|
|
144
|
+
test: {
|
|
145
|
+
zzz: 'xxx',
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('get claims sync', () => {
|
|
151
|
+
const sdjwt =
|
|
152
|
+
'eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJfc2QiOlsiaWQ1azZ1ZVplVTY4bExaMlU2YjJJbF9QR3ZKb1RDMlpkMkpwY0RwMzFIWSJdLCJfc2RfYWxnIjoic2hhLTI1NiJ9.GiLF_HhacrstqCJ223VvWOoJJWU8qk4dYQHklSMwxv36pPF_7ER53Wbty1qYRlQ6NeMUdBRRdj9JQLLCzz1gCQ~WyI2NTMxNDA2ZmVhZmU0YjBmIiwiZm9vIiwiYmFyIl0~';
|
|
153
|
+
const decodedSdJwt = decodeSdJwtSync(sdjwt, digest);
|
|
154
|
+
const claims = getClaimsSync(
|
|
155
|
+
decodedSdJwt.jwt.payload,
|
|
156
|
+
decodedSdJwt.disclosures,
|
|
157
|
+
digest,
|
|
158
|
+
);
|
|
159
|
+
expect(claims).toStrictEqual({
|
|
160
|
+
foo: 'bar',
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('getClaims sync #2', () => {
|
|
165
|
+
const sdjwt =
|
|
166
|
+
'eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJ0ZXN0Ijp7Il9zZCI6WyJqVEszMHNleDZhYV9kUk1KSWZDR056Q0FwbVB5MzRRNjNBa3QzS3hhSktzIl19LCJfc2QiOlsiME9nMi1ReG95eW1UOGNnVzZZUjVSSFpQLUJuR2tHUi1NM2otLV92RWlzSSIsIkcwZ3lHNnExVFMyUlQxMkZ3X2RRRDVVcjlZc1AwZlVWOXVtQWdGMC1jQ1EiXSwiX3NkX2FsZyI6InNoYS0yNTYifQ.ggEyE4SeDO2Hu3tol3VLmi7NQj56yKzKQDaafocgkLrUBdivghohtzrfcbrMN7CRufJ_Cnh0EL54kymXLGTdDQ~WyIwNGU0MjAzOWU4ZWFiOWRjIiwiYSIsIjEiXQ~WyIwOGE1Yjc5MjMyYjAzYzBhIiwiMSJd~WyJiNWE2YjUzZGQwYTFmMGIwIiwienp6IiwieHh4Il0~WyIxYzdmOTE4ZTE0MjA2NzZiIiwiZm9vIiwiYmFyIl0~WyJmZjYxYzQ5ZGU2NjFiYzMxIiwiYXJyIixbeyIuLi4iOiJTSG96VW5KNUpkd0ZtTjVCbXB5dXZCWGZfZWRjckVvcExPYThTVlBFUmg0In0sIjIiLHsiX3NkIjpbIkpuODNhZkp0OGx4NG1FMzZpRkZyS2U2R2VnN0dlVUQ4Z3UwdVo3NnRZcW8iXX1dXQ~';
|
|
167
|
+
const decodedSdJwt = decodeSdJwtSync(sdjwt, digest);
|
|
168
|
+
const claims = getClaimsSync(
|
|
169
|
+
decodedSdJwt.jwt.payload,
|
|
170
|
+
decodedSdJwt.disclosures,
|
|
171
|
+
digest,
|
|
172
|
+
);
|
|
173
|
+
expect(claims).toStrictEqual({
|
|
174
|
+
foo: 'bar',
|
|
175
|
+
arr: ['1', '2', { a: '1' }],
|
|
176
|
+
test: {
|
|
177
|
+
zzz: 'xxx',
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test('Test default sd hash algorithm', () => {
|
|
183
|
+
const { _sd_alg } = getSDAlgAndPayload({});
|
|
184
|
+
expect(_sd_alg).toBe('sha-256');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test('unpackObj clone', async () => {
|
|
188
|
+
const sdjwt =
|
|
189
|
+
'eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJ0ZXN0Ijp7Il9zZCI6WyJqVEszMHNleDZhYV9kUk1KSWZDR056Q0FwbVB5MzRRNjNBa3QzS3hhSktzIl19LCJfc2QiOlsiME9nMi1ReG95eW1UOGNnVzZZUjVSSFpQLUJuR2tHUi1NM2otLV92RWlzSSIsIkcwZ3lHNnExVFMyUlQxMkZ3X2RRRDVVcjlZc1AwZlVWOXVtQWdGMC1jQ1EiXSwiX3NkX2FsZyI6InNoYS0yNTYifQ.ggEyE4SeDO2Hu3tol3VLmi7NQj56yKzKQDaafocgkLrUBdivghohtzrfcbrMN7CRufJ_Cnh0EL54kymXLGTdDQ~WyIwNGU0MjAzOWU4ZWFiOWRjIiwiYSIsIjEiXQ~WyIwOGE1Yjc5MjMyYjAzYzBhIiwiMSJd~WyJiNWE2YjUzZGQwYTFmMGIwIiwienp6IiwieHh4Il0~WyIxYzdmOTE4ZTE0MjA2NzZiIiwiZm9vIiwiYmFyIl0~WyJmZjYxYzQ5ZGU2NjFiYzMxIiwiYXJyIixbeyIuLi4iOiJTSG96VW5KNUpkd0ZtTjVCbXB5dXZCWGZfZWRjckVvcExPYThTVlBFUmg0In0sIjIiLHsiX3NkIjpbIkpuODNhZkp0OGx4NG1FMzZpRkZyS2U2R2VnN0dlVUQ4Z3UwdVo3NnRZcW8iXX1dXQ~';
|
|
190
|
+
const decodedSdJwt = await decodeSdJwt(sdjwt, digest);
|
|
191
|
+
const jwtPayload = JSON.parse(JSON.stringify(decodedSdJwt.jwt.payload));
|
|
192
|
+
|
|
193
|
+
const { _sd_alg } = getSDAlgAndPayload(decodedSdJwt.jwt.payload);
|
|
194
|
+
const hash = { hasher: digest, alg: _sd_alg };
|
|
195
|
+
const map = await createHashMapping(decodedSdJwt.disclosures, hash);
|
|
196
|
+
|
|
197
|
+
const { unpackedObj } = unpackObj(decodedSdJwt.jwt.payload, map);
|
|
198
|
+
|
|
199
|
+
expect(unpackedObj).toBeDefined();
|
|
200
|
+
expect(jwtPayload).toStrictEqual(decodedSdJwt.jwt.payload);
|
|
201
|
+
});
|
|
202
|
+
});
|
package/src/test/decoy.spec.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { digest, generateSalt } from '@
|
|
2
|
-
import { base64urlEncode } from '@sd-jwt/utils';
|
|
1
|
+
import { hasher as digest, generateSalt } from '@owf/crypto';
|
|
3
2
|
import { describe, expect, test } from 'vitest';
|
|
4
3
|
import { createDecoy } from '../decoy';
|
|
4
|
+
import { base64urlEncode } from '../utils';
|
|
5
5
|
|
|
6
6
|
const hash = {
|
|
7
7
|
hasher: digest,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import Crypto from 'node:crypto';
|
|
2
|
-
import type { Signer, Verifier } from '@sd-jwt/types';
|
|
3
2
|
import { describe, expect, test } from 'vitest';
|
|
4
3
|
import { GeneralJSON } from '..';
|
|
4
|
+
import type { Signer, Verifier } from '../types';
|
|
5
5
|
|
|
6
6
|
const createSignerVerifier = () => {
|
|
7
7
|
const { privateKey, publicKey } = Crypto.generateKeyPairSync('ed25519');
|
package/src/test/index.spec.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import Crypto, { type KeyLike } from 'node:crypto';
|
|
2
|
-
import { digest, ES256, generateSalt } from '@
|
|
3
|
-
import type { JwtPayload, KbVerifier, Signer, Verifier } from '@sd-jwt/types';
|
|
2
|
+
import { hasher as digest, ES256, generateSalt } from '@owf/crypto';
|
|
4
3
|
import { exportJWK, importJWK, type JWK } from 'jose';
|
|
5
4
|
import { describe, expect, test } from 'vitest';
|
|
6
5
|
import { SDJwtInstance, type SdJwtPayload } from '../index';
|
|
6
|
+
import type { JwtPayload, KbVerifier, Signer, Verifier } from '../types';
|
|
7
7
|
|
|
8
8
|
// Extract the major version as a number
|
|
9
9
|
const nodeVersionMajor = Number.parseInt(
|
|
@@ -627,4 +627,156 @@ describe('index', () => {
|
|
|
627
627
|
expect(decode).toBeDefined();
|
|
628
628
|
},
|
|
629
629
|
);
|
|
630
|
+
|
|
631
|
+
test('safeVerify - success case', async () => {
|
|
632
|
+
const { signer, verifier } = createSignerVerifier();
|
|
633
|
+
const sdjwt = new SDJwtInstance<SdJwtPayload>({
|
|
634
|
+
signer,
|
|
635
|
+
signAlg: 'EdDSA',
|
|
636
|
+
verifier,
|
|
637
|
+
hasher: digest,
|
|
638
|
+
saltGenerator: generateSalt,
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
const credential = await sdjwt.issue(
|
|
642
|
+
{
|
|
643
|
+
foo: 'bar',
|
|
644
|
+
iss: 'Issuer',
|
|
645
|
+
iat: Math.floor(Date.now() / 1000),
|
|
646
|
+
},
|
|
647
|
+
{
|
|
648
|
+
_sd: ['foo'],
|
|
649
|
+
},
|
|
650
|
+
);
|
|
651
|
+
|
|
652
|
+
const result = await sdjwt.safeVerify(credential);
|
|
653
|
+
|
|
654
|
+
expect(result.success).toBe(true);
|
|
655
|
+
if (result.success) {
|
|
656
|
+
expect(result.data.payload).toBeDefined();
|
|
657
|
+
expect(result.data.payload.foo).toBe('bar');
|
|
658
|
+
}
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
test('safeVerify - collect multiple errors', async () => {
|
|
662
|
+
const sdjwt = new SDJwtInstance<SdJwtPayload>({});
|
|
663
|
+
|
|
664
|
+
const result = await sdjwt.safeVerify('invalid.jwt.token');
|
|
665
|
+
|
|
666
|
+
expect(result.success).toBe(false);
|
|
667
|
+
if (!result.success) {
|
|
668
|
+
// Should have multiple errors: hasher not found, verifier not found
|
|
669
|
+
expect(result.errors.length).toBeGreaterThanOrEqual(2);
|
|
670
|
+
const errorCodes = result.errors.map((e) => e.code);
|
|
671
|
+
expect(errorCodes).toContain('HASHER_NOT_FOUND');
|
|
672
|
+
expect(errorCodes).toContain('VERIFIER_NOT_FOUND');
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
test('safeVerify - invalid signature error', async () => {
|
|
677
|
+
const { signer } = createSignerVerifier();
|
|
678
|
+
const { verifier: wrongVerifier } = createSignerVerifier(); // Different key pair
|
|
679
|
+
|
|
680
|
+
const issuer = new SDJwtInstance<SdJwtPayload>({
|
|
681
|
+
signer,
|
|
682
|
+
signAlg: 'EdDSA',
|
|
683
|
+
hasher: digest,
|
|
684
|
+
saltGenerator: generateSalt,
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
const verifierInstance = new SDJwtInstance<SdJwtPayload>({
|
|
688
|
+
verifier: wrongVerifier,
|
|
689
|
+
hasher: digest,
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
const credential = await issuer.issue(
|
|
693
|
+
{
|
|
694
|
+
foo: 'bar',
|
|
695
|
+
iss: 'Issuer',
|
|
696
|
+
iat: Math.floor(Date.now() / 1000),
|
|
697
|
+
},
|
|
698
|
+
{
|
|
699
|
+
_sd: ['foo'],
|
|
700
|
+
},
|
|
701
|
+
);
|
|
702
|
+
|
|
703
|
+
const result = await verifierInstance.safeVerify(credential);
|
|
704
|
+
|
|
705
|
+
expect(result.success).toBe(false);
|
|
706
|
+
if (!result.success) {
|
|
707
|
+
// The error message from the verifier contains 'signature' which maps to INVALID_JWT_SIGNATURE
|
|
708
|
+
const hasSignatureError = result.errors.some(
|
|
709
|
+
(e) =>
|
|
710
|
+
e.code === 'INVALID_JWT_SIGNATURE' ||
|
|
711
|
+
e.message.toLowerCase().includes('signature'),
|
|
712
|
+
);
|
|
713
|
+
expect(hasSignatureError).toBe(true);
|
|
714
|
+
}
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
test('safeVerify - expired JWT error', async () => {
|
|
718
|
+
const { signer, verifier } = createSignerVerifier();
|
|
719
|
+
const sdjwt = new SDJwtInstance<SdJwtPayload>({
|
|
720
|
+
signer,
|
|
721
|
+
signAlg: 'EdDSA',
|
|
722
|
+
verifier,
|
|
723
|
+
hasher: digest,
|
|
724
|
+
saltGenerator: generateSalt,
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
const credential = await sdjwt.issue(
|
|
728
|
+
{
|
|
729
|
+
foo: 'bar',
|
|
730
|
+
iss: 'Issuer',
|
|
731
|
+
iat: Math.floor(Date.now() / 1000) - 3600, // 1 hour ago
|
|
732
|
+
exp: Math.floor(Date.now() / 1000) - 1800, // Expired 30 min ago
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
_sd: ['foo'],
|
|
736
|
+
},
|
|
737
|
+
);
|
|
738
|
+
|
|
739
|
+
const result = await sdjwt.safeVerify(credential);
|
|
740
|
+
|
|
741
|
+
expect(result.success).toBe(false);
|
|
742
|
+
if (!result.success) {
|
|
743
|
+
expect(result.errors.some((e) => e.code === 'JWT_EXPIRED')).toBe(true);
|
|
744
|
+
}
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
test('safeVerify - missing required claims error', async () => {
|
|
748
|
+
const { signer, verifier } = createSignerVerifier();
|
|
749
|
+
const sdjwt = new SDJwtInstance<SdJwtPayload>({
|
|
750
|
+
signer,
|
|
751
|
+
signAlg: 'EdDSA',
|
|
752
|
+
verifier,
|
|
753
|
+
hasher: digest,
|
|
754
|
+
saltGenerator: generateSalt,
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
const credential = await sdjwt.issue(
|
|
758
|
+
{
|
|
759
|
+
foo: 'bar',
|
|
760
|
+
iss: 'Issuer',
|
|
761
|
+
iat: Math.floor(Date.now() / 1000),
|
|
762
|
+
},
|
|
763
|
+
{
|
|
764
|
+
_sd: ['foo'],
|
|
765
|
+
},
|
|
766
|
+
);
|
|
767
|
+
|
|
768
|
+
// Present without disclosing 'foo'
|
|
769
|
+
const presentation = await sdjwt.present(credential, {});
|
|
770
|
+
|
|
771
|
+
const result = await sdjwt.safeVerify(presentation, {
|
|
772
|
+
requiredClaimKeys: ['foo', 'missing_claim'],
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
expect(result.success).toBe(false);
|
|
776
|
+
if (!result.success) {
|
|
777
|
+
expect(
|
|
778
|
+
result.errors.some((e) => e.code === 'MISSING_REQUIRED_CLAIMS'),
|
|
779
|
+
).toBe(true);
|
|
780
|
+
}
|
|
781
|
+
});
|
|
630
782
|
});
|
package/src/test/jwt.spec.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import Crypto from 'node:crypto';
|
|
2
|
-
import type { Signer, Verifier } from '@sd-jwt/types';
|
|
3
|
-
import { SDJWTException } from '@sd-jwt/utils';
|
|
4
2
|
import { describe, expect, test } from 'vitest';
|
|
5
3
|
import { Jwt } from '../jwt';
|
|
4
|
+
import type { Signer, Verifier } from '../types';
|
|
5
|
+
import { base64urlEncode, SDJWTException } from '../utils';
|
|
6
6
|
|
|
7
7
|
describe('JWT', () => {
|
|
8
8
|
test('create', async () => {
|
|
@@ -16,17 +16,11 @@ describe('JWT', () => {
|
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
test('returns decoded JWT when correct JWT string is provided', () => {
|
|
19
|
-
|
|
20
|
-
// These objects are turned into strings with JSON.stringify. The resulting strings are encoded with base64 encoding using Buffer.from(string).toString('base64').
|
|
21
|
-
// These base64 encoded strings are concatenated with a period (.) between them, following the structure of a JWT, which is composed of three Base64-URL strings separated by dots (header.payload.signature).
|
|
22
|
-
// A 'signature' string is added at the end to represent a JWT signature.
|
|
23
|
-
// So, the jwt variable ends up being a string with the format of a base64Url encoded Header, a period, a base64Url encoded Payload, another period, and a 'signature' string.
|
|
24
|
-
// It's important to note that the 'signature' here is just a placeholder string and not an actual cryptographic signature generated from the header and payload data.
|
|
25
|
-
const jwt = `${Buffer.from(
|
|
19
|
+
const jwt = `${base64urlEncode(
|
|
26
20
|
JSON.stringify({ alg: 'HS256', typ: 'JWT' }),
|
|
27
|
-
)
|
|
21
|
+
)}.${base64urlEncode(
|
|
28
22
|
JSON.stringify({ sub: '1234567890', name: 'John Doe' }),
|
|
29
|
-
)
|
|
23
|
+
)}.signature`;
|
|
30
24
|
const result = Jwt.decodeJWT(jwt);
|
|
31
25
|
expect(result).toEqual({
|
|
32
26
|
header: { alg: 'HS256', typ: 'JWT' },
|
|
@@ -41,9 +35,9 @@ describe('JWT', () => {
|
|
|
41
35
|
});
|
|
42
36
|
|
|
43
37
|
test('throws an error when JWT parts are missing', () => {
|
|
44
|
-
const jwt = `${
|
|
38
|
+
const jwt = `${base64urlEncode(
|
|
45
39
|
JSON.stringify({ alg: 'HS256', typ: 'JWT' }),
|
|
46
|
-
)
|
|
40
|
+
)}`;
|
|
47
41
|
expect(() => Jwt.decodeJWT(jwt)).toThrow('Invalid JWT as input');
|
|
48
42
|
});
|
|
49
43
|
|
package/src/test/kbjwt.spec.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import Crypto, { type KeyLike } from 'node:crypto';
|
|
2
|
+
import { exportJWK, importJWK, type JWK } from 'jose';
|
|
3
|
+
import { describe, expect, test } from 'vitest';
|
|
4
|
+
import { KBJwt } from '../kbjwt';
|
|
2
5
|
import {
|
|
3
6
|
type JwtPayload,
|
|
4
7
|
KB_JWT_TYP,
|
|
5
8
|
type KbVerifier,
|
|
6
9
|
type Signer,
|
|
7
|
-
} from '
|
|
8
|
-
import type { SDJWTException } from '
|
|
9
|
-
import { exportJWK, importJWK, type JWK } from 'jose';
|
|
10
|
-
import { describe, expect, test } from 'vitest';
|
|
11
|
-
import { KBJwt } from '../kbjwt';
|
|
10
|
+
} from '../types';
|
|
11
|
+
import type { SDJWTException } from '../utils';
|
|
12
12
|
|
|
13
13
|
describe('KB JWT', () => {
|
|
14
14
|
test('create', async () => {
|