@sd-jwt/core 0.20.1-next.1 → 0.20.1-next.3
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/dist/index.js +21 -34
- package/dist/index.mjs +21 -34
- package/package.json +2 -2
- package/src/index.ts +33 -40
- package/src/test/index.spec.ts +48 -3
package/dist/index.js
CHANGED
|
@@ -1117,6 +1117,23 @@ var pack = (claims, disclosureFrame, hash, saltGenerator) => __async(null, null,
|
|
|
1117
1117
|
});
|
|
1118
1118
|
|
|
1119
1119
|
// src/index.ts
|
|
1120
|
+
function validateReservedFieldsInternal(payload) {
|
|
1121
|
+
const reservedFields = /* @__PURE__ */ new Set([SD_DIGEST, "_sd_alg", SD_DECOY, SD_LIST_KEY]);
|
|
1122
|
+
const visit = (node) => {
|
|
1123
|
+
if (!node || typeof node !== "object") {
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
for (const [key, value] of Object.entries(
|
|
1127
|
+
node
|
|
1128
|
+
)) {
|
|
1129
|
+
if (reservedFields.has(key)) {
|
|
1130
|
+
throw new SDJWTException(`Reserved field name "${key}" is not allowed`);
|
|
1131
|
+
}
|
|
1132
|
+
visit(value);
|
|
1133
|
+
}
|
|
1134
|
+
};
|
|
1135
|
+
visit(payload);
|
|
1136
|
+
}
|
|
1120
1137
|
var _SDJwtInstance = class _SDJwtInstance {
|
|
1121
1138
|
constructor(userConfig) {
|
|
1122
1139
|
this.userConfig = {};
|
|
@@ -1212,23 +1229,7 @@ var _SDJwtInstance = class _SDJwtInstance {
|
|
|
1212
1229
|
* @returns
|
|
1213
1230
|
*/
|
|
1214
1231
|
validateReservedFields(payload) {
|
|
1215
|
-
|
|
1216
|
-
const visit = (node) => {
|
|
1217
|
-
if (!node || typeof node !== "object") {
|
|
1218
|
-
return;
|
|
1219
|
-
}
|
|
1220
|
-
for (const [key, value] of Object.entries(
|
|
1221
|
-
node
|
|
1222
|
-
)) {
|
|
1223
|
-
if (reservedFields.has(key)) {
|
|
1224
|
-
throw new SDJWTException(
|
|
1225
|
-
`Reserved field name "${key}" is not allowed`
|
|
1226
|
-
);
|
|
1227
|
-
}
|
|
1228
|
-
visit(value);
|
|
1229
|
-
}
|
|
1230
|
-
};
|
|
1231
|
-
visit(payload);
|
|
1232
|
+
validateReservedFieldsInternal(payload);
|
|
1232
1233
|
}
|
|
1233
1234
|
validateDisclosureFrame(_disclosureFrame) {
|
|
1234
1235
|
return;
|
|
@@ -1514,6 +1515,7 @@ var _SDJwtInstance = class _SDJwtInstance {
|
|
|
1514
1515
|
}
|
|
1515
1516
|
const verifiedPayloads = yield this.VerifyJwt(sdjwt.jwt, options);
|
|
1516
1517
|
const claims = yield sdjwt.getClaims(hasher);
|
|
1518
|
+
validateReservedFieldsInternal(claims);
|
|
1517
1519
|
return { payload: claims, header: verifiedPayloads.header };
|
|
1518
1520
|
});
|
|
1519
1521
|
}
|
|
@@ -1653,23 +1655,7 @@ var SDJwtGeneralJSONInstance = class {
|
|
|
1653
1655
|
* @returns
|
|
1654
1656
|
*/
|
|
1655
1657
|
validateReservedFields(payload) {
|
|
1656
|
-
|
|
1657
|
-
const visit = (node) => {
|
|
1658
|
-
if (!node || typeof node !== "object") {
|
|
1659
|
-
return;
|
|
1660
|
-
}
|
|
1661
|
-
for (const [key, value] of Object.entries(
|
|
1662
|
-
node
|
|
1663
|
-
)) {
|
|
1664
|
-
if (reservedFields.has(key)) {
|
|
1665
|
-
throw new SDJWTException(
|
|
1666
|
-
`Reserved field name "${key}" is not allowed`
|
|
1667
|
-
);
|
|
1668
|
-
}
|
|
1669
|
-
visit(value);
|
|
1670
|
-
}
|
|
1671
|
-
};
|
|
1672
|
-
visit(payload);
|
|
1658
|
+
validateReservedFieldsInternal(payload);
|
|
1673
1659
|
}
|
|
1674
1660
|
validateDisclosureFrame(_disclosureFrame) {
|
|
1675
1661
|
return;
|
|
@@ -1820,6 +1806,7 @@ var SDJwtGeneralJSONInstance = class {
|
|
|
1820
1806
|
throw new SDJWTException("Invalid SD JWT");
|
|
1821
1807
|
}
|
|
1822
1808
|
const claims = yield sdjwt.getClaims(hasher);
|
|
1809
|
+
validateReservedFieldsInternal(claims);
|
|
1823
1810
|
return { payload: claims, headers: results.map((r) => r.header) };
|
|
1824
1811
|
});
|
|
1825
1812
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -1058,6 +1058,23 @@ var pack = (claims, disclosureFrame, hash, saltGenerator) => __async(null, null,
|
|
|
1058
1058
|
});
|
|
1059
1059
|
|
|
1060
1060
|
// src/index.ts
|
|
1061
|
+
function validateReservedFieldsInternal(payload) {
|
|
1062
|
+
const reservedFields = /* @__PURE__ */ new Set([SD_DIGEST, "_sd_alg", SD_DECOY, SD_LIST_KEY]);
|
|
1063
|
+
const visit = (node) => {
|
|
1064
|
+
if (!node || typeof node !== "object") {
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
for (const [key, value] of Object.entries(
|
|
1068
|
+
node
|
|
1069
|
+
)) {
|
|
1070
|
+
if (reservedFields.has(key)) {
|
|
1071
|
+
throw new SDJWTException(`Reserved field name "${key}" is not allowed`);
|
|
1072
|
+
}
|
|
1073
|
+
visit(value);
|
|
1074
|
+
}
|
|
1075
|
+
};
|
|
1076
|
+
visit(payload);
|
|
1077
|
+
}
|
|
1061
1078
|
var _SDJwtInstance = class _SDJwtInstance {
|
|
1062
1079
|
constructor(userConfig) {
|
|
1063
1080
|
this.userConfig = {};
|
|
@@ -1153,23 +1170,7 @@ var _SDJwtInstance = class _SDJwtInstance {
|
|
|
1153
1170
|
* @returns
|
|
1154
1171
|
*/
|
|
1155
1172
|
validateReservedFields(payload) {
|
|
1156
|
-
|
|
1157
|
-
const visit = (node) => {
|
|
1158
|
-
if (!node || typeof node !== "object") {
|
|
1159
|
-
return;
|
|
1160
|
-
}
|
|
1161
|
-
for (const [key, value] of Object.entries(
|
|
1162
|
-
node
|
|
1163
|
-
)) {
|
|
1164
|
-
if (reservedFields.has(key)) {
|
|
1165
|
-
throw new SDJWTException(
|
|
1166
|
-
`Reserved field name "${key}" is not allowed`
|
|
1167
|
-
);
|
|
1168
|
-
}
|
|
1169
|
-
visit(value);
|
|
1170
|
-
}
|
|
1171
|
-
};
|
|
1172
|
-
visit(payload);
|
|
1173
|
+
validateReservedFieldsInternal(payload);
|
|
1173
1174
|
}
|
|
1174
1175
|
validateDisclosureFrame(_disclosureFrame) {
|
|
1175
1176
|
return;
|
|
@@ -1455,6 +1456,7 @@ var _SDJwtInstance = class _SDJwtInstance {
|
|
|
1455
1456
|
}
|
|
1456
1457
|
const verifiedPayloads = yield this.VerifyJwt(sdjwt.jwt, options);
|
|
1457
1458
|
const claims = yield sdjwt.getClaims(hasher);
|
|
1459
|
+
validateReservedFieldsInternal(claims);
|
|
1458
1460
|
return { payload: claims, header: verifiedPayloads.header };
|
|
1459
1461
|
});
|
|
1460
1462
|
}
|
|
@@ -1594,23 +1596,7 @@ var SDJwtGeneralJSONInstance = class {
|
|
|
1594
1596
|
* @returns
|
|
1595
1597
|
*/
|
|
1596
1598
|
validateReservedFields(payload) {
|
|
1597
|
-
|
|
1598
|
-
const visit = (node) => {
|
|
1599
|
-
if (!node || typeof node !== "object") {
|
|
1600
|
-
return;
|
|
1601
|
-
}
|
|
1602
|
-
for (const [key, value] of Object.entries(
|
|
1603
|
-
node
|
|
1604
|
-
)) {
|
|
1605
|
-
if (reservedFields.has(key)) {
|
|
1606
|
-
throw new SDJWTException(
|
|
1607
|
-
`Reserved field name "${key}" is not allowed`
|
|
1608
|
-
);
|
|
1609
|
-
}
|
|
1610
|
-
visit(value);
|
|
1611
|
-
}
|
|
1612
|
-
};
|
|
1613
|
-
visit(payload);
|
|
1599
|
+
validateReservedFieldsInternal(payload);
|
|
1614
1600
|
}
|
|
1615
1601
|
validateDisclosureFrame(_disclosureFrame) {
|
|
1616
1602
|
return;
|
|
@@ -1761,6 +1747,7 @@ var SDJwtGeneralJSONInstance = class {
|
|
|
1761
1747
|
throw new SDJWTException("Invalid SD JWT");
|
|
1762
1748
|
}
|
|
1763
1749
|
const claims = yield sdjwt.getClaims(hasher);
|
|
1750
|
+
validateReservedFieldsInternal(claims);
|
|
1764
1751
|
return { payload: claims, headers: results.map((r) => r.header) };
|
|
1765
1752
|
});
|
|
1766
1753
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sd-jwt/core",
|
|
3
|
-
"version": "0.20.1-next.
|
|
3
|
+
"version": "0.20.1-next.3+7390003",
|
|
4
4
|
"description": "sd-jwt draft 7 implementation in typescript",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -58,5 +58,5 @@
|
|
|
58
58
|
"esm"
|
|
59
59
|
]
|
|
60
60
|
},
|
|
61
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "73900032a1c009d05583c1a422073f8eab0fa276"
|
|
62
62
|
}
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
type SafeVerifyResult,
|
|
15
15
|
SD_DECOY,
|
|
16
16
|
SD_DIGEST,
|
|
17
|
+
SD_LIST_KEY,
|
|
17
18
|
type SDJWTCompact,
|
|
18
19
|
type SDJWTConfig,
|
|
19
20
|
type Signer,
|
|
@@ -42,6 +43,32 @@ export * from './utils';
|
|
|
42
43
|
|
|
43
44
|
export type SdJwtPayload = Record<string, unknown>;
|
|
44
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Internal utility function to validate that a payload does not contain reserved field names.
|
|
48
|
+
*/
|
|
49
|
+
function validateReservedFieldsInternal(
|
|
50
|
+
payload: Record<string, unknown>,
|
|
51
|
+
): void {
|
|
52
|
+
const reservedFields = new Set([SD_DIGEST, '_sd_alg', SD_DECOY, SD_LIST_KEY]);
|
|
53
|
+
|
|
54
|
+
const visit = (node: unknown) => {
|
|
55
|
+
if (!node || typeof node !== 'object') {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
for (const [key, value] of Object.entries(
|
|
60
|
+
node as Record<string, unknown>,
|
|
61
|
+
)) {
|
|
62
|
+
if (reservedFields.has(key)) {
|
|
63
|
+
throw new SDJWTException(`Reserved field name "${key}" is not allowed`);
|
|
64
|
+
}
|
|
65
|
+
visit(value);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
visit(payload);
|
|
70
|
+
}
|
|
71
|
+
|
|
45
72
|
export class SDJwtInstance<ExtendedPayload extends SdJwtPayload, T = unknown> {
|
|
46
73
|
//header type
|
|
47
74
|
protected type?: string;
|
|
@@ -163,26 +190,7 @@ export class SDJwtInstance<ExtendedPayload extends SdJwtPayload, T = unknown> {
|
|
|
163
190
|
* @returns
|
|
164
191
|
*/
|
|
165
192
|
protected validateReservedFields<T extends ExtendedPayload>(payload: T) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const visit = (node: unknown) => {
|
|
169
|
-
if (!node || typeof node !== 'object') {
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
for (const [key, value] of Object.entries(
|
|
174
|
-
node as Record<string, unknown>,
|
|
175
|
-
)) {
|
|
176
|
-
if (reservedFields.has(key)) {
|
|
177
|
-
throw new SDJWTException(
|
|
178
|
-
`Reserved field name "${key}" is not allowed`,
|
|
179
|
-
);
|
|
180
|
-
}
|
|
181
|
-
visit(value);
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
visit(payload);
|
|
193
|
+
validateReservedFieldsInternal(payload as Record<string, unknown>);
|
|
186
194
|
}
|
|
187
195
|
|
|
188
196
|
protected validateDisclosureFrame<T extends ExtendedPayload>(
|
|
@@ -532,6 +540,8 @@ export class SDJwtInstance<ExtendedPayload extends SdJwtPayload, T = unknown> {
|
|
|
532
540
|
|
|
533
541
|
const verifiedPayloads = await this.VerifyJwt(sdjwt.jwt, options);
|
|
534
542
|
const claims = await sdjwt.getClaims<ExtendedPayload>(hasher);
|
|
543
|
+
// Validate that unpacked claims do not contain reserved field names
|
|
544
|
+
validateReservedFieldsInternal(claims);
|
|
535
545
|
return { payload: claims, header: verifiedPayloads.header };
|
|
536
546
|
}
|
|
537
547
|
|
|
@@ -706,26 +716,7 @@ export class SDJwtGeneralJSONInstance<ExtendedPayload extends SdJwtPayload> {
|
|
|
706
716
|
* @returns
|
|
707
717
|
*/
|
|
708
718
|
protected validateReservedFields<T extends ExtendedPayload>(payload: T) {
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
const visit = (node: unknown) => {
|
|
712
|
-
if (!node || typeof node !== 'object') {
|
|
713
|
-
return;
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
for (const [key, value] of Object.entries(
|
|
717
|
-
node as Record<string, unknown>,
|
|
718
|
-
)) {
|
|
719
|
-
if (reservedFields.has(key)) {
|
|
720
|
-
throw new SDJWTException(
|
|
721
|
-
`Reserved field name "${key}" is not allowed`,
|
|
722
|
-
);
|
|
723
|
-
}
|
|
724
|
-
visit(value);
|
|
725
|
-
}
|
|
726
|
-
};
|
|
727
|
-
|
|
728
|
-
visit(payload);
|
|
719
|
+
validateReservedFieldsInternal(payload as Record<string, unknown>);
|
|
729
720
|
}
|
|
730
721
|
|
|
731
722
|
protected validateDisclosureFrame<T extends ExtendedPayload>(
|
|
@@ -901,6 +892,8 @@ export class SDJwtGeneralJSONInstance<ExtendedPayload extends SdJwtPayload> {
|
|
|
901
892
|
}
|
|
902
893
|
|
|
903
894
|
const claims = await sdjwt.getClaims<ExtendedPayload>(hasher);
|
|
895
|
+
// Validate that unpacked claims do not contain reserved field names
|
|
896
|
+
validateReservedFieldsInternal(claims);
|
|
904
897
|
return { payload: claims, headers: results.map((r) => r.header) };
|
|
905
898
|
}
|
|
906
899
|
|
package/src/test/index.spec.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { exportJWK, importJWK, type JWK } from 'jose';
|
|
|
4
4
|
import { describe, expect, test } from 'vitest';
|
|
5
5
|
import { SDJwtInstance, type SdJwtPayload } from '../index';
|
|
6
6
|
import type { JwtPayload, KbVerifier, Signer, Verifier } from '../types';
|
|
7
|
+
import { Disclosure } from '../utils';
|
|
7
8
|
|
|
8
9
|
// Extract the major version as a number
|
|
9
10
|
const nodeVersionMajor = Number.parseInt(
|
|
@@ -126,6 +127,50 @@ describe('index', () => {
|
|
|
126
127
|
).rejects.toThrow('Reserved field name "_sd" is not allowed');
|
|
127
128
|
});
|
|
128
129
|
|
|
130
|
+
test.each([
|
|
131
|
+
'_sd',
|
|
132
|
+
'...',
|
|
133
|
+
])('validate rejects disclosure resolving to reserved claim name %s', async (reservedClaimName) => {
|
|
134
|
+
const { signer, verifier } = createSignerVerifier();
|
|
135
|
+
const sdjwt = new SDJwtInstance<SdJwtPayload>({
|
|
136
|
+
signer,
|
|
137
|
+
signAlg: 'EdDSA',
|
|
138
|
+
verifier,
|
|
139
|
+
hasher: digest,
|
|
140
|
+
saltGenerator: generateSalt,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const disclosure = new Disclosure([
|
|
144
|
+
await generateSalt(16),
|
|
145
|
+
reservedClaimName,
|
|
146
|
+
'reserved',
|
|
147
|
+
]);
|
|
148
|
+
const disclosureDigest = await disclosure.digest({
|
|
149
|
+
hasher: digest,
|
|
150
|
+
alg: 'sha-256',
|
|
151
|
+
});
|
|
152
|
+
const header = Buffer.from(JSON.stringify({ alg: 'EdDSA' })).toString(
|
|
153
|
+
'base64url',
|
|
154
|
+
);
|
|
155
|
+
const payload = Buffer.from(
|
|
156
|
+
JSON.stringify({
|
|
157
|
+
_sd: [disclosureDigest],
|
|
158
|
+
iss: 'Issuer',
|
|
159
|
+
iat: Math.floor(Date.now() / 1000),
|
|
160
|
+
vct: '',
|
|
161
|
+
_sd_alg: 'sha-256',
|
|
162
|
+
}),
|
|
163
|
+
).toString('base64url');
|
|
164
|
+
const unsignedJwt = `${header}.${payload}`;
|
|
165
|
+
const signature = await signer(unsignedJwt);
|
|
166
|
+
|
|
167
|
+
await expect(
|
|
168
|
+
sdjwt.validate(`${unsignedJwt}.${signature}~${disclosure.encode()}~`),
|
|
169
|
+
).rejects.toThrow(
|
|
170
|
+
`Reserved field name "${reservedClaimName}" is not allowed`,
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
|
|
129
174
|
test('verify failed', async () => {
|
|
130
175
|
const { signer } = createSignerVerifier();
|
|
131
176
|
const { publicKey } = Crypto.generateKeyPairSync('ed25519');
|
|
@@ -236,7 +281,7 @@ describe('index', () => {
|
|
|
236
281
|
// use the key from the cnf
|
|
237
282
|
publicKey = payload.cnf.jwk;
|
|
238
283
|
} else {
|
|
239
|
-
throw Error('key binding not supported');
|
|
284
|
+
throw new Error('key binding not supported');
|
|
240
285
|
}
|
|
241
286
|
// get the key of the holder to verify the signature
|
|
242
287
|
return Crypto.verify(
|
|
@@ -304,7 +349,7 @@ describe('index', () => {
|
|
|
304
349
|
sig: string,
|
|
305
350
|
payload: JwtPayload,
|
|
306
351
|
) => {
|
|
307
|
-
if (!payload.cnf) throw Error('key binding not supported');
|
|
352
|
+
if (!payload.cnf) throw new Error('key binding not supported');
|
|
308
353
|
return Crypto.verify(
|
|
309
354
|
null,
|
|
310
355
|
Buffer.from(data),
|
|
@@ -372,7 +417,7 @@ describe('index', () => {
|
|
|
372
417
|
sig: string,
|
|
373
418
|
payload: JwtPayload,
|
|
374
419
|
) => {
|
|
375
|
-
if (!payload.cnf) throw Error('key binding not supported');
|
|
420
|
+
if (!payload.cnf) throw new Error('key binding not supported');
|
|
376
421
|
return Crypto.verify(
|
|
377
422
|
null,
|
|
378
423
|
Buffer.from(data),
|