@sd-jwt/core 0.19.1-next.1 → 0.19.1-next.11
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 +364 -16
- package/dist/index.d.ts +364 -16
- package/dist/index.js +803 -148
- package/dist/index.mjs +744 -133
- 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 +230 -14
- package/src/jwt.ts +10 -15
- package/src/kbjwt.ts +19 -15
- 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 +288 -2
- package/src/test/jwt.spec.ts +7 -13
- package/src/test/kbjwt.spec.ts +216 -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(
|
|
@@ -269,6 +269,140 @@ describe('index', () => {
|
|
|
269
269
|
expect(results).toBeDefined();
|
|
270
270
|
});
|
|
271
271
|
|
|
272
|
+
test('verify rejects an expired kbJwt during full verification', async () => {
|
|
273
|
+
const { signer, verifier } = createSignerVerifier();
|
|
274
|
+
const { privateKey, publicKey } = Crypto.generateKeyPairSync('ed25519');
|
|
275
|
+
|
|
276
|
+
const kbVerifier: KbVerifier = async (
|
|
277
|
+
data: string,
|
|
278
|
+
sig: string,
|
|
279
|
+
payload: JwtPayload,
|
|
280
|
+
) => {
|
|
281
|
+
if (!payload.cnf) throw Error('key binding not supported');
|
|
282
|
+
return Crypto.verify(
|
|
283
|
+
null,
|
|
284
|
+
Buffer.from(data),
|
|
285
|
+
(await importJWK(payload.cnf.jwk as JWK, 'EdDSA')) as KeyLike,
|
|
286
|
+
Buffer.from(sig, 'base64url'),
|
|
287
|
+
);
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const kbSigner = (data: string) => {
|
|
291
|
+
const sig = Crypto.sign(null, Buffer.from(data), privateKey);
|
|
292
|
+
return Buffer.from(sig).toString('base64url');
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const sdjwt = new SDJwtInstance<SdJwtPayload>({
|
|
296
|
+
signer,
|
|
297
|
+
signAlg: 'EdDSA',
|
|
298
|
+
verifier,
|
|
299
|
+
hasher: digest,
|
|
300
|
+
saltGenerator: generateSalt,
|
|
301
|
+
kbSigner,
|
|
302
|
+
kbVerifier,
|
|
303
|
+
kbSignAlg: 'EdDSA',
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
const now = Math.floor(Date.now() / 1000);
|
|
307
|
+
const credential = await sdjwt.issue(
|
|
308
|
+
{
|
|
309
|
+
foo: 'bar',
|
|
310
|
+
iat: now,
|
|
311
|
+
cnf: { jwk: await exportJWK(publicKey) },
|
|
312
|
+
},
|
|
313
|
+
{ _sd: ['foo'] },
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
const presentation = await sdjwt.present(
|
|
317
|
+
credential,
|
|
318
|
+
{ foo: true },
|
|
319
|
+
{
|
|
320
|
+
kb: {
|
|
321
|
+
payload: {
|
|
322
|
+
aud: '1',
|
|
323
|
+
iat: now - 7200,
|
|
324
|
+
nonce: '342',
|
|
325
|
+
// kb+jwt expired one hour ago
|
|
326
|
+
exp: now - 3600,
|
|
327
|
+
} as never,
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
await expect(
|
|
333
|
+
sdjwt.verify(presentation, {
|
|
334
|
+
requiredClaimKeys: ['foo'],
|
|
335
|
+
keyBindingNonce: '342',
|
|
336
|
+
}),
|
|
337
|
+
).rejects.toThrow('Verify Error: JWT is expired');
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
test('verify accepts a kbJwt with a future exp during full verification', async () => {
|
|
341
|
+
const { signer, verifier } = createSignerVerifier();
|
|
342
|
+
const { privateKey, publicKey } = Crypto.generateKeyPairSync('ed25519');
|
|
343
|
+
|
|
344
|
+
const kbVerifier: KbVerifier = async (
|
|
345
|
+
data: string,
|
|
346
|
+
sig: string,
|
|
347
|
+
payload: JwtPayload,
|
|
348
|
+
) => {
|
|
349
|
+
if (!payload.cnf) throw Error('key binding not supported');
|
|
350
|
+
return Crypto.verify(
|
|
351
|
+
null,
|
|
352
|
+
Buffer.from(data),
|
|
353
|
+
(await importJWK(payload.cnf.jwk as JWK, 'EdDSA')) as KeyLike,
|
|
354
|
+
Buffer.from(sig, 'base64url'),
|
|
355
|
+
);
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const kbSigner = (data: string) => {
|
|
359
|
+
const sig = Crypto.sign(null, Buffer.from(data), privateKey);
|
|
360
|
+
return Buffer.from(sig).toString('base64url');
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const sdjwt = new SDJwtInstance<SdJwtPayload>({
|
|
364
|
+
signer,
|
|
365
|
+
signAlg: 'EdDSA',
|
|
366
|
+
verifier,
|
|
367
|
+
hasher: digest,
|
|
368
|
+
saltGenerator: generateSalt,
|
|
369
|
+
kbSigner,
|
|
370
|
+
kbVerifier,
|
|
371
|
+
kbSignAlg: 'EdDSA',
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
const now = Math.floor(Date.now() / 1000);
|
|
375
|
+
const credential = await sdjwt.issue(
|
|
376
|
+
{
|
|
377
|
+
foo: 'bar',
|
|
378
|
+
iat: now,
|
|
379
|
+
cnf: { jwk: await exportJWK(publicKey) },
|
|
380
|
+
},
|
|
381
|
+
{ _sd: ['foo'] },
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
const presentation = await sdjwt.present(
|
|
385
|
+
credential,
|
|
386
|
+
{ foo: true },
|
|
387
|
+
{
|
|
388
|
+
kb: {
|
|
389
|
+
payload: {
|
|
390
|
+
aud: '1',
|
|
391
|
+
iat: now,
|
|
392
|
+
nonce: '342',
|
|
393
|
+
exp: now + 3600,
|
|
394
|
+
} as never,
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
const results = await sdjwt.verify(presentation, {
|
|
400
|
+
requiredClaimKeys: ['foo'],
|
|
401
|
+
keyBindingNonce: '342',
|
|
402
|
+
});
|
|
403
|
+
expect(results.kb).toBeDefined();
|
|
404
|
+
});
|
|
405
|
+
|
|
272
406
|
test('Hasher not found', async () => {
|
|
273
407
|
const sdjwt = new SDJwtInstance<SdJwtPayload>({});
|
|
274
408
|
try {
|
|
@@ -627,4 +761,156 @@ describe('index', () => {
|
|
|
627
761
|
expect(decode).toBeDefined();
|
|
628
762
|
},
|
|
629
763
|
);
|
|
764
|
+
|
|
765
|
+
test('safeVerify - success case', async () => {
|
|
766
|
+
const { signer, verifier } = createSignerVerifier();
|
|
767
|
+
const sdjwt = new SDJwtInstance<SdJwtPayload>({
|
|
768
|
+
signer,
|
|
769
|
+
signAlg: 'EdDSA',
|
|
770
|
+
verifier,
|
|
771
|
+
hasher: digest,
|
|
772
|
+
saltGenerator: generateSalt,
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
const credential = await sdjwt.issue(
|
|
776
|
+
{
|
|
777
|
+
foo: 'bar',
|
|
778
|
+
iss: 'Issuer',
|
|
779
|
+
iat: Math.floor(Date.now() / 1000),
|
|
780
|
+
},
|
|
781
|
+
{
|
|
782
|
+
_sd: ['foo'],
|
|
783
|
+
},
|
|
784
|
+
);
|
|
785
|
+
|
|
786
|
+
const result = await sdjwt.safeVerify(credential);
|
|
787
|
+
|
|
788
|
+
expect(result.success).toBe(true);
|
|
789
|
+
if (result.success) {
|
|
790
|
+
expect(result.data.payload).toBeDefined();
|
|
791
|
+
expect(result.data.payload.foo).toBe('bar');
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
test('safeVerify - collect multiple errors', async () => {
|
|
796
|
+
const sdjwt = new SDJwtInstance<SdJwtPayload>({});
|
|
797
|
+
|
|
798
|
+
const result = await sdjwt.safeVerify('invalid.jwt.token');
|
|
799
|
+
|
|
800
|
+
expect(result.success).toBe(false);
|
|
801
|
+
if (!result.success) {
|
|
802
|
+
// Should have multiple errors: hasher not found, verifier not found
|
|
803
|
+
expect(result.errors.length).toBeGreaterThanOrEqual(2);
|
|
804
|
+
const errorCodes = result.errors.map((e) => e.code);
|
|
805
|
+
expect(errorCodes).toContain('HASHER_NOT_FOUND');
|
|
806
|
+
expect(errorCodes).toContain('VERIFIER_NOT_FOUND');
|
|
807
|
+
}
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
test('safeVerify - invalid signature error', async () => {
|
|
811
|
+
const { signer } = createSignerVerifier();
|
|
812
|
+
const { verifier: wrongVerifier } = createSignerVerifier(); // Different key pair
|
|
813
|
+
|
|
814
|
+
const issuer = new SDJwtInstance<SdJwtPayload>({
|
|
815
|
+
signer,
|
|
816
|
+
signAlg: 'EdDSA',
|
|
817
|
+
hasher: digest,
|
|
818
|
+
saltGenerator: generateSalt,
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
const verifierInstance = new SDJwtInstance<SdJwtPayload>({
|
|
822
|
+
verifier: wrongVerifier,
|
|
823
|
+
hasher: digest,
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
const credential = await issuer.issue(
|
|
827
|
+
{
|
|
828
|
+
foo: 'bar',
|
|
829
|
+
iss: 'Issuer',
|
|
830
|
+
iat: Math.floor(Date.now() / 1000),
|
|
831
|
+
},
|
|
832
|
+
{
|
|
833
|
+
_sd: ['foo'],
|
|
834
|
+
},
|
|
835
|
+
);
|
|
836
|
+
|
|
837
|
+
const result = await verifierInstance.safeVerify(credential);
|
|
838
|
+
|
|
839
|
+
expect(result.success).toBe(false);
|
|
840
|
+
if (!result.success) {
|
|
841
|
+
// The error message from the verifier contains 'signature' which maps to INVALID_JWT_SIGNATURE
|
|
842
|
+
const hasSignatureError = result.errors.some(
|
|
843
|
+
(e) =>
|
|
844
|
+
e.code === 'INVALID_JWT_SIGNATURE' ||
|
|
845
|
+
e.message.toLowerCase().includes('signature'),
|
|
846
|
+
);
|
|
847
|
+
expect(hasSignatureError).toBe(true);
|
|
848
|
+
}
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
test('safeVerify - expired JWT error', async () => {
|
|
852
|
+
const { signer, verifier } = createSignerVerifier();
|
|
853
|
+
const sdjwt = new SDJwtInstance<SdJwtPayload>({
|
|
854
|
+
signer,
|
|
855
|
+
signAlg: 'EdDSA',
|
|
856
|
+
verifier,
|
|
857
|
+
hasher: digest,
|
|
858
|
+
saltGenerator: generateSalt,
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
const credential = await sdjwt.issue(
|
|
862
|
+
{
|
|
863
|
+
foo: 'bar',
|
|
864
|
+
iss: 'Issuer',
|
|
865
|
+
iat: Math.floor(Date.now() / 1000) - 3600, // 1 hour ago
|
|
866
|
+
exp: Math.floor(Date.now() / 1000) - 1800, // Expired 30 min ago
|
|
867
|
+
},
|
|
868
|
+
{
|
|
869
|
+
_sd: ['foo'],
|
|
870
|
+
},
|
|
871
|
+
);
|
|
872
|
+
|
|
873
|
+
const result = await sdjwt.safeVerify(credential);
|
|
874
|
+
|
|
875
|
+
expect(result.success).toBe(false);
|
|
876
|
+
if (!result.success) {
|
|
877
|
+
expect(result.errors.some((e) => e.code === 'JWT_EXPIRED')).toBe(true);
|
|
878
|
+
}
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
test('safeVerify - missing required claims error', async () => {
|
|
882
|
+
const { signer, verifier } = createSignerVerifier();
|
|
883
|
+
const sdjwt = new SDJwtInstance<SdJwtPayload>({
|
|
884
|
+
signer,
|
|
885
|
+
signAlg: 'EdDSA',
|
|
886
|
+
verifier,
|
|
887
|
+
hasher: digest,
|
|
888
|
+
saltGenerator: generateSalt,
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
const credential = await sdjwt.issue(
|
|
892
|
+
{
|
|
893
|
+
foo: 'bar',
|
|
894
|
+
iss: 'Issuer',
|
|
895
|
+
iat: Math.floor(Date.now() / 1000),
|
|
896
|
+
},
|
|
897
|
+
{
|
|
898
|
+
_sd: ['foo'],
|
|
899
|
+
},
|
|
900
|
+
);
|
|
901
|
+
|
|
902
|
+
// Present without disclosing 'foo'
|
|
903
|
+
const presentation = await sdjwt.present(credential, {});
|
|
904
|
+
|
|
905
|
+
const result = await sdjwt.safeVerify(presentation, {
|
|
906
|
+
requiredClaimKeys: ['foo', 'missing_claim'],
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
expect(result.success).toBe(false);
|
|
910
|
+
if (!result.success) {
|
|
911
|
+
expect(
|
|
912
|
+
result.errors.some((e) => e.code === 'MISSING_REQUIRED_CLAIMS'),
|
|
913
|
+
).toBe(true);
|
|
914
|
+
}
|
|
915
|
+
});
|
|
630
916
|
});
|
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
|
|