@sd-jwt/core 0.20.1-next.4 → 0.20.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/dist/index.d.mts +35 -8
- package/dist/index.d.ts +35 -8
- package/dist/index.js +262 -60
- package/dist/index.mjs +257 -59
- package/package.json +3 -3
- package/src/decode/decode.ts +59 -28
- package/src/generalJSON.ts +9 -0
- package/src/index.ts +68 -6
- package/src/jwt.ts +109 -13
- package/src/kbjwt.ts +30 -10
- package/src/present/present.ts +11 -5
- package/src/sdjwt.ts +40 -10
- package/src/test/decode/decode.spec.ts +12 -17
- package/src/test/index.spec.ts +5 -38
- package/src/test/kbjwt.spec.ts +7 -17
- package/src/types/type.ts +21 -0
- package/src/utils/disclosure.ts +24 -4
- package/test/rfc9901-audit-fixes.spec.ts +252 -0
package/dist/index.mjs
CHANGED
|
@@ -56,6 +56,8 @@ var SD_LIST_KEY = "...";
|
|
|
56
56
|
var SD_DIGEST = "_sd";
|
|
57
57
|
var SD_DECOY = "_sd_decoy";
|
|
58
58
|
var KB_JWT_TYP = "kb+jwt";
|
|
59
|
+
var encodePathSegment = (segment) => segment.replace(/~/g, "~0").replace(/\./g, "~1");
|
|
60
|
+
var encodePath = (segments) => segments.map(encodePathSegment).join(".");
|
|
59
61
|
var IANA_HASH_ALGORITHMS = [
|
|
60
62
|
"sha-256",
|
|
61
63
|
"sha-256-128",
|
|
@@ -75,6 +77,19 @@ var IANA_HASH_ALGORITHMS = [
|
|
|
75
77
|
"k12-256",
|
|
76
78
|
"k12-512"
|
|
77
79
|
];
|
|
80
|
+
var DEFAULT_SECURE_HASH_ALGORITHMS = [
|
|
81
|
+
"sha-256",
|
|
82
|
+
"sha-384",
|
|
83
|
+
"sha-512",
|
|
84
|
+
"sha3-256",
|
|
85
|
+
"sha3-384",
|
|
86
|
+
"sha3-512",
|
|
87
|
+
"blake2s-256",
|
|
88
|
+
"blake2b-256",
|
|
89
|
+
"blake2b-512",
|
|
90
|
+
"k12-256",
|
|
91
|
+
"k12-512"
|
|
92
|
+
];
|
|
78
93
|
|
|
79
94
|
// src/utils/base64url.ts
|
|
80
95
|
import {
|
|
@@ -117,6 +132,12 @@ var decodeBase64urlJsonStrict = (encoded, errorMessage) => {
|
|
|
117
132
|
// src/utils/disclosure.ts
|
|
118
133
|
var Disclosure = class _Disclosure {
|
|
119
134
|
constructor(data, _meta) {
|
|
135
|
+
if (!Array.isArray(data) || data.length !== 2 && data.length !== 3) {
|
|
136
|
+
throw new SDJWTException("Invalid disclosure data");
|
|
137
|
+
}
|
|
138
|
+
if (typeof data[0] !== "string") {
|
|
139
|
+
throw new SDJWTException("Invalid disclosure salt");
|
|
140
|
+
}
|
|
120
141
|
this._digest = _meta == null ? void 0 : _meta.digest;
|
|
121
142
|
this._encoded = _meta == null ? void 0 : _meta.encoded;
|
|
122
143
|
if (data.length === 2) {
|
|
@@ -125,12 +146,19 @@ var Disclosure = class _Disclosure {
|
|
|
125
146
|
return;
|
|
126
147
|
}
|
|
127
148
|
if (data.length === 3) {
|
|
149
|
+
if (typeof data[1] !== "string") {
|
|
150
|
+
throw new SDJWTException("Invalid disclosure claim name");
|
|
151
|
+
}
|
|
152
|
+
if (data[1] === SD_DIGEST || data[1] === SD_LIST_KEY) {
|
|
153
|
+
throw new SDJWTException(
|
|
154
|
+
`Reserved disclosure claim name "${data[1]}" is not allowed`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
128
157
|
this.salt = data[0];
|
|
129
158
|
this.key = data[1];
|
|
130
159
|
this.value = data[2];
|
|
131
160
|
return;
|
|
132
161
|
}
|
|
133
|
-
throw new SDJWTException("Invalid disclosure data");
|
|
134
162
|
}
|
|
135
163
|
// We need to digest of the original encoded data.
|
|
136
164
|
// After decode process, we use JSON.stringify to encode the data.
|
|
@@ -204,10 +232,7 @@ var decodeJwt = (jwt) => {
|
|
|
204
232
|
var splitSdJwt = (sdjwt) => {
|
|
205
233
|
const [encodedJwt, ...encodedDisclosures] = sdjwt.split(SD_SEPARATOR);
|
|
206
234
|
if (encodedDisclosures.length === 0) {
|
|
207
|
-
|
|
208
|
-
jwt: encodedJwt,
|
|
209
|
-
disclosures: []
|
|
210
|
-
};
|
|
235
|
+
throw new SDJWTException("Invalid SD-JWT: missing SD-JWT separator");
|
|
211
236
|
}
|
|
212
237
|
const encodedKeyBindingJwt = encodedDisclosures.pop();
|
|
213
238
|
return {
|
|
@@ -220,10 +245,7 @@ var decodeSdJwt = (sdjwt, hasher) => __async(null, null, function* () {
|
|
|
220
245
|
const [encodedJwt, ...encodedDisclosures] = sdjwt.split(SD_SEPARATOR);
|
|
221
246
|
const jwt = decodeJwt(encodedJwt);
|
|
222
247
|
if (encodedDisclosures.length === 0) {
|
|
223
|
-
|
|
224
|
-
jwt,
|
|
225
|
-
disclosures: []
|
|
226
|
-
};
|
|
248
|
+
throw new SDJWTException("Invalid SD-JWT: missing SD-JWT separator");
|
|
227
249
|
}
|
|
228
250
|
const encodedKeyBindingJwt = encodedDisclosures.pop();
|
|
229
251
|
const kbJwt = encodedKeyBindingJwt ? decodeJwt(encodedKeyBindingJwt) : void 0;
|
|
@@ -243,10 +265,7 @@ var decodeSdJwtSync = (sdjwt, hasher) => {
|
|
|
243
265
|
const [encodedJwt, ...encodedDisclosures] = sdjwt.split(SD_SEPARATOR);
|
|
244
266
|
const jwt = decodeJwt(encodedJwt);
|
|
245
267
|
if (encodedDisclosures.length === 0) {
|
|
246
|
-
|
|
247
|
-
jwt,
|
|
248
|
-
disclosures: []
|
|
249
|
-
};
|
|
268
|
+
throw new SDJWTException("Invalid SD-JWT: missing SD-JWT separator");
|
|
250
269
|
}
|
|
251
270
|
const encodedKeyBindingJwt = encodedDisclosures.pop();
|
|
252
271
|
const kbJwt = encodedKeyBindingJwt ? decodeJwt(encodedKeyBindingJwt) : void 0;
|
|
@@ -275,7 +294,10 @@ var unpackArray = (arr, map, prefix = "", seenDigests) => {
|
|
|
275
294
|
arr.forEach((item, idx) => {
|
|
276
295
|
if (isRecord(item)) {
|
|
277
296
|
const hash = item[SD_LIST_KEY];
|
|
278
|
-
if (
|
|
297
|
+
if (SD_LIST_KEY in item) {
|
|
298
|
+
if (Object.keys(item).length !== 1 || typeof hash !== "string") {
|
|
299
|
+
throw new SDJWTException("Invalid array disclosure placeholder");
|
|
300
|
+
}
|
|
279
301
|
if (seenDigests) {
|
|
280
302
|
if (seenDigests.has(hash)) {
|
|
281
303
|
throw new SDJWTException(
|
|
@@ -286,6 +308,11 @@ var unpackArray = (arr, map, prefix = "", seenDigests) => {
|
|
|
286
308
|
}
|
|
287
309
|
const disclosed = map[hash];
|
|
288
310
|
if (disclosed) {
|
|
311
|
+
if (typeof disclosed.key === "string") {
|
|
312
|
+
throw new SDJWTException(
|
|
313
|
+
"Object-property disclosure cannot be used as an array element"
|
|
314
|
+
);
|
|
315
|
+
}
|
|
289
316
|
const presentKey = prefix ? `${prefix}.${idx}` : `${idx}`;
|
|
290
317
|
keys[presentKey] = hash;
|
|
291
318
|
const { unpackedObj, disclosureKeymap: disclosureKeys } = unpackObjInternal(disclosed.value, map, presentKey, seenDigests);
|
|
@@ -328,8 +355,12 @@ var unpackObjInternal = (obj, map, prefix = "", seenDigests) => {
|
|
|
328
355
|
}
|
|
329
356
|
const record = obj;
|
|
330
357
|
for (const key in record) {
|
|
358
|
+
if (prefix && key === "_sd_alg") {
|
|
359
|
+
throw new SDJWTException("Nested _sd_alg is not allowed");
|
|
360
|
+
}
|
|
331
361
|
if (key !== SD_DIGEST && key !== SD_LIST_KEY && typeof record[key] === "object") {
|
|
332
|
-
const
|
|
362
|
+
const escapedKey = encodePathSegment(key);
|
|
363
|
+
const newKey = prefix ? `${prefix}.${escapedKey}` : escapedKey;
|
|
333
364
|
const { unpackedObj: unpackedObj2, disclosureKeymap: disclosureKeys } = unpackObjInternal(record[key], map, newKey, seenDigests);
|
|
334
365
|
record[key] = unpackedObj2;
|
|
335
366
|
Object.assign(keys, disclosureKeys);
|
|
@@ -337,7 +368,12 @@ var unpackObjInternal = (obj, map, prefix = "", seenDigests) => {
|
|
|
337
368
|
}
|
|
338
369
|
const _a = record, { _sd } = _a, payload = __objRest(_a, ["_sd"]);
|
|
339
370
|
const claims = {};
|
|
340
|
-
if (_sd) {
|
|
371
|
+
if (_sd !== void 0) {
|
|
372
|
+
if (!Array.isArray(_sd) || !_sd.every((hash) => typeof hash === "string")) {
|
|
373
|
+
throw new SDJWTException(
|
|
374
|
+
"Invalid _sd claim: expected array of strings"
|
|
375
|
+
);
|
|
376
|
+
}
|
|
341
377
|
for (const hash of _sd) {
|
|
342
378
|
if (seenDigests) {
|
|
343
379
|
if (seenDigests.has(hash)) {
|
|
@@ -348,13 +384,24 @@ var unpackObjInternal = (obj, map, prefix = "", seenDigests) => {
|
|
|
348
384
|
seenDigests.add(hash);
|
|
349
385
|
}
|
|
350
386
|
const disclosed = map[hash];
|
|
351
|
-
if (disclosed
|
|
387
|
+
if (disclosed) {
|
|
388
|
+
if (typeof disclosed.key !== "string") {
|
|
389
|
+
throw new SDJWTException(
|
|
390
|
+
"Array disclosure cannot be used as an object property"
|
|
391
|
+
);
|
|
392
|
+
}
|
|
352
393
|
if (disclosed.key in payload) {
|
|
353
394
|
throw new SDJWTException(
|
|
354
395
|
`Disclosed claim name "${disclosed.key}" conflicts with existing payload key`
|
|
355
396
|
);
|
|
356
397
|
}
|
|
357
|
-
|
|
398
|
+
if (disclosed.key in claims) {
|
|
399
|
+
throw new SDJWTException(
|
|
400
|
+
`Disclosed claim name "${disclosed.key}" conflicts with another disclosure`
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
const escapedKey = encodePathSegment(disclosed.key);
|
|
404
|
+
const presentKey = prefix ? `${prefix}.${escapedKey}` : escapedKey;
|
|
358
405
|
keys[presentKey] = hash;
|
|
359
406
|
const { unpackedObj: unpackedObj2, disclosureKeymap: disclosureKeys } = unpackObjInternal(disclosed.value, map, presentKey, seenDigests);
|
|
360
407
|
claims[disclosed.key] = unpackedObj2;
|
|
@@ -372,6 +419,9 @@ var createHashMapping = (disclosures, hash) => __async(null, null, function* ()
|
|
|
372
419
|
for (let i = 0; i < disclosures.length; i++) {
|
|
373
420
|
const disclosure = disclosures[i];
|
|
374
421
|
const digest = yield disclosure.digest(hash);
|
|
422
|
+
if (digest in map) {
|
|
423
|
+
throw new SDJWTException("Duplicate disclosure digest detected");
|
|
424
|
+
}
|
|
375
425
|
map[digest] = disclosure;
|
|
376
426
|
}
|
|
377
427
|
return map;
|
|
@@ -381,20 +431,29 @@ var createHashMappingSync = (disclosures, hash) => {
|
|
|
381
431
|
for (let i = 0; i < disclosures.length; i++) {
|
|
382
432
|
const disclosure = disclosures[i];
|
|
383
433
|
const digest = disclosure.digestSync(hash);
|
|
434
|
+
if (digest in map) {
|
|
435
|
+
throw new SDJWTException("Duplicate disclosure digest detected");
|
|
436
|
+
}
|
|
384
437
|
map[digest] = disclosure;
|
|
385
438
|
}
|
|
386
439
|
return map;
|
|
387
440
|
};
|
|
388
|
-
var getSDAlgAndPayload = (SdJwtPayload) => {
|
|
441
|
+
var getSDAlgAndPayload = (SdJwtPayload, allowedAlgorithms = DEFAULT_SECURE_HASH_ALGORITHMS) => {
|
|
389
442
|
const _a = SdJwtPayload, { _sd_alg } = _a, payload = __objRest(_a, ["_sd_alg"]);
|
|
390
|
-
if (
|
|
443
|
+
if (_sd_alg === void 0) {
|
|
391
444
|
return { _sd_alg: "sha-256", payload };
|
|
392
445
|
}
|
|
446
|
+
if (typeof _sd_alg !== "string") {
|
|
447
|
+
throw new SDJWTException("Invalid _sd_alg: expected string");
|
|
448
|
+
}
|
|
393
449
|
if (!IANA_HASH_ALGORITHMS.includes(
|
|
394
450
|
_sd_alg
|
|
395
451
|
)) {
|
|
396
452
|
throw new SDJWTException(`Invalid _sd_alg: ${_sd_alg}`);
|
|
397
453
|
}
|
|
454
|
+
if (!allowedAlgorithms.includes(_sd_alg)) {
|
|
455
|
+
throw new SDJWTException(`Disallowed _sd_alg: ${_sd_alg}`);
|
|
456
|
+
}
|
|
398
457
|
return { _sd_alg, payload };
|
|
399
458
|
};
|
|
400
459
|
var unpack = (SdJwtPayload, disclosures, hasher) => __async(null, null, function* () {
|
|
@@ -503,6 +562,14 @@ var GeneralJSON = class _GeneralJSON {
|
|
|
503
562
|
if (!json.signatures[0]) {
|
|
504
563
|
throw new SDJWTException("Invalid JSON");
|
|
505
564
|
}
|
|
565
|
+
for (let index = 1; index < json.signatures.length; index++) {
|
|
566
|
+
const header = json.signatures[index].header;
|
|
567
|
+
if (header && ("disclosures" in header || "kb_jwt" in header)) {
|
|
568
|
+
throw new SDJWTException(
|
|
569
|
+
"disclosures and kb_jwt MUST only appear in the first unprotected header"
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
506
573
|
const disclosures = (_b = (_a = json.signatures[0].header) == null ? void 0 : _a.disclosures) != null ? _b : [];
|
|
507
574
|
const kb_jwt = (_c = json.signatures[0].header) == null ? void 0 : _c.kb_jwt;
|
|
508
575
|
return new _GeneralJSON({
|
|
@@ -575,6 +642,49 @@ var GeneralJSON = class _GeneralJSON {
|
|
|
575
642
|
};
|
|
576
643
|
|
|
577
644
|
// src/jwt.ts
|
|
645
|
+
var isStringArray = (value) => Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
646
|
+
var validateNumericDate = (payload, claim) => {
|
|
647
|
+
const value = payload[claim];
|
|
648
|
+
if (value === void 0) return void 0;
|
|
649
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
650
|
+
throw new SDJWTException(`Verify Error: JWT ${claim} must be a number`);
|
|
651
|
+
}
|
|
652
|
+
return value;
|
|
653
|
+
};
|
|
654
|
+
var getAudiences = (audience) => {
|
|
655
|
+
if (typeof audience === "string") return [audience];
|
|
656
|
+
if (isStringArray(audience)) return audience;
|
|
657
|
+
return void 0;
|
|
658
|
+
};
|
|
659
|
+
var validateAudience = (payload, expectedAudience) => {
|
|
660
|
+
if (expectedAudience === void 0) return;
|
|
661
|
+
const expectedAudiences = Array.isArray(expectedAudience) ? expectedAudience : [expectedAudience];
|
|
662
|
+
const audiences = getAudiences(payload.aud);
|
|
663
|
+
if (!audiences || !expectedAudiences.some((expected) => audiences.includes(expected))) {
|
|
664
|
+
throw new SDJWTException("Verify Error: Invalid audience");
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
var validateJwtPayload = (payload, options) => {
|
|
668
|
+
var _a;
|
|
669
|
+
if (!payload) {
|
|
670
|
+
throw new SDJWTException("Verify Error: JWT payload is missing");
|
|
671
|
+
}
|
|
672
|
+
const skew = (options == null ? void 0 : options.skewSeconds) ? options.skewSeconds : 0;
|
|
673
|
+
const currentDate = (_a = options == null ? void 0 : options.currentDate) != null ? _a : Math.floor(Date.now() / 1e3);
|
|
674
|
+
const iat = validateNumericDate(payload, "iat");
|
|
675
|
+
const nbf = validateNumericDate(payload, "nbf");
|
|
676
|
+
const exp = validateNumericDate(payload, "exp");
|
|
677
|
+
if (iat !== void 0 && iat - skew > currentDate) {
|
|
678
|
+
throw new SDJWTException("Verify Error: JWT is not yet valid");
|
|
679
|
+
}
|
|
680
|
+
if (nbf !== void 0 && nbf - skew > currentDate) {
|
|
681
|
+
throw new SDJWTException("Verify Error: JWT is not yet valid");
|
|
682
|
+
}
|
|
683
|
+
if (exp !== void 0 && exp + skew <= currentDate) {
|
|
684
|
+
throw new SDJWTException("Verify Error: JWT is expired");
|
|
685
|
+
}
|
|
686
|
+
validateAudience(payload, options == null ? void 0 : options.expectedAudience);
|
|
687
|
+
};
|
|
578
688
|
var Jwt = class _Jwt {
|
|
579
689
|
constructor(data) {
|
|
580
690
|
this.header = data == null ? void 0 : data.header;
|
|
@@ -625,6 +735,9 @@ var Jwt = class _Jwt {
|
|
|
625
735
|
}
|
|
626
736
|
sign(signer) {
|
|
627
737
|
return __async(this, null, function* () {
|
|
738
|
+
if (!this.header || this.header.alg === "none") {
|
|
739
|
+
throw new SDJWTException('Sign Error: alg "none" is not allowed');
|
|
740
|
+
}
|
|
628
741
|
const data = this.getUnsignedToken();
|
|
629
742
|
this.signature = yield signer(data);
|
|
630
743
|
return this.encodeJwt();
|
|
@@ -653,20 +766,16 @@ var Jwt = class _Jwt {
|
|
|
653
766
|
*/
|
|
654
767
|
verify(verifier, options) {
|
|
655
768
|
return __async(this, null, function* () {
|
|
656
|
-
var _a
|
|
657
|
-
const
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
const nbf = (_c = this.payload) == null ? void 0 : _c.nbf;
|
|
661
|
-
const exp = (_d = this.payload) == null ? void 0 : _d.exp;
|
|
662
|
-
if (typeof iat === "number" && iat - skew > currentDate) {
|
|
663
|
-
throw new SDJWTException("Verify Error: JWT is not yet valid");
|
|
769
|
+
var _a;
|
|
770
|
+
const alg = (_a = this.header) == null ? void 0 : _a.alg;
|
|
771
|
+
if (typeof alg !== "string" || alg === "none") {
|
|
772
|
+
throw new SDJWTException('Verify Error: alg "none" is not allowed');
|
|
664
773
|
}
|
|
665
|
-
if (
|
|
666
|
-
throw new SDJWTException(
|
|
774
|
+
if ((options == null ? void 0 : options.allowedIssuerAlgorithms) && !options.allowedIssuerAlgorithms.includes(alg)) {
|
|
775
|
+
throw new SDJWTException(`Verify Error: Disallowed alg ${alg}`);
|
|
667
776
|
}
|
|
668
|
-
if (
|
|
669
|
-
|
|
777
|
+
if (!(options == null ? void 0 : options.skipJwtClaimValidation)) {
|
|
778
|
+
validateJwtPayload(this.payload, options);
|
|
670
779
|
}
|
|
671
780
|
if (!this.signature) {
|
|
672
781
|
throw new SDJWTException("Verify Error: no signature in JWT");
|
|
@@ -687,16 +796,31 @@ var KBJwt = class _KBJwt extends Jwt {
|
|
|
687
796
|
// the type unknown is not good, but we don't know at this point how to get the public key of the signer, this is defined in the kbVerifier
|
|
688
797
|
verifyKB(values) {
|
|
689
798
|
return __async(this, null, function* () {
|
|
799
|
+
var _a, _b, _c, _d;
|
|
690
800
|
if (!this.header || !this.payload || !this.signature) {
|
|
691
801
|
throw new SDJWTException("Verify Error: Invalid JWT");
|
|
692
802
|
}
|
|
693
|
-
if (
|
|
694
|
-
!(this.payload.sd_hash || "_sd_hash" in this.payload && this.payload._sd_hash)) {
|
|
803
|
+
if (typeof this.header.alg !== "string" || this.header.alg === "none" || typeof this.header.typ !== "string" || this.header.typ !== KB_JWT_TYP || typeof this.payload.iat !== "number" || typeof this.payload.aud !== "string" || typeof this.payload.nonce !== "string" || typeof this.payload.sd_hash !== "string" || this.payload.sd_hash.length === 0) {
|
|
695
804
|
throw new SDJWTException("Invalid Key Binding Jwt");
|
|
696
805
|
}
|
|
697
806
|
if (this.payload.nonce !== values.nonce) {
|
|
698
807
|
throw new SDJWTException("Verify Error: Invalid Nonce");
|
|
699
808
|
}
|
|
809
|
+
if (((_a = values.options) == null ? void 0 : _a.expectedKeyBindingAudience) !== void 0) {
|
|
810
|
+
const expectedAudiences = Array.isArray(
|
|
811
|
+
values.options.expectedKeyBindingAudience
|
|
812
|
+
) ? values.options.expectedKeyBindingAudience : [values.options.expectedKeyBindingAudience];
|
|
813
|
+
if (!expectedAudiences.includes(this.payload.aud)) {
|
|
814
|
+
throw new SDJWTException("Verify Error: Invalid Key Binding audience");
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
if (((_b = values.options) == null ? void 0 : _b.keyBindingMaxAgeSeconds) !== void 0) {
|
|
818
|
+
const currentDate = (_c = values.options.currentDate) != null ? _c : Math.floor(Date.now() / 1e3);
|
|
819
|
+
const skew = (_d = values.options.skewSeconds) != null ? _d : 0;
|
|
820
|
+
if (this.payload.iat + values.options.keyBindingMaxAgeSeconds + skew < currentDate) {
|
|
821
|
+
throw new SDJWTException("Verify Error: Key Binding JWT is too old");
|
|
822
|
+
}
|
|
823
|
+
}
|
|
700
824
|
yield this.verify(
|
|
701
825
|
(data, sig) => values.verifier(data, sig, values.payload),
|
|
702
826
|
values.options
|
|
@@ -772,16 +896,17 @@ var presentSync = (sdJwt, presentFrame, hasher) => {
|
|
|
772
896
|
kbJwt != null ? kbJwt : ""
|
|
773
897
|
].join(SD_SEPARATOR);
|
|
774
898
|
};
|
|
775
|
-
var transformPresentationFrame = (obj, prefix =
|
|
899
|
+
var transformPresentationFrame = (obj, prefix = []) => {
|
|
776
900
|
return Object.entries(obj).reduce((acc, [key, value]) => {
|
|
777
|
-
const newPrefix = prefix
|
|
901
|
+
const newPrefix = [...prefix, key];
|
|
902
|
+
const encodedPrefix = encodePath(newPrefix);
|
|
778
903
|
if (typeof value === "boolean") {
|
|
779
904
|
if (value) {
|
|
780
|
-
acc.push(
|
|
905
|
+
acc.push(encodedPrefix);
|
|
781
906
|
}
|
|
782
907
|
} else if (typeof value === "object" && value !== null) {
|
|
783
908
|
acc.push(
|
|
784
|
-
|
|
909
|
+
encodedPrefix,
|
|
785
910
|
...transformPresentationFrame(
|
|
786
911
|
value,
|
|
787
912
|
newPrefix
|
|
@@ -832,6 +957,23 @@ var selectDisclosures = (payload, disclosures, presentationFrame) => {
|
|
|
832
957
|
};
|
|
833
958
|
|
|
834
959
|
// src/sdjwt.ts
|
|
960
|
+
var createDisclosureSalt = (saltGenerator, seenSalts) => __async(null, null, function* () {
|
|
961
|
+
const salt = yield saltGenerator(16);
|
|
962
|
+
if (typeof salt !== "string") {
|
|
963
|
+
throw new SDJWTException("SaltGenerator must return a string");
|
|
964
|
+
}
|
|
965
|
+
if (seenSalts.has(salt)) {
|
|
966
|
+
throw new SDJWTException("Duplicate disclosure salt detected");
|
|
967
|
+
}
|
|
968
|
+
seenSalts.add(salt);
|
|
969
|
+
return salt;
|
|
970
|
+
});
|
|
971
|
+
var addDisclosureDigest = (digest, seenDigests) => {
|
|
972
|
+
if (seenDigests.has(digest)) {
|
|
973
|
+
throw new SDJWTException("Duplicate disclosure digest detected");
|
|
974
|
+
}
|
|
975
|
+
seenDigests.add(digest);
|
|
976
|
+
};
|
|
835
977
|
var SDJwt = class _SDJwt {
|
|
836
978
|
constructor(data) {
|
|
837
979
|
this.jwt = data == null ? void 0 : data.jwt;
|
|
@@ -841,16 +983,13 @@ var SDJwt = class _SDJwt {
|
|
|
841
983
|
static decodeSDJwt(sdjwt, hasher) {
|
|
842
984
|
return __async(this, null, function* () {
|
|
843
985
|
const [encodedJwt, ...encodedDisclosures] = sdjwt.split(SD_SEPARATOR);
|
|
986
|
+
if (encodedDisclosures.length === 0) {
|
|
987
|
+
throw new SDJWTException("Invalid SD-JWT: missing SD-JWT separator");
|
|
988
|
+
}
|
|
844
989
|
const jwt = Jwt.fromEncode(encodedJwt);
|
|
845
990
|
if (!jwt.payload) {
|
|
846
991
|
throw new Error("Payload is undefined on the JWT. Invalid state reached");
|
|
847
992
|
}
|
|
848
|
-
if (encodedDisclosures.length === 0) {
|
|
849
|
-
return {
|
|
850
|
-
jwt,
|
|
851
|
-
disclosures: []
|
|
852
|
-
};
|
|
853
|
-
}
|
|
854
993
|
const encodedKeyBindingJwt = encodedDisclosures.pop();
|
|
855
994
|
const kbJwt = encodedKeyBindingJwt ? KBJwt.fromKBEncode(encodedKeyBindingJwt) : void 0;
|
|
856
995
|
const { _sd_alg } = getSDAlgAndPayload(jwt.payload);
|
|
@@ -964,7 +1103,8 @@ var listKeys = (obj, prefix = "") => {
|
|
|
964
1103
|
const keys = [];
|
|
965
1104
|
for (const key in obj) {
|
|
966
1105
|
if (obj[key] === void 0) continue;
|
|
967
|
-
const
|
|
1106
|
+
const escapedKey = encodePathSegment(key);
|
|
1107
|
+
const newKey = prefix ? `${prefix}.${escapedKey}` : escapedKey;
|
|
968
1108
|
keys.push(newKey);
|
|
969
1109
|
const value = obj[key];
|
|
970
1110
|
if (value && typeof value === "object") {
|
|
@@ -973,7 +1113,7 @@ var listKeys = (obj, prefix = "") => {
|
|
|
973
1113
|
}
|
|
974
1114
|
return keys;
|
|
975
1115
|
};
|
|
976
|
-
var pack = (
|
|
1116
|
+
var pack = (_0, _1, _2, _3, ..._4) => __async(null, [_0, _1, _2, _3, ..._4], function* (claims, disclosureFrame, hash, saltGenerator, seenSalts = /* @__PURE__ */ new Set(), seenDigests = /* @__PURE__ */ new Set()) {
|
|
977
1117
|
var _a, _b;
|
|
978
1118
|
if (!disclosureFrame) {
|
|
979
1119
|
return {
|
|
@@ -994,7 +1134,9 @@ var pack = (claims, disclosureFrame, hash, saltGenerator) => __async(null, null,
|
|
|
994
1134
|
claims[idx],
|
|
995
1135
|
disclosureFrame[idx],
|
|
996
1136
|
hash,
|
|
997
|
-
saltGenerator
|
|
1137
|
+
saltGenerator,
|
|
1138
|
+
seenSalts,
|
|
1139
|
+
seenDigests
|
|
998
1140
|
);
|
|
999
1141
|
recursivePackedClaims2[idx] = packed.packedClaims;
|
|
1000
1142
|
disclosures2.push(...packed.disclosures);
|
|
@@ -1003,9 +1145,10 @@ var pack = (claims, disclosureFrame, hash, saltGenerator) => __async(null, null,
|
|
|
1003
1145
|
for (let i = 0; i < claims.length; i++) {
|
|
1004
1146
|
const claim = recursivePackedClaims2[i] ? recursivePackedClaims2[i] : claims[i];
|
|
1005
1147
|
if (sd.includes(i)) {
|
|
1006
|
-
const salt = yield saltGenerator
|
|
1148
|
+
const salt = yield createDisclosureSalt(saltGenerator, seenSalts);
|
|
1007
1149
|
const disclosure = new Disclosure([salt, claim]);
|
|
1008
1150
|
const digest = yield disclosure.digest(hash);
|
|
1151
|
+
addDisclosureDigest(digest, seenDigests);
|
|
1009
1152
|
packedClaims2.push({ [SD_LIST_KEY]: digest });
|
|
1010
1153
|
disclosures2.push(disclosure);
|
|
1011
1154
|
} else {
|
|
@@ -1014,6 +1157,7 @@ var pack = (claims, disclosureFrame, hash, saltGenerator) => __async(null, null,
|
|
|
1014
1157
|
}
|
|
1015
1158
|
for (let j = 0; j < decoyCount; j++) {
|
|
1016
1159
|
const decoyDigest = yield createDecoy(hash, saltGenerator);
|
|
1160
|
+
addDisclosureDigest(decoyDigest, seenDigests);
|
|
1017
1161
|
packedClaims2.push({ [SD_LIST_KEY]: decoyDigest });
|
|
1018
1162
|
}
|
|
1019
1163
|
return { packedClaims: packedClaims2, disclosures: disclosures2 };
|
|
@@ -1028,7 +1172,9 @@ var pack = (claims, disclosureFrame, hash, saltGenerator) => __async(null, null,
|
|
|
1028
1172
|
claims[key],
|
|
1029
1173
|
disclosureFrame[key],
|
|
1030
1174
|
hash,
|
|
1031
|
-
saltGenerator
|
|
1175
|
+
saltGenerator,
|
|
1176
|
+
seenSalts,
|
|
1177
|
+
seenDigests
|
|
1032
1178
|
);
|
|
1033
1179
|
recursivePackedClaims[key] = packed.packedClaims;
|
|
1034
1180
|
disclosures.push(...packed.disclosures);
|
|
@@ -1038,9 +1184,10 @@ var pack = (claims, disclosureFrame, hash, saltGenerator) => __async(null, null,
|
|
|
1038
1184
|
for (const key in claims) {
|
|
1039
1185
|
const claim = recursivePackedClaims[key] ? recursivePackedClaims[key] : claims[key];
|
|
1040
1186
|
if (sd.includes(key)) {
|
|
1041
|
-
const salt = yield saltGenerator
|
|
1187
|
+
const salt = yield createDisclosureSalt(saltGenerator, seenSalts);
|
|
1042
1188
|
const disclosure = new Disclosure([salt, key, claim]);
|
|
1043
1189
|
const digest = yield disclosure.digest(hash);
|
|
1190
|
+
addDisclosureDigest(digest, seenDigests);
|
|
1044
1191
|
_sd.push(digest);
|
|
1045
1192
|
disclosures.push(disclosure);
|
|
1046
1193
|
} else {
|
|
@@ -1049,6 +1196,7 @@ var pack = (claims, disclosureFrame, hash, saltGenerator) => __async(null, null,
|
|
|
1049
1196
|
}
|
|
1050
1197
|
for (let j = 0; j < decoyCount; j++) {
|
|
1051
1198
|
const decoyDigest = yield createDecoy(hash, saltGenerator);
|
|
1199
|
+
addDisclosureDigest(decoyDigest, seenDigests);
|
|
1052
1200
|
_sd.push(decoyDigest);
|
|
1053
1201
|
}
|
|
1054
1202
|
if (_sd.length > 0) {
|
|
@@ -1078,12 +1226,19 @@ function validateReservedFieldsInternal(payload) {
|
|
|
1078
1226
|
var _SDJwtInstance = class _SDJwtInstance {
|
|
1079
1227
|
constructor(userConfig) {
|
|
1080
1228
|
this.userConfig = {};
|
|
1229
|
+
var _a;
|
|
1081
1230
|
if (userConfig) {
|
|
1082
1231
|
if (userConfig.hashAlg && !IANA_HASH_ALGORITHMS.includes(userConfig.hashAlg)) {
|
|
1083
1232
|
throw new SDJWTException(
|
|
1084
1233
|
`Invalid hash algorithm: ${userConfig.hashAlg}`
|
|
1085
1234
|
);
|
|
1086
1235
|
}
|
|
1236
|
+
const allowedDisclosureHashAlgorithms = (_a = userConfig.allowedDisclosureHashAlgorithms) != null ? _a : DEFAULT_SECURE_HASH_ALGORITHMS;
|
|
1237
|
+
if (userConfig.hashAlg && !allowedDisclosureHashAlgorithms.includes(userConfig.hashAlg)) {
|
|
1238
|
+
throw new SDJWTException(
|
|
1239
|
+
`Disallowed hash algorithm: ${userConfig.hashAlg}`
|
|
1240
|
+
);
|
|
1241
|
+
}
|
|
1087
1242
|
this.userConfig = userConfig;
|
|
1088
1243
|
}
|
|
1089
1244
|
}
|
|
@@ -1126,7 +1281,7 @@ var _SDJwtInstance = class _SDJwtInstance {
|
|
|
1126
1281
|
}
|
|
1127
1282
|
issue(payload, disclosureFrame, options) {
|
|
1128
1283
|
return __async(this, null, function* () {
|
|
1129
|
-
var _a, _b;
|
|
1284
|
+
var _a, _b, _c;
|
|
1130
1285
|
if (!this.userConfig.hasher) {
|
|
1131
1286
|
throw new SDJWTException("Hasher not found");
|
|
1132
1287
|
}
|
|
@@ -1136,10 +1291,17 @@ var _SDJwtInstance = class _SDJwtInstance {
|
|
|
1136
1291
|
if (!this.userConfig.signAlg) {
|
|
1137
1292
|
throw new SDJWTException("sign alogrithm not specified");
|
|
1138
1293
|
}
|
|
1294
|
+
if (this.userConfig.signAlg === "none") {
|
|
1295
|
+
throw new SDJWTException('sign algorithm "none" is not allowed');
|
|
1296
|
+
}
|
|
1139
1297
|
this.validateReservedFields(payload);
|
|
1140
1298
|
this.validateDisclosureFrame(disclosureFrame);
|
|
1141
1299
|
const hasher = this.userConfig.hasher;
|
|
1142
1300
|
const hashAlg = (_a = this.userConfig.hashAlg) != null ? _a : _SDJwtInstance.DEFAULT_hashAlg;
|
|
1301
|
+
const allowedDisclosureHashAlgorithms = (_b = this.userConfig.allowedDisclosureHashAlgorithms) != null ? _b : DEFAULT_SECURE_HASH_ALGORITHMS;
|
|
1302
|
+
if (!allowedDisclosureHashAlgorithms.includes(hashAlg)) {
|
|
1303
|
+
throw new SDJWTException(`Disallowed hash algorithm: ${hashAlg}`);
|
|
1304
|
+
}
|
|
1143
1305
|
const { packedClaims, disclosures } = yield pack(
|
|
1144
1306
|
payload,
|
|
1145
1307
|
disclosureFrame,
|
|
@@ -1147,7 +1309,7 @@ var _SDJwtInstance = class _SDJwtInstance {
|
|
|
1147
1309
|
this.userConfig.saltGenerator
|
|
1148
1310
|
);
|
|
1149
1311
|
const alg = this.userConfig.signAlg;
|
|
1150
|
-
const OptionHeader = (
|
|
1312
|
+
const OptionHeader = (_c = options == null ? void 0 : options.header) != null ? _c : {};
|
|
1151
1313
|
const CustomHeader = this.userConfig.omitTyp ? OptionHeader : __spreadValues({ typ: this.type }, OptionHeader);
|
|
1152
1314
|
const header = __spreadProps(__spreadValues({}, CustomHeader), { alg });
|
|
1153
1315
|
const jwt = new Jwt({
|
|
@@ -1183,6 +1345,9 @@ var _SDJwtInstance = class _SDJwtInstance {
|
|
|
1183
1345
|
}
|
|
1184
1346
|
const hasher = this.userConfig.hasher;
|
|
1185
1347
|
const sdjwt = yield SDJwt.fromEncode(encodedSDJwt, hasher);
|
|
1348
|
+
if (sdjwt.kbJwt) {
|
|
1349
|
+
throw new SDJWTException("Holder cannot present an SD-JWT with KB-JWT");
|
|
1350
|
+
}
|
|
1186
1351
|
if (!((_a = sdjwt.jwt) == null ? void 0 : _a.payload)) throw new SDJWTException("Payload not found");
|
|
1187
1352
|
const presentSdJwtWithoutKb = yield sdjwt.present(
|
|
1188
1353
|
presentationFrame,
|
|
@@ -1325,10 +1490,13 @@ var _SDJwtInstance = class _SDJwtInstance {
|
|
|
1325
1490
|
}
|
|
1326
1491
|
if (sdjwt == null ? void 0 : sdjwt.jwt) {
|
|
1327
1492
|
try {
|
|
1328
|
-
const result = yield this.VerifyJwt(sdjwt.jwt, options)
|
|
1493
|
+
const result = yield this.VerifyJwt(sdjwt.jwt, __spreadProps(__spreadValues({}, options), {
|
|
1494
|
+
skipJwtClaimValidation: true
|
|
1495
|
+
}));
|
|
1329
1496
|
header = result.header;
|
|
1330
1497
|
const claims = yield sdjwt.getClaims(hasher);
|
|
1331
1498
|
payload = claims;
|
|
1499
|
+
validateJwtPayload(payload, options);
|
|
1332
1500
|
} catch (e) {
|
|
1333
1501
|
const error = ensureError(e);
|
|
1334
1502
|
const code = exceptionToCode(error);
|
|
@@ -1454,9 +1622,12 @@ var _SDJwtInstance = class _SDJwtInstance {
|
|
|
1454
1622
|
if (!sdjwt.jwt) {
|
|
1455
1623
|
throw new SDJWTException("Invalid SD JWT");
|
|
1456
1624
|
}
|
|
1457
|
-
const verifiedPayloads = yield this.VerifyJwt(sdjwt.jwt, options)
|
|
1625
|
+
const verifiedPayloads = yield this.VerifyJwt(sdjwt.jwt, __spreadProps(__spreadValues({}, options), {
|
|
1626
|
+
skipJwtClaimValidation: true
|
|
1627
|
+
}));
|
|
1458
1628
|
const claims = yield sdjwt.getClaims(hasher);
|
|
1459
1629
|
validateReservedFieldsInternal(claims);
|
|
1630
|
+
validateJwtPayload(claims, options);
|
|
1460
1631
|
return { payload: claims, header: verifiedPayloads.header };
|
|
1461
1632
|
});
|
|
1462
1633
|
}
|
|
@@ -1511,12 +1682,19 @@ var SDJwtInstance = _SDJwtInstance;
|
|
|
1511
1682
|
var SDJwtGeneralJSONInstance = class {
|
|
1512
1683
|
constructor(userConfig) {
|
|
1513
1684
|
this.userConfig = {};
|
|
1685
|
+
var _a;
|
|
1514
1686
|
if (userConfig) {
|
|
1515
1687
|
if (userConfig.hashAlg && !IANA_HASH_ALGORITHMS.includes(userConfig.hashAlg)) {
|
|
1516
1688
|
throw new SDJWTException(
|
|
1517
1689
|
`Invalid hash algorithm: ${userConfig.hashAlg}`
|
|
1518
1690
|
);
|
|
1519
1691
|
}
|
|
1692
|
+
const allowedDisclosureHashAlgorithms = (_a = userConfig.allowedDisclosureHashAlgorithms) != null ? _a : DEFAULT_SECURE_HASH_ALGORITHMS;
|
|
1693
|
+
if (userConfig.hashAlg && !allowedDisclosureHashAlgorithms.includes(userConfig.hashAlg)) {
|
|
1694
|
+
throw new SDJWTException(
|
|
1695
|
+
`Disallowed hash algorithm: ${userConfig.hashAlg}`
|
|
1696
|
+
);
|
|
1697
|
+
}
|
|
1520
1698
|
this.userConfig = userConfig;
|
|
1521
1699
|
}
|
|
1522
1700
|
}
|
|
@@ -1610,6 +1788,9 @@ var SDJwtGeneralJSONInstance = class {
|
|
|
1610
1788
|
const hasher = this.userConfig.hasher;
|
|
1611
1789
|
const encodedSDJwt = generalJSON.toEncoded(0);
|
|
1612
1790
|
const sdjwt = yield SDJwt.fromEncode(encodedSDJwt, hasher);
|
|
1791
|
+
if (sdjwt.kbJwt) {
|
|
1792
|
+
throw new SDJWTException("Holder cannot present an SD-JWT with KB-JWT");
|
|
1793
|
+
}
|
|
1613
1794
|
if (!((_a = sdjwt.jwt) == null ? void 0 : _a.payload)) throw new SDJWTException("Payload not found");
|
|
1614
1795
|
const disclosures = yield sdjwt.getPresentDisclosures(
|
|
1615
1796
|
presentationFrame,
|
|
@@ -1649,7 +1830,7 @@ var SDJwtGeneralJSONInstance = class {
|
|
|
1649
1830
|
throw new SDJWTException("Hasher not found");
|
|
1650
1831
|
}
|
|
1651
1832
|
const hasher = this.userConfig.hasher;
|
|
1652
|
-
const { payload, headers } = yield this.validate(generalJSON);
|
|
1833
|
+
const { payload, headers } = yield this.validate(generalJSON, options);
|
|
1653
1834
|
const encodedSDJwt = generalJSON.toEncoded(0);
|
|
1654
1835
|
const sdjwt = yield SDJwt.fromEncode(encodedSDJwt, hasher);
|
|
1655
1836
|
if (!((_a = sdjwt.jwt) == null ? void 0 : _a.payload)) {
|
|
@@ -1715,7 +1896,7 @@ var SDJwtGeneralJSONInstance = class {
|
|
|
1715
1896
|
}
|
|
1716
1897
|
// This function is for validating the SD JWT
|
|
1717
1898
|
// Just checking signature and return its the claims
|
|
1718
|
-
validate(generalJSON) {
|
|
1899
|
+
validate(generalJSON, options) {
|
|
1719
1900
|
return __async(this, null, function* () {
|
|
1720
1901
|
if (!this.userConfig.hasher) {
|
|
1721
1902
|
throw new SDJWTException("Hasher not found");
|
|
@@ -1731,9 +1912,21 @@ var SDJwtGeneralJSONInstance = class {
|
|
|
1731
1912
|
const { protected: encodedHeader, signature } = s;
|
|
1732
1913
|
const verified2 = yield verifier(
|
|
1733
1914
|
`${encodedHeader}.${payload}`,
|
|
1734
|
-
signature
|
|
1915
|
+
signature,
|
|
1916
|
+
__spreadProps(__spreadValues({}, options), { skipJwtClaimValidation: true })
|
|
1917
|
+
);
|
|
1918
|
+
const header = decodeBase64urlJsonStrict(
|
|
1919
|
+
encodedHeader,
|
|
1920
|
+
"Invalid JWT"
|
|
1735
1921
|
);
|
|
1736
|
-
|
|
1922
|
+
if (typeof header.alg !== "string" || header.alg === "none") {
|
|
1923
|
+
throw new SDJWTException('Verify Error: alg "none" is not allowed');
|
|
1924
|
+
}
|
|
1925
|
+
if ((options == null ? void 0 : options.allowedIssuerAlgorithms) && !options.allowedIssuerAlgorithms.includes(header.alg)) {
|
|
1926
|
+
throw new SDJWTException(
|
|
1927
|
+
`Verify Error: Disallowed alg ${header.alg}`
|
|
1928
|
+
);
|
|
1929
|
+
}
|
|
1737
1930
|
return { verified: verified2, header };
|
|
1738
1931
|
}))
|
|
1739
1932
|
);
|
|
@@ -1748,6 +1941,7 @@ var SDJwtGeneralJSONInstance = class {
|
|
|
1748
1941
|
}
|
|
1749
1942
|
const claims = yield sdjwt.getClaims(hasher);
|
|
1750
1943
|
validateReservedFieldsInternal(claims);
|
|
1944
|
+
validateJwtPayload(claims, options);
|
|
1751
1945
|
return { payload: claims, headers: results.map((r) => r.header) };
|
|
1752
1946
|
});
|
|
1753
1947
|
}
|
|
@@ -1793,6 +1987,7 @@ var SDJwtGeneralJSONInstance = class {
|
|
|
1793
1987
|
};
|
|
1794
1988
|
SDJwtGeneralJSONInstance.DEFAULT_hashAlg = "sha-256";
|
|
1795
1989
|
export {
|
|
1990
|
+
DEFAULT_SECURE_HASH_ALGORITHMS,
|
|
1796
1991
|
Disclosure,
|
|
1797
1992
|
FlattenJSON,
|
|
1798
1993
|
GeneralJSON,
|
|
@@ -1818,6 +2013,8 @@ export {
|
|
|
1818
2013
|
decodeJwt,
|
|
1819
2014
|
decodeSdJwt,
|
|
1820
2015
|
decodeSdJwtSync,
|
|
2016
|
+
encodePath,
|
|
2017
|
+
encodePathSegment,
|
|
1821
2018
|
ensureError,
|
|
1822
2019
|
getClaims,
|
|
1823
2020
|
getClaimsSync,
|
|
@@ -1834,5 +2031,6 @@ export {
|
|
|
1834
2031
|
uint8ArrayToBase64Url,
|
|
1835
2032
|
unpack,
|
|
1836
2033
|
unpackObj,
|
|
1837
|
-
unpackSync
|
|
2034
|
+
unpackSync,
|
|
2035
|
+
validateJwtPayload
|
|
1838
2036
|
};
|