@sd-jwt/core 0.19.1-next.12 → 0.19.1-next.13
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 +11 -9
- package/dist/index.d.ts +11 -9
- package/dist/index.js +79 -19
- package/dist/index.mjs +79 -19
- package/package.json +2 -2
- package/src/decode/decode.ts +14 -4
- package/src/index.ts +66 -18
- package/src/test/index.spec.ts +58 -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/dist/index.d.mts
CHANGED
|
@@ -548,11 +548,12 @@ declare class SDJwtInstance<ExtendedPayload extends SdJwtPayload, T = unknown> {
|
|
|
548
548
|
header?: object;
|
|
549
549
|
}): Promise<SDJWTCompact>;
|
|
550
550
|
/**
|
|
551
|
-
* Validates if the
|
|
552
|
-
* @param
|
|
551
|
+
* Validates if the payload contains any reserved claim names. If so it will throw an error.
|
|
552
|
+
* @param payload
|
|
553
553
|
* @returns
|
|
554
554
|
*/
|
|
555
|
-
protected validateReservedFields<T extends ExtendedPayload>(
|
|
555
|
+
protected validateReservedFields<T extends ExtendedPayload>(payload: T): void;
|
|
556
|
+
protected validateDisclosureFrame<T extends ExtendedPayload>(_disclosureFrame?: DisclosureFrame<T>): void;
|
|
556
557
|
present<T extends Record<string, unknown>>(encodedSDJwt: string, presentationFrame?: PresentationFrame<T>, options?: {
|
|
557
558
|
kb?: KBOptions;
|
|
558
559
|
}): Promise<SDJWTCompact>;
|
|
@@ -621,21 +622,22 @@ declare class SDJwtGeneralJSONInstance<ExtendedPayload extends SdJwtPayload> {
|
|
|
621
622
|
}>;
|
|
622
623
|
}): Promise<GeneralJSON>;
|
|
623
624
|
/**
|
|
624
|
-
* Validates if the
|
|
625
|
-
* @param
|
|
625
|
+
* Validates if the payload contains any reserved claim names. If so it will throw an error.
|
|
626
|
+
* @param payload
|
|
626
627
|
* @returns
|
|
627
628
|
*/
|
|
628
|
-
protected validateReservedFields<T extends ExtendedPayload>(
|
|
629
|
+
protected validateReservedFields<T extends ExtendedPayload>(payload: T): void;
|
|
630
|
+
protected validateDisclosureFrame<T extends ExtendedPayload>(_disclosureFrame?: DisclosureFrame<T>): void;
|
|
629
631
|
present<T extends Record<string, unknown>>(generalJSON: GeneralJSON, presentationFrame?: PresentationFrame<T>, options?: {
|
|
630
632
|
kb?: KBOptions;
|
|
631
633
|
}): Promise<GeneralJSON>;
|
|
632
634
|
verify(generalJSON: GeneralJSON, options?: VerifierOptions): Promise<{
|
|
633
635
|
payload: ExtendedPayload;
|
|
634
|
-
headers:
|
|
636
|
+
headers: unknown[];
|
|
635
637
|
kb?: undefined;
|
|
636
638
|
} | {
|
|
637
639
|
payload: ExtendedPayload;
|
|
638
|
-
headers:
|
|
640
|
+
headers: unknown[];
|
|
639
641
|
kb: {
|
|
640
642
|
payload: kbPayload;
|
|
641
643
|
header: kbHeader;
|
|
@@ -644,7 +646,7 @@ declare class SDJwtGeneralJSONInstance<ExtendedPayload extends SdJwtPayload> {
|
|
|
644
646
|
private calculateSDHash;
|
|
645
647
|
validate(generalJSON: GeneralJSON): Promise<{
|
|
646
648
|
payload: ExtendedPayload;
|
|
647
|
-
headers:
|
|
649
|
+
headers: unknown[];
|
|
648
650
|
}>;
|
|
649
651
|
config(newConfig: SDJWTConfig): void;
|
|
650
652
|
encode(sdJwt: GeneralJSON, index: number): SDJWTCompact;
|
package/dist/index.d.ts
CHANGED
|
@@ -548,11 +548,12 @@ declare class SDJwtInstance<ExtendedPayload extends SdJwtPayload, T = unknown> {
|
|
|
548
548
|
header?: object;
|
|
549
549
|
}): Promise<SDJWTCompact>;
|
|
550
550
|
/**
|
|
551
|
-
* Validates if the
|
|
552
|
-
* @param
|
|
551
|
+
* Validates if the payload contains any reserved claim names. If so it will throw an error.
|
|
552
|
+
* @param payload
|
|
553
553
|
* @returns
|
|
554
554
|
*/
|
|
555
|
-
protected validateReservedFields<T extends ExtendedPayload>(
|
|
555
|
+
protected validateReservedFields<T extends ExtendedPayload>(payload: T): void;
|
|
556
|
+
protected validateDisclosureFrame<T extends ExtendedPayload>(_disclosureFrame?: DisclosureFrame<T>): void;
|
|
556
557
|
present<T extends Record<string, unknown>>(encodedSDJwt: string, presentationFrame?: PresentationFrame<T>, options?: {
|
|
557
558
|
kb?: KBOptions;
|
|
558
559
|
}): Promise<SDJWTCompact>;
|
|
@@ -621,21 +622,22 @@ declare class SDJwtGeneralJSONInstance<ExtendedPayload extends SdJwtPayload> {
|
|
|
621
622
|
}>;
|
|
622
623
|
}): Promise<GeneralJSON>;
|
|
623
624
|
/**
|
|
624
|
-
* Validates if the
|
|
625
|
-
* @param
|
|
625
|
+
* Validates if the payload contains any reserved claim names. If so it will throw an error.
|
|
626
|
+
* @param payload
|
|
626
627
|
* @returns
|
|
627
628
|
*/
|
|
628
|
-
protected validateReservedFields<T extends ExtendedPayload>(
|
|
629
|
+
protected validateReservedFields<T extends ExtendedPayload>(payload: T): void;
|
|
630
|
+
protected validateDisclosureFrame<T extends ExtendedPayload>(_disclosureFrame?: DisclosureFrame<T>): void;
|
|
629
631
|
present<T extends Record<string, unknown>>(generalJSON: GeneralJSON, presentationFrame?: PresentationFrame<T>, options?: {
|
|
630
632
|
kb?: KBOptions;
|
|
631
633
|
}): Promise<GeneralJSON>;
|
|
632
634
|
verify(generalJSON: GeneralJSON, options?: VerifierOptions): Promise<{
|
|
633
635
|
payload: ExtendedPayload;
|
|
634
|
-
headers:
|
|
636
|
+
headers: unknown[];
|
|
635
637
|
kb?: undefined;
|
|
636
638
|
} | {
|
|
637
639
|
payload: ExtendedPayload;
|
|
638
|
-
headers:
|
|
640
|
+
headers: unknown[];
|
|
639
641
|
kb: {
|
|
640
642
|
payload: kbPayload;
|
|
641
643
|
header: kbHeader;
|
|
@@ -644,7 +646,7 @@ declare class SDJwtGeneralJSONInstance<ExtendedPayload extends SdJwtPayload> {
|
|
|
644
646
|
private calculateSDHash;
|
|
645
647
|
validate(generalJSON: GeneralJSON): Promise<{
|
|
646
648
|
payload: ExtendedPayload;
|
|
647
|
-
headers:
|
|
649
|
+
headers: unknown[];
|
|
648
650
|
}>;
|
|
649
651
|
config(newConfig: SDJWTConfig): void;
|
|
650
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* () {
|
|
@@ -1155,9 +1178,8 @@ var _SDJwtInstance = class _SDJwtInstance {
|
|
|
1155
1178
|
if (!this.userConfig.signAlg) {
|
|
1156
1179
|
throw new SDJWTException("sign alogrithm not specified");
|
|
1157
1180
|
}
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
}
|
|
1181
|
+
this.validateReservedFields(payload);
|
|
1182
|
+
this.validateDisclosureFrame(disclosureFrame);
|
|
1161
1183
|
const hasher = this.userConfig.hasher;
|
|
1162
1184
|
const hashAlg = (_a = this.userConfig.hashAlg) != null ? _a : _SDJwtInstance.DEFAULT_hashAlg;
|
|
1163
1185
|
const { packedClaims, disclosures } = yield pack(
|
|
@@ -1185,11 +1207,30 @@ var _SDJwtInstance = class _SDJwtInstance {
|
|
|
1185
1207
|
});
|
|
1186
1208
|
}
|
|
1187
1209
|
/**
|
|
1188
|
-
* Validates if the
|
|
1189
|
-
* @param
|
|
1210
|
+
* Validates if the payload contains any reserved claim names. If so it will throw an error.
|
|
1211
|
+
* @param payload
|
|
1190
1212
|
* @returns
|
|
1191
1213
|
*/
|
|
1192
|
-
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) {
|
|
1193
1234
|
return;
|
|
1194
1235
|
}
|
|
1195
1236
|
present(encodedSDJwt, presentationFrame, options) {
|
|
@@ -1565,9 +1606,8 @@ var SDJwtGeneralJSONInstance = class {
|
|
|
1565
1606
|
if (!this.userConfig.saltGenerator) {
|
|
1566
1607
|
throw new SDJWTException("SaltGenerator not found");
|
|
1567
1608
|
}
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
}
|
|
1609
|
+
this.validateReservedFields(payload);
|
|
1610
|
+
this.validateDisclosureFrame(disclosureFrame);
|
|
1571
1611
|
const hasher = this.userConfig.hasher;
|
|
1572
1612
|
const hashAlg = (_a = this.userConfig.hashAlg) != null ? _a : SDJwtInstance.DEFAULT_hashAlg;
|
|
1573
1613
|
const { packedClaims, disclosures } = yield pack(
|
|
@@ -1576,10 +1616,12 @@ var SDJwtGeneralJSONInstance = class {
|
|
|
1576
1616
|
{ hasher, alg: hashAlg },
|
|
1577
1617
|
this.userConfig.saltGenerator
|
|
1578
1618
|
);
|
|
1579
|
-
const encodedDisclosures = disclosures.map((d) => d.encode());
|
|
1580
1619
|
const encodedSDJwtPayload = this.encodeObj(__spreadProps(__spreadValues({}, packedClaims), {
|
|
1581
1620
|
_sd_alg: disclosureFrame ? hashAlg : void 0
|
|
1582
1621
|
}));
|
|
1622
|
+
const encodedDisclosures = disclosures.map(
|
|
1623
|
+
(disclosure) => disclosure.encode()
|
|
1624
|
+
);
|
|
1583
1625
|
const signatures = yield Promise.all(
|
|
1584
1626
|
options.sigs.map((s) => __async(this, null, function* () {
|
|
1585
1627
|
const { signer, alg, kid, header } = s;
|
|
@@ -1590,7 +1632,6 @@ var SDJwtGeneralJSONInstance = class {
|
|
|
1590
1632
|
);
|
|
1591
1633
|
return {
|
|
1592
1634
|
protected: encodedProtectedHeader,
|
|
1593
|
-
kid,
|
|
1594
1635
|
signature
|
|
1595
1636
|
};
|
|
1596
1637
|
}))
|
|
@@ -1604,11 +1645,30 @@ var SDJwtGeneralJSONInstance = class {
|
|
|
1604
1645
|
});
|
|
1605
1646
|
}
|
|
1606
1647
|
/**
|
|
1607
|
-
* Validates if the
|
|
1608
|
-
* @param
|
|
1648
|
+
* Validates if the payload contains any reserved claim names. If so it will throw an error.
|
|
1649
|
+
* @param payload
|
|
1609
1650
|
* @returns
|
|
1610
1651
|
*/
|
|
1611
|
-
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) {
|
|
1612
1672
|
return;
|
|
1613
1673
|
}
|
|
1614
1674
|
present(generalJSON, presentationFrame, options) {
|
|
@@ -1741,7 +1801,7 @@ var SDJwtGeneralJSONInstance = class {
|
|
|
1741
1801
|
`${encodedHeader}.${payload}`,
|
|
1742
1802
|
signature
|
|
1743
1803
|
);
|
|
1744
|
-
const header =
|
|
1804
|
+
const header = decodeBase64urlJsonStrict(encodedHeader, "Invalid JWT");
|
|
1745
1805
|
return { verified: verified2, header };
|
|
1746
1806
|
}))
|
|
1747
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* () {
|
|
@@ -1096,9 +1119,8 @@ var _SDJwtInstance = class _SDJwtInstance {
|
|
|
1096
1119
|
if (!this.userConfig.signAlg) {
|
|
1097
1120
|
throw new SDJWTException("sign alogrithm not specified");
|
|
1098
1121
|
}
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
}
|
|
1122
|
+
this.validateReservedFields(payload);
|
|
1123
|
+
this.validateDisclosureFrame(disclosureFrame);
|
|
1102
1124
|
const hasher = this.userConfig.hasher;
|
|
1103
1125
|
const hashAlg = (_a = this.userConfig.hashAlg) != null ? _a : _SDJwtInstance.DEFAULT_hashAlg;
|
|
1104
1126
|
const { packedClaims, disclosures } = yield pack(
|
|
@@ -1126,11 +1148,30 @@ var _SDJwtInstance = class _SDJwtInstance {
|
|
|
1126
1148
|
});
|
|
1127
1149
|
}
|
|
1128
1150
|
/**
|
|
1129
|
-
* Validates if the
|
|
1130
|
-
* @param
|
|
1151
|
+
* Validates if the payload contains any reserved claim names. If so it will throw an error.
|
|
1152
|
+
* @param payload
|
|
1131
1153
|
* @returns
|
|
1132
1154
|
*/
|
|
1133
|
-
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) {
|
|
1134
1175
|
return;
|
|
1135
1176
|
}
|
|
1136
1177
|
present(encodedSDJwt, presentationFrame, options) {
|
|
@@ -1506,9 +1547,8 @@ var SDJwtGeneralJSONInstance = class {
|
|
|
1506
1547
|
if (!this.userConfig.saltGenerator) {
|
|
1507
1548
|
throw new SDJWTException("SaltGenerator not found");
|
|
1508
1549
|
}
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
}
|
|
1550
|
+
this.validateReservedFields(payload);
|
|
1551
|
+
this.validateDisclosureFrame(disclosureFrame);
|
|
1512
1552
|
const hasher = this.userConfig.hasher;
|
|
1513
1553
|
const hashAlg = (_a = this.userConfig.hashAlg) != null ? _a : SDJwtInstance.DEFAULT_hashAlg;
|
|
1514
1554
|
const { packedClaims, disclosures } = yield pack(
|
|
@@ -1517,10 +1557,12 @@ var SDJwtGeneralJSONInstance = class {
|
|
|
1517
1557
|
{ hasher, alg: hashAlg },
|
|
1518
1558
|
this.userConfig.saltGenerator
|
|
1519
1559
|
);
|
|
1520
|
-
const encodedDisclosures = disclosures.map((d) => d.encode());
|
|
1521
1560
|
const encodedSDJwtPayload = this.encodeObj(__spreadProps(__spreadValues({}, packedClaims), {
|
|
1522
1561
|
_sd_alg: disclosureFrame ? hashAlg : void 0
|
|
1523
1562
|
}));
|
|
1563
|
+
const encodedDisclosures = disclosures.map(
|
|
1564
|
+
(disclosure) => disclosure.encode()
|
|
1565
|
+
);
|
|
1524
1566
|
const signatures = yield Promise.all(
|
|
1525
1567
|
options.sigs.map((s) => __async(this, null, function* () {
|
|
1526
1568
|
const { signer, alg, kid, header } = s;
|
|
@@ -1531,7 +1573,6 @@ var SDJwtGeneralJSONInstance = class {
|
|
|
1531
1573
|
);
|
|
1532
1574
|
return {
|
|
1533
1575
|
protected: encodedProtectedHeader,
|
|
1534
|
-
kid,
|
|
1535
1576
|
signature
|
|
1536
1577
|
};
|
|
1537
1578
|
}))
|
|
@@ -1545,11 +1586,30 @@ var SDJwtGeneralJSONInstance = class {
|
|
|
1545
1586
|
});
|
|
1546
1587
|
}
|
|
1547
1588
|
/**
|
|
1548
|
-
* Validates if the
|
|
1549
|
-
* @param
|
|
1589
|
+
* Validates if the payload contains any reserved claim names. If so it will throw an error.
|
|
1590
|
+
* @param payload
|
|
1550
1591
|
* @returns
|
|
1551
1592
|
*/
|
|
1552
|
-
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) {
|
|
1553
1613
|
return;
|
|
1554
1614
|
}
|
|
1555
1615
|
present(generalJSON, presentationFrame, options) {
|
|
@@ -1682,7 +1742,7 @@ var SDJwtGeneralJSONInstance = class {
|
|
|
1682
1742
|
`${encodedHeader}.${payload}`,
|
|
1683
1743
|
signature
|
|
1684
1744
|
);
|
|
1685
|
-
const header =
|
|
1745
|
+
const header = decodeBase64urlJsonStrict(encodedHeader, "Invalid JWT");
|
|
1686
1746
|
return { verified: verified2, header };
|
|
1687
1747
|
}))
|
|
1688
1748
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sd-jwt/core",
|
|
3
|
-
"version": "0.19.1-next.
|
|
3
|
+
"version": "0.19.1-next.13+3d8a72a",
|
|
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": "3d8a72a0a279d67439db038c9c2b52621568b866"
|
|
62
62
|
}
|
package/src/decode/decode.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import type { HasherAndAlgSync, HasherSync } from '../types';
|
|
2
1
|
import {
|
|
3
2
|
type Hasher,
|
|
4
3
|
type HasherAndAlg,
|
|
4
|
+
type HasherAndAlgSync,
|
|
5
|
+
type HasherSync,
|
|
6
|
+
IANA_HASH_ALGORITHMS,
|
|
5
7
|
SD_DIGEST,
|
|
6
8
|
SD_LIST_KEY,
|
|
7
9
|
SD_SEPARATOR,
|
|
8
10
|
} from '../types';
|
|
9
|
-
import {
|
|
11
|
+
import { Disclosure, SDJWTException } from '../utils';
|
|
12
|
+
import { decodeBase64urlJsonStrict } from '../utils/strict-json';
|
|
10
13
|
|
|
11
14
|
export const decodeJwt = <
|
|
12
15
|
H extends Record<string, unknown>,
|
|
@@ -20,8 +23,8 @@ export const decodeJwt = <
|
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
return {
|
|
23
|
-
header:
|
|
24
|
-
payload:
|
|
26
|
+
header: decodeBase64urlJsonStrict(header, 'Invalid JWT as input'),
|
|
27
|
+
payload: decodeBase64urlJsonStrict(payload, 'Invalid JWT as input'),
|
|
25
28
|
signature: signature,
|
|
26
29
|
};
|
|
27
30
|
};
|
|
@@ -319,6 +322,13 @@ export const getSDAlgAndPayload = (SdJwtPayload: Record<string, unknown>) => {
|
|
|
319
322
|
// This is for compatibility
|
|
320
323
|
return { _sd_alg: 'sha-256', payload };
|
|
321
324
|
}
|
|
325
|
+
if (
|
|
326
|
+
!IANA_HASH_ALGORITHMS.includes(
|
|
327
|
+
_sd_alg as (typeof IANA_HASH_ALGORITHMS)[number],
|
|
328
|
+
)
|
|
329
|
+
) {
|
|
330
|
+
throw new SDJWTException(`Invalid _sd_alg: ${_sd_alg}`);
|
|
331
|
+
}
|
|
322
332
|
return { _sd_alg, payload };
|
|
323
333
|
};
|
|
324
334
|
|
package/src/index.ts
CHANGED
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
type KBOptions,
|
|
13
13
|
type PresentationFrame,
|
|
14
14
|
type SafeVerifyResult,
|
|
15
|
+
SD_DECOY,
|
|
16
|
+
SD_DIGEST,
|
|
15
17
|
type SDJWTCompact,
|
|
16
18
|
type SDJWTConfig,
|
|
17
19
|
type Signer,
|
|
@@ -19,12 +21,12 @@ import {
|
|
|
19
21
|
type VerificationErrorCode,
|
|
20
22
|
} from './types';
|
|
21
23
|
import {
|
|
22
|
-
base64urlDecode,
|
|
23
24
|
base64urlEncode,
|
|
24
25
|
ensureError,
|
|
25
26
|
SDJWTException,
|
|
26
27
|
uint8ArrayToBase64Url,
|
|
27
28
|
} from './utils';
|
|
29
|
+
import { decodeBase64urlJsonStrict } from './utils/strict-json';
|
|
28
30
|
|
|
29
31
|
export * from './decode';
|
|
30
32
|
export * from './decoy';
|
|
@@ -120,9 +122,8 @@ export class SDJwtInstance<ExtendedPayload extends SdJwtPayload, T = unknown> {
|
|
|
120
122
|
throw new SDJWTException('sign alogrithm not specified');
|
|
121
123
|
}
|
|
122
124
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
125
|
+
this.validateReservedFields<Payload>(payload);
|
|
126
|
+
this.validateDisclosureFrame<Payload>(disclosureFrame);
|
|
126
127
|
|
|
127
128
|
const hasher = this.userConfig.hasher;
|
|
128
129
|
const hashAlg = this.userConfig.hashAlg ?? SDJwtInstance.DEFAULT_hashAlg;
|
|
@@ -157,12 +158,35 @@ export class SDJwtInstance<ExtendedPayload extends SdJwtPayload, T = unknown> {
|
|
|
157
158
|
}
|
|
158
159
|
|
|
159
160
|
/**
|
|
160
|
-
* Validates if the
|
|
161
|
-
* @param
|
|
161
|
+
* Validates if the payload contains any reserved claim names. If so it will throw an error.
|
|
162
|
+
* @param payload
|
|
162
163
|
* @returns
|
|
163
164
|
*/
|
|
164
|
-
protected validateReservedFields<T extends ExtendedPayload>(
|
|
165
|
-
|
|
165
|
+
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);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
protected validateDisclosureFrame<T extends ExtendedPayload>(
|
|
189
|
+
_disclosureFrame?: DisclosureFrame<T>,
|
|
166
190
|
) {
|
|
167
191
|
return;
|
|
168
192
|
}
|
|
@@ -629,9 +653,8 @@ export class SDJwtGeneralJSONInstance<ExtendedPayload extends SdJwtPayload> {
|
|
|
629
653
|
throw new SDJWTException('SaltGenerator not found');
|
|
630
654
|
}
|
|
631
655
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
}
|
|
656
|
+
this.validateReservedFields<Payload>(payload);
|
|
657
|
+
this.validateDisclosureFrame<Payload>(disclosureFrame);
|
|
635
658
|
|
|
636
659
|
const hasher = this.userConfig.hasher;
|
|
637
660
|
const hashAlg = this.userConfig.hashAlg ?? SDJwtInstance.DEFAULT_hashAlg;
|
|
@@ -643,12 +666,15 @@ export class SDJwtGeneralJSONInstance<ExtendedPayload extends SdJwtPayload> {
|
|
|
643
666
|
this.userConfig.saltGenerator,
|
|
644
667
|
);
|
|
645
668
|
|
|
646
|
-
const encodedDisclosures = disclosures.map((d) => d.encode());
|
|
647
669
|
const encodedSDJwtPayload = this.encodeObj({
|
|
648
670
|
...packedClaims,
|
|
649
671
|
_sd_alg: disclosureFrame ? hashAlg : undefined,
|
|
650
672
|
});
|
|
651
673
|
|
|
674
|
+
const encodedDisclosures = disclosures.map((disclosure) =>
|
|
675
|
+
disclosure.encode(),
|
|
676
|
+
);
|
|
677
|
+
|
|
652
678
|
const signatures = await Promise.all(
|
|
653
679
|
options.sigs.map(async (s) => {
|
|
654
680
|
const { signer, alg, kid, header } = s;
|
|
@@ -660,7 +686,6 @@ export class SDJwtGeneralJSONInstance<ExtendedPayload extends SdJwtPayload> {
|
|
|
660
686
|
|
|
661
687
|
return {
|
|
662
688
|
protected: encodedProtectedHeader,
|
|
663
|
-
kid,
|
|
664
689
|
signature,
|
|
665
690
|
};
|
|
666
691
|
}),
|
|
@@ -676,12 +701,35 @@ export class SDJwtGeneralJSONInstance<ExtendedPayload extends SdJwtPayload> {
|
|
|
676
701
|
}
|
|
677
702
|
|
|
678
703
|
/**
|
|
679
|
-
* Validates if the
|
|
680
|
-
* @param
|
|
704
|
+
* Validates if the payload contains any reserved claim names. If so it will throw an error.
|
|
705
|
+
* @param payload
|
|
681
706
|
* @returns
|
|
682
707
|
*/
|
|
683
|
-
protected validateReservedFields<T extends ExtendedPayload>(
|
|
684
|
-
|
|
708
|
+
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);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
protected validateDisclosureFrame<T extends ExtendedPayload>(
|
|
732
|
+
_disclosureFrame?: DisclosureFrame<T>,
|
|
685
733
|
) {
|
|
686
734
|
return;
|
|
687
735
|
}
|
|
@@ -836,7 +884,7 @@ export class SDJwtGeneralJSONInstance<ExtendedPayload extends SdJwtPayload> {
|
|
|
836
884
|
`${encodedHeader}.${payload}`,
|
|
837
885
|
signature,
|
|
838
886
|
);
|
|
839
|
-
const header =
|
|
887
|
+
const header = decodeBase64urlJsonStrict(encodedHeader, 'Invalid JWT');
|
|
840
888
|
return { verified, header };
|
|
841
889
|
}),
|
|
842
890
|
);
|
package/src/test/index.spec.ts
CHANGED
|
@@ -100,6 +100,32 @@ describe('index', () => {
|
|
|
100
100
|
expect(credential).toBeDefined();
|
|
101
101
|
});
|
|
102
102
|
|
|
103
|
+
test('issue rejects reserved disclosure frame keys', async () => {
|
|
104
|
+
const { signer, verifier } = createSignerVerifier();
|
|
105
|
+
const sdjwt = new SDJwtInstance<SdJwtPayload>({
|
|
106
|
+
signer,
|
|
107
|
+
signAlg: 'EdDSA',
|
|
108
|
+
verifier,
|
|
109
|
+
hasher: digest,
|
|
110
|
+
saltGenerator: generateSalt,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
await expect(
|
|
114
|
+
sdjwt.issue(
|
|
115
|
+
{
|
|
116
|
+
foo: 'bar',
|
|
117
|
+
_sd: 'reserved',
|
|
118
|
+
iss: 'Issuer',
|
|
119
|
+
iat: Math.floor(Date.now() / 1000),
|
|
120
|
+
vct: '',
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
_sd: ['foo'],
|
|
124
|
+
},
|
|
125
|
+
),
|
|
126
|
+
).rejects.toThrow('Reserved field name "_sd" is not allowed');
|
|
127
|
+
});
|
|
128
|
+
|
|
103
129
|
test('verify failed', async () => {
|
|
104
130
|
const { signer } = createSignerVerifier();
|
|
105
131
|
const { publicKey } = Crypto.generateKeyPairSync('ed25519');
|
|
@@ -424,6 +450,38 @@ describe('index', () => {
|
|
|
424
450
|
}
|
|
425
451
|
});
|
|
426
452
|
|
|
453
|
+
test('decode rejects invalid _sd_alg values', async () => {
|
|
454
|
+
const { signer, verifier } = createSignerVerifier();
|
|
455
|
+
const sdjwt = new SDJwtInstance<SdJwtPayload>({
|
|
456
|
+
signer,
|
|
457
|
+
signAlg: 'EdDSA',
|
|
458
|
+
verifier,
|
|
459
|
+
hasher: digest,
|
|
460
|
+
saltGenerator: generateSalt,
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
const credential = await sdjwt.issue(
|
|
464
|
+
{
|
|
465
|
+
foo: 'bar',
|
|
466
|
+
iss: 'Issuer',
|
|
467
|
+
iat: Math.floor(Date.now() / 1000),
|
|
468
|
+
vct: '',
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
_sd: ['foo'],
|
|
472
|
+
},
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
const parts = credential.split('.');
|
|
476
|
+
const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString());
|
|
477
|
+
payload._sd_alg = 'SHA-256';
|
|
478
|
+
parts[1] = Buffer.from(JSON.stringify(payload)).toString('base64url');
|
|
479
|
+
|
|
480
|
+
await expect(sdjwt.decode(parts.join('.'))).rejects.toThrow(
|
|
481
|
+
'Invalid _sd_alg: SHA-256',
|
|
482
|
+
);
|
|
483
|
+
});
|
|
484
|
+
|
|
427
485
|
test('SaltGenerator not found', async () => {
|
|
428
486
|
const sdjwt = new SDJwtInstance<SdJwtPayload>({
|
|
429
487
|
hasher: digest,
|
package/src/utils/disclosure.ts
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import type { DisclosureData, HasherAndAlg, HasherAndAlgSync } from '../types';
|
|
2
|
-
import {
|
|
3
|
-
base64urlDecode,
|
|
4
|
-
base64urlEncode,
|
|
5
|
-
uint8ArrayToBase64Url,
|
|
6
|
-
} from './base64url';
|
|
2
|
+
import { base64urlEncode, uint8ArrayToBase64Url } from './base64url';
|
|
7
3
|
import { SDJWTException } from './error';
|
|
4
|
+
import { decodeBase64urlJsonStrict } from './strict-json';
|
|
8
5
|
|
|
9
6
|
export class Disclosure<T = unknown> {
|
|
10
7
|
public salt: string;
|
|
@@ -42,7 +39,10 @@ export class Disclosure<T = unknown> {
|
|
|
42
39
|
const { hasher, alg } = hash;
|
|
43
40
|
const digest = await hasher(s, alg);
|
|
44
41
|
const digestStr = uint8ArrayToBase64Url(digest);
|
|
45
|
-
const item =
|
|
42
|
+
const item = decodeBase64urlJsonStrict<DisclosureData<T>>(
|
|
43
|
+
s,
|
|
44
|
+
'Invalid disclosure data',
|
|
45
|
+
);
|
|
46
46
|
return Disclosure.fromArray<T>(item, { digest: digestStr, encoded: s });
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -50,7 +50,10 @@ export class Disclosure<T = unknown> {
|
|
|
50
50
|
const { hasher, alg } = hash;
|
|
51
51
|
const digest = hasher(s, alg);
|
|
52
52
|
const digestStr = uint8ArrayToBase64Url(digest);
|
|
53
|
-
const item =
|
|
53
|
+
const item = decodeBase64urlJsonStrict<DisclosureData<T>>(
|
|
54
|
+
s,
|
|
55
|
+
'Invalid disclosure data',
|
|
56
|
+
);
|
|
54
57
|
return Disclosure.fromArray<T>(item, { digest: digestStr, encoded: s });
|
|
55
58
|
}
|
|
56
59
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { base64UrlToUint8Array } from './base64url';
|
|
2
|
+
import { SDJWTException } from './error';
|
|
3
|
+
|
|
4
|
+
const utf8Decoder = new TextDecoder('utf-8', { fatal: true });
|
|
5
|
+
|
|
6
|
+
export const decodeBase64urlJsonStrict = <T>(
|
|
7
|
+
encoded: string,
|
|
8
|
+
errorMessage: string,
|
|
9
|
+
): T => {
|
|
10
|
+
try {
|
|
11
|
+
const bytes = base64UrlToUint8Array(encoded);
|
|
12
|
+
const decoded = utf8Decoder.decode(bytes);
|
|
13
|
+
return JSON.parse(decoded) as T;
|
|
14
|
+
} catch {
|
|
15
|
+
throw new SDJWTException(errorMessage);
|
|
16
|
+
}
|
|
17
|
+
};
|
package/test/app-e2e.spec.ts
CHANGED
|
@@ -135,6 +135,37 @@ describe('App', () => {
|
|
|
135
135
|
expect(verified).toBeDefined();
|
|
136
136
|
});
|
|
137
137
|
|
|
138
|
+
test('rejects tampered disclosure bytes', async () => {
|
|
139
|
+
const { signer, verifier } = createSignerVerifier();
|
|
140
|
+
const sdjwt = new SDJwtInstance<SdJwtPayload>({
|
|
141
|
+
signer,
|
|
142
|
+
signAlg: 'EdDSA',
|
|
143
|
+
verifier,
|
|
144
|
+
hasher: digest,
|
|
145
|
+
hashAlg: 'sha-256',
|
|
146
|
+
saltGenerator: generateSalt,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const claims = {
|
|
150
|
+
firstname: 'John',
|
|
151
|
+
lastname: 'Doe',
|
|
152
|
+
};
|
|
153
|
+
const disclosureFrame: DisclosureFrame<typeof claims> = {
|
|
154
|
+
_sd: ['firstname', 'lastname'],
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const encodedSdjwt = await sdjwt.issue(claims, disclosureFrame);
|
|
158
|
+
const parts = encodedSdjwt.split('~');
|
|
159
|
+
const disclosureBytes = Buffer.from(parts[1], 'base64url');
|
|
160
|
+
disclosureBytes[2] = 0xf2;
|
|
161
|
+
parts[1] = Buffer.from(disclosureBytes).toString('base64url');
|
|
162
|
+
const tamperedSdjwt = parts.join('~');
|
|
163
|
+
|
|
164
|
+
await expect(sdjwt.verify(tamperedSdjwt)).rejects.toThrow(
|
|
165
|
+
'Invalid disclosure data',
|
|
166
|
+
);
|
|
167
|
+
});
|
|
168
|
+
|
|
138
169
|
test('From JSON (complex)', async () => {
|
|
139
170
|
await JSONtest('./complex.json');
|
|
140
171
|
});
|