@sd-jwt/core 0.19.1-next.9 → 0.20.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 +28 -0
- package/dist/index.d.mts +22 -9
- package/dist/index.d.ts +22 -9
- package/dist/index.js +89 -31
- package/dist/index.mjs +89 -31
- package/package.json +2 -2
- package/src/decode/decode.ts +14 -4
- package/src/index.ts +69 -18
- package/src/jwt.ts +7 -0
- package/src/kbjwt.ts +15 -10
- package/src/test/index.spec.ts +192 -0
- package/src/test/kbjwt.spec.ts +211 -0
- package/src/utils/disclosure.ts +10 -7
- package/src/utils/strict-json.ts +17 -0
- package/test/app-e2e.spec.ts +31 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Change Log
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
|
+
|
|
6
|
+
# [0.20.0](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.1.0...v0.20.0) (2026-06-29)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* version fixing to current 0.19.0 ([#382](https://github.com/openwallet-foundation/sd-jwt-js/issues/382)) ([d88c631](https://github.com/openwallet-foundation/sd-jwt-js/commit/d88c631fe4c3cefcbc96f268cb9d340296facbba))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# [0.1.0](https://github.com/openwallet-foundation/sd-jwt-js/compare/v0.19.0...v0.1.0) (2026-06-29)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Bug Fixes
|
|
21
|
+
|
|
22
|
+
* **core:** reject tampered/unreferenced disclosures and reserved claims ([#380](https://github.com/openwallet-foundation/sd-jwt-js/issues/380)) ([3d8a72a](https://github.com/openwallet-foundation/sd-jwt-js/commit/3d8a72a0a279d67439db038c9c2b52621568b866))
|
|
23
|
+
* verify common jwt claims for kb-jwt ([#378](https://github.com/openwallet-foundation/sd-jwt-js/issues/378)) ([9e9bca1](https://github.com/openwallet-foundation/sd-jwt-js/commit/9e9bca10de3ad8574bfcbc875c85e37cee541505))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Features
|
|
27
|
+
|
|
28
|
+
* allow disabling status verification ([#381](https://github.com/openwallet-foundation/sd-jwt-js/issues/381)) ([c9d73e4](https://github.com/openwallet-foundation/sd-jwt-js/commit/c9d73e4db2a925e5096988fffe7eded62db1b6a7))
|
package/dist/index.d.mts
CHANGED
|
@@ -336,6 +336,12 @@ type VerifierOptions = {
|
|
|
336
336
|
* nonce used to verify the key binding jwt to prevent replay attacks.
|
|
337
337
|
*/
|
|
338
338
|
keyBindingNonce?: string;
|
|
339
|
+
/**
|
|
340
|
+
* disable the verification of the status claim in the payload.
|
|
341
|
+
*
|
|
342
|
+
* @default false
|
|
343
|
+
*/
|
|
344
|
+
disableStatusVerification?: boolean;
|
|
339
345
|
/**
|
|
340
346
|
* any other custom options
|
|
341
347
|
*/
|
|
@@ -376,6 +382,11 @@ declare class KBJwt<Header extends kbHeader = kbHeader, Payload extends kbPayloa
|
|
|
376
382
|
verifier: KbVerifier;
|
|
377
383
|
payload: Record<string, unknown>;
|
|
378
384
|
nonce: string;
|
|
385
|
+
/**
|
|
386
|
+
* Options forwarded to the common JWT verification, e.g. currentDate and
|
|
387
|
+
* skewSeconds used to validate the iat, nbf and exp claims.
|
|
388
|
+
*/
|
|
389
|
+
options?: VerifierOptions;
|
|
379
390
|
}): Promise<{
|
|
380
391
|
payload: Payload;
|
|
381
392
|
header: Header;
|
|
@@ -537,11 +548,12 @@ declare class SDJwtInstance<ExtendedPayload extends SdJwtPayload, T = unknown> {
|
|
|
537
548
|
header?: object;
|
|
538
549
|
}): Promise<SDJWTCompact>;
|
|
539
550
|
/**
|
|
540
|
-
* Validates if the
|
|
541
|
-
* @param
|
|
551
|
+
* Validates if the payload contains any reserved claim names. If so it will throw an error.
|
|
552
|
+
* @param payload
|
|
542
553
|
* @returns
|
|
543
554
|
*/
|
|
544
|
-
protected validateReservedFields<T extends ExtendedPayload>(
|
|
555
|
+
protected validateReservedFields<T extends ExtendedPayload>(payload: T): void;
|
|
556
|
+
protected validateDisclosureFrame<T extends ExtendedPayload>(_disclosureFrame?: DisclosureFrame<T>): void;
|
|
545
557
|
present<T extends Record<string, unknown>>(encodedSDJwt: string, presentationFrame?: PresentationFrame<T>, options?: {
|
|
546
558
|
kb?: KBOptions;
|
|
547
559
|
}): Promise<SDJWTCompact>;
|
|
@@ -610,21 +622,22 @@ declare class SDJwtGeneralJSONInstance<ExtendedPayload extends SdJwtPayload> {
|
|
|
610
622
|
}>;
|
|
611
623
|
}): Promise<GeneralJSON>;
|
|
612
624
|
/**
|
|
613
|
-
* Validates if the
|
|
614
|
-
* @param
|
|
625
|
+
* Validates if the payload contains any reserved claim names. If so it will throw an error.
|
|
626
|
+
* @param payload
|
|
615
627
|
* @returns
|
|
616
628
|
*/
|
|
617
|
-
protected validateReservedFields<T extends ExtendedPayload>(
|
|
629
|
+
protected validateReservedFields<T extends ExtendedPayload>(payload: T): void;
|
|
630
|
+
protected validateDisclosureFrame<T extends ExtendedPayload>(_disclosureFrame?: DisclosureFrame<T>): void;
|
|
618
631
|
present<T extends Record<string, unknown>>(generalJSON: GeneralJSON, presentationFrame?: PresentationFrame<T>, options?: {
|
|
619
632
|
kb?: KBOptions;
|
|
620
633
|
}): Promise<GeneralJSON>;
|
|
621
634
|
verify(generalJSON: GeneralJSON, options?: VerifierOptions): Promise<{
|
|
622
635
|
payload: ExtendedPayload;
|
|
623
|
-
headers:
|
|
636
|
+
headers: unknown[];
|
|
624
637
|
kb?: undefined;
|
|
625
638
|
} | {
|
|
626
639
|
payload: ExtendedPayload;
|
|
627
|
-
headers:
|
|
640
|
+
headers: unknown[];
|
|
628
641
|
kb: {
|
|
629
642
|
payload: kbPayload;
|
|
630
643
|
header: kbHeader;
|
|
@@ -633,7 +646,7 @@ declare class SDJwtGeneralJSONInstance<ExtendedPayload extends SdJwtPayload> {
|
|
|
633
646
|
private calculateSDHash;
|
|
634
647
|
validate(generalJSON: GeneralJSON): Promise<{
|
|
635
648
|
payload: ExtendedPayload;
|
|
636
|
-
headers:
|
|
649
|
+
headers: unknown[];
|
|
637
650
|
}>;
|
|
638
651
|
config(newConfig: SDJWTConfig): void;
|
|
639
652
|
encode(sdJwt: GeneralJSON, index: number): SDJWTCompact;
|
package/dist/index.d.ts
CHANGED
|
@@ -336,6 +336,12 @@ type VerifierOptions = {
|
|
|
336
336
|
* nonce used to verify the key binding jwt to prevent replay attacks.
|
|
337
337
|
*/
|
|
338
338
|
keyBindingNonce?: string;
|
|
339
|
+
/**
|
|
340
|
+
* disable the verification of the status claim in the payload.
|
|
341
|
+
*
|
|
342
|
+
* @default false
|
|
343
|
+
*/
|
|
344
|
+
disableStatusVerification?: boolean;
|
|
339
345
|
/**
|
|
340
346
|
* any other custom options
|
|
341
347
|
*/
|
|
@@ -376,6 +382,11 @@ declare class KBJwt<Header extends kbHeader = kbHeader, Payload extends kbPayloa
|
|
|
376
382
|
verifier: KbVerifier;
|
|
377
383
|
payload: Record<string, unknown>;
|
|
378
384
|
nonce: string;
|
|
385
|
+
/**
|
|
386
|
+
* Options forwarded to the common JWT verification, e.g. currentDate and
|
|
387
|
+
* skewSeconds used to validate the iat, nbf and exp claims.
|
|
388
|
+
*/
|
|
389
|
+
options?: VerifierOptions;
|
|
379
390
|
}): Promise<{
|
|
380
391
|
payload: Payload;
|
|
381
392
|
header: Header;
|
|
@@ -537,11 +548,12 @@ declare class SDJwtInstance<ExtendedPayload extends SdJwtPayload, T = unknown> {
|
|
|
537
548
|
header?: object;
|
|
538
549
|
}): Promise<SDJWTCompact>;
|
|
539
550
|
/**
|
|
540
|
-
* Validates if the
|
|
541
|
-
* @param
|
|
551
|
+
* Validates if the payload contains any reserved claim names. If so it will throw an error.
|
|
552
|
+
* @param payload
|
|
542
553
|
* @returns
|
|
543
554
|
*/
|
|
544
|
-
protected validateReservedFields<T extends ExtendedPayload>(
|
|
555
|
+
protected validateReservedFields<T extends ExtendedPayload>(payload: T): void;
|
|
556
|
+
protected validateDisclosureFrame<T extends ExtendedPayload>(_disclosureFrame?: DisclosureFrame<T>): void;
|
|
545
557
|
present<T extends Record<string, unknown>>(encodedSDJwt: string, presentationFrame?: PresentationFrame<T>, options?: {
|
|
546
558
|
kb?: KBOptions;
|
|
547
559
|
}): Promise<SDJWTCompact>;
|
|
@@ -610,21 +622,22 @@ declare class SDJwtGeneralJSONInstance<ExtendedPayload extends SdJwtPayload> {
|
|
|
610
622
|
}>;
|
|
611
623
|
}): Promise<GeneralJSON>;
|
|
612
624
|
/**
|
|
613
|
-
* Validates if the
|
|
614
|
-
* @param
|
|
625
|
+
* Validates if the payload contains any reserved claim names. If so it will throw an error.
|
|
626
|
+
* @param payload
|
|
615
627
|
* @returns
|
|
616
628
|
*/
|
|
617
|
-
protected validateReservedFields<T extends ExtendedPayload>(
|
|
629
|
+
protected validateReservedFields<T extends ExtendedPayload>(payload: T): void;
|
|
630
|
+
protected validateDisclosureFrame<T extends ExtendedPayload>(_disclosureFrame?: DisclosureFrame<T>): void;
|
|
618
631
|
present<T extends Record<string, unknown>>(generalJSON: GeneralJSON, presentationFrame?: PresentationFrame<T>, options?: {
|
|
619
632
|
kb?: KBOptions;
|
|
620
633
|
}): Promise<GeneralJSON>;
|
|
621
634
|
verify(generalJSON: GeneralJSON, options?: VerifierOptions): Promise<{
|
|
622
635
|
payload: ExtendedPayload;
|
|
623
|
-
headers:
|
|
636
|
+
headers: unknown[];
|
|
624
637
|
kb?: undefined;
|
|
625
638
|
} | {
|
|
626
639
|
payload: ExtendedPayload;
|
|
627
|
-
headers:
|
|
640
|
+
headers: unknown[];
|
|
628
641
|
kb: {
|
|
629
642
|
payload: kbPayload;
|
|
630
643
|
header: kbHeader;
|
|
@@ -633,7 +646,7 @@ declare class SDJwtGeneralJSONInstance<ExtendedPayload extends SdJwtPayload> {
|
|
|
633
646
|
private calculateSDHash;
|
|
634
647
|
validate(generalJSON: GeneralJSON): Promise<{
|
|
635
648
|
payload: ExtendedPayload;
|
|
636
|
-
headers:
|
|
649
|
+
headers: unknown[];
|
|
637
650
|
}>;
|
|
638
651
|
config(newConfig: SDJWTConfig): void;
|
|
639
652
|
encode(sdJwt: GeneralJSON, index: number): SDJWTCompact;
|
package/dist/index.js
CHANGED
|
@@ -161,6 +161,18 @@ function ensureError(value) {
|
|
|
161
161
|
return new Error(String(value));
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
// src/utils/strict-json.ts
|
|
165
|
+
var utf8Decoder = new TextDecoder("utf-8", { fatal: true });
|
|
166
|
+
var decodeBase64urlJsonStrict = (encoded, errorMessage) => {
|
|
167
|
+
try {
|
|
168
|
+
const bytes = (0, import_identity_common.base64UrlToUint8Array)(encoded);
|
|
169
|
+
const decoded = utf8Decoder.decode(bytes);
|
|
170
|
+
return JSON.parse(decoded);
|
|
171
|
+
} catch (e) {
|
|
172
|
+
throw new SDJWTException(errorMessage);
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
164
176
|
// src/utils/disclosure.ts
|
|
165
177
|
var Disclosure = class _Disclosure {
|
|
166
178
|
constructor(data, _meta) {
|
|
@@ -187,7 +199,10 @@ var Disclosure = class _Disclosure {
|
|
|
187
199
|
const { hasher, alg } = hash;
|
|
188
200
|
const digest = yield hasher(s, alg);
|
|
189
201
|
const digestStr = (0, import_identity_common.uint8ArrayToBase64Url)(digest);
|
|
190
|
-
const item =
|
|
202
|
+
const item = decodeBase64urlJsonStrict(
|
|
203
|
+
s,
|
|
204
|
+
"Invalid disclosure data"
|
|
205
|
+
);
|
|
191
206
|
return _Disclosure.fromArray(item, { digest: digestStr, encoded: s });
|
|
192
207
|
});
|
|
193
208
|
}
|
|
@@ -195,7 +210,10 @@ var Disclosure = class _Disclosure {
|
|
|
195
210
|
const { hasher, alg } = hash;
|
|
196
211
|
const digest = hasher(s, alg);
|
|
197
212
|
const digestStr = (0, import_identity_common.uint8ArrayToBase64Url)(digest);
|
|
198
|
-
const item =
|
|
213
|
+
const item = decodeBase64urlJsonStrict(
|
|
214
|
+
s,
|
|
215
|
+
"Invalid disclosure data"
|
|
216
|
+
);
|
|
199
217
|
return _Disclosure.fromArray(item, { digest: digestStr, encoded: s });
|
|
200
218
|
}
|
|
201
219
|
static fromArray(item, _meta) {
|
|
@@ -237,8 +255,8 @@ var decodeJwt = (jwt) => {
|
|
|
237
255
|
throw new SDJWTException("Invalid JWT as input");
|
|
238
256
|
}
|
|
239
257
|
return {
|
|
240
|
-
header:
|
|
241
|
-
payload:
|
|
258
|
+
header: decodeBase64urlJsonStrict(header, "Invalid JWT as input"),
|
|
259
|
+
payload: decodeBase64urlJsonStrict(payload, "Invalid JWT as input"),
|
|
242
260
|
signature
|
|
243
261
|
};
|
|
244
262
|
};
|
|
@@ -431,6 +449,11 @@ var getSDAlgAndPayload = (SdJwtPayload) => {
|
|
|
431
449
|
if (typeof _sd_alg !== "string") {
|
|
432
450
|
return { _sd_alg: "sha-256", payload };
|
|
433
451
|
}
|
|
452
|
+
if (!IANA_HASH_ALGORITHMS.includes(
|
|
453
|
+
_sd_alg
|
|
454
|
+
)) {
|
|
455
|
+
throw new SDJWTException(`Invalid _sd_alg: ${_sd_alg}`);
|
|
456
|
+
}
|
|
434
457
|
return { _sd_alg, payload };
|
|
435
458
|
};
|
|
436
459
|
var unpack = (SdJwtPayload, disclosures, hasher) => __async(null, null, function* () {
|
|
@@ -730,18 +753,13 @@ var KBJwt = class _KBJwt extends Jwt {
|
|
|
730
753
|
!(this.payload.sd_hash || "_sd_hash" in this.payload && this.payload._sd_hash)) {
|
|
731
754
|
throw new SDJWTException("Invalid Key Binding Jwt");
|
|
732
755
|
}
|
|
733
|
-
const data = this.getUnsignedToken();
|
|
734
|
-
const verified = yield values.verifier(
|
|
735
|
-
data,
|
|
736
|
-
this.signature,
|
|
737
|
-
values.payload
|
|
738
|
-
);
|
|
739
|
-
if (!verified) {
|
|
740
|
-
throw new SDJWTException("Verify Error: Invalid JWT Signature");
|
|
741
|
-
}
|
|
742
756
|
if (this.payload.nonce !== values.nonce) {
|
|
743
757
|
throw new SDJWTException("Verify Error: Invalid Nonce");
|
|
744
758
|
}
|
|
759
|
+
yield this.verify(
|
|
760
|
+
(data, sig) => values.verifier(data, sig, values.payload),
|
|
761
|
+
values.options
|
|
762
|
+
);
|
|
745
763
|
return { payload: this.payload, header: this.header };
|
|
746
764
|
});
|
|
747
765
|
}
|
|
@@ -1160,9 +1178,8 @@ var _SDJwtInstance = class _SDJwtInstance {
|
|
|
1160
1178
|
if (!this.userConfig.signAlg) {
|
|
1161
1179
|
throw new SDJWTException("sign alogrithm not specified");
|
|
1162
1180
|
}
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
}
|
|
1181
|
+
this.validateReservedFields(payload);
|
|
1182
|
+
this.validateDisclosureFrame(disclosureFrame);
|
|
1166
1183
|
const hasher = this.userConfig.hasher;
|
|
1167
1184
|
const hashAlg = (_a = this.userConfig.hashAlg) != null ? _a : _SDJwtInstance.DEFAULT_hashAlg;
|
|
1168
1185
|
const { packedClaims, disclosures } = yield pack(
|
|
@@ -1190,11 +1207,30 @@ var _SDJwtInstance = class _SDJwtInstance {
|
|
|
1190
1207
|
});
|
|
1191
1208
|
}
|
|
1192
1209
|
/**
|
|
1193
|
-
* Validates if the
|
|
1194
|
-
* @param
|
|
1210
|
+
* Validates if the payload contains any reserved claim names. If so it will throw an error.
|
|
1211
|
+
* @param payload
|
|
1195
1212
|
* @returns
|
|
1196
1213
|
*/
|
|
1197
|
-
validateReservedFields(
|
|
1214
|
+
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
|
+
}
|
|
1233
|
+
validateDisclosureFrame(_disclosureFrame) {
|
|
1198
1234
|
return;
|
|
1199
1235
|
}
|
|
1200
1236
|
present(encodedSDJwt, presentationFrame, options) {
|
|
@@ -1259,7 +1295,8 @@ var _SDJwtInstance = class _SDJwtInstance {
|
|
|
1259
1295
|
const kb = yield sdjwt.kbJwt.verifyKB({
|
|
1260
1296
|
verifier: this.userConfig.kbVerifier,
|
|
1261
1297
|
payload,
|
|
1262
|
-
nonce: options.keyBindingNonce
|
|
1298
|
+
nonce: options.keyBindingNonce,
|
|
1299
|
+
options
|
|
1263
1300
|
});
|
|
1264
1301
|
if (!kb) {
|
|
1265
1302
|
throw new Error("signature is not valid");
|
|
@@ -1390,7 +1427,8 @@ var _SDJwtInstance = class _SDJwtInstance {
|
|
|
1390
1427
|
const kbResult = yield sdjwt.kbJwt.verifyKB({
|
|
1391
1428
|
verifier: this.userConfig.kbVerifier,
|
|
1392
1429
|
payload,
|
|
1393
|
-
nonce: options.keyBindingNonce
|
|
1430
|
+
nonce: options.keyBindingNonce,
|
|
1431
|
+
options
|
|
1394
1432
|
});
|
|
1395
1433
|
if (!kbResult) {
|
|
1396
1434
|
addError(
|
|
@@ -1568,9 +1606,8 @@ var SDJwtGeneralJSONInstance = class {
|
|
|
1568
1606
|
if (!this.userConfig.saltGenerator) {
|
|
1569
1607
|
throw new SDJWTException("SaltGenerator not found");
|
|
1570
1608
|
}
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
}
|
|
1609
|
+
this.validateReservedFields(payload);
|
|
1610
|
+
this.validateDisclosureFrame(disclosureFrame);
|
|
1574
1611
|
const hasher = this.userConfig.hasher;
|
|
1575
1612
|
const hashAlg = (_a = this.userConfig.hashAlg) != null ? _a : SDJwtInstance.DEFAULT_hashAlg;
|
|
1576
1613
|
const { packedClaims, disclosures } = yield pack(
|
|
@@ -1579,10 +1616,12 @@ var SDJwtGeneralJSONInstance = class {
|
|
|
1579
1616
|
{ hasher, alg: hashAlg },
|
|
1580
1617
|
this.userConfig.saltGenerator
|
|
1581
1618
|
);
|
|
1582
|
-
const encodedDisclosures = disclosures.map((d) => d.encode());
|
|
1583
1619
|
const encodedSDJwtPayload = this.encodeObj(__spreadProps(__spreadValues({}, packedClaims), {
|
|
1584
1620
|
_sd_alg: disclosureFrame ? hashAlg : void 0
|
|
1585
1621
|
}));
|
|
1622
|
+
const encodedDisclosures = disclosures.map(
|
|
1623
|
+
(disclosure) => disclosure.encode()
|
|
1624
|
+
);
|
|
1586
1625
|
const signatures = yield Promise.all(
|
|
1587
1626
|
options.sigs.map((s) => __async(this, null, function* () {
|
|
1588
1627
|
const { signer, alg, kid, header } = s;
|
|
@@ -1593,7 +1632,6 @@ var SDJwtGeneralJSONInstance = class {
|
|
|
1593
1632
|
);
|
|
1594
1633
|
return {
|
|
1595
1634
|
protected: encodedProtectedHeader,
|
|
1596
|
-
kid,
|
|
1597
1635
|
signature
|
|
1598
1636
|
};
|
|
1599
1637
|
}))
|
|
@@ -1607,11 +1645,30 @@ var SDJwtGeneralJSONInstance = class {
|
|
|
1607
1645
|
});
|
|
1608
1646
|
}
|
|
1609
1647
|
/**
|
|
1610
|
-
* Validates if the
|
|
1611
|
-
* @param
|
|
1648
|
+
* Validates if the payload contains any reserved claim names. If so it will throw an error.
|
|
1649
|
+
* @param payload
|
|
1612
1650
|
* @returns
|
|
1613
1651
|
*/
|
|
1614
|
-
validateReservedFields(
|
|
1652
|
+
validateReservedFields(payload) {
|
|
1653
|
+
const reservedFields = /* @__PURE__ */ new Set([SD_DIGEST, "_sd_alg", SD_DECOY]);
|
|
1654
|
+
const visit = (node) => {
|
|
1655
|
+
if (!node || typeof node !== "object") {
|
|
1656
|
+
return;
|
|
1657
|
+
}
|
|
1658
|
+
for (const [key, value] of Object.entries(
|
|
1659
|
+
node
|
|
1660
|
+
)) {
|
|
1661
|
+
if (reservedFields.has(key)) {
|
|
1662
|
+
throw new SDJWTException(
|
|
1663
|
+
`Reserved field name "${key}" is not allowed`
|
|
1664
|
+
);
|
|
1665
|
+
}
|
|
1666
|
+
visit(value);
|
|
1667
|
+
}
|
|
1668
|
+
};
|
|
1669
|
+
visit(payload);
|
|
1670
|
+
}
|
|
1671
|
+
validateDisclosureFrame(_disclosureFrame) {
|
|
1615
1672
|
return;
|
|
1616
1673
|
}
|
|
1617
1674
|
present(generalJSON, presentationFrame, options) {
|
|
@@ -1690,7 +1747,8 @@ var SDJwtGeneralJSONInstance = class {
|
|
|
1690
1747
|
const kb = yield sdjwt.kbJwt.verifyKB({
|
|
1691
1748
|
verifier: this.userConfig.kbVerifier,
|
|
1692
1749
|
payload,
|
|
1693
|
-
nonce: options.keyBindingNonce
|
|
1750
|
+
nonce: options.keyBindingNonce,
|
|
1751
|
+
options
|
|
1694
1752
|
});
|
|
1695
1753
|
if (!kb) {
|
|
1696
1754
|
throw new Error("signature is not valid");
|
|
@@ -1743,7 +1801,7 @@ var SDJwtGeneralJSONInstance = class {
|
|
|
1743
1801
|
`${encodedHeader}.${payload}`,
|
|
1744
1802
|
signature
|
|
1745
1803
|
);
|
|
1746
|
-
const header =
|
|
1804
|
+
const header = decodeBase64urlJsonStrict(encodedHeader, "Invalid JWT");
|
|
1747
1805
|
return { verified: verified2, header };
|
|
1748
1806
|
}))
|
|
1749
1807
|
);
|
package/dist/index.mjs
CHANGED
|
@@ -102,6 +102,18 @@ function ensureError(value) {
|
|
|
102
102
|
return new Error(String(value));
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
// src/utils/strict-json.ts
|
|
106
|
+
var utf8Decoder = new TextDecoder("utf-8", { fatal: true });
|
|
107
|
+
var decodeBase64urlJsonStrict = (encoded, errorMessage) => {
|
|
108
|
+
try {
|
|
109
|
+
const bytes = base64UrlToUint8Array(encoded);
|
|
110
|
+
const decoded = utf8Decoder.decode(bytes);
|
|
111
|
+
return JSON.parse(decoded);
|
|
112
|
+
} catch (e) {
|
|
113
|
+
throw new SDJWTException(errorMessage);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
105
117
|
// src/utils/disclosure.ts
|
|
106
118
|
var Disclosure = class _Disclosure {
|
|
107
119
|
constructor(data, _meta) {
|
|
@@ -128,7 +140,10 @@ var Disclosure = class _Disclosure {
|
|
|
128
140
|
const { hasher, alg } = hash;
|
|
129
141
|
const digest = yield hasher(s, alg);
|
|
130
142
|
const digestStr = uint8ArrayToBase64Url(digest);
|
|
131
|
-
const item =
|
|
143
|
+
const item = decodeBase64urlJsonStrict(
|
|
144
|
+
s,
|
|
145
|
+
"Invalid disclosure data"
|
|
146
|
+
);
|
|
132
147
|
return _Disclosure.fromArray(item, { digest: digestStr, encoded: s });
|
|
133
148
|
});
|
|
134
149
|
}
|
|
@@ -136,7 +151,10 @@ var Disclosure = class _Disclosure {
|
|
|
136
151
|
const { hasher, alg } = hash;
|
|
137
152
|
const digest = hasher(s, alg);
|
|
138
153
|
const digestStr = uint8ArrayToBase64Url(digest);
|
|
139
|
-
const item =
|
|
154
|
+
const item = decodeBase64urlJsonStrict(
|
|
155
|
+
s,
|
|
156
|
+
"Invalid disclosure data"
|
|
157
|
+
);
|
|
140
158
|
return _Disclosure.fromArray(item, { digest: digestStr, encoded: s });
|
|
141
159
|
}
|
|
142
160
|
static fromArray(item, _meta) {
|
|
@@ -178,8 +196,8 @@ var decodeJwt = (jwt) => {
|
|
|
178
196
|
throw new SDJWTException("Invalid JWT as input");
|
|
179
197
|
}
|
|
180
198
|
return {
|
|
181
|
-
header:
|
|
182
|
-
payload:
|
|
199
|
+
header: decodeBase64urlJsonStrict(header, "Invalid JWT as input"),
|
|
200
|
+
payload: decodeBase64urlJsonStrict(payload, "Invalid JWT as input"),
|
|
183
201
|
signature
|
|
184
202
|
};
|
|
185
203
|
};
|
|
@@ -372,6 +390,11 @@ var getSDAlgAndPayload = (SdJwtPayload) => {
|
|
|
372
390
|
if (typeof _sd_alg !== "string") {
|
|
373
391
|
return { _sd_alg: "sha-256", payload };
|
|
374
392
|
}
|
|
393
|
+
if (!IANA_HASH_ALGORITHMS.includes(
|
|
394
|
+
_sd_alg
|
|
395
|
+
)) {
|
|
396
|
+
throw new SDJWTException(`Invalid _sd_alg: ${_sd_alg}`);
|
|
397
|
+
}
|
|
375
398
|
return { _sd_alg, payload };
|
|
376
399
|
};
|
|
377
400
|
var unpack = (SdJwtPayload, disclosures, hasher) => __async(null, null, function* () {
|
|
@@ -671,18 +694,13 @@ var KBJwt = class _KBJwt extends Jwt {
|
|
|
671
694
|
!(this.payload.sd_hash || "_sd_hash" in this.payload && this.payload._sd_hash)) {
|
|
672
695
|
throw new SDJWTException("Invalid Key Binding Jwt");
|
|
673
696
|
}
|
|
674
|
-
const data = this.getUnsignedToken();
|
|
675
|
-
const verified = yield values.verifier(
|
|
676
|
-
data,
|
|
677
|
-
this.signature,
|
|
678
|
-
values.payload
|
|
679
|
-
);
|
|
680
|
-
if (!verified) {
|
|
681
|
-
throw new SDJWTException("Verify Error: Invalid JWT Signature");
|
|
682
|
-
}
|
|
683
697
|
if (this.payload.nonce !== values.nonce) {
|
|
684
698
|
throw new SDJWTException("Verify Error: Invalid Nonce");
|
|
685
699
|
}
|
|
700
|
+
yield this.verify(
|
|
701
|
+
(data, sig) => values.verifier(data, sig, values.payload),
|
|
702
|
+
values.options
|
|
703
|
+
);
|
|
686
704
|
return { payload: this.payload, header: this.header };
|
|
687
705
|
});
|
|
688
706
|
}
|
|
@@ -1101,9 +1119,8 @@ var _SDJwtInstance = class _SDJwtInstance {
|
|
|
1101
1119
|
if (!this.userConfig.signAlg) {
|
|
1102
1120
|
throw new SDJWTException("sign alogrithm not specified");
|
|
1103
1121
|
}
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
}
|
|
1122
|
+
this.validateReservedFields(payload);
|
|
1123
|
+
this.validateDisclosureFrame(disclosureFrame);
|
|
1107
1124
|
const hasher = this.userConfig.hasher;
|
|
1108
1125
|
const hashAlg = (_a = this.userConfig.hashAlg) != null ? _a : _SDJwtInstance.DEFAULT_hashAlg;
|
|
1109
1126
|
const { packedClaims, disclosures } = yield pack(
|
|
@@ -1131,11 +1148,30 @@ var _SDJwtInstance = class _SDJwtInstance {
|
|
|
1131
1148
|
});
|
|
1132
1149
|
}
|
|
1133
1150
|
/**
|
|
1134
|
-
* Validates if the
|
|
1135
|
-
* @param
|
|
1151
|
+
* Validates if the payload contains any reserved claim names. If so it will throw an error.
|
|
1152
|
+
* @param payload
|
|
1136
1153
|
* @returns
|
|
1137
1154
|
*/
|
|
1138
|
-
validateReservedFields(
|
|
1155
|
+
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
|
+
}
|
|
1174
|
+
validateDisclosureFrame(_disclosureFrame) {
|
|
1139
1175
|
return;
|
|
1140
1176
|
}
|
|
1141
1177
|
present(encodedSDJwt, presentationFrame, options) {
|
|
@@ -1200,7 +1236,8 @@ var _SDJwtInstance = class _SDJwtInstance {
|
|
|
1200
1236
|
const kb = yield sdjwt.kbJwt.verifyKB({
|
|
1201
1237
|
verifier: this.userConfig.kbVerifier,
|
|
1202
1238
|
payload,
|
|
1203
|
-
nonce: options.keyBindingNonce
|
|
1239
|
+
nonce: options.keyBindingNonce,
|
|
1240
|
+
options
|
|
1204
1241
|
});
|
|
1205
1242
|
if (!kb) {
|
|
1206
1243
|
throw new Error("signature is not valid");
|
|
@@ -1331,7 +1368,8 @@ var _SDJwtInstance = class _SDJwtInstance {
|
|
|
1331
1368
|
const kbResult = yield sdjwt.kbJwt.verifyKB({
|
|
1332
1369
|
verifier: this.userConfig.kbVerifier,
|
|
1333
1370
|
payload,
|
|
1334
|
-
nonce: options.keyBindingNonce
|
|
1371
|
+
nonce: options.keyBindingNonce,
|
|
1372
|
+
options
|
|
1335
1373
|
});
|
|
1336
1374
|
if (!kbResult) {
|
|
1337
1375
|
addError(
|
|
@@ -1509,9 +1547,8 @@ var SDJwtGeneralJSONInstance = class {
|
|
|
1509
1547
|
if (!this.userConfig.saltGenerator) {
|
|
1510
1548
|
throw new SDJWTException("SaltGenerator not found");
|
|
1511
1549
|
}
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
}
|
|
1550
|
+
this.validateReservedFields(payload);
|
|
1551
|
+
this.validateDisclosureFrame(disclosureFrame);
|
|
1515
1552
|
const hasher = this.userConfig.hasher;
|
|
1516
1553
|
const hashAlg = (_a = this.userConfig.hashAlg) != null ? _a : SDJwtInstance.DEFAULT_hashAlg;
|
|
1517
1554
|
const { packedClaims, disclosures } = yield pack(
|
|
@@ -1520,10 +1557,12 @@ var SDJwtGeneralJSONInstance = class {
|
|
|
1520
1557
|
{ hasher, alg: hashAlg },
|
|
1521
1558
|
this.userConfig.saltGenerator
|
|
1522
1559
|
);
|
|
1523
|
-
const encodedDisclosures = disclosures.map((d) => d.encode());
|
|
1524
1560
|
const encodedSDJwtPayload = this.encodeObj(__spreadProps(__spreadValues({}, packedClaims), {
|
|
1525
1561
|
_sd_alg: disclosureFrame ? hashAlg : void 0
|
|
1526
1562
|
}));
|
|
1563
|
+
const encodedDisclosures = disclosures.map(
|
|
1564
|
+
(disclosure) => disclosure.encode()
|
|
1565
|
+
);
|
|
1527
1566
|
const signatures = yield Promise.all(
|
|
1528
1567
|
options.sigs.map((s) => __async(this, null, function* () {
|
|
1529
1568
|
const { signer, alg, kid, header } = s;
|
|
@@ -1534,7 +1573,6 @@ var SDJwtGeneralJSONInstance = class {
|
|
|
1534
1573
|
);
|
|
1535
1574
|
return {
|
|
1536
1575
|
protected: encodedProtectedHeader,
|
|
1537
|
-
kid,
|
|
1538
1576
|
signature
|
|
1539
1577
|
};
|
|
1540
1578
|
}))
|
|
@@ -1548,11 +1586,30 @@ var SDJwtGeneralJSONInstance = class {
|
|
|
1548
1586
|
});
|
|
1549
1587
|
}
|
|
1550
1588
|
/**
|
|
1551
|
-
* Validates if the
|
|
1552
|
-
* @param
|
|
1589
|
+
* Validates if the payload contains any reserved claim names. If so it will throw an error.
|
|
1590
|
+
* @param payload
|
|
1553
1591
|
* @returns
|
|
1554
1592
|
*/
|
|
1555
|
-
validateReservedFields(
|
|
1593
|
+
validateReservedFields(payload) {
|
|
1594
|
+
const reservedFields = /* @__PURE__ */ new Set([SD_DIGEST, "_sd_alg", SD_DECOY]);
|
|
1595
|
+
const visit = (node) => {
|
|
1596
|
+
if (!node || typeof node !== "object") {
|
|
1597
|
+
return;
|
|
1598
|
+
}
|
|
1599
|
+
for (const [key, value] of Object.entries(
|
|
1600
|
+
node
|
|
1601
|
+
)) {
|
|
1602
|
+
if (reservedFields.has(key)) {
|
|
1603
|
+
throw new SDJWTException(
|
|
1604
|
+
`Reserved field name "${key}" is not allowed`
|
|
1605
|
+
);
|
|
1606
|
+
}
|
|
1607
|
+
visit(value);
|
|
1608
|
+
}
|
|
1609
|
+
};
|
|
1610
|
+
visit(payload);
|
|
1611
|
+
}
|
|
1612
|
+
validateDisclosureFrame(_disclosureFrame) {
|
|
1556
1613
|
return;
|
|
1557
1614
|
}
|
|
1558
1615
|
present(generalJSON, presentationFrame, options) {
|
|
@@ -1631,7 +1688,8 @@ var SDJwtGeneralJSONInstance = class {
|
|
|
1631
1688
|
const kb = yield sdjwt.kbJwt.verifyKB({
|
|
1632
1689
|
verifier: this.userConfig.kbVerifier,
|
|
1633
1690
|
payload,
|
|
1634
|
-
nonce: options.keyBindingNonce
|
|
1691
|
+
nonce: options.keyBindingNonce,
|
|
1692
|
+
options
|
|
1635
1693
|
});
|
|
1636
1694
|
if (!kb) {
|
|
1637
1695
|
throw new Error("signature is not valid");
|
|
@@ -1684,7 +1742,7 @@ var SDJwtGeneralJSONInstance = class {
|
|
|
1684
1742
|
`${encodedHeader}.${payload}`,
|
|
1685
1743
|
signature
|
|
1686
1744
|
);
|
|
1687
|
-
const header =
|
|
1745
|
+
const header = decodeBase64urlJsonStrict(encodedHeader, "Invalid JWT");
|
|
1688
1746
|
return { verified: verified2, header };
|
|
1689
1747
|
}))
|
|
1690
1748
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sd-jwt/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
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": "af170ace9b42984f8c6447caf23256fde6204b31"
|
|
62
62
|
}
|