@sd-jwt/core 0.1.0 → 0.1.1-next.0
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/CHANGELOG.md +17 -0
- package/LICENSE +201 -0
- package/README.md +106 -0
- package/dist/index.d.mts +659 -0
- package/dist/index.d.ts +659 -0
- package/dist/index.js +1906 -0
- package/dist/index.mjs +1846 -0
- package/package.json +59 -47
- package/src/decode/decode.ts +376 -0
- package/src/decode/index.ts +1 -0
- package/src/decoy.ts +15 -0
- package/src/flattenJSON.ts +99 -0
- package/src/generalJSON.ts +148 -0
- package/src/index.ts +945 -0
- package/src/jwt.ts +190 -0
- package/src/kbjwt.ts +81 -0
- package/src/present/index.ts +1 -0
- package/src/present/present.ts +210 -0
- package/src/sdjwt.ts +354 -0
- package/src/test/decode/decode.spec.ts +202 -0
- package/src/test/decoy.spec.ts +30 -0
- package/src/test/flattenJSON.spec.ts +122 -0
- package/src/test/generalJSON.spec.ts +198 -0
- package/src/test/index.spec.ts +974 -0
- package/src/test/jwt.spec.ts +301 -0
- package/src/test/kbjwt.spec.ts +565 -0
- package/src/test/pass.spec.ts +7 -0
- package/src/test/present/present.spec.ts +305 -0
- package/src/test/sdjwt.spec.ts +384 -0
- 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 +101 -0
- package/src/utils/error.ts +25 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/strict-json.ts +17 -0
- package/test/app-e2e.spec.ts +292 -0
- package/test/array_data_types.json +31 -0
- package/test/array_full_sd.json +21 -0
- package/test/array_in_sd.json +13 -0
- package/test/array_nested_in_plain.json +29 -0
- package/test/array_none_disclosed.json +17 -0
- package/test/array_of_nulls.json +15 -0
- package/test/array_of_objects.json +65 -0
- package/test/array_of_scalars.json +19 -0
- package/test/array_recursive_sd.json +35 -0
- package/test/array_recursive_sd_some_disclosed.json +63 -0
- package/test/complex.json +43 -0
- package/test/header_mod.json +48 -0
- package/test/json_serialization.json +48 -0
- package/test/key_binding.json +48 -0
- package/test/no_sd.json +36 -0
- package/test/object_data_types.json +62 -0
- package/test/recursions.json +117 -0
- package/test/rfc9901-validation.spec.ts +150 -0
- package/tsconfig.json +7 -0
- package/vitest.config.mts +4 -0
- package/build/base64url.d.ts +0 -28
- package/build/base64url.js +0 -40
- package/build/base64url.js.map +0 -1
- package/build/hasherAlgorithm.d.ts +0 -70
- package/build/hasherAlgorithm.js +0 -75
- package/build/hasherAlgorithm.js.map +0 -1
- package/build/index.d.ts +0 -11
- package/build/index.js +0 -20
- package/build/index.js.map +0 -1
- package/build/jwt/compact.d.ts +0 -6
- package/build/jwt/compact.js +0 -27
- package/build/jwt/compact.js.map +0 -1
- package/build/jwt/error.d.ts +0 -2
- package/build/jwt/error.js +0 -7
- package/build/jwt/error.js.map +0 -1
- package/build/jwt/index.d.ts +0 -3
- package/build/jwt/index.js +0 -20
- package/build/jwt/index.js.map +0 -1
- package/build/jwt/jwt.d.ts +0 -204
- package/build/jwt/jwt.js +0 -324
- package/build/jwt/jwt.js.map +0 -1
- package/build/keyBinding/index.d.ts +0 -1
- package/build/keyBinding/index.js +0 -18
- package/build/keyBinding/index.js.map +0 -1
- package/build/keyBinding/keyBinding.d.ts +0 -56
- package/build/keyBinding/keyBinding.js +0 -99
- package/build/keyBinding/keyBinding.js.map +0 -1
- package/build/sdJwt/compact.d.ts +0 -8
- package/build/sdJwt/compact.js +0 -39
- package/build/sdJwt/compact.js.map +0 -1
- package/build/sdJwt/decoys.d.ts +0 -2
- package/build/sdJwt/decoys.js +0 -35
- package/build/sdJwt/decoys.js.map +0 -1
- package/build/sdJwt/disclosureFrame.d.ts +0 -7
- package/build/sdJwt/disclosureFrame.js +0 -89
- package/build/sdJwt/disclosureFrame.js.map +0 -1
- package/build/sdJwt/disclosureMapping.d.ts +0 -44
- package/build/sdJwt/disclosureMapping.js +0 -289
- package/build/sdJwt/disclosureMapping.js.map +0 -1
- package/build/sdJwt/disclosures.d.ts +0 -14
- package/build/sdJwt/disclosures.js +0 -59
- package/build/sdJwt/disclosures.js.map +0 -1
- package/build/sdJwt/error.d.ts +0 -2
- package/build/sdJwt/error.js +0 -7
- package/build/sdJwt/error.js.map +0 -1
- package/build/sdJwt/index.d.ts +0 -7
- package/build/sdJwt/index.js +0 -24
- package/build/sdJwt/index.js.map +0 -1
- package/build/sdJwt/presentationFrame.d.ts +0 -4
- package/build/sdJwt/presentationFrame.js +0 -73
- package/build/sdJwt/presentationFrame.js.map +0 -1
- package/build/sdJwt/sdJwt.d.ts +0 -204
- package/build/sdJwt/sdJwt.js +0 -383
- package/build/sdJwt/sdJwt.js.map +0 -1
- package/build/sdJwt/swapClaim.d.ts +0 -3
- package/build/sdJwt/swapClaim.js +0 -90
- package/build/sdJwt/swapClaim.js.map +0 -1
- package/build/sdJwt/types.d.ts +0 -5
- package/build/sdJwt/types.js +0 -3
- package/build/sdJwt/types.js.map +0 -1
- package/build/sdJwtVc/error.d.ts +0 -2
- package/build/sdJwtVc/error.js +0 -7
- package/build/sdJwtVc/error.js.map +0 -1
- package/build/sdJwtVc/index.d.ts +0 -2
- package/build/sdJwtVc/index.js +0 -19
- package/build/sdJwtVc/index.js.map +0 -1
- package/build/sdJwtVc/sdJwtVc.d.ts +0 -47
- package/build/sdJwtVc/sdJwtVc.js +0 -125
- package/build/sdJwtVc/sdJwtVc.js.map +0 -1
- package/build/signatureAndEncryptionAlgorithm.d.ts +0 -105
- package/build/signatureAndEncryptionAlgorithm.js +0 -110
- package/build/signatureAndEncryptionAlgorithm.js.map +0 -1
- package/build/types/disclosure.d.ts +0 -5
- package/build/types/disclosure.js +0 -3
- package/build/types/disclosure.js.map +0 -1
- package/build/types/frame.d.ts +0 -5
- package/build/types/frame.js +0 -3
- package/build/types/frame.js.map +0 -1
- package/build/types/hasher.d.ts +0 -14
- package/build/types/hasher.js +0 -3
- package/build/types/hasher.js.map +0 -1
- package/build/types/index.d.ts +0 -6
- package/build/types/index.js +0 -23
- package/build/types/index.js.map +0 -1
- package/build/types/present.d.ts +0 -2
- package/build/types/present.js +0 -3
- package/build/types/present.js.map +0 -1
- package/build/types/saltGenerator.d.ts +0 -17
- package/build/types/saltGenerator.js +0 -3
- package/build/types/saltGenerator.js.map +0 -1
- package/build/types/signer.d.ts +0 -2
- package/build/types/signer.js +0 -3
- package/build/types/signer.js.map +0 -1
- package/build/types/utils.d.ts +0 -5
- package/build/types/utils.js +0 -3
- package/build/types/utils.js.map +0 -1
- package/build/types/verifier.d.ts +0 -14
- package/build/types/verifier.js +0 -3
- package/build/types/verifier.js.map +0 -1
- package/build/utils/index.d.ts +0 -2
- package/build/utils/index.js +0 -19
- package/build/utils/index.js.map +0 -1
- package/build/utils/traverse.d.ts +0 -8
- package/build/utils/traverse.js +0 -29
- package/build/utils/traverse.js.map +0 -1
- package/build/utils/utils.d.ts +0 -7
- package/build/utils/utils.js +0 -109
- package/build/utils/utils.js.map +0 -1
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
type DisclosureFrame,
|
|
4
|
+
KB_JWT_TYP,
|
|
5
|
+
type PresentationFrame,
|
|
6
|
+
SD_DECOY,
|
|
7
|
+
SD_DIGEST,
|
|
8
|
+
SD_LIST_KEY,
|
|
9
|
+
SD_SEPARATOR,
|
|
10
|
+
} from '../../types';
|
|
11
|
+
|
|
12
|
+
const claims = {
|
|
13
|
+
firstname: 'John',
|
|
14
|
+
lastname: 'Doe',
|
|
15
|
+
ssn: '123-45-6789',
|
|
16
|
+
id: '1234',
|
|
17
|
+
data: {
|
|
18
|
+
firstname: 'John',
|
|
19
|
+
lastname: 'Doe',
|
|
20
|
+
ssn: '123-45-6789',
|
|
21
|
+
list: [{ r: 'd' }, 'b', 'c'],
|
|
22
|
+
list2: ['1', '2', '3'],
|
|
23
|
+
list3: ['1', null, 2],
|
|
24
|
+
},
|
|
25
|
+
data2: {
|
|
26
|
+
hi: 'bye',
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
describe('Variable tests', () => {
|
|
31
|
+
test('SD_SEPARATOR', () => {
|
|
32
|
+
expect(SD_SEPARATOR).toBe('~');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('SD_LIST_KEY', () => {
|
|
36
|
+
expect(SD_LIST_KEY).toBe('...');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('SD_DIGEST', () => {
|
|
40
|
+
expect(SD_DIGEST).toBe('_sd');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('SD_DECOY', () => {
|
|
44
|
+
expect(SD_DECOY).toBe('_sd_decoy');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('KB_JWT_TYP', () => {
|
|
48
|
+
expect(KB_JWT_TYP).toBe('kb+jwt');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('DisclosureFrameType test', () => {
|
|
52
|
+
const disclosureFrame: DisclosureFrame<typeof claims> = {
|
|
53
|
+
_sd: ['data', 'firstname', 'data2'],
|
|
54
|
+
data: {
|
|
55
|
+
_sd: ['list', 'ssn'],
|
|
56
|
+
_sd_decoy: 2,
|
|
57
|
+
list: {
|
|
58
|
+
_sd: [0, 2],
|
|
59
|
+
0: {
|
|
60
|
+
_sd: ['r'],
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
expect(disclosureFrame).toBeDefined();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('PresentationFrameType test', () => {
|
|
69
|
+
const presentationFrame: PresentationFrame<typeof claims> = {
|
|
70
|
+
firstname: true,
|
|
71
|
+
data: {
|
|
72
|
+
firstname: true,
|
|
73
|
+
list: {
|
|
74
|
+
1: true,
|
|
75
|
+
0: {
|
|
76
|
+
r: true,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
list2: {
|
|
80
|
+
1: true,
|
|
81
|
+
},
|
|
82
|
+
list3: true,
|
|
83
|
+
},
|
|
84
|
+
data2: true,
|
|
85
|
+
};
|
|
86
|
+
expect(presentationFrame).toBeDefined();
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
base64UrlToUint8Array,
|
|
4
|
+
base64urlDecode,
|
|
5
|
+
base64urlEncode,
|
|
6
|
+
uint8ArrayToBase64Url,
|
|
7
|
+
} from '../../utils/base64url';
|
|
8
|
+
|
|
9
|
+
describe('Base64url', () => {
|
|
10
|
+
const raw = 'abcdefghijklmnopqrstuvwxyz';
|
|
11
|
+
const encoded = 'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo';
|
|
12
|
+
test('Encode', () => {
|
|
13
|
+
expect(base64urlEncode(raw)).toStrictEqual(encoded);
|
|
14
|
+
});
|
|
15
|
+
test('Decode', () => {
|
|
16
|
+
expect(base64urlDecode(encoded)).toStrictEqual(raw);
|
|
17
|
+
});
|
|
18
|
+
test('Encode and decode', () => {
|
|
19
|
+
const str = 'hello world';
|
|
20
|
+
expect(base64urlDecode(base64urlEncode(str))).toStrictEqual(str);
|
|
21
|
+
});
|
|
22
|
+
test('Uint8Array', () => {
|
|
23
|
+
const str = 'hello world';
|
|
24
|
+
const uint8 = new TextEncoder().encode(str);
|
|
25
|
+
expect(uint8ArrayToBase64Url(uint8)).toStrictEqual(base64urlEncode(str));
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('Base64Url to Uint8Array', () => {
|
|
29
|
+
const str = 'hello world';
|
|
30
|
+
const uint8 = new TextEncoder().encode(str);
|
|
31
|
+
expect(base64UrlToUint8Array(base64urlEncode(str))).toStrictEqual(uint8);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { generateSalt, hasher } from '@owf/crypto';
|
|
2
|
+
import { describe, expect, test } from 'vitest';
|
|
3
|
+
import { base64urlEncode, type SDJWTException } from '../../utils';
|
|
4
|
+
import { Disclosure } from '../../utils/disclosure';
|
|
5
|
+
|
|
6
|
+
const hash = { alg: 'SHA256', hasher };
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
ref draft-ietf-oauth-selective-disclosure-jwt-07
|
|
10
|
+
Claim given_name:
|
|
11
|
+
SHA-256 Hash: jsu9yVulwQQlhFlM_3JlzMaSFzglhQG0DpfayQwLUK4
|
|
12
|
+
Disclosure: WyIyR0xDNDJzS1F2ZUNmR2ZyeU5STjl3IiwgImdpdmVuX25hbWUiLCAiSm9obiJd
|
|
13
|
+
Contents: ["2GLC42sKQveCfGfryNRN9w", "given_name", "John"]
|
|
14
|
+
For example, the SHA-256 digest of the Disclosure
|
|
15
|
+
WyI2cU1RdlJMNWhhaiIsICJmYW1pbHlfbmFtZSIsICJNw7ZiaXVzIl0 would be uutlBuYeMDyjLLTpf6Jxi7yNkEF35jdyWMn9U7b_RYY.
|
|
16
|
+
The SHA-256 digest of the Disclosure
|
|
17
|
+
WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgIkZSIl0 would be w0I8EKcdCtUPkGCNUrfwVp2xEgNjtoIDlOxc9-PlOhs.
|
|
18
|
+
*/
|
|
19
|
+
const TestDataDraft7 = {
|
|
20
|
+
claimTests: [
|
|
21
|
+
{
|
|
22
|
+
contents: '["2GLC42sKQveCfGfryNRN9w", "given_name", "John"]',
|
|
23
|
+
digest: 'jsu9yVulwQQlhFlM_3JlzMaSFzglhQG0DpfayQwLUK4',
|
|
24
|
+
disclosure:
|
|
25
|
+
'WyIyR0xDNDJzS1F2ZUNmR2ZyeU5STjl3IiwgImdpdmVuX25hbWUiLCAiSm9obiJd',
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
sha256Tests: [
|
|
29
|
+
{
|
|
30
|
+
digest: 'uutlBuYeMDyjLLTpf6Jxi7yNkEF35jdyWMn9U7b_RYY',
|
|
31
|
+
disclosure: 'WyI2cU1RdlJMNWhhaiIsICJmYW1pbHlfbmFtZSIsICJNw7ZiaXVzIl0',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
digest: 'w0I8EKcdCtUPkGCNUrfwVp2xEgNjtoIDlOxc9-PlOhs',
|
|
35
|
+
disclosure: 'WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgIkZSIl0',
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
describe('Disclosure', () => {
|
|
41
|
+
test('create object disclosure', async () => {
|
|
42
|
+
const salt = generateSalt(16);
|
|
43
|
+
const disclosure = new Disclosure([salt, 'name', 'James']);
|
|
44
|
+
expect(disclosure).toBeDefined();
|
|
45
|
+
expect(disclosure.key).toBe('name');
|
|
46
|
+
expect(disclosure.value).toBe('James');
|
|
47
|
+
expect(disclosure.salt).toBe(salt);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('create array disclosure', async () => {
|
|
51
|
+
const salt = generateSalt(16);
|
|
52
|
+
const disclosure = new Disclosure([salt, 'US']);
|
|
53
|
+
expect(disclosure).toBeDefined();
|
|
54
|
+
expect(disclosure.key).toBe(undefined);
|
|
55
|
+
expect(disclosure.value).toBe('US');
|
|
56
|
+
expect(disclosure.salt).toBe(salt);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('create disclosure error', async () => {
|
|
60
|
+
const salt = generateSalt(16);
|
|
61
|
+
const data: [string, string, string] = [salt, 'name', 'James'];
|
|
62
|
+
data.push('any');
|
|
63
|
+
expect(() => new Disclosure(data)).toThrow();
|
|
64
|
+
try {
|
|
65
|
+
new Disclosure(data);
|
|
66
|
+
} catch (e: unknown) {
|
|
67
|
+
const error = e as SDJWTException;
|
|
68
|
+
expect(typeof error.getFullMessage()).toBe('string');
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('encode disclosure', async () => {
|
|
73
|
+
const salt = generateSalt(16);
|
|
74
|
+
const disclosure = new Disclosure([salt, 'name', 'James']);
|
|
75
|
+
const encodedDisclosure = disclosure.encode();
|
|
76
|
+
expect(encodedDisclosure).toBeDefined();
|
|
77
|
+
expect(typeof encodedDisclosure).toBe('string');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('decode disclosure', async () => {
|
|
81
|
+
const salt = generateSalt(16);
|
|
82
|
+
const disclosure = new Disclosure([salt, 'name', 'James']);
|
|
83
|
+
const encodedDisclosure = disclosure.encode();
|
|
84
|
+
const newDisclosure = await Disclosure.fromEncode(encodedDisclosure, {
|
|
85
|
+
alg: 'SHA256',
|
|
86
|
+
hasher,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
expect(newDisclosure).toBeDefined();
|
|
90
|
+
expect(newDisclosure.key).toBe('name');
|
|
91
|
+
expect(newDisclosure.value).toBe('James');
|
|
92
|
+
expect(newDisclosure.salt).toBe(salt);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('decode disclosure sync', () => {
|
|
96
|
+
const salt = generateSalt(16);
|
|
97
|
+
const disclosure = new Disclosure([salt, 'name', 'James']);
|
|
98
|
+
const encodedDisclosure = disclosure.encode();
|
|
99
|
+
const newDisclosure = Disclosure.fromEncodeSync(encodedDisclosure, {
|
|
100
|
+
alg: 'SHA256',
|
|
101
|
+
hasher,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
expect(newDisclosure).toBeDefined();
|
|
105
|
+
expect(newDisclosure.key).toBe('name');
|
|
106
|
+
expect(newDisclosure.value).toBe('James');
|
|
107
|
+
expect(newDisclosure.salt).toBe(salt);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('digest disclosure #1', async () => {
|
|
111
|
+
const salt = generateSalt(16);
|
|
112
|
+
const disclosure = new Disclosure([salt, 'name', 'James']);
|
|
113
|
+
const digest = await disclosure.digest({ alg: 'SHA256', hasher });
|
|
114
|
+
expect(digest).toBeDefined();
|
|
115
|
+
expect(typeof digest).toBe('string');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('digest disclosure #2', async () => {
|
|
119
|
+
const disclosure = new Disclosure([
|
|
120
|
+
'2GLC42sKQveCfGfryNRN9w',
|
|
121
|
+
'given_name',
|
|
122
|
+
'John',
|
|
123
|
+
]);
|
|
124
|
+
const encode = disclosure.encode();
|
|
125
|
+
expect(encode).toBe(
|
|
126
|
+
'WyIyR0xDNDJzS1F2ZUNmR2ZyeU5STjl3IiwiZ2l2ZW5fbmFtZSIsIkpvaG4iXQ',
|
|
127
|
+
);
|
|
128
|
+
const digest = await disclosure.digest(hash);
|
|
129
|
+
expect(digest).toBe('8VHiz7qTXavxvpiTYDCSr_shkUO6qRcVXjkhEnt1os4');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('digest disclosure #2 sync', () => {
|
|
133
|
+
const disclosure = new Disclosure([
|
|
134
|
+
'2GLC42sKQveCfGfryNRN9w',
|
|
135
|
+
'given_name',
|
|
136
|
+
'John',
|
|
137
|
+
]);
|
|
138
|
+
const encode = disclosure.encode();
|
|
139
|
+
expect(encode).toBe(
|
|
140
|
+
'WyIyR0xDNDJzS1F2ZUNmR2ZyeU5STjl3IiwiZ2l2ZW5fbmFtZSIsIkpvaG4iXQ',
|
|
141
|
+
);
|
|
142
|
+
const digest = disclosure.digestSync(hash);
|
|
143
|
+
expect(digest).toBe('8VHiz7qTXavxvpiTYDCSr_shkUO6qRcVXjkhEnt1os4');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('digest disclosure #3', async () => {
|
|
147
|
+
const encoded = base64urlEncode(TestDataDraft7.claimTests[0].contents);
|
|
148
|
+
expect(encoded).toStrictEqual(TestDataDraft7.claimTests[0].disclosure);
|
|
149
|
+
|
|
150
|
+
const disclosure = await Disclosure.fromEncode(
|
|
151
|
+
TestDataDraft7.claimTests[0].disclosure,
|
|
152
|
+
hash,
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
const digest = await disclosure.digest(hash);
|
|
156
|
+
expect(digest).toBe(TestDataDraft7.claimTests[0].digest);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('digest disclosure #4', async () => {
|
|
160
|
+
for (const sha256Test of TestDataDraft7.sha256Tests) {
|
|
161
|
+
const disclosure = await Disclosure.fromEncode(
|
|
162
|
+
sha256Test.disclosure,
|
|
163
|
+
hash,
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
const digest = await disclosure.digest(hash);
|
|
167
|
+
expect(digest).toBe(sha256Test.digest);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { SDJWTException } from '../../utils/error';
|
|
3
|
+
|
|
4
|
+
describe('Error tests', () => {
|
|
5
|
+
test('Detail', () => {
|
|
6
|
+
try {
|
|
7
|
+
throw new SDJWTException('msg', { info: 'details' });
|
|
8
|
+
} catch (e: unknown) {
|
|
9
|
+
const exception = e as SDJWTException;
|
|
10
|
+
expect(exception.getFullMessage()).toEqual(
|
|
11
|
+
'SDJWTException: msg - {"info":"details"}',
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
export const SD_SEPARATOR = '~';
|
|
2
|
+
export const SD_LIST_KEY = '...';
|
|
3
|
+
export const SD_DIGEST = '_sd';
|
|
4
|
+
export const SD_DECOY = '_sd_decoy';
|
|
5
|
+
export const KB_JWT_TYP = 'kb+jwt';
|
|
6
|
+
|
|
7
|
+
export type SDJWTCompact = string;
|
|
8
|
+
export type Base64urlString = string;
|
|
9
|
+
|
|
10
|
+
export type DisclosureData<T> = [string, string, T] | [string, T];
|
|
11
|
+
|
|
12
|
+
// based on https://www.iana.org/assignments/named-information/named-information.xhtml
|
|
13
|
+
export const IANA_HASH_ALGORITHMS = [
|
|
14
|
+
'sha-256',
|
|
15
|
+
'sha-256-128',
|
|
16
|
+
'sha-256-120',
|
|
17
|
+
'sha-256-96',
|
|
18
|
+
'sha-256-64',
|
|
19
|
+
'sha-256-32',
|
|
20
|
+
'sha-384',
|
|
21
|
+
'sha-512',
|
|
22
|
+
'sha3-224',
|
|
23
|
+
'sha3-256',
|
|
24
|
+
'sha3-384',
|
|
25
|
+
'sha3-512',
|
|
26
|
+
'blake2s-256',
|
|
27
|
+
'blake2b-256',
|
|
28
|
+
'blake2b-512',
|
|
29
|
+
'k12-256',
|
|
30
|
+
'k12-512',
|
|
31
|
+
] as const;
|
|
32
|
+
|
|
33
|
+
export type HashAlgorithm = (typeof IANA_HASH_ALGORITHMS)[number];
|
|
34
|
+
|
|
35
|
+
export type SDJWTConfig<T = unknown> = {
|
|
36
|
+
omitTyp?: boolean;
|
|
37
|
+
hasher?: Hasher;
|
|
38
|
+
hashAlg?: HashAlgorithm;
|
|
39
|
+
saltGenerator?: SaltGenerator;
|
|
40
|
+
signer?: Signer;
|
|
41
|
+
signAlg?: string;
|
|
42
|
+
verifier?: Verifier<T>;
|
|
43
|
+
kbSigner?: Signer;
|
|
44
|
+
kbSignAlg?: string;
|
|
45
|
+
kbVerifier?: KbVerifier;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type kbHeader = { typ: 'kb+jwt'; alg: string };
|
|
49
|
+
export type kbPayload = {
|
|
50
|
+
iat: number;
|
|
51
|
+
aud: string;
|
|
52
|
+
nonce: string;
|
|
53
|
+
sd_hash: string;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type KBOptions = {
|
|
57
|
+
payload: Omit<kbPayload, 'sd_hash'>;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// This type declaration is from lib.dom.ts
|
|
61
|
+
interface RsaOtherPrimesInfo {
|
|
62
|
+
d?: string;
|
|
63
|
+
r?: string;
|
|
64
|
+
t?: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface JsonWebKey {
|
|
68
|
+
alg?: string;
|
|
69
|
+
crv?: string;
|
|
70
|
+
d?: string;
|
|
71
|
+
dp?: string;
|
|
72
|
+
dq?: string;
|
|
73
|
+
e?: string;
|
|
74
|
+
ext?: boolean;
|
|
75
|
+
k?: string;
|
|
76
|
+
key_ops?: string[];
|
|
77
|
+
kty?: string;
|
|
78
|
+
n?: string;
|
|
79
|
+
oth?: RsaOtherPrimesInfo[];
|
|
80
|
+
p?: string;
|
|
81
|
+
q?: string;
|
|
82
|
+
qi?: string;
|
|
83
|
+
use?: string;
|
|
84
|
+
x?: string;
|
|
85
|
+
y?: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface JwtPayload {
|
|
89
|
+
cnf?: {
|
|
90
|
+
jwk: JsonWebKey;
|
|
91
|
+
};
|
|
92
|
+
exp?: number;
|
|
93
|
+
[key: string]: unknown;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export type OrPromise<T> = T | Promise<T>;
|
|
97
|
+
|
|
98
|
+
export type Signer = (data: string) => OrPromise<string>;
|
|
99
|
+
export type Verifier<T = unknown> = (
|
|
100
|
+
data: string,
|
|
101
|
+
sig: string,
|
|
102
|
+
options?: T,
|
|
103
|
+
) => OrPromise<boolean>;
|
|
104
|
+
export type KbVerifier = (
|
|
105
|
+
data: string,
|
|
106
|
+
sig: string,
|
|
107
|
+
payload: JwtPayload,
|
|
108
|
+
) => OrPromise<boolean>;
|
|
109
|
+
export type Hasher = (
|
|
110
|
+
data: string | ArrayBuffer,
|
|
111
|
+
alg: string,
|
|
112
|
+
) => OrPromise<Uint8Array>;
|
|
113
|
+
export type SaltGenerator = (length: number) => OrPromise<string>;
|
|
114
|
+
export type HasherAndAlg = {
|
|
115
|
+
hasher: Hasher;
|
|
116
|
+
alg: string;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// This functions are sync versions
|
|
120
|
+
export type SignerSync = (data: string) => string;
|
|
121
|
+
export type VerifierSync = (data: string, sig: string) => boolean;
|
|
122
|
+
export type HasherSync = (data: string, alg: string) => Uint8Array;
|
|
123
|
+
export type SaltGeneratorSync = (length: number) => string;
|
|
124
|
+
export type HasherAndAlgSync = {
|
|
125
|
+
hasher: HasherSync;
|
|
126
|
+
alg: string;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
type NonNever<T> = {
|
|
130
|
+
[P in keyof T as T[P] extends never ? never : P]: T[P];
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export type SD<Payload> = { [SD_DIGEST]?: Array<keyof Payload> };
|
|
134
|
+
export type DECOY = { [SD_DECOY]?: number };
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* This is a disclosureFrame type that is used to represent the structure of what is being disclosed.
|
|
138
|
+
* DisclosureFrame is made from the payload type.
|
|
139
|
+
*
|
|
140
|
+
* For example, if the payload is
|
|
141
|
+
* {
|
|
142
|
+
* foo: 'bar',
|
|
143
|
+
* test: {
|
|
144
|
+
* zzz: 'yyy',
|
|
145
|
+
* }
|
|
146
|
+
* arr: ['1', '2', {a: 'b'}]
|
|
147
|
+
* }
|
|
148
|
+
*
|
|
149
|
+
* The disclosureFrame can be subset of:
|
|
150
|
+
* {
|
|
151
|
+
* _sd: ["foo", "test", "arr"],
|
|
152
|
+
* test: {
|
|
153
|
+
* _sd: ["zzz"],
|
|
154
|
+
* },
|
|
155
|
+
* arr: {
|
|
156
|
+
* _sd: ["0", "1", "2"],
|
|
157
|
+
* "2": {
|
|
158
|
+
* _sd: ["a"],
|
|
159
|
+
* }
|
|
160
|
+
* }
|
|
161
|
+
* }
|
|
162
|
+
*
|
|
163
|
+
* The disclosureFrame can be used with decoy.
|
|
164
|
+
* Decoy can be used like this:
|
|
165
|
+
* {
|
|
166
|
+
* ...
|
|
167
|
+
* _sd: ...
|
|
168
|
+
* _sd_decoy: 1 // number of decoy in this layer
|
|
169
|
+
* }
|
|
170
|
+
*
|
|
171
|
+
*/
|
|
172
|
+
type Frame<Payload> =
|
|
173
|
+
Payload extends Array<infer U>
|
|
174
|
+
? U extends object
|
|
175
|
+
? Record<number, Frame<U>> & SD<Payload> & DECOY
|
|
176
|
+
: SD<Payload> & DECOY
|
|
177
|
+
: Payload extends Record<string, unknown>
|
|
178
|
+
? NonNever<
|
|
179
|
+
{
|
|
180
|
+
[K in keyof Payload]?: NonNullable<Payload[K]> extends object
|
|
181
|
+
? Frame<Payload[K]>
|
|
182
|
+
: never;
|
|
183
|
+
} & SD<Payload> &
|
|
184
|
+
DECOY
|
|
185
|
+
>
|
|
186
|
+
: SD<Payload> & DECOY;
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* This is a disclosureFrame type that is used to represent the structure of what is being disclosed.
|
|
190
|
+
*/
|
|
191
|
+
export type Extensible = Record<string, unknown | boolean>;
|
|
192
|
+
|
|
193
|
+
export type DisclosureFrame<T extends Extensible> = Frame<T>;
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* This is a presentationFrame type that is used to represent the structure of what is being presented.
|
|
197
|
+
* PresentationFrame is made from the payload type.
|
|
198
|
+
* const claims = {
|
|
199
|
+
firstname: 'John',
|
|
200
|
+
lastname: 'Doe',
|
|
201
|
+
ssn: '123-45-6789',
|
|
202
|
+
id: '1234',
|
|
203
|
+
data: {
|
|
204
|
+
firstname: 'John',
|
|
205
|
+
lastname: 'Doe',
|
|
206
|
+
ssn: '123-45-6789',
|
|
207
|
+
list: [{ r: 'd' }, 'b', 'c'],
|
|
208
|
+
list2: ['1', '2', '3'],
|
|
209
|
+
list3: ['1', null, 2],
|
|
210
|
+
},
|
|
211
|
+
data2: {
|
|
212
|
+
hi: 'bye',
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
Example of a presentationFrame:
|
|
217
|
+
const presentationFrame: PresentationFrame<typeof claims> = {
|
|
218
|
+
firstname: true,
|
|
219
|
+
lastname: true,
|
|
220
|
+
ssn: true,
|
|
221
|
+
id: 'true',
|
|
222
|
+
data: {
|
|
223
|
+
firstname: true,
|
|
224
|
+
list: {
|
|
225
|
+
1: true,
|
|
226
|
+
0: {
|
|
227
|
+
r: true,
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
list2: {
|
|
231
|
+
1: true,
|
|
232
|
+
},
|
|
233
|
+
list3: true,
|
|
234
|
+
},
|
|
235
|
+
data2: true,
|
|
236
|
+
};
|
|
237
|
+
*/
|
|
238
|
+
type PFrame<Payload> =
|
|
239
|
+
Payload extends Array<infer U>
|
|
240
|
+
? U extends object
|
|
241
|
+
? Record<number, PFrame<U> | boolean> | boolean
|
|
242
|
+
: Record<number, boolean> | boolean
|
|
243
|
+
: {
|
|
244
|
+
[K in keyof Payload]?: NonNullable<Payload[K]> extends object
|
|
245
|
+
? PFrame<Payload[K]> | boolean
|
|
246
|
+
: boolean;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
export type PresentationFrame<T extends Extensible> = PFrame<T>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error codes for SD-JWT verification errors.
|
|
3
|
+
*/
|
|
4
|
+
export type VerificationErrorCode =
|
|
5
|
+
| 'HASHER_NOT_FOUND'
|
|
6
|
+
| 'VERIFIER_NOT_FOUND'
|
|
7
|
+
| 'INVALID_SD_JWT'
|
|
8
|
+
| 'INVALID_JWT_FORMAT'
|
|
9
|
+
| 'JWT_NOT_YET_VALID'
|
|
10
|
+
| 'JWT_EXPIRED'
|
|
11
|
+
| 'INVALID_JWT_SIGNATURE'
|
|
12
|
+
| 'MISSING_REQUIRED_CLAIMS'
|
|
13
|
+
| 'KEY_BINDING_JWT_MISSING'
|
|
14
|
+
| 'KEY_BINDING_VERIFIER_NOT_FOUND'
|
|
15
|
+
| 'KEY_BINDING_SIGNATURE_INVALID'
|
|
16
|
+
| 'KEY_BINDING_SD_HASH_INVALID'
|
|
17
|
+
| 'STATUS_VERIFICATION_FAILED'
|
|
18
|
+
| 'STATUS_INVALID'
|
|
19
|
+
| 'VCT_VERIFICATION_FAILED'
|
|
20
|
+
| 'UNKNOWN_ERROR';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Represents a single verification error.
|
|
24
|
+
*/
|
|
25
|
+
export type VerificationError = {
|
|
26
|
+
/**
|
|
27
|
+
* The error code identifying the type of error.
|
|
28
|
+
*/
|
|
29
|
+
code: VerificationErrorCode;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Human-readable error message.
|
|
33
|
+
*/
|
|
34
|
+
message: string;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Optional additional details about the error.
|
|
38
|
+
*/
|
|
39
|
+
details?: unknown;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Result type for safe verification that collects all errors.
|
|
44
|
+
*/
|
|
45
|
+
export type SafeVerifyResult<T> =
|
|
46
|
+
| {
|
|
47
|
+
success: true;
|
|
48
|
+
data: T;
|
|
49
|
+
errors?: never;
|
|
50
|
+
}
|
|
51
|
+
| {
|
|
52
|
+
success: false;
|
|
53
|
+
data?: never;
|
|
54
|
+
errors: VerificationError[];
|
|
55
|
+
};
|