@sd-jwt/core 0.19.1-next.5 → 0.19.1-next.6
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 +5 -7
- package/dist/index.d.mts +343 -16
- package/dist/index.d.ts +343 -16
- package/dist/index.js +644 -142
- package/dist/index.mjs +585 -127
- 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 +39 -25
- 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 +2 -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,210 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createHashMapping,
|
|
3
|
+
createHashMappingSync,
|
|
4
|
+
decodeSdJwt,
|
|
5
|
+
decodeSdJwtSync,
|
|
6
|
+
getSDAlgAndPayload,
|
|
7
|
+
splitSdJwt,
|
|
8
|
+
unpack,
|
|
9
|
+
unpackObj,
|
|
10
|
+
unpackSync,
|
|
11
|
+
} from '../decode';
|
|
12
|
+
import type { Extensible, HasherSync } from '../types';
|
|
13
|
+
import { type Hasher, type PresentationFrame, SD_SEPARATOR } from '../types';
|
|
14
|
+
import { Disclosure, SDJWTException } from '../utils';
|
|
15
|
+
|
|
16
|
+
// Presentable keys
|
|
17
|
+
// The presentable keys are the path of JSON object that are presentable in the SD JWT
|
|
18
|
+
// e.g. if the SD JWT has the following payload and set sd like this:
|
|
19
|
+
// {
|
|
20
|
+
// "foo": "bar", // sd
|
|
21
|
+
// "arr": [ // sd
|
|
22
|
+
// "1", // sd
|
|
23
|
+
// "2",
|
|
24
|
+
// {
|
|
25
|
+
// "a": "1" // sd
|
|
26
|
+
// }
|
|
27
|
+
// ],
|
|
28
|
+
// "test": {
|
|
29
|
+
// "zzz": "xxx" // sd
|
|
30
|
+
// }
|
|
31
|
+
// }
|
|
32
|
+
// The presentable keys are: ["arr", "arr.0", "arr.2.a", "foo", "test.zzz"]
|
|
33
|
+
export const presentableKeys = async (
|
|
34
|
+
rawPayload: Record<string, unknown>,
|
|
35
|
+
disclosures: Array<Disclosure>,
|
|
36
|
+
hasher: Hasher,
|
|
37
|
+
): Promise<string[]> => {
|
|
38
|
+
const { disclosureKeymap } = await unpack(rawPayload, disclosures, hasher);
|
|
39
|
+
return Object.keys(disclosureKeymap).sort();
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const presentableKeysSync = (
|
|
43
|
+
rawPayload: Record<string, unknown>,
|
|
44
|
+
disclosures: Array<Disclosure>,
|
|
45
|
+
hasher: HasherSync,
|
|
46
|
+
): string[] => {
|
|
47
|
+
const { disclosureKeymap } = unpackSync(rawPayload, disclosures, hasher);
|
|
48
|
+
return Object.keys(disclosureKeymap).sort();
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const present = async <T extends Record<string, unknown>>(
|
|
52
|
+
sdJwt: string,
|
|
53
|
+
presentFrame: PresentationFrame<T>,
|
|
54
|
+
hasher: Hasher,
|
|
55
|
+
): Promise<string> => {
|
|
56
|
+
const { jwt, kbJwt } = splitSdJwt(sdJwt);
|
|
57
|
+
const {
|
|
58
|
+
jwt: { payload },
|
|
59
|
+
disclosures,
|
|
60
|
+
} = await decodeSdJwt(sdJwt, hasher);
|
|
61
|
+
|
|
62
|
+
const { _sd_alg: alg } = getSDAlgAndPayload(payload);
|
|
63
|
+
const hash = { alg, hasher };
|
|
64
|
+
const keys = transformPresentationFrame(presentFrame);
|
|
65
|
+
|
|
66
|
+
// hashmap: <digest> => <disclosure>
|
|
67
|
+
// to match the digest with the disclosure
|
|
68
|
+
const hashmap = await createHashMapping(disclosures, hash);
|
|
69
|
+
const { disclosureKeymap } = await unpack(payload, disclosures, hasher);
|
|
70
|
+
const presentedDisclosures = keys
|
|
71
|
+
.map((k) => hashmap[disclosureKeymap[k]])
|
|
72
|
+
.filter((d) => d !== undefined);
|
|
73
|
+
|
|
74
|
+
return [
|
|
75
|
+
jwt,
|
|
76
|
+
...presentedDisclosures.map((d) => d.encode()),
|
|
77
|
+
kbJwt ?? '',
|
|
78
|
+
].join(SD_SEPARATOR);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const presentSync = <T extends Record<string, unknown>>(
|
|
82
|
+
sdJwt: string,
|
|
83
|
+
presentFrame: PresentationFrame<T>,
|
|
84
|
+
hasher: HasherSync,
|
|
85
|
+
): string => {
|
|
86
|
+
const { jwt, kbJwt } = splitSdJwt(sdJwt);
|
|
87
|
+
const {
|
|
88
|
+
jwt: { payload },
|
|
89
|
+
disclosures,
|
|
90
|
+
} = decodeSdJwtSync(sdJwt, hasher);
|
|
91
|
+
|
|
92
|
+
const { _sd_alg: alg } = getSDAlgAndPayload(payload);
|
|
93
|
+
const hash = { alg, hasher };
|
|
94
|
+
const keys = transformPresentationFrame(presentFrame);
|
|
95
|
+
|
|
96
|
+
// hashmap: <digest> => <disclosure>
|
|
97
|
+
// to match the digest with the disclosure
|
|
98
|
+
const hashmap = createHashMappingSync(disclosures, hash);
|
|
99
|
+
const { disclosureKeymap } = unpackSync(payload, disclosures, hasher);
|
|
100
|
+
|
|
101
|
+
const presentedDisclosures = keys
|
|
102
|
+
.map((k) => hashmap[disclosureKeymap[k]])
|
|
103
|
+
.filter((d) => d !== undefined);
|
|
104
|
+
|
|
105
|
+
return [
|
|
106
|
+
jwt,
|
|
107
|
+
...presentedDisclosures.map((d) => d.encode()),
|
|
108
|
+
kbJwt ?? '',
|
|
109
|
+
].join(SD_SEPARATOR);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Transform the object keys into an array of strings. We are not sorting the array in any way.
|
|
114
|
+
* @param obj The object to transform
|
|
115
|
+
* @param prefix The prefix to add to the keys
|
|
116
|
+
* @returns
|
|
117
|
+
*/
|
|
118
|
+
export const transformPresentationFrame = (
|
|
119
|
+
obj: PresentationFrame<Extensible>,
|
|
120
|
+
prefix = '',
|
|
121
|
+
): string[] => {
|
|
122
|
+
return Object.entries(obj).reduce<string[]>((acc, [key, value]) => {
|
|
123
|
+
const newPrefix = prefix ? `${prefix}.${key}` : key;
|
|
124
|
+
if (typeof value === 'boolean') {
|
|
125
|
+
// only add it, when it's true
|
|
126
|
+
if (value) {
|
|
127
|
+
acc.push(newPrefix);
|
|
128
|
+
}
|
|
129
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
130
|
+
acc.push(
|
|
131
|
+
newPrefix,
|
|
132
|
+
...transformPresentationFrame(
|
|
133
|
+
value as PresentationFrame<Extensible>,
|
|
134
|
+
newPrefix,
|
|
135
|
+
),
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
return acc;
|
|
139
|
+
}, []);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export type SerializedDisclosure = {
|
|
143
|
+
digest: string;
|
|
144
|
+
encoded: string;
|
|
145
|
+
salt: string;
|
|
146
|
+
key: string | undefined;
|
|
147
|
+
value: unknown;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
export const createHashMappingForSerializedDisclosure = (
|
|
151
|
+
disclosures: SerializedDisclosure[],
|
|
152
|
+
) => {
|
|
153
|
+
const map: Record<string, Disclosure> = {};
|
|
154
|
+
for (let i = 0; i < disclosures.length; i++) {
|
|
155
|
+
const disclosure = disclosures[i];
|
|
156
|
+
const { digest, encoded, key, salt, value } = disclosure;
|
|
157
|
+
// we made Disclosure to fit the interface of unpack
|
|
158
|
+
map[digest] = Disclosure.fromArray(
|
|
159
|
+
key ? [salt, key, value] : [salt, value],
|
|
160
|
+
{ digest, encoded },
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
return map;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* This function selects the serialized disclosures from the payload
|
|
168
|
+
* and array of serialized disclosure based on the presentation frame.
|
|
169
|
+
* If you want to know what is serialized disclosures, check type SerializedDisclosure.
|
|
170
|
+
* @param payload: Record<string, unknown>
|
|
171
|
+
* @param disclosures: SerializedDisclosure[]
|
|
172
|
+
* @param presentationFrame: PresentationFrame<T>
|
|
173
|
+
*/
|
|
174
|
+
export const selectDisclosures = <T extends Record<string, unknown>>(
|
|
175
|
+
payload: Record<string, unknown>,
|
|
176
|
+
disclosures: SerializedDisclosure[],
|
|
177
|
+
presentationFrame: PresentationFrame<T>,
|
|
178
|
+
) => {
|
|
179
|
+
if (disclosures.length === 0) {
|
|
180
|
+
return [];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const hashmap = createHashMappingForSerializedDisclosure(disclosures);
|
|
184
|
+
const { disclosureKeymap } = unpackObj(payload, hashmap);
|
|
185
|
+
const keys = transformPresentationFrame(presentationFrame);
|
|
186
|
+
|
|
187
|
+
const presentedDisclosures = keys
|
|
188
|
+
.map((k) => hashmap[disclosureKeymap[k]])
|
|
189
|
+
.filter((d) => d !== undefined);
|
|
190
|
+
|
|
191
|
+
const selectedDisclosures: SerializedDisclosure[] = presentedDisclosures.map(
|
|
192
|
+
(d) => {
|
|
193
|
+
const { salt, key, value, _digest } = d;
|
|
194
|
+
if (!_digest) {
|
|
195
|
+
throw new SDJWTException(
|
|
196
|
+
'Implementation error: _digest is not defined',
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
digest: _digest,
|
|
201
|
+
encoded: d.encode(),
|
|
202
|
+
salt,
|
|
203
|
+
key,
|
|
204
|
+
value,
|
|
205
|
+
};
|
|
206
|
+
},
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
return selectedDisclosures;
|
|
210
|
+
};
|
package/src/sdjwt.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { createHashMapping, getSDAlgAndPayload, unpack } from '
|
|
2
|
-
import {
|
|
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';
|
|
3
6
|
import {
|
|
4
7
|
type DisclosureFrame,
|
|
5
8
|
type Hasher,
|
|
@@ -13,11 +16,8 @@ import {
|
|
|
13
16
|
SD_LIST_KEY,
|
|
14
17
|
SD_SEPARATOR,
|
|
15
18
|
type SDJWTCompact,
|
|
16
|
-
} from '
|
|
17
|
-
import { Disclosure, SDJWTException } from '
|
|
18
|
-
import { createDecoy } from './decoy';
|
|
19
|
-
import { Jwt } from './jwt';
|
|
20
|
-
import { KBJwt } from './kbjwt';
|
|
19
|
+
} from './types';
|
|
20
|
+
import { Disclosure, SDJWTException } from './utils';
|
|
21
21
|
|
|
22
22
|
export type SDJwtData<
|
|
23
23
|
Header extends Record<string, unknown>,
|
|
@@ -81,7 +81,7 @@ export class SDJwt<
|
|
|
81
81
|
const { _sd_alg } = getSDAlgAndPayload(jwt.payload);
|
|
82
82
|
|
|
83
83
|
const disclosures = await Promise.all(
|
|
84
|
-
|
|
84
|
+
encodedDisclosures.map((ed) =>
|
|
85
85
|
Disclosure.fromEncode(ed, { alg: _sd_alg, hasher }),
|
|
86
86
|
),
|
|
87
87
|
);
|
|
@@ -221,8 +221,9 @@ export const listKeys = (obj: Record<string, unknown>, prefix = '') => {
|
|
|
221
221
|
const newKey = prefix ? `${prefix}.${key}` : key;
|
|
222
222
|
keys.push(newKey);
|
|
223
223
|
|
|
224
|
-
|
|
225
|
-
|
|
224
|
+
const value = obj[key];
|
|
225
|
+
if (value && typeof value === 'object') {
|
|
226
|
+
keys.push(...listKeys(value as Record<string, unknown>, newKey));
|
|
226
227
|
}
|
|
227
228
|
}
|
|
228
229
|
return keys;
|
|
@@ -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(
|
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 () => {
|