@sd-jwt/core 0.20.1-next.2 → 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 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
- const reservedFields = /* @__PURE__ */ new Set([SD_DIGEST, "_sd_alg", SD_DECOY]);
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
- const reservedFields = /* @__PURE__ */ new Set([SD_DIGEST, "_sd_alg", SD_DECOY]);
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
- const reservedFields = /* @__PURE__ */ new Set([SD_DIGEST, "_sd_alg", SD_DECOY]);
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
- const reservedFields = /* @__PURE__ */ new Set([SD_DIGEST, "_sd_alg", SD_DECOY]);
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.2+a1ae536",
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": "a1ae5366775a74770f58ef04fe12d6ad6df8eb1a"
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
- const reservedFields = new Set([SD_DIGEST, '_sd_alg', SD_DECOY]);
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
- const reservedFields = new Set([SD_DIGEST, '_sd_alg', SD_DECOY]);
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
 
@@ -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),