@sd-jwt/core 0.20.1 → 0.21.0-alpha-20260916100413
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/LICENSE +2 -2
- package/README.md +82 -80
- package/dist/index.cjs +1526 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +696 -0
- package/dist/index.d.mts +537 -527
- package/dist/index.mjs +1422 -1998
- package/dist/index.mjs.map +1 -0
- package/package.json +29 -49
- package/CHANGELOG.md +0 -39
- package/dist/index.d.ts +0 -686
- package/dist/index.js +0 -2100
- package/src/decode/decode.ts +0 -407
- package/src/decode/index.ts +0 -1
- package/src/decoy.ts +0 -15
- package/src/flattenJSON.ts +0 -99
- package/src/generalJSON.ts +0 -157
- package/src/index.ts +0 -1000
- package/src/jwt.ts +0 -286
- package/src/kbjwt.ts +0 -101
- package/src/present/index.ts +0 -1
- package/src/present/present.ts +0 -216
- package/src/sdjwt.ts +0 -384
- package/src/test/decode/decode.spec.ts +0 -197
- package/src/test/decoy.spec.ts +0 -30
- package/src/test/flattenJSON.spec.ts +0 -122
- package/src/test/generalJSON.spec.ts +0 -198
- package/src/test/index.spec.ts +0 -986
- package/src/test/jwt.spec.ts +0 -301
- package/src/test/kbjwt.spec.ts +0 -555
- package/src/test/pass.spec.ts +0 -7
- package/src/test/present/present.spec.ts +0 -305
- package/src/test/sdjwt.spec.ts +0 -384
- package/src/test/types/type.spec.ts +0 -88
- package/src/test/utils/base64url.spec.ts +0 -33
- package/src/test/utils/disclosure.spec.ts +0 -170
- package/src/test/utils/error.spec.ts +0 -15
- package/src/types/index.ts +0 -2
- package/src/types/type.ts +0 -270
- package/src/types/verification-error.ts +0 -55
- package/src/utils/base64url.ts +0 -6
- package/src/utils/disclosure.ts +0 -121
- package/src/utils/error.ts +0 -25
- package/src/utils/index.ts +0 -3
- package/src/utils/strict-json.ts +0 -17
- package/test/app-e2e.spec.ts +0 -292
- package/test/array_data_types.json +0 -31
- package/test/array_full_sd.json +0 -21
- package/test/array_in_sd.json +0 -13
- package/test/array_nested_in_plain.json +0 -29
- package/test/array_none_disclosed.json +0 -17
- package/test/array_of_nulls.json +0 -15
- package/test/array_of_objects.json +0 -65
- package/test/array_of_scalars.json +0 -19
- package/test/array_recursive_sd.json +0 -35
- package/test/array_recursive_sd_some_disclosed.json +0 -63
- package/test/complex.json +0 -43
- package/test/header_mod.json +0 -48
- package/test/json_serialization.json +0 -48
- package/test/key_binding.json +0 -48
- package/test/no_sd.json +0 -36
- package/test/object_data_types.json +0 -62
- package/test/recursions.json +0 -117
- package/test/rfc9901-audit-fixes.spec.ts +0 -252
- package/test/rfc9901-validation.spec.ts +0 -150
- package/test/tsconfig.json +0 -4
- package/tsconfig.json +0 -7
- package/vitest.config.mts +0 -4
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1526 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _owf_identity_common = require("@owf/identity-common");
|
|
3
|
+
//#region src/types/type.ts
|
|
4
|
+
const SD_SEPARATOR = "~";
|
|
5
|
+
const SD_LIST_KEY = "...";
|
|
6
|
+
const SD_DIGEST = "_sd";
|
|
7
|
+
const SD_DECOY = "_sd_decoy";
|
|
8
|
+
const KB_JWT_TYP = "kb+jwt";
|
|
9
|
+
const encodePathSegment = (segment) => segment.replace(/~/g, "~0").replace(/\./g, "~1");
|
|
10
|
+
const encodePath = (segments) => segments.map(encodePathSegment).join(".");
|
|
11
|
+
const IANA_HASH_ALGORITHMS = [
|
|
12
|
+
"sha-256",
|
|
13
|
+
"sha-256-128",
|
|
14
|
+
"sha-256-120",
|
|
15
|
+
"sha-256-96",
|
|
16
|
+
"sha-256-64",
|
|
17
|
+
"sha-256-32",
|
|
18
|
+
"sha-384",
|
|
19
|
+
"sha-512",
|
|
20
|
+
"sha3-224",
|
|
21
|
+
"sha3-256",
|
|
22
|
+
"sha3-384",
|
|
23
|
+
"sha3-512",
|
|
24
|
+
"blake2s-256",
|
|
25
|
+
"blake2b-256",
|
|
26
|
+
"blake2b-512",
|
|
27
|
+
"k12-256",
|
|
28
|
+
"k12-512"
|
|
29
|
+
];
|
|
30
|
+
const DEFAULT_SECURE_HASH_ALGORITHMS = [
|
|
31
|
+
"sha-256",
|
|
32
|
+
"sha-384",
|
|
33
|
+
"sha-512",
|
|
34
|
+
"sha3-256",
|
|
35
|
+
"sha3-384",
|
|
36
|
+
"sha3-512",
|
|
37
|
+
"blake2s-256",
|
|
38
|
+
"blake2b-256",
|
|
39
|
+
"blake2b-512",
|
|
40
|
+
"k12-256",
|
|
41
|
+
"k12-512"
|
|
42
|
+
];
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/utils/error.ts
|
|
45
|
+
var SDJWTException = class SDJWTException extends Error {
|
|
46
|
+
constructor(message, details) {
|
|
47
|
+
super(message);
|
|
48
|
+
Object.setPrototypeOf(this, SDJWTException.prototype);
|
|
49
|
+
this.name = "SDJWTException";
|
|
50
|
+
this.details = details;
|
|
51
|
+
}
|
|
52
|
+
getFullMessage() {
|
|
53
|
+
return `${this.name}: ${this.message} ${this.details ? `- ${JSON.stringify(this.details)}` : ""}`;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Narrows an unknown caught value to an Error instance.
|
|
58
|
+
*/
|
|
59
|
+
function ensureError(value) {
|
|
60
|
+
if (value instanceof Error) return value;
|
|
61
|
+
if (typeof value === "string") return new Error(value);
|
|
62
|
+
return new Error(String(value));
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region src/utils/strict-json.ts
|
|
66
|
+
/**
|
|
67
|
+
* Decode base64url encoded JSON with {@link base64urlDecodeJson}, rethrowing failures as an
|
|
68
|
+
* `SDJWTException` with the given message.
|
|
69
|
+
*/
|
|
70
|
+
const decodeBase64urlJsonStrict = (encoded, errorMessage) => {
|
|
71
|
+
try {
|
|
72
|
+
return (0, _owf_identity_common.base64urlDecodeJson)(encoded);
|
|
73
|
+
} catch {
|
|
74
|
+
throw new SDJWTException(errorMessage);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
//#endregion
|
|
78
|
+
//#region src/utils/disclosure.ts
|
|
79
|
+
var Disclosure = class Disclosure {
|
|
80
|
+
constructor(data, _meta) {
|
|
81
|
+
if (!Array.isArray(data) || data.length !== 2 && data.length !== 3) throw new SDJWTException("Invalid disclosure data");
|
|
82
|
+
if (typeof data[0] !== "string") throw new SDJWTException("Invalid disclosure salt");
|
|
83
|
+
this._digest = _meta?.digest;
|
|
84
|
+
this._encoded = _meta?.encoded;
|
|
85
|
+
if (data.length === 2) {
|
|
86
|
+
this.salt = data[0];
|
|
87
|
+
this.value = data[1];
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (data.length === 3) {
|
|
91
|
+
if (typeof data[1] !== "string") throw new SDJWTException("Invalid disclosure claim name");
|
|
92
|
+
if (data[1] === "_sd" || data[1] === "...") throw new SDJWTException(`Reserved disclosure claim name "${data[1]}" is not allowed`);
|
|
93
|
+
this.salt = data[0];
|
|
94
|
+
this.key = data[1];
|
|
95
|
+
this.value = data[2];
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
static async fromEncode(s, hash) {
|
|
100
|
+
const { hasher, alg } = hash;
|
|
101
|
+
const digest = await hasher(s, alg);
|
|
102
|
+
const digestStr = (0, _owf_identity_common.uint8ArrayToBase64Url)(digest);
|
|
103
|
+
const item = decodeBase64urlJsonStrict(s, "Invalid disclosure data");
|
|
104
|
+
return Disclosure.fromArray(item, {
|
|
105
|
+
digest: digestStr,
|
|
106
|
+
encoded: s
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
static fromEncodeSync(s, hash) {
|
|
110
|
+
const { hasher, alg } = hash;
|
|
111
|
+
const digest = hasher(s, alg);
|
|
112
|
+
const digestStr = (0, _owf_identity_common.uint8ArrayToBase64Url)(digest);
|
|
113
|
+
const item = decodeBase64urlJsonStrict(s, "Invalid disclosure data");
|
|
114
|
+
return Disclosure.fromArray(item, {
|
|
115
|
+
digest: digestStr,
|
|
116
|
+
encoded: s
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
static fromArray(item, _meta) {
|
|
120
|
+
return new Disclosure(item, _meta);
|
|
121
|
+
}
|
|
122
|
+
encode() {
|
|
123
|
+
if (!this._encoded) this._encoded = (0, _owf_identity_common.base64urlEncode)(JSON.stringify(this.decode()));
|
|
124
|
+
return this._encoded;
|
|
125
|
+
}
|
|
126
|
+
decode() {
|
|
127
|
+
return this.key ? [
|
|
128
|
+
this.salt,
|
|
129
|
+
this.key,
|
|
130
|
+
this.value
|
|
131
|
+
] : [this.salt, this.value];
|
|
132
|
+
}
|
|
133
|
+
async digest(hash) {
|
|
134
|
+
const { hasher, alg } = hash;
|
|
135
|
+
if (!this._digest) {
|
|
136
|
+
const hash = await hasher(this.encode(), alg);
|
|
137
|
+
this._digest = (0, _owf_identity_common.uint8ArrayToBase64Url)(hash);
|
|
138
|
+
}
|
|
139
|
+
return this._digest;
|
|
140
|
+
}
|
|
141
|
+
digestSync(hash) {
|
|
142
|
+
const { hasher, alg } = hash;
|
|
143
|
+
if (!this._digest) {
|
|
144
|
+
const hash = hasher(this.encode(), alg);
|
|
145
|
+
this._digest = (0, _owf_identity_common.uint8ArrayToBase64Url)(hash);
|
|
146
|
+
}
|
|
147
|
+
return this._digest;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
//#endregion
|
|
151
|
+
//#region src/decode/decode.ts
|
|
152
|
+
/**
|
|
153
|
+
* Decode a JWT with `decodeJwt` from `@owf/identity-common`, rethrowing failures as an `SDJWTException`.
|
|
154
|
+
*/
|
|
155
|
+
const decodeJwt = (jwt) => {
|
|
156
|
+
try {
|
|
157
|
+
return (0, _owf_identity_common.decodeJwt)(jwt);
|
|
158
|
+
} catch {
|
|
159
|
+
throw new SDJWTException("Invalid JWT as input");
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
const splitSdJwt = (sdjwt) => {
|
|
163
|
+
const [encodedJwt, ...encodedDisclosures] = sdjwt.split("~");
|
|
164
|
+
if (encodedDisclosures.length === 0) throw new SDJWTException("Invalid SD-JWT: missing SD-JWT separator");
|
|
165
|
+
return {
|
|
166
|
+
jwt: encodedJwt,
|
|
167
|
+
disclosures: encodedDisclosures,
|
|
168
|
+
kbJwt: encodedDisclosures.pop() || void 0
|
|
169
|
+
};
|
|
170
|
+
};
|
|
171
|
+
const decodeSdJwt = async (sdjwt, hasher) => {
|
|
172
|
+
const [encodedJwt, ...encodedDisclosures] = sdjwt.split("~");
|
|
173
|
+
const jwt = decodeJwt(encodedJwt);
|
|
174
|
+
if (encodedDisclosures.length === 0) throw new SDJWTException("Invalid SD-JWT: missing SD-JWT separator");
|
|
175
|
+
const encodedKeyBindingJwt = encodedDisclosures.pop();
|
|
176
|
+
const kbJwt = encodedKeyBindingJwt ? decodeJwt(encodedKeyBindingJwt) : void 0;
|
|
177
|
+
const { _sd_alg } = getSDAlgAndPayload(jwt.payload);
|
|
178
|
+
return {
|
|
179
|
+
jwt,
|
|
180
|
+
disclosures: await Promise.all(encodedDisclosures.map((ed) => Disclosure.fromEncode(ed, {
|
|
181
|
+
alg: _sd_alg,
|
|
182
|
+
hasher
|
|
183
|
+
}))),
|
|
184
|
+
kbJwt
|
|
185
|
+
};
|
|
186
|
+
};
|
|
187
|
+
const decodeSdJwtSync = (sdjwt, hasher) => {
|
|
188
|
+
const [encodedJwt, ...encodedDisclosures] = sdjwt.split("~");
|
|
189
|
+
const jwt = decodeJwt(encodedJwt);
|
|
190
|
+
if (encodedDisclosures.length === 0) throw new SDJWTException("Invalid SD-JWT: missing SD-JWT separator");
|
|
191
|
+
const encodedKeyBindingJwt = encodedDisclosures.pop();
|
|
192
|
+
const kbJwt = encodedKeyBindingJwt ? decodeJwt(encodedKeyBindingJwt) : void 0;
|
|
193
|
+
const { _sd_alg } = getSDAlgAndPayload(jwt.payload);
|
|
194
|
+
return {
|
|
195
|
+
jwt,
|
|
196
|
+
disclosures: encodedDisclosures.map((ed) => Disclosure.fromEncodeSync(ed, {
|
|
197
|
+
alg: _sd_alg,
|
|
198
|
+
hasher
|
|
199
|
+
})),
|
|
200
|
+
kbJwt
|
|
201
|
+
};
|
|
202
|
+
};
|
|
203
|
+
const getClaims = async (rawPayload, disclosures, hasher) => {
|
|
204
|
+
const { unpackedObj } = await unpack(rawPayload, disclosures, hasher);
|
|
205
|
+
return unpackedObj;
|
|
206
|
+
};
|
|
207
|
+
const getClaimsSync = (rawPayload, disclosures, hasher) => {
|
|
208
|
+
const { unpackedObj } = unpackSync(rawPayload, disclosures, hasher);
|
|
209
|
+
return unpackedObj;
|
|
210
|
+
};
|
|
211
|
+
const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
212
|
+
const unpackArray = (arr, map, prefix = "", seenDigests) => {
|
|
213
|
+
const keys = {};
|
|
214
|
+
const unpackedArray = [];
|
|
215
|
+
arr.forEach((item, idx) => {
|
|
216
|
+
if (isRecord(item)) {
|
|
217
|
+
const hash = item["..."];
|
|
218
|
+
if ("..." in item) {
|
|
219
|
+
if (Object.keys(item).length !== 1 || typeof hash !== "string") throw new SDJWTException("Invalid array disclosure placeholder");
|
|
220
|
+
if (seenDigests) {
|
|
221
|
+
if (seenDigests.has(hash)) throw new SDJWTException("Duplicate digest found in SD-JWT payload");
|
|
222
|
+
seenDigests.add(hash);
|
|
223
|
+
}
|
|
224
|
+
const disclosed = map[hash];
|
|
225
|
+
if (disclosed) {
|
|
226
|
+
if (typeof disclosed.key === "string") throw new SDJWTException("Object-property disclosure cannot be used as an array element");
|
|
227
|
+
const presentKey = prefix ? `${prefix}.${idx}` : `${idx}`;
|
|
228
|
+
keys[presentKey] = hash;
|
|
229
|
+
const { unpackedObj, disclosureKeymap: disclosureKeys } = unpackObjInternal(disclosed.value, map, presentKey, seenDigests);
|
|
230
|
+
unpackedArray.push(unpackedObj);
|
|
231
|
+
Object.assign(keys, disclosureKeys);
|
|
232
|
+
}
|
|
233
|
+
} else {
|
|
234
|
+
const newKey = prefix ? `${prefix}.${idx}` : `${idx}`;
|
|
235
|
+
const { unpackedObj, disclosureKeymap: disclosureKeys } = unpackObjInternal(item, map, newKey, seenDigests);
|
|
236
|
+
unpackedArray.push(unpackedObj);
|
|
237
|
+
Object.assign(keys, disclosureKeys);
|
|
238
|
+
}
|
|
239
|
+
} else if (Array.isArray(item)) {
|
|
240
|
+
const newKey = prefix ? `${prefix}.${idx}` : `${idx}`;
|
|
241
|
+
const { unpackedObj, disclosureKeymap: disclosureKeys } = unpackObjInternal(item, map, newKey, seenDigests);
|
|
242
|
+
unpackedArray.push(unpackedObj);
|
|
243
|
+
Object.assign(keys, disclosureKeys);
|
|
244
|
+
} else unpackedArray.push(item);
|
|
245
|
+
});
|
|
246
|
+
return {
|
|
247
|
+
unpackedObj: unpackedArray,
|
|
248
|
+
disclosureKeymap: keys
|
|
249
|
+
};
|
|
250
|
+
};
|
|
251
|
+
const unpackObj = (obj, map) => {
|
|
252
|
+
const copiedObj = JSON.parse(JSON.stringify(obj));
|
|
253
|
+
const seenDigests = /* @__PURE__ */ new Set();
|
|
254
|
+
const result = unpackObjInternal(copiedObj, map, "", seenDigests);
|
|
255
|
+
if (Object.keys(map).filter((d) => !seenDigests.has(d)).length > 0) throw new SDJWTException("Unreferenced disclosure(s) detected in SD-JWT");
|
|
256
|
+
return result;
|
|
257
|
+
};
|
|
258
|
+
const unpackObjInternal = (obj, map, prefix = "", seenDigests) => {
|
|
259
|
+
const keys = {};
|
|
260
|
+
if (typeof obj === "object" && obj !== null) {
|
|
261
|
+
if (Array.isArray(obj)) return unpackArray(obj, map, prefix, seenDigests);
|
|
262
|
+
const record = obj;
|
|
263
|
+
for (const key in record) {
|
|
264
|
+
if (prefix && key === "_sd_alg") throw new SDJWTException("Nested _sd_alg is not allowed");
|
|
265
|
+
if (key !== "_sd" && key !== "..." && typeof record[key] === "object") {
|
|
266
|
+
const escapedKey = encodePathSegment(key);
|
|
267
|
+
const newKey = prefix ? `${prefix}.${escapedKey}` : escapedKey;
|
|
268
|
+
const { unpackedObj, disclosureKeymap: disclosureKeys } = unpackObjInternal(record[key], map, newKey, seenDigests);
|
|
269
|
+
record[key] = unpackedObj;
|
|
270
|
+
Object.assign(keys, disclosureKeys);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const { _sd, ...payload } = record;
|
|
274
|
+
const claims = {};
|
|
275
|
+
if (_sd !== void 0) {
|
|
276
|
+
if (!Array.isArray(_sd) || !_sd.every((hash) => typeof hash === "string")) throw new SDJWTException("Invalid _sd claim: expected array of strings");
|
|
277
|
+
for (const hash of _sd) {
|
|
278
|
+
if (seenDigests) {
|
|
279
|
+
if (seenDigests.has(hash)) throw new SDJWTException("Duplicate digest found in SD-JWT payload");
|
|
280
|
+
seenDigests.add(hash);
|
|
281
|
+
}
|
|
282
|
+
const disclosed = map[hash];
|
|
283
|
+
if (disclosed) {
|
|
284
|
+
if (typeof disclosed.key !== "string") throw new SDJWTException("Array disclosure cannot be used as an object property");
|
|
285
|
+
if (disclosed.key in payload) throw new SDJWTException(`Disclosed claim name "${disclosed.key}" conflicts with existing payload key`);
|
|
286
|
+
if (disclosed.key in claims) throw new SDJWTException(`Disclosed claim name "${disclosed.key}" conflicts with another disclosure`);
|
|
287
|
+
const escapedKey = encodePathSegment(disclosed.key);
|
|
288
|
+
const presentKey = prefix ? `${prefix}.${escapedKey}` : escapedKey;
|
|
289
|
+
keys[presentKey] = hash;
|
|
290
|
+
const { unpackedObj, disclosureKeymap: disclosureKeys } = unpackObjInternal(disclosed.value, map, presentKey, seenDigests);
|
|
291
|
+
claims[disclosed.key] = unpackedObj;
|
|
292
|
+
Object.assign(keys, disclosureKeys);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
unpackedObj: Object.assign(payload, claims),
|
|
298
|
+
disclosureKeymap: keys
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
return {
|
|
302
|
+
unpackedObj: obj,
|
|
303
|
+
disclosureKeymap: keys
|
|
304
|
+
};
|
|
305
|
+
};
|
|
306
|
+
const createHashMapping = async (disclosures, hash) => {
|
|
307
|
+
const map = {};
|
|
308
|
+
for (let i = 0; i < disclosures.length; i++) {
|
|
309
|
+
const disclosure = disclosures[i];
|
|
310
|
+
const digest = await disclosure.digest(hash);
|
|
311
|
+
if (digest in map) throw new SDJWTException("Duplicate disclosure digest detected");
|
|
312
|
+
map[digest] = disclosure;
|
|
313
|
+
}
|
|
314
|
+
return map;
|
|
315
|
+
};
|
|
316
|
+
const createHashMappingSync = (disclosures, hash) => {
|
|
317
|
+
const map = {};
|
|
318
|
+
for (let i = 0; i < disclosures.length; i++) {
|
|
319
|
+
const disclosure = disclosures[i];
|
|
320
|
+
const digest = disclosure.digestSync(hash);
|
|
321
|
+
if (digest in map) throw new SDJWTException("Duplicate disclosure digest detected");
|
|
322
|
+
map[digest] = disclosure;
|
|
323
|
+
}
|
|
324
|
+
return map;
|
|
325
|
+
};
|
|
326
|
+
const getSDAlgAndPayload = (SdJwtPayload, allowedAlgorithms = DEFAULT_SECURE_HASH_ALGORITHMS) => {
|
|
327
|
+
const { _sd_alg, ...payload } = SdJwtPayload;
|
|
328
|
+
if (_sd_alg === void 0) return {
|
|
329
|
+
_sd_alg: "sha-256",
|
|
330
|
+
payload
|
|
331
|
+
};
|
|
332
|
+
if (typeof _sd_alg !== "string") throw new SDJWTException("Invalid _sd_alg: expected string");
|
|
333
|
+
if (!IANA_HASH_ALGORITHMS.includes(_sd_alg)) throw new SDJWTException(`Invalid _sd_alg: ${_sd_alg}`);
|
|
334
|
+
if (!allowedAlgorithms.includes(_sd_alg)) throw new SDJWTException(`Disallowed _sd_alg: ${_sd_alg}`);
|
|
335
|
+
return {
|
|
336
|
+
_sd_alg,
|
|
337
|
+
payload
|
|
338
|
+
};
|
|
339
|
+
};
|
|
340
|
+
const unpack = async (SdJwtPayload, disclosures, hasher) => {
|
|
341
|
+
const { _sd_alg, payload } = getSDAlgAndPayload(SdJwtPayload);
|
|
342
|
+
const map = await createHashMapping(disclosures, {
|
|
343
|
+
hasher,
|
|
344
|
+
alg: _sd_alg
|
|
345
|
+
});
|
|
346
|
+
return unpackObj(payload, map);
|
|
347
|
+
};
|
|
348
|
+
const unpackSync = (SdJwtPayload, disclosures, hasher) => {
|
|
349
|
+
const { _sd_alg, payload } = getSDAlgAndPayload(SdJwtPayload);
|
|
350
|
+
const map = createHashMappingSync(disclosures, {
|
|
351
|
+
hasher,
|
|
352
|
+
alg: _sd_alg
|
|
353
|
+
});
|
|
354
|
+
return unpackObj(payload, map);
|
|
355
|
+
};
|
|
356
|
+
//#endregion
|
|
357
|
+
//#region src/flattenJSON.ts
|
|
358
|
+
var FlattenJSON = class FlattenJSON {
|
|
359
|
+
constructor(data) {
|
|
360
|
+
this.disclosures = data.disclosures;
|
|
361
|
+
this.kb_jwt = data.kb_jwt;
|
|
362
|
+
this.payload = data.jwtData.payload;
|
|
363
|
+
this.signature = data.jwtData.signature;
|
|
364
|
+
this.protected = data.jwtData.protected;
|
|
365
|
+
}
|
|
366
|
+
static fromEncode(encodedSdJwt) {
|
|
367
|
+
const { jwt, disclosures, kbJwt } = splitSdJwt(encodedSdJwt);
|
|
368
|
+
const { 0: protectedHeader, 1: payload, 2: signature } = jwt.split(".");
|
|
369
|
+
if (!protectedHeader || !payload || !signature) throw new SDJWTException("Invalid JWT");
|
|
370
|
+
return new FlattenJSON({
|
|
371
|
+
jwtData: {
|
|
372
|
+
protected: protectedHeader,
|
|
373
|
+
payload,
|
|
374
|
+
signature
|
|
375
|
+
},
|
|
376
|
+
disclosures,
|
|
377
|
+
kb_jwt: kbJwt
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
static fromSerialized(json) {
|
|
381
|
+
return new FlattenJSON({
|
|
382
|
+
jwtData: {
|
|
383
|
+
protected: json.protected,
|
|
384
|
+
payload: json.payload,
|
|
385
|
+
signature: json.signature
|
|
386
|
+
},
|
|
387
|
+
disclosures: json.header.disclosures,
|
|
388
|
+
kb_jwt: json.header.kb_jwt
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
toJson() {
|
|
392
|
+
return {
|
|
393
|
+
payload: this.payload,
|
|
394
|
+
signature: this.signature,
|
|
395
|
+
protected: this.protected,
|
|
396
|
+
header: {
|
|
397
|
+
disclosures: this.disclosures,
|
|
398
|
+
kb_jwt: this.kb_jwt
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
toEncoded() {
|
|
403
|
+
const data = [];
|
|
404
|
+
const jwt = `${this.protected}.${this.payload}.${this.signature}`;
|
|
405
|
+
data.push(jwt);
|
|
406
|
+
if (this.disclosures && this.disclosures.length > 0) {
|
|
407
|
+
const disclosures = this.disclosures.join("~");
|
|
408
|
+
data.push(disclosures);
|
|
409
|
+
}
|
|
410
|
+
const kb_jwt = this.kb_jwt ?? "";
|
|
411
|
+
data.push(kb_jwt);
|
|
412
|
+
return data.join("~");
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
//#endregion
|
|
416
|
+
//#region src/generalJSON.ts
|
|
417
|
+
var GeneralJSON = class GeneralJSON {
|
|
418
|
+
constructor(data) {
|
|
419
|
+
this.payload = data.payload;
|
|
420
|
+
this.disclosures = data.disclosures;
|
|
421
|
+
this.kb_jwt = data.kb_jwt;
|
|
422
|
+
this.signatures = data.signatures;
|
|
423
|
+
}
|
|
424
|
+
static fromEncode(encodedSdJwt) {
|
|
425
|
+
const { jwt, disclosures, kbJwt } = splitSdJwt(encodedSdJwt);
|
|
426
|
+
const { 0: protectedHeader, 1: payload, 2: signature } = jwt.split(".");
|
|
427
|
+
if (!protectedHeader || !payload || !signature) throw new SDJWTException("Invalid JWT");
|
|
428
|
+
return new GeneralJSON({
|
|
429
|
+
payload,
|
|
430
|
+
disclosures,
|
|
431
|
+
kb_jwt: kbJwt,
|
|
432
|
+
signatures: [{
|
|
433
|
+
protected: protectedHeader,
|
|
434
|
+
signature
|
|
435
|
+
}]
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
static fromSerialized(json) {
|
|
439
|
+
if (!json.signatures[0]) throw new SDJWTException("Invalid JSON");
|
|
440
|
+
for (let index = 1; index < json.signatures.length; index++) {
|
|
441
|
+
const header = json.signatures[index].header;
|
|
442
|
+
if (header && ("disclosures" in header || "kb_jwt" in header)) throw new SDJWTException("disclosures and kb_jwt MUST only appear in the first unprotected header");
|
|
443
|
+
}
|
|
444
|
+
const disclosures = json.signatures[0].header?.disclosures ?? [];
|
|
445
|
+
const kb_jwt = json.signatures[0].header?.kb_jwt;
|
|
446
|
+
return new GeneralJSON({
|
|
447
|
+
payload: json.payload,
|
|
448
|
+
disclosures,
|
|
449
|
+
kb_jwt,
|
|
450
|
+
signatures: json.signatures.map((s) => {
|
|
451
|
+
return {
|
|
452
|
+
protected: s.protected,
|
|
453
|
+
signature: s.signature,
|
|
454
|
+
kid: s.header?.kid
|
|
455
|
+
};
|
|
456
|
+
})
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
toJson() {
|
|
460
|
+
return {
|
|
461
|
+
payload: this.payload,
|
|
462
|
+
signatures: this.signatures.map((s, i) => {
|
|
463
|
+
if (i !== 0) return {
|
|
464
|
+
header: { kid: s.kid },
|
|
465
|
+
protected: s.protected,
|
|
466
|
+
signature: s.signature
|
|
467
|
+
};
|
|
468
|
+
return {
|
|
469
|
+
header: {
|
|
470
|
+
disclosures: this.disclosures,
|
|
471
|
+
kid: s.kid,
|
|
472
|
+
kb_jwt: this.kb_jwt
|
|
473
|
+
},
|
|
474
|
+
protected: s.protected,
|
|
475
|
+
signature: s.signature
|
|
476
|
+
};
|
|
477
|
+
})
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
toEncoded(index) {
|
|
481
|
+
if (index < 0 || index >= this.signatures.length) throw new SDJWTException("Index out of bounds");
|
|
482
|
+
const data = [];
|
|
483
|
+
const { protected: protectedHeader, signature } = this.signatures[index];
|
|
484
|
+
const jwt = `${protectedHeader}.${this.payload}.${signature}`;
|
|
485
|
+
data.push(jwt);
|
|
486
|
+
if (this.disclosures && this.disclosures.length > 0) {
|
|
487
|
+
const disclosures = this.disclosures.join("~");
|
|
488
|
+
data.push(disclosures);
|
|
489
|
+
}
|
|
490
|
+
const kb = this.kb_jwt ?? "";
|
|
491
|
+
data.push(kb);
|
|
492
|
+
return data.join("~");
|
|
493
|
+
}
|
|
494
|
+
async addSignature(protectedHeader, signer, kid) {
|
|
495
|
+
const header = (0, _owf_identity_common.base64urlEncode)(JSON.stringify(protectedHeader));
|
|
496
|
+
const signature = await signer(`${header}.${this.payload}`);
|
|
497
|
+
this.signatures.push({
|
|
498
|
+
protected: header,
|
|
499
|
+
signature,
|
|
500
|
+
kid
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
//#endregion
|
|
505
|
+
//#region src/jwt.ts
|
|
506
|
+
const isStringArray = (value) => Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
507
|
+
const validateNumericDate = (payload, claim) => {
|
|
508
|
+
const value = payload[claim];
|
|
509
|
+
if (value === void 0) return void 0;
|
|
510
|
+
if (typeof value !== "number" || !Number.isFinite(value)) throw new SDJWTException(`Verify Error: JWT ${claim} must be a number`);
|
|
511
|
+
return value;
|
|
512
|
+
};
|
|
513
|
+
const getAudiences = (audience) => {
|
|
514
|
+
if (typeof audience === "string") return [audience];
|
|
515
|
+
if (isStringArray(audience)) return audience;
|
|
516
|
+
};
|
|
517
|
+
const validateAudience = (payload, expectedAudience) => {
|
|
518
|
+
if (expectedAudience === void 0) return;
|
|
519
|
+
const expectedAudiences = Array.isArray(expectedAudience) ? expectedAudience : [expectedAudience];
|
|
520
|
+
const audiences = getAudiences(payload.aud);
|
|
521
|
+
if (!audiences || !expectedAudiences.some((expected) => audiences.includes(expected))) throw new SDJWTException("Verify Error: Invalid audience");
|
|
522
|
+
};
|
|
523
|
+
const validateJwtPayload = (payload, options) => {
|
|
524
|
+
if (!payload) throw new SDJWTException("Verify Error: JWT payload is missing");
|
|
525
|
+
const skew = options?.skewSeconds ? options.skewSeconds : 0;
|
|
526
|
+
const currentDate = options?.currentDate ?? (0, _owf_identity_common.nowInSeconds)();
|
|
527
|
+
const iat = validateNumericDate(payload, "iat");
|
|
528
|
+
const nbf = validateNumericDate(payload, "nbf");
|
|
529
|
+
const exp = validateNumericDate(payload, "exp");
|
|
530
|
+
if (iat !== void 0 && iat - skew > currentDate) throw new SDJWTException("Verify Error: JWT is not yet valid");
|
|
531
|
+
if (nbf !== void 0 && nbf - skew > currentDate) throw new SDJWTException("Verify Error: JWT is not yet valid");
|
|
532
|
+
if (exp !== void 0 && exp + skew <= currentDate) throw new SDJWTException("Verify Error: JWT is expired");
|
|
533
|
+
validateAudience(payload, options?.expectedAudience);
|
|
534
|
+
};
|
|
535
|
+
var Jwt = class Jwt {
|
|
536
|
+
constructor(data) {
|
|
537
|
+
this.header = data?.header;
|
|
538
|
+
this.payload = data?.payload;
|
|
539
|
+
this.signature = data?.signature;
|
|
540
|
+
this.encoded = data?.encoded;
|
|
541
|
+
}
|
|
542
|
+
static decodeJWT(jwt) {
|
|
543
|
+
return decodeJwt(jwt);
|
|
544
|
+
}
|
|
545
|
+
static fromEncode(encodedJwt) {
|
|
546
|
+
const { header, payload, signature } = Jwt.decodeJWT(encodedJwt);
|
|
547
|
+
return new Jwt({
|
|
548
|
+
header,
|
|
549
|
+
payload,
|
|
550
|
+
signature,
|
|
551
|
+
encoded: encodedJwt
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
setHeader(header) {
|
|
555
|
+
this.header = header;
|
|
556
|
+
this.encoded = void 0;
|
|
557
|
+
return this;
|
|
558
|
+
}
|
|
559
|
+
setPayload(payload) {
|
|
560
|
+
this.payload = payload;
|
|
561
|
+
this.encoded = void 0;
|
|
562
|
+
return this;
|
|
563
|
+
}
|
|
564
|
+
getUnsignedToken() {
|
|
565
|
+
if (!this.header || !this.payload) throw new SDJWTException("Serialize Error: Invalid JWT");
|
|
566
|
+
if (this.encoded) {
|
|
567
|
+
const parts = this.encoded.split(".");
|
|
568
|
+
if (parts.length !== 3) throw new SDJWTException(`Invalid JWT format: ${this.encoded}`);
|
|
569
|
+
return parts.slice(0, 2).join(".");
|
|
570
|
+
}
|
|
571
|
+
return `${(0, _owf_identity_common.base64urlEncode)(JSON.stringify(this.header))}.${(0, _owf_identity_common.base64urlEncode)(JSON.stringify(this.payload))}`;
|
|
572
|
+
}
|
|
573
|
+
async sign(signer) {
|
|
574
|
+
if (!this.header || this.header.alg === "none") throw new SDJWTException("Sign Error: alg \"none\" is not allowed");
|
|
575
|
+
const data = this.getUnsignedToken();
|
|
576
|
+
this.signature = await signer(data);
|
|
577
|
+
return this.encodeJwt();
|
|
578
|
+
}
|
|
579
|
+
encodeJwt() {
|
|
580
|
+
if (this.encoded) return this.encoded;
|
|
581
|
+
if (!this.header || !this.payload || !this.signature) throw new SDJWTException("Serialize Error: Invalid JWT");
|
|
582
|
+
const compact = `${(0, _owf_identity_common.base64urlEncode)(JSON.stringify(this.header))}.${(0, _owf_identity_common.base64urlEncode)(JSON.stringify(this.payload))}.${this.signature}`;
|
|
583
|
+
this.encoded = compact;
|
|
584
|
+
return compact;
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Verify the JWT using the provided verifier function.
|
|
588
|
+
* It checks the signature and validates the iat, nbf, and exp claims if they are present.
|
|
589
|
+
* @param verifier
|
|
590
|
+
* @param options - Options for verification, such as current date and skew seconds
|
|
591
|
+
* @returns
|
|
592
|
+
*/
|
|
593
|
+
async verify(verifier, options) {
|
|
594
|
+
const alg = this.header?.alg;
|
|
595
|
+
if (typeof alg !== "string" || alg === "none") throw new SDJWTException("Verify Error: alg \"none\" is not allowed");
|
|
596
|
+
if (options?.allowedIssuerAlgorithms && !options.allowedIssuerAlgorithms.includes(alg)) throw new SDJWTException(`Verify Error: Disallowed alg ${alg}`);
|
|
597
|
+
if (!options?.skipJwtClaimValidation) validateJwtPayload(this.payload, options);
|
|
598
|
+
if (!this.signature) throw new SDJWTException("Verify Error: no signature in JWT");
|
|
599
|
+
if (!await verifier(this.getUnsignedToken(), this.signature, options)) throw new SDJWTException("Verify Error: Invalid JWT Signature");
|
|
600
|
+
return {
|
|
601
|
+
payload: this.payload,
|
|
602
|
+
header: this.header
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
//#endregion
|
|
607
|
+
//#region src/kbjwt.ts
|
|
608
|
+
var KBJwt = class KBJwt extends Jwt {
|
|
609
|
+
async verifyKB(values) {
|
|
610
|
+
if (!this.header || !this.payload || !this.signature) throw new SDJWTException("Verify Error: Invalid JWT");
|
|
611
|
+
if (typeof this.header.alg !== "string" || this.header.alg === "none" || typeof this.header.typ !== "string" || this.header.typ !== "kb+jwt" || 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) throw new SDJWTException("Invalid Key Binding Jwt");
|
|
612
|
+
if (this.payload.nonce !== values.nonce) throw new SDJWTException("Verify Error: Invalid Nonce");
|
|
613
|
+
if (values.options?.expectedKeyBindingAudience !== void 0) {
|
|
614
|
+
if (!(Array.isArray(values.options.expectedKeyBindingAudience) ? values.options.expectedKeyBindingAudience : [values.options.expectedKeyBindingAudience]).includes(this.payload.aud)) throw new SDJWTException("Verify Error: Invalid Key Binding audience");
|
|
615
|
+
}
|
|
616
|
+
if (values.options?.keyBindingMaxAgeSeconds !== void 0) {
|
|
617
|
+
const currentDate = values.options.currentDate ?? (0, _owf_identity_common.nowInSeconds)();
|
|
618
|
+
const skew = values.options.skewSeconds ?? 0;
|
|
619
|
+
if (this.payload.iat + values.options.keyBindingMaxAgeSeconds + skew < currentDate) throw new SDJWTException("Verify Error: Key Binding JWT is too old");
|
|
620
|
+
}
|
|
621
|
+
await this.verify((data, sig) => values.verifier(data, sig, values.payload), values.options);
|
|
622
|
+
return {
|
|
623
|
+
payload: this.payload,
|
|
624
|
+
header: this.header
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
static fromKBEncode(encodedJwt) {
|
|
628
|
+
const { header, payload, signature } = Jwt.decodeJWT(encodedJwt);
|
|
629
|
+
return new KBJwt({
|
|
630
|
+
header,
|
|
631
|
+
payload,
|
|
632
|
+
signature,
|
|
633
|
+
encoded: encodedJwt
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
//#endregion
|
|
638
|
+
//#region src/decoy.ts
|
|
639
|
+
const createDecoy = async (hash, saltGenerator) => {
|
|
640
|
+
const { hasher, alg } = hash;
|
|
641
|
+
const decoy = await hasher(await saltGenerator(16), alg);
|
|
642
|
+
return (0, _owf_identity_common.uint8ArrayToBase64Url)(decoy);
|
|
643
|
+
};
|
|
644
|
+
//#endregion
|
|
645
|
+
//#region src/present/present.ts
|
|
646
|
+
const presentableKeys = async (rawPayload, disclosures, hasher) => {
|
|
647
|
+
const { disclosureKeymap } = await unpack(rawPayload, disclosures, hasher);
|
|
648
|
+
return Object.keys(disclosureKeymap).sort();
|
|
649
|
+
};
|
|
650
|
+
const presentableKeysSync = (rawPayload, disclosures, hasher) => {
|
|
651
|
+
const { disclosureKeymap } = unpackSync(rawPayload, disclosures, hasher);
|
|
652
|
+
return Object.keys(disclosureKeymap).sort();
|
|
653
|
+
};
|
|
654
|
+
const present = async (sdJwt, presentFrame, hasher) => {
|
|
655
|
+
const { jwt, kbJwt } = splitSdJwt(sdJwt);
|
|
656
|
+
const { jwt: { payload }, disclosures } = await decodeSdJwt(sdJwt, hasher);
|
|
657
|
+
const { _sd_alg: alg } = getSDAlgAndPayload(payload);
|
|
658
|
+
const hash = {
|
|
659
|
+
alg,
|
|
660
|
+
hasher
|
|
661
|
+
};
|
|
662
|
+
const keys = transformPresentationFrame(presentFrame);
|
|
663
|
+
const hashmap = await createHashMapping(disclosures, hash);
|
|
664
|
+
const { disclosureKeymap } = await unpack(payload, disclosures, hasher);
|
|
665
|
+
return [
|
|
666
|
+
jwt,
|
|
667
|
+
...keys.map((k) => hashmap[disclosureKeymap[k]]).filter((d) => d !== void 0).map((d) => d.encode()),
|
|
668
|
+
kbJwt ?? ""
|
|
669
|
+
].join("~");
|
|
670
|
+
};
|
|
671
|
+
const presentSync = (sdJwt, presentFrame, hasher) => {
|
|
672
|
+
const { jwt, kbJwt } = splitSdJwt(sdJwt);
|
|
673
|
+
const { jwt: { payload }, disclosures } = decodeSdJwtSync(sdJwt, hasher);
|
|
674
|
+
const { _sd_alg: alg } = getSDAlgAndPayload(payload);
|
|
675
|
+
const hash = {
|
|
676
|
+
alg,
|
|
677
|
+
hasher
|
|
678
|
+
};
|
|
679
|
+
const keys = transformPresentationFrame(presentFrame);
|
|
680
|
+
const hashmap = createHashMappingSync(disclosures, hash);
|
|
681
|
+
const { disclosureKeymap } = unpackSync(payload, disclosures, hasher);
|
|
682
|
+
return [
|
|
683
|
+
jwt,
|
|
684
|
+
...keys.map((k) => hashmap[disclosureKeymap[k]]).filter((d) => d !== void 0).map((d) => d.encode()),
|
|
685
|
+
kbJwt ?? ""
|
|
686
|
+
].join("~");
|
|
687
|
+
};
|
|
688
|
+
/**
|
|
689
|
+
* Transform the object keys into an array of strings. We are not sorting the array in any way.
|
|
690
|
+
* @param obj The object to transform
|
|
691
|
+
* @param prefix The prefix to add to the keys
|
|
692
|
+
* @returns
|
|
693
|
+
*/
|
|
694
|
+
const transformPresentationFrame = (obj, prefix = []) => {
|
|
695
|
+
return Object.entries(obj).reduce((acc, [key, value]) => {
|
|
696
|
+
const newPrefix = [...prefix, key];
|
|
697
|
+
const encodedPrefix = encodePath(newPrefix);
|
|
698
|
+
if (typeof value === "boolean") {
|
|
699
|
+
if (value) acc.push(encodedPrefix);
|
|
700
|
+
} else if (typeof value === "object" && value !== null) acc.push(encodedPrefix, ...transformPresentationFrame(value, newPrefix));
|
|
701
|
+
return acc;
|
|
702
|
+
}, []);
|
|
703
|
+
};
|
|
704
|
+
const createHashMappingForSerializedDisclosure = (disclosures) => {
|
|
705
|
+
const map = {};
|
|
706
|
+
for (let i = 0; i < disclosures.length; i++) {
|
|
707
|
+
const { digest, encoded, key, salt, value } = disclosures[i];
|
|
708
|
+
map[digest] = Disclosure.fromArray(key ? [
|
|
709
|
+
salt,
|
|
710
|
+
key,
|
|
711
|
+
value
|
|
712
|
+
] : [salt, value], {
|
|
713
|
+
digest,
|
|
714
|
+
encoded
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
return map;
|
|
718
|
+
};
|
|
719
|
+
/**
|
|
720
|
+
* This function selects the serialized disclosures from the payload
|
|
721
|
+
* and array of serialized disclosure based on the presentation frame.
|
|
722
|
+
* If you want to know what is serialized disclosures, check type SerializedDisclosure.
|
|
723
|
+
* @param payload: Record<string, unknown>
|
|
724
|
+
* @param disclosures: SerializedDisclosure[]
|
|
725
|
+
* @param presentationFrame: PresentationFrame<T>
|
|
726
|
+
*/
|
|
727
|
+
const selectDisclosures = (payload, disclosures, presentationFrame) => {
|
|
728
|
+
if (disclosures.length === 0) return [];
|
|
729
|
+
const hashmap = createHashMappingForSerializedDisclosure(disclosures);
|
|
730
|
+
const { disclosureKeymap } = unpackObj(payload, hashmap);
|
|
731
|
+
return transformPresentationFrame(presentationFrame).map((k) => hashmap[disclosureKeymap[k]]).filter((d) => d !== void 0).map((d) => {
|
|
732
|
+
const { salt, key, value, _digest } = d;
|
|
733
|
+
if (!_digest) throw new SDJWTException("Implementation error: _digest is not defined");
|
|
734
|
+
return {
|
|
735
|
+
digest: _digest,
|
|
736
|
+
encoded: d.encode(),
|
|
737
|
+
salt,
|
|
738
|
+
key,
|
|
739
|
+
value
|
|
740
|
+
};
|
|
741
|
+
});
|
|
742
|
+
};
|
|
743
|
+
//#endregion
|
|
744
|
+
//#region src/sdjwt.ts
|
|
745
|
+
const createDisclosureSalt = async (saltGenerator, seenSalts) => {
|
|
746
|
+
const salt = await saltGenerator(16);
|
|
747
|
+
if (typeof salt !== "string") throw new SDJWTException("SaltGenerator must return a string");
|
|
748
|
+
if (seenSalts.has(salt)) throw new SDJWTException("Duplicate disclosure salt detected");
|
|
749
|
+
seenSalts.add(salt);
|
|
750
|
+
return salt;
|
|
751
|
+
};
|
|
752
|
+
const addDisclosureDigest = (digest, seenDigests) => {
|
|
753
|
+
if (seenDigests.has(digest)) throw new SDJWTException("Duplicate disclosure digest detected");
|
|
754
|
+
seenDigests.add(digest);
|
|
755
|
+
};
|
|
756
|
+
var SDJwt = class SDJwt {
|
|
757
|
+
constructor(data) {
|
|
758
|
+
this.jwt = data?.jwt;
|
|
759
|
+
this.disclosures = data?.disclosures;
|
|
760
|
+
this.kbJwt = data?.kbJwt;
|
|
761
|
+
}
|
|
762
|
+
static async decodeSDJwt(sdjwt, hasher) {
|
|
763
|
+
const [encodedJwt, ...encodedDisclosures] = sdjwt.split("~");
|
|
764
|
+
if (encodedDisclosures.length === 0) throw new SDJWTException("Invalid SD-JWT: missing SD-JWT separator");
|
|
765
|
+
const jwt = Jwt.fromEncode(encodedJwt);
|
|
766
|
+
if (!jwt.payload) throw new Error("Payload is undefined on the JWT. Invalid state reached");
|
|
767
|
+
const encodedKeyBindingJwt = encodedDisclosures.pop();
|
|
768
|
+
const kbJwt = encodedKeyBindingJwt ? KBJwt.fromKBEncode(encodedKeyBindingJwt) : void 0;
|
|
769
|
+
const { _sd_alg } = getSDAlgAndPayload(jwt.payload);
|
|
770
|
+
return {
|
|
771
|
+
jwt,
|
|
772
|
+
disclosures: await Promise.all(encodedDisclosures.map((ed) => Disclosure.fromEncode(ed, {
|
|
773
|
+
alg: _sd_alg,
|
|
774
|
+
hasher
|
|
775
|
+
}))),
|
|
776
|
+
kbJwt
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
static async extractJwt(encodedSdJwt) {
|
|
780
|
+
const [encodedJwt, ..._encodedDisclosures] = encodedSdJwt.split("~");
|
|
781
|
+
return Jwt.fromEncode(encodedJwt);
|
|
782
|
+
}
|
|
783
|
+
static async fromEncode(encodedSdJwt, hasher) {
|
|
784
|
+
const { jwt, disclosures, kbJwt } = await SDJwt.decodeSDJwt(encodedSdJwt, hasher);
|
|
785
|
+
return new SDJwt({
|
|
786
|
+
jwt,
|
|
787
|
+
disclosures,
|
|
788
|
+
kbJwt
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
async present(presentFrame, hasher) {
|
|
792
|
+
const disclosures = await this.getPresentDisclosures(presentFrame, hasher);
|
|
793
|
+
return new SDJwt({
|
|
794
|
+
jwt: this.jwt,
|
|
795
|
+
disclosures,
|
|
796
|
+
kbJwt: this.kbJwt
|
|
797
|
+
}).encodeSDJwt();
|
|
798
|
+
}
|
|
799
|
+
async getPresentDisclosures(presentFrame, hasher) {
|
|
800
|
+
if (!this.jwt?.payload || !this.disclosures) throw new SDJWTException("Invalid sd-jwt: jwt or disclosures is missing");
|
|
801
|
+
const { _sd_alg: alg } = getSDAlgAndPayload(this.jwt.payload);
|
|
802
|
+
const hash = {
|
|
803
|
+
alg,
|
|
804
|
+
hasher
|
|
805
|
+
};
|
|
806
|
+
const hashmap = await createHashMapping(this.disclosures, hash);
|
|
807
|
+
const { disclosureKeymap } = await unpack(this.jwt.payload, this.disclosures, hasher);
|
|
808
|
+
return (presentFrame ? transformPresentationFrame(presentFrame) : await this.presentableKeys(hasher)).map((k) => hashmap[disclosureKeymap[k]]).filter((d) => d !== void 0);
|
|
809
|
+
}
|
|
810
|
+
encodeSDJwt() {
|
|
811
|
+
const data = [];
|
|
812
|
+
if (!this.jwt) throw new SDJWTException("Invalid sd-jwt: jwt is missing");
|
|
813
|
+
const encodedJwt = this.jwt.encodeJwt();
|
|
814
|
+
data.push(encodedJwt);
|
|
815
|
+
if (this.disclosures && this.disclosures.length > 0) {
|
|
816
|
+
const encodeddisclosures = this.disclosures.map((dc) => dc.encode()).join("~");
|
|
817
|
+
data.push(encodeddisclosures);
|
|
818
|
+
}
|
|
819
|
+
data.push(this.kbJwt ? this.kbJwt.encodeJwt() : "");
|
|
820
|
+
return data.join("~");
|
|
821
|
+
}
|
|
822
|
+
async keys(hasher) {
|
|
823
|
+
return listKeys(await this.getClaims(hasher)).sort();
|
|
824
|
+
}
|
|
825
|
+
async presentableKeys(hasher) {
|
|
826
|
+
if (!this.jwt?.payload || !this.disclosures) throw new SDJWTException("Invalid sd-jwt: jwt or disclosures is missing");
|
|
827
|
+
const { disclosureKeymap } = await unpack(this.jwt?.payload, this.disclosures, hasher);
|
|
828
|
+
return Object.keys(disclosureKeymap).sort();
|
|
829
|
+
}
|
|
830
|
+
async getClaims(hasher) {
|
|
831
|
+
if (!this.jwt?.payload || !this.disclosures) throw new SDJWTException("Invalid sd-jwt: jwt or disclosures is missing");
|
|
832
|
+
const { unpackedObj } = await unpack(this.jwt.payload, this.disclosures, hasher);
|
|
833
|
+
return unpackedObj;
|
|
834
|
+
}
|
|
835
|
+
};
|
|
836
|
+
const listKeys = (obj, prefix = "") => {
|
|
837
|
+
const keys = [];
|
|
838
|
+
for (const key in obj) {
|
|
839
|
+
if (obj[key] === void 0) continue;
|
|
840
|
+
const escapedKey = encodePathSegment(key);
|
|
841
|
+
const newKey = prefix ? `${prefix}.${escapedKey}` : escapedKey;
|
|
842
|
+
keys.push(newKey);
|
|
843
|
+
const value = obj[key];
|
|
844
|
+
if (value && typeof value === "object") keys.push(...listKeys(value, newKey));
|
|
845
|
+
}
|
|
846
|
+
return keys;
|
|
847
|
+
};
|
|
848
|
+
const pack = async (claims, disclosureFrame, hash, saltGenerator, seenSalts = /* @__PURE__ */ new Set(), seenDigests = /* @__PURE__ */ new Set()) => {
|
|
849
|
+
if (!disclosureFrame) return {
|
|
850
|
+
packedClaims: claims,
|
|
851
|
+
disclosures: []
|
|
852
|
+
};
|
|
853
|
+
const sd = disclosureFrame["_sd"] ?? [];
|
|
854
|
+
const decoyCount = disclosureFrame["_sd_decoy"] ?? 0;
|
|
855
|
+
if (Array.isArray(claims)) {
|
|
856
|
+
const packedClaims = [];
|
|
857
|
+
const disclosures = [];
|
|
858
|
+
const recursivePackedClaims = {};
|
|
859
|
+
for (const key in disclosureFrame) if (key !== "_sd") {
|
|
860
|
+
const idx = Number.parseInt(key, 10);
|
|
861
|
+
const packed = await pack(claims[idx], disclosureFrame[idx], hash, saltGenerator, seenSalts, seenDigests);
|
|
862
|
+
recursivePackedClaims[idx] = packed.packedClaims;
|
|
863
|
+
disclosures.push(...packed.disclosures);
|
|
864
|
+
}
|
|
865
|
+
for (let i = 0; i < claims.length; i++) {
|
|
866
|
+
const claim = recursivePackedClaims[i] ? recursivePackedClaims[i] : claims[i];
|
|
867
|
+
/** This part is set discloure for array items.
|
|
868
|
+
* The example of disclosureFrame of an Array is
|
|
869
|
+
*
|
|
870
|
+
* const claims = {
|
|
871
|
+
* array: ['a', 'b', 'c']
|
|
872
|
+
* }
|
|
873
|
+
*
|
|
874
|
+
* diclosureFrame: DisclosureFrame<typeof claims> = {
|
|
875
|
+
* array: {
|
|
876
|
+
* _sd: [0, 2]
|
|
877
|
+
* }
|
|
878
|
+
* }
|
|
879
|
+
*
|
|
880
|
+
* It means that we want to disclose the first and the third item of the array
|
|
881
|
+
*
|
|
882
|
+
* So If the index `i` is in the disclosure list(sd), then we create a disclosure for the claim
|
|
883
|
+
*/
|
|
884
|
+
if (sd.includes(i)) {
|
|
885
|
+
const disclosure = new Disclosure([await createDisclosureSalt(saltGenerator, seenSalts), claim]);
|
|
886
|
+
const digest = await disclosure.digest(hash);
|
|
887
|
+
addDisclosureDigest(digest, seenDigests);
|
|
888
|
+
packedClaims.push({ ["..."]: digest });
|
|
889
|
+
disclosures.push(disclosure);
|
|
890
|
+
} else packedClaims.push(claim);
|
|
891
|
+
}
|
|
892
|
+
for (let j = 0; j < decoyCount; j++) {
|
|
893
|
+
const decoyDigest = await createDecoy(hash, saltGenerator);
|
|
894
|
+
addDisclosureDigest(decoyDigest, seenDigests);
|
|
895
|
+
packedClaims.push({ ["..."]: decoyDigest });
|
|
896
|
+
}
|
|
897
|
+
return {
|
|
898
|
+
packedClaims,
|
|
899
|
+
disclosures
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
const packedClaims = {};
|
|
903
|
+
const disclosures = [];
|
|
904
|
+
const recursivePackedClaims = {};
|
|
905
|
+
for (const key in disclosureFrame) if (key !== "_sd") {
|
|
906
|
+
const packed = await pack(claims[key], disclosureFrame[key], hash, saltGenerator, seenSalts, seenDigests);
|
|
907
|
+
recursivePackedClaims[key] = packed.packedClaims;
|
|
908
|
+
disclosures.push(...packed.disclosures);
|
|
909
|
+
}
|
|
910
|
+
const _sd = [];
|
|
911
|
+
for (const key in claims) {
|
|
912
|
+
const claim = recursivePackedClaims[key] ? recursivePackedClaims[key] : claims[key];
|
|
913
|
+
if (sd.includes(key)) {
|
|
914
|
+
const disclosure = new Disclosure([
|
|
915
|
+
await createDisclosureSalt(saltGenerator, seenSalts),
|
|
916
|
+
key,
|
|
917
|
+
claim
|
|
918
|
+
]);
|
|
919
|
+
const digest = await disclosure.digest(hash);
|
|
920
|
+
addDisclosureDigest(digest, seenDigests);
|
|
921
|
+
_sd.push(digest);
|
|
922
|
+
disclosures.push(disclosure);
|
|
923
|
+
} else packedClaims[key] = claim;
|
|
924
|
+
}
|
|
925
|
+
for (let j = 0; j < decoyCount; j++) {
|
|
926
|
+
const decoyDigest = await createDecoy(hash, saltGenerator);
|
|
927
|
+
addDisclosureDigest(decoyDigest, seenDigests);
|
|
928
|
+
_sd.push(decoyDigest);
|
|
929
|
+
}
|
|
930
|
+
if (_sd.length > 0) packedClaims["_sd"] = _sd.sort();
|
|
931
|
+
return {
|
|
932
|
+
packedClaims,
|
|
933
|
+
disclosures
|
|
934
|
+
};
|
|
935
|
+
};
|
|
936
|
+
//#endregion
|
|
937
|
+
//#region src/index.ts
|
|
938
|
+
/**
|
|
939
|
+
* Internal utility function to validate that a payload does not contain reserved field names.
|
|
940
|
+
*/
|
|
941
|
+
function validateReservedFieldsInternal(payload) {
|
|
942
|
+
const reservedFields = /* @__PURE__ */ new Set([
|
|
943
|
+
"_sd",
|
|
944
|
+
"_sd_alg",
|
|
945
|
+
SD_DECOY,
|
|
946
|
+
"..."
|
|
947
|
+
]);
|
|
948
|
+
const visit = (node) => {
|
|
949
|
+
if (!node || typeof node !== "object") return;
|
|
950
|
+
for (const [key, value] of Object.entries(node)) {
|
|
951
|
+
if (reservedFields.has(key)) throw new SDJWTException(`Reserved field name "${key}" is not allowed`);
|
|
952
|
+
visit(value);
|
|
953
|
+
}
|
|
954
|
+
};
|
|
955
|
+
visit(payload);
|
|
956
|
+
}
|
|
957
|
+
var SDJwtInstance = class SDJwtInstance {
|
|
958
|
+
static {
|
|
959
|
+
this.DEFAULT_hashAlg = "sha-256";
|
|
960
|
+
}
|
|
961
|
+
constructor(userConfig) {
|
|
962
|
+
this.userConfig = {};
|
|
963
|
+
if (userConfig) {
|
|
964
|
+
if (userConfig.hashAlg && !IANA_HASH_ALGORITHMS.includes(userConfig.hashAlg)) throw new SDJWTException(`Invalid hash algorithm: ${userConfig.hashAlg}`);
|
|
965
|
+
const allowedDisclosureHashAlgorithms = userConfig.allowedDisclosureHashAlgorithms ?? DEFAULT_SECURE_HASH_ALGORITHMS;
|
|
966
|
+
if (userConfig.hashAlg && !allowedDisclosureHashAlgorithms.includes(userConfig.hashAlg)) throw new SDJWTException(`Disallowed hash algorithm: ${userConfig.hashAlg}`);
|
|
967
|
+
this.userConfig = userConfig;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
async createKBJwt(options, sdHash) {
|
|
971
|
+
if (!this.userConfig.kbSigner) throw new SDJWTException("Key Binding Signer not found");
|
|
972
|
+
if (!this.userConfig.kbSignAlg) throw new SDJWTException("Key Binding sign algorithm not specified");
|
|
973
|
+
const { payload } = options;
|
|
974
|
+
const kbJwt = new KBJwt({
|
|
975
|
+
header: {
|
|
976
|
+
typ: KB_JWT_TYP,
|
|
977
|
+
alg: this.userConfig.kbSignAlg
|
|
978
|
+
},
|
|
979
|
+
payload: {
|
|
980
|
+
...payload,
|
|
981
|
+
sd_hash: sdHash
|
|
982
|
+
}
|
|
983
|
+
});
|
|
984
|
+
await kbJwt.sign(this.userConfig.kbSigner);
|
|
985
|
+
return kbJwt;
|
|
986
|
+
}
|
|
987
|
+
async SignJwt(jwt) {
|
|
988
|
+
if (!this.userConfig.signer) throw new SDJWTException("Signer not found");
|
|
989
|
+
await jwt.sign(this.userConfig.signer);
|
|
990
|
+
return jwt;
|
|
991
|
+
}
|
|
992
|
+
async VerifyJwt(jwt, options) {
|
|
993
|
+
if (!this.userConfig.verifier) throw new SDJWTException("Verifier not found");
|
|
994
|
+
return jwt.verify(this.userConfig.verifier, options);
|
|
995
|
+
}
|
|
996
|
+
async issue(payload, disclosureFrame, options) {
|
|
997
|
+
if (!this.userConfig.hasher) throw new SDJWTException("Hasher not found");
|
|
998
|
+
if (!this.userConfig.saltGenerator) throw new SDJWTException("SaltGenerator not found");
|
|
999
|
+
if (!this.userConfig.signAlg) throw new SDJWTException("sign alogrithm not specified");
|
|
1000
|
+
if (this.userConfig.signAlg === "none") throw new SDJWTException("sign algorithm \"none\" is not allowed");
|
|
1001
|
+
this.validateReservedFields(payload);
|
|
1002
|
+
this.validateDisclosureFrame(disclosureFrame);
|
|
1003
|
+
const hasher = this.userConfig.hasher;
|
|
1004
|
+
const hashAlg = this.userConfig.hashAlg ?? SDJwtInstance.DEFAULT_hashAlg;
|
|
1005
|
+
if (!(this.userConfig.allowedDisclosureHashAlgorithms ?? DEFAULT_SECURE_HASH_ALGORITHMS).includes(hashAlg)) throw new SDJWTException(`Disallowed hash algorithm: ${hashAlg}`);
|
|
1006
|
+
const { packedClaims, disclosures } = await pack(payload, disclosureFrame, {
|
|
1007
|
+
hasher,
|
|
1008
|
+
alg: hashAlg
|
|
1009
|
+
}, this.userConfig.saltGenerator);
|
|
1010
|
+
const alg = this.userConfig.signAlg;
|
|
1011
|
+
const OptionHeader = options?.header ?? {};
|
|
1012
|
+
const jwt = new Jwt({
|
|
1013
|
+
header: {
|
|
1014
|
+
...this.userConfig.omitTyp ? OptionHeader : {
|
|
1015
|
+
typ: this.type,
|
|
1016
|
+
...OptionHeader
|
|
1017
|
+
},
|
|
1018
|
+
alg
|
|
1019
|
+
},
|
|
1020
|
+
payload: {
|
|
1021
|
+
...packedClaims,
|
|
1022
|
+
_sd_alg: disclosureFrame ? hashAlg : void 0
|
|
1023
|
+
}
|
|
1024
|
+
});
|
|
1025
|
+
await this.SignJwt(jwt);
|
|
1026
|
+
return new SDJwt({
|
|
1027
|
+
jwt,
|
|
1028
|
+
disclosures
|
|
1029
|
+
}).encodeSDJwt();
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Validates if the payload contains any reserved claim names. If so it will throw an error.
|
|
1033
|
+
* @param payload
|
|
1034
|
+
* @returns
|
|
1035
|
+
*/
|
|
1036
|
+
validateReservedFields(payload) {
|
|
1037
|
+
validateReservedFieldsInternal(payload);
|
|
1038
|
+
}
|
|
1039
|
+
validateDisclosureFrame(_disclosureFrame) {}
|
|
1040
|
+
async present(encodedSDJwt, presentationFrame, options) {
|
|
1041
|
+
if (!this.userConfig.hasher) throw new SDJWTException("Hasher not found");
|
|
1042
|
+
const hasher = this.userConfig.hasher;
|
|
1043
|
+
const sdjwt = await SDJwt.fromEncode(encodedSDJwt, hasher);
|
|
1044
|
+
if (sdjwt.kbJwt) throw new SDJWTException("Holder cannot present an SD-JWT with KB-JWT");
|
|
1045
|
+
if (!sdjwt.jwt?.payload) throw new SDJWTException("Payload not found");
|
|
1046
|
+
const presentSdJwtWithoutKb = await sdjwt.present(presentationFrame, hasher);
|
|
1047
|
+
if (!options?.kb) return presentSdJwtWithoutKb;
|
|
1048
|
+
const sdHashStr = await this.calculateSDHash(presentSdJwtWithoutKb, sdjwt, hasher);
|
|
1049
|
+
sdjwt.kbJwt = await this.createKBJwt(options.kb, sdHashStr);
|
|
1050
|
+
return sdjwt.present(presentationFrame, hasher);
|
|
1051
|
+
}
|
|
1052
|
+
async verify(encodedSDJwt, options) {
|
|
1053
|
+
if (!this.userConfig.hasher) throw new SDJWTException("Hasher not found");
|
|
1054
|
+
const hasher = this.userConfig.hasher;
|
|
1055
|
+
const sdjwt = await SDJwt.fromEncode(encodedSDJwt, hasher);
|
|
1056
|
+
if (!sdjwt.jwt?.payload) throw new SDJWTException("Invalid SD JWT");
|
|
1057
|
+
const { payload, header } = await this.validate(encodedSDJwt, options);
|
|
1058
|
+
if (options?.requiredClaimKeys) {
|
|
1059
|
+
const keys = await sdjwt.keys(hasher);
|
|
1060
|
+
const missingKeys = options.requiredClaimKeys.filter((k) => !keys.includes(k));
|
|
1061
|
+
if (missingKeys.length > 0) throw new SDJWTException(`Missing required claim keys: ${missingKeys.join(", ")}`);
|
|
1062
|
+
}
|
|
1063
|
+
if (!options?.keyBindingNonce) return {
|
|
1064
|
+
payload,
|
|
1065
|
+
header
|
|
1066
|
+
};
|
|
1067
|
+
if (!sdjwt.kbJwt) throw new SDJWTException("Key Binding JWT not exist");
|
|
1068
|
+
if (!this.userConfig.kbVerifier) throw new SDJWTException("Key Binding Verifier not found");
|
|
1069
|
+
const kb = await sdjwt.kbJwt.verifyKB({
|
|
1070
|
+
verifier: this.userConfig.kbVerifier,
|
|
1071
|
+
payload,
|
|
1072
|
+
nonce: options.keyBindingNonce,
|
|
1073
|
+
options
|
|
1074
|
+
});
|
|
1075
|
+
if (!kb) throw new Error("signature is not valid");
|
|
1076
|
+
const sdHashfromKb = kb.payload.sd_hash;
|
|
1077
|
+
const presentSdJwtWithoutKb = new SDJwt({
|
|
1078
|
+
jwt: sdjwt.jwt,
|
|
1079
|
+
disclosures: sdjwt.disclosures
|
|
1080
|
+
}).encodeSDJwt();
|
|
1081
|
+
if (await this.calculateSDHash(presentSdJwtWithoutKb, sdjwt, hasher) !== sdHashfromKb) throw new SDJWTException("Invalid sd_hash in Key Binding JWT");
|
|
1082
|
+
return {
|
|
1083
|
+
payload,
|
|
1084
|
+
header,
|
|
1085
|
+
kb
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
/**
|
|
1089
|
+
* Safe verification that collects all errors instead of failing fast.
|
|
1090
|
+
* Returns a result object with either the verified data or an array of all errors.
|
|
1091
|
+
*
|
|
1092
|
+
* @param encodedSDJwt - The encoded SD-JWT to verify
|
|
1093
|
+
* @param options - Verification options
|
|
1094
|
+
* @returns A SafeVerifyResult containing either success data or collected errors
|
|
1095
|
+
*/
|
|
1096
|
+
async safeVerify(encodedSDJwt, options) {
|
|
1097
|
+
const errors = [];
|
|
1098
|
+
const addError = (code, message, details) => {
|
|
1099
|
+
errors.push({
|
|
1100
|
+
code,
|
|
1101
|
+
message,
|
|
1102
|
+
details
|
|
1103
|
+
});
|
|
1104
|
+
};
|
|
1105
|
+
const exceptionToCode = (error) => {
|
|
1106
|
+
const message = error.message.toLowerCase();
|
|
1107
|
+
if (message.includes("hasher not found")) return "HASHER_NOT_FOUND";
|
|
1108
|
+
if (message.includes("verifier not found")) return "VERIFIER_NOT_FOUND";
|
|
1109
|
+
if (message.includes("invalid sd jwt") || message.includes("invalid jwt")) return "INVALID_SD_JWT";
|
|
1110
|
+
if (message.includes("not yet valid")) return "JWT_NOT_YET_VALID";
|
|
1111
|
+
if (message.includes("expired")) return "JWT_EXPIRED";
|
|
1112
|
+
if (message.includes("signature")) return "INVALID_JWT_SIGNATURE";
|
|
1113
|
+
if (message.includes("missing required claim")) return "MISSING_REQUIRED_CLAIMS";
|
|
1114
|
+
if (message.includes("key binding jwt not exist")) return "KEY_BINDING_JWT_MISSING";
|
|
1115
|
+
if (message.includes("key binding verifier not found")) return "KEY_BINDING_VERIFIER_NOT_FOUND";
|
|
1116
|
+
if (message.includes("sd_hash")) return "KEY_BINDING_SD_HASH_INVALID";
|
|
1117
|
+
return "UNKNOWN_ERROR";
|
|
1118
|
+
};
|
|
1119
|
+
if (!this.userConfig.hasher) addError("HASHER_NOT_FOUND", "Hasher not found");
|
|
1120
|
+
if (!this.userConfig.verifier) addError("VERIFIER_NOT_FOUND", "Verifier not found");
|
|
1121
|
+
if (errors.length > 0) return {
|
|
1122
|
+
success: false,
|
|
1123
|
+
errors
|
|
1124
|
+
};
|
|
1125
|
+
if (!this.userConfig.hasher) throw new SDJWTException("Hasher not found");
|
|
1126
|
+
const hasher = this.userConfig.hasher;
|
|
1127
|
+
let sdjwt;
|
|
1128
|
+
let payload;
|
|
1129
|
+
let header;
|
|
1130
|
+
try {
|
|
1131
|
+
sdjwt = await SDJwt.fromEncode(encodedSDJwt, hasher);
|
|
1132
|
+
if (!sdjwt.jwt?.payload) addError("INVALID_SD_JWT", "Invalid SD JWT: missing JWT or payload");
|
|
1133
|
+
} catch (e) {
|
|
1134
|
+
const error = ensureError(e);
|
|
1135
|
+
addError("INVALID_SD_JWT", `Failed to decode SD-JWT: ${error.message}`, error);
|
|
1136
|
+
}
|
|
1137
|
+
if (sdjwt?.jwt) try {
|
|
1138
|
+
header = (await this.VerifyJwt(sdjwt.jwt, {
|
|
1139
|
+
...options,
|
|
1140
|
+
skipJwtClaimValidation: true
|
|
1141
|
+
})).header;
|
|
1142
|
+
payload = await sdjwt.getClaims(hasher);
|
|
1143
|
+
validateJwtPayload(payload, options);
|
|
1144
|
+
} catch (e) {
|
|
1145
|
+
const error = ensureError(e);
|
|
1146
|
+
addError(exceptionToCode(error), error.message, error);
|
|
1147
|
+
}
|
|
1148
|
+
if (sdjwt && options?.requiredClaimKeys) try {
|
|
1149
|
+
const keys = await sdjwt.keys(hasher);
|
|
1150
|
+
const missingKeys = options.requiredClaimKeys.filter((k) => !keys.includes(k));
|
|
1151
|
+
if (missingKeys.length > 0) addError("MISSING_REQUIRED_CLAIMS", `Missing required claim keys: ${missingKeys.join(", ")}`, { missingKeys });
|
|
1152
|
+
} catch (e) {
|
|
1153
|
+
const error = ensureError(e);
|
|
1154
|
+
addError("UNKNOWN_ERROR", `Failed to check required claims: ${error.message}`, error);
|
|
1155
|
+
}
|
|
1156
|
+
let kb;
|
|
1157
|
+
if (options?.keyBindingNonce && sdjwt) {
|
|
1158
|
+
if (!sdjwt.kbJwt) addError("KEY_BINDING_JWT_MISSING", "Key Binding JWT not exist");
|
|
1159
|
+
else if (!this.userConfig.kbVerifier) addError("KEY_BINDING_VERIFIER_NOT_FOUND", "Key Binding Verifier not found");
|
|
1160
|
+
else if (payload) try {
|
|
1161
|
+
const kbResult = await sdjwt.kbJwt.verifyKB({
|
|
1162
|
+
verifier: this.userConfig.kbVerifier,
|
|
1163
|
+
payload,
|
|
1164
|
+
nonce: options.keyBindingNonce,
|
|
1165
|
+
options
|
|
1166
|
+
});
|
|
1167
|
+
if (!kbResult) addError("KEY_BINDING_SIGNATURE_INVALID", "Key binding signature is not valid");
|
|
1168
|
+
else {
|
|
1169
|
+
kb = kbResult;
|
|
1170
|
+
const presentSdJwtWithoutKb = new SDJwt({
|
|
1171
|
+
jwt: sdjwt.jwt,
|
|
1172
|
+
disclosures: sdjwt.disclosures
|
|
1173
|
+
}).encodeSDJwt();
|
|
1174
|
+
const sdHashStr = await this.calculateSDHash(presentSdJwtWithoutKb, sdjwt, hasher);
|
|
1175
|
+
if (sdHashStr !== kbResult.payload.sd_hash) addError("KEY_BINDING_SD_HASH_INVALID", "Invalid sd_hash in Key Binding JWT", {
|
|
1176
|
+
expected: sdHashStr,
|
|
1177
|
+
received: kbResult.payload.sd_hash
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
} catch (e) {
|
|
1181
|
+
const error = ensureError(e);
|
|
1182
|
+
addError("KEY_BINDING_SIGNATURE_INVALID", `Key binding verification failed: ${error.message}`, error);
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
if (errors.length > 0) return {
|
|
1186
|
+
success: false,
|
|
1187
|
+
errors
|
|
1188
|
+
};
|
|
1189
|
+
return {
|
|
1190
|
+
success: true,
|
|
1191
|
+
data: {
|
|
1192
|
+
payload,
|
|
1193
|
+
header,
|
|
1194
|
+
kb
|
|
1195
|
+
}
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
async calculateSDHash(presentSdJwtWithoutKb, sdjwt, hasher) {
|
|
1199
|
+
if (!sdjwt.jwt?.payload) throw new SDJWTException("Invalid SD JWT");
|
|
1200
|
+
const { _sd_alg } = getSDAlgAndPayload(sdjwt.jwt.payload);
|
|
1201
|
+
const sdHash = await hasher(presentSdJwtWithoutKb, _sd_alg);
|
|
1202
|
+
return (0, _owf_identity_common.uint8ArrayToBase64Url)(sdHash);
|
|
1203
|
+
}
|
|
1204
|
+
/**
|
|
1205
|
+
* This function is for validating the SD JWT
|
|
1206
|
+
* Checking signature, if provided the iat and exp when provided and return its the claims
|
|
1207
|
+
* @param encodedSDJwt
|
|
1208
|
+
* @param options
|
|
1209
|
+
* @returns
|
|
1210
|
+
*/
|
|
1211
|
+
async validate(encodedSDJwt, options) {
|
|
1212
|
+
if (!this.userConfig.hasher) throw new SDJWTException("Hasher not found");
|
|
1213
|
+
const hasher = this.userConfig.hasher;
|
|
1214
|
+
const sdjwt = await SDJwt.fromEncode(encodedSDJwt, hasher);
|
|
1215
|
+
if (!sdjwt.jwt) throw new SDJWTException("Invalid SD JWT");
|
|
1216
|
+
const verifiedPayloads = await this.VerifyJwt(sdjwt.jwt, {
|
|
1217
|
+
...options,
|
|
1218
|
+
skipJwtClaimValidation: true
|
|
1219
|
+
});
|
|
1220
|
+
const claims = await sdjwt.getClaims(hasher);
|
|
1221
|
+
validateReservedFieldsInternal(claims);
|
|
1222
|
+
validateJwtPayload(claims, options);
|
|
1223
|
+
return {
|
|
1224
|
+
payload: claims,
|
|
1225
|
+
header: verifiedPayloads.header
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
config(newConfig) {
|
|
1229
|
+
this.userConfig = {
|
|
1230
|
+
...this.userConfig,
|
|
1231
|
+
...newConfig
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
encode(sdJwt) {
|
|
1235
|
+
return sdJwt.encodeSDJwt();
|
|
1236
|
+
}
|
|
1237
|
+
decode(endcodedSDJwt) {
|
|
1238
|
+
if (!this.userConfig.hasher) throw new SDJWTException("Hasher not found");
|
|
1239
|
+
return SDJwt.fromEncode(endcodedSDJwt, this.userConfig.hasher);
|
|
1240
|
+
}
|
|
1241
|
+
async keys(endcodedSDJwt) {
|
|
1242
|
+
if (!this.userConfig.hasher) throw new SDJWTException("Hasher not found");
|
|
1243
|
+
return (await SDJwt.fromEncode(endcodedSDJwt, this.userConfig.hasher)).keys(this.userConfig.hasher);
|
|
1244
|
+
}
|
|
1245
|
+
async presentableKeys(endcodedSDJwt) {
|
|
1246
|
+
if (!this.userConfig.hasher) throw new SDJWTException("Hasher not found");
|
|
1247
|
+
return (await SDJwt.fromEncode(endcodedSDJwt, this.userConfig.hasher)).presentableKeys(this.userConfig.hasher);
|
|
1248
|
+
}
|
|
1249
|
+
async getClaims(endcodedSDJwt) {
|
|
1250
|
+
if (!this.userConfig.hasher) throw new SDJWTException("Hasher not found");
|
|
1251
|
+
return (await SDJwt.fromEncode(endcodedSDJwt, this.userConfig.hasher)).getClaims(this.userConfig.hasher);
|
|
1252
|
+
}
|
|
1253
|
+
toFlattenJSON(endcodedSDJwt) {
|
|
1254
|
+
return FlattenJSON.fromEncode(endcodedSDJwt);
|
|
1255
|
+
}
|
|
1256
|
+
toGeneralJSON(endcodedSDJwt) {
|
|
1257
|
+
return GeneralJSON.fromEncode(endcodedSDJwt);
|
|
1258
|
+
}
|
|
1259
|
+
};
|
|
1260
|
+
var SDJwtGeneralJSONInstance = class {
|
|
1261
|
+
static {
|
|
1262
|
+
this.DEFAULT_hashAlg = "sha-256";
|
|
1263
|
+
}
|
|
1264
|
+
constructor(userConfig) {
|
|
1265
|
+
this.userConfig = {};
|
|
1266
|
+
if (userConfig) {
|
|
1267
|
+
if (userConfig.hashAlg && !IANA_HASH_ALGORITHMS.includes(userConfig.hashAlg)) throw new SDJWTException(`Invalid hash algorithm: ${userConfig.hashAlg}`);
|
|
1268
|
+
const allowedDisclosureHashAlgorithms = userConfig.allowedDisclosureHashAlgorithms ?? DEFAULT_SECURE_HASH_ALGORITHMS;
|
|
1269
|
+
if (userConfig.hashAlg && !allowedDisclosureHashAlgorithms.includes(userConfig.hashAlg)) throw new SDJWTException(`Disallowed hash algorithm: ${userConfig.hashAlg}`);
|
|
1270
|
+
this.userConfig = userConfig;
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
async createKBJwt(options, sdHash) {
|
|
1274
|
+
if (!this.userConfig.kbSigner) throw new SDJWTException("Key Binding Signer not found");
|
|
1275
|
+
if (!this.userConfig.kbSignAlg) throw new SDJWTException("Key Binding sign algorithm not specified");
|
|
1276
|
+
const { payload } = options;
|
|
1277
|
+
const kbJwt = new KBJwt({
|
|
1278
|
+
header: {
|
|
1279
|
+
typ: KB_JWT_TYP,
|
|
1280
|
+
alg: this.userConfig.kbSignAlg
|
|
1281
|
+
},
|
|
1282
|
+
payload: {
|
|
1283
|
+
...payload,
|
|
1284
|
+
sd_hash: sdHash
|
|
1285
|
+
}
|
|
1286
|
+
});
|
|
1287
|
+
await kbJwt.sign(this.userConfig.kbSigner);
|
|
1288
|
+
return kbJwt;
|
|
1289
|
+
}
|
|
1290
|
+
encodeObj(obj) {
|
|
1291
|
+
return (0, _owf_identity_common.base64urlEncode)(JSON.stringify(obj));
|
|
1292
|
+
}
|
|
1293
|
+
async issue(payload, disclosureFrame, options) {
|
|
1294
|
+
if (!this.userConfig.hasher) throw new SDJWTException("Hasher not found");
|
|
1295
|
+
if (!this.userConfig.saltGenerator) throw new SDJWTException("SaltGenerator not found");
|
|
1296
|
+
this.validateReservedFields(payload);
|
|
1297
|
+
this.validateDisclosureFrame(disclosureFrame);
|
|
1298
|
+
const hasher = this.userConfig.hasher;
|
|
1299
|
+
const hashAlg = this.userConfig.hashAlg ?? SDJwtInstance.DEFAULT_hashAlg;
|
|
1300
|
+
const { packedClaims, disclosures } = await pack(payload, disclosureFrame, {
|
|
1301
|
+
hasher,
|
|
1302
|
+
alg: hashAlg
|
|
1303
|
+
}, this.userConfig.saltGenerator);
|
|
1304
|
+
const encodedSDJwtPayload = this.encodeObj({
|
|
1305
|
+
...packedClaims,
|
|
1306
|
+
_sd_alg: disclosureFrame ? hashAlg : void 0
|
|
1307
|
+
});
|
|
1308
|
+
return new GeneralJSON({
|
|
1309
|
+
payload: encodedSDJwtPayload,
|
|
1310
|
+
disclosures: disclosures.map((disclosure) => disclosure.encode()),
|
|
1311
|
+
signatures: await Promise.all(options.sigs.map(async (s) => {
|
|
1312
|
+
const { signer, alg, kid, header } = s;
|
|
1313
|
+
const protectedHeader = {
|
|
1314
|
+
typ: this.type,
|
|
1315
|
+
alg,
|
|
1316
|
+
kid,
|
|
1317
|
+
...header
|
|
1318
|
+
};
|
|
1319
|
+
const encodedProtectedHeader = this.encodeObj(protectedHeader);
|
|
1320
|
+
return {
|
|
1321
|
+
protected: encodedProtectedHeader,
|
|
1322
|
+
signature: await signer(`${encodedProtectedHeader}.${encodedSDJwtPayload}`)
|
|
1323
|
+
};
|
|
1324
|
+
}))
|
|
1325
|
+
});
|
|
1326
|
+
}
|
|
1327
|
+
/**
|
|
1328
|
+
* Validates if the payload contains any reserved claim names. If so it will throw an error.
|
|
1329
|
+
* @param payload
|
|
1330
|
+
* @returns
|
|
1331
|
+
*/
|
|
1332
|
+
validateReservedFields(payload) {
|
|
1333
|
+
validateReservedFieldsInternal(payload);
|
|
1334
|
+
}
|
|
1335
|
+
validateDisclosureFrame(_disclosureFrame) {}
|
|
1336
|
+
async present(generalJSON, presentationFrame, options) {
|
|
1337
|
+
if (!this.userConfig.hasher) throw new SDJWTException("Hasher not found");
|
|
1338
|
+
const hasher = this.userConfig.hasher;
|
|
1339
|
+
const encodedSDJwt = generalJSON.toEncoded(0);
|
|
1340
|
+
const sdjwt = await SDJwt.fromEncode(encodedSDJwt, hasher);
|
|
1341
|
+
if (sdjwt.kbJwt) throw new SDJWTException("Holder cannot present an SD-JWT with KB-JWT");
|
|
1342
|
+
if (!sdjwt.jwt?.payload) throw new SDJWTException("Payload not found");
|
|
1343
|
+
const encodedDisclosures = (await sdjwt.getPresentDisclosures(presentationFrame, hasher)).map((d) => d.encode());
|
|
1344
|
+
const presentedGeneralJSON = new GeneralJSON({
|
|
1345
|
+
payload: generalJSON.payload,
|
|
1346
|
+
disclosures: encodedDisclosures,
|
|
1347
|
+
signatures: generalJSON.signatures
|
|
1348
|
+
});
|
|
1349
|
+
if (!options?.kb) return presentedGeneralJSON;
|
|
1350
|
+
const presentSdJwtWithoutKb = await sdjwt.present(presentationFrame, hasher);
|
|
1351
|
+
const sdHashStr = await this.calculateSDHash(presentSdJwtWithoutKb, sdjwt, hasher);
|
|
1352
|
+
presentedGeneralJSON.kb_jwt = (await this.createKBJwt(options.kb, sdHashStr)).encodeJwt();
|
|
1353
|
+
return presentedGeneralJSON;
|
|
1354
|
+
}
|
|
1355
|
+
async verify(generalJSON, options) {
|
|
1356
|
+
if (!this.userConfig.hasher) throw new SDJWTException("Hasher not found");
|
|
1357
|
+
const hasher = this.userConfig.hasher;
|
|
1358
|
+
const { payload, headers } = await this.validate(generalJSON, options);
|
|
1359
|
+
const encodedSDJwt = generalJSON.toEncoded(0);
|
|
1360
|
+
const sdjwt = await SDJwt.fromEncode(encodedSDJwt, hasher);
|
|
1361
|
+
if (!sdjwt.jwt?.payload) throw new SDJWTException("Invalid SD JWT");
|
|
1362
|
+
if (options?.requiredClaimKeys) {
|
|
1363
|
+
const keys = await sdjwt.keys(hasher);
|
|
1364
|
+
const missingKeys = options?.requiredClaimKeys.filter((k) => !keys.includes(k));
|
|
1365
|
+
if (missingKeys.length > 0) throw new SDJWTException(`Missing required claim keys: ${missingKeys.join(", ")}`);
|
|
1366
|
+
}
|
|
1367
|
+
if (!options?.keyBindingNonce) return {
|
|
1368
|
+
payload,
|
|
1369
|
+
headers
|
|
1370
|
+
};
|
|
1371
|
+
if (!sdjwt.kbJwt) throw new SDJWTException("Key Binding JWT not exist");
|
|
1372
|
+
if (!this.userConfig.kbVerifier) throw new SDJWTException("Key Binding Verifier not found");
|
|
1373
|
+
const kb = await sdjwt.kbJwt.verifyKB({
|
|
1374
|
+
verifier: this.userConfig.kbVerifier,
|
|
1375
|
+
payload,
|
|
1376
|
+
nonce: options.keyBindingNonce,
|
|
1377
|
+
options
|
|
1378
|
+
});
|
|
1379
|
+
if (!kb) throw new Error("signature is not valid");
|
|
1380
|
+
const sdHashfromKb = kb.payload.sd_hash;
|
|
1381
|
+
const presentSdJwtWithoutKb = new SDJwt({
|
|
1382
|
+
jwt: sdjwt.jwt,
|
|
1383
|
+
disclosures: sdjwt.disclosures
|
|
1384
|
+
}).encodeSDJwt();
|
|
1385
|
+
if (await this.calculateSDHash(presentSdJwtWithoutKb, sdjwt, hasher) !== sdHashfromKb) throw new SDJWTException("Invalid sd_hash in Key Binding JWT");
|
|
1386
|
+
return {
|
|
1387
|
+
payload,
|
|
1388
|
+
headers,
|
|
1389
|
+
kb
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
async calculateSDHash(presentSdJwtWithoutKb, sdjwt, hasher) {
|
|
1393
|
+
if (!sdjwt.jwt?.payload) throw new SDJWTException("Invalid SD JWT");
|
|
1394
|
+
const { _sd_alg } = getSDAlgAndPayload(sdjwt.jwt.payload);
|
|
1395
|
+
const sdHash = await hasher(presentSdJwtWithoutKb, _sd_alg);
|
|
1396
|
+
return (0, _owf_identity_common.uint8ArrayToBase64Url)(sdHash);
|
|
1397
|
+
}
|
|
1398
|
+
async validate(generalJSON, options) {
|
|
1399
|
+
if (!this.userConfig.hasher) throw new SDJWTException("Hasher not found");
|
|
1400
|
+
if (!this.userConfig.verifier) throw new SDJWTException("Verifier not found");
|
|
1401
|
+
const hasher = this.userConfig.hasher;
|
|
1402
|
+
const verifier = this.userConfig.verifier;
|
|
1403
|
+
const { payload, signatures } = generalJSON;
|
|
1404
|
+
const results = await Promise.all(signatures.map(async (s) => {
|
|
1405
|
+
const { protected: encodedHeader, signature } = s;
|
|
1406
|
+
const verified = await verifier(`${encodedHeader}.${payload}`, signature, {
|
|
1407
|
+
...options,
|
|
1408
|
+
skipJwtClaimValidation: true
|
|
1409
|
+
});
|
|
1410
|
+
const header = decodeBase64urlJsonStrict(encodedHeader, "Invalid JWT");
|
|
1411
|
+
if (typeof header.alg !== "string" || header.alg === "none") throw new SDJWTException("Verify Error: alg \"none\" is not allowed");
|
|
1412
|
+
if (options?.allowedIssuerAlgorithms && !options.allowedIssuerAlgorithms.includes(header.alg)) throw new SDJWTException(`Verify Error: Disallowed alg ${header.alg}`);
|
|
1413
|
+
return {
|
|
1414
|
+
verified,
|
|
1415
|
+
header
|
|
1416
|
+
};
|
|
1417
|
+
}));
|
|
1418
|
+
if (!results.every((r) => r.verified)) throw new SDJWTException("Signature is not valid");
|
|
1419
|
+
const encodedSDJwt = generalJSON.toEncoded(0);
|
|
1420
|
+
const sdjwt = await SDJwt.fromEncode(encodedSDJwt, hasher);
|
|
1421
|
+
if (!sdjwt.jwt) throw new SDJWTException("Invalid SD JWT");
|
|
1422
|
+
const claims = await sdjwt.getClaims(hasher);
|
|
1423
|
+
validateReservedFieldsInternal(claims);
|
|
1424
|
+
validateJwtPayload(claims, options);
|
|
1425
|
+
return {
|
|
1426
|
+
payload: claims,
|
|
1427
|
+
headers: results.map((r) => r.header)
|
|
1428
|
+
};
|
|
1429
|
+
}
|
|
1430
|
+
config(newConfig) {
|
|
1431
|
+
this.userConfig = {
|
|
1432
|
+
...this.userConfig,
|
|
1433
|
+
...newConfig
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1436
|
+
encode(sdJwt, index) {
|
|
1437
|
+
return sdJwt.toEncoded(index);
|
|
1438
|
+
}
|
|
1439
|
+
decode(endcodedSDJwt) {
|
|
1440
|
+
return GeneralJSON.fromEncode(endcodedSDJwt);
|
|
1441
|
+
}
|
|
1442
|
+
async keys(generalSdjwt) {
|
|
1443
|
+
if (!this.userConfig.hasher) throw new SDJWTException("Hasher not found");
|
|
1444
|
+
const endcodedSDJwt = generalSdjwt.toEncoded(0);
|
|
1445
|
+
return (await SDJwt.fromEncode(endcodedSDJwt, this.userConfig.hasher)).keys(this.userConfig.hasher);
|
|
1446
|
+
}
|
|
1447
|
+
async presentableKeys(generalSdjwt) {
|
|
1448
|
+
if (!this.userConfig.hasher) throw new SDJWTException("Hasher not found");
|
|
1449
|
+
const endcodedSDJwt = generalSdjwt.toEncoded(0);
|
|
1450
|
+
return (await SDJwt.fromEncode(endcodedSDJwt, this.userConfig.hasher)).presentableKeys(this.userConfig.hasher);
|
|
1451
|
+
}
|
|
1452
|
+
async getClaims(generalSdjwt) {
|
|
1453
|
+
if (!this.userConfig.hasher) throw new SDJWTException("Hasher not found");
|
|
1454
|
+
const endcodedSDJwt = generalSdjwt.toEncoded(0);
|
|
1455
|
+
return (await SDJwt.fromEncode(endcodedSDJwt, this.userConfig.hasher)).getClaims(this.userConfig.hasher);
|
|
1456
|
+
}
|
|
1457
|
+
};
|
|
1458
|
+
//#endregion
|
|
1459
|
+
exports.DEFAULT_SECURE_HASH_ALGORITHMS = DEFAULT_SECURE_HASH_ALGORITHMS;
|
|
1460
|
+
exports.Disclosure = Disclosure;
|
|
1461
|
+
exports.FlattenJSON = FlattenJSON;
|
|
1462
|
+
exports.GeneralJSON = GeneralJSON;
|
|
1463
|
+
exports.IANA_HASH_ALGORITHMS = IANA_HASH_ALGORITHMS;
|
|
1464
|
+
exports.Jwt = Jwt;
|
|
1465
|
+
exports.KBJwt = KBJwt;
|
|
1466
|
+
exports.KB_JWT_TYP = KB_JWT_TYP;
|
|
1467
|
+
exports.SDJWTException = SDJWTException;
|
|
1468
|
+
exports.SDJwt = SDJwt;
|
|
1469
|
+
exports.SDJwtGeneralJSONInstance = SDJwtGeneralJSONInstance;
|
|
1470
|
+
exports.SDJwtInstance = SDJwtInstance;
|
|
1471
|
+
exports.SD_DECOY = SD_DECOY;
|
|
1472
|
+
exports.SD_DIGEST = SD_DIGEST;
|
|
1473
|
+
exports.SD_LIST_KEY = SD_LIST_KEY;
|
|
1474
|
+
exports.SD_SEPARATOR = SD_SEPARATOR;
|
|
1475
|
+
Object.defineProperty(exports, "base64UrlToUint8Array", {
|
|
1476
|
+
enumerable: true,
|
|
1477
|
+
get: function() {
|
|
1478
|
+
return _owf_identity_common.base64UrlToUint8Array;
|
|
1479
|
+
}
|
|
1480
|
+
});
|
|
1481
|
+
Object.defineProperty(exports, "base64urlDecode", {
|
|
1482
|
+
enumerable: true,
|
|
1483
|
+
get: function() {
|
|
1484
|
+
return _owf_identity_common.base64urlDecode;
|
|
1485
|
+
}
|
|
1486
|
+
});
|
|
1487
|
+
Object.defineProperty(exports, "base64urlEncode", {
|
|
1488
|
+
enumerable: true,
|
|
1489
|
+
get: function() {
|
|
1490
|
+
return _owf_identity_common.base64urlEncode;
|
|
1491
|
+
}
|
|
1492
|
+
});
|
|
1493
|
+
exports.createDecoy = createDecoy;
|
|
1494
|
+
exports.createHashMapping = createHashMapping;
|
|
1495
|
+
exports.createHashMappingForSerializedDisclosure = createHashMappingForSerializedDisclosure;
|
|
1496
|
+
exports.createHashMappingSync = createHashMappingSync;
|
|
1497
|
+
exports.decodeJwt = decodeJwt;
|
|
1498
|
+
exports.decodeSdJwt = decodeSdJwt;
|
|
1499
|
+
exports.decodeSdJwtSync = decodeSdJwtSync;
|
|
1500
|
+
exports.encodePath = encodePath;
|
|
1501
|
+
exports.encodePathSegment = encodePathSegment;
|
|
1502
|
+
exports.ensureError = ensureError;
|
|
1503
|
+
exports.getClaims = getClaims;
|
|
1504
|
+
exports.getClaimsSync = getClaimsSync;
|
|
1505
|
+
exports.getSDAlgAndPayload = getSDAlgAndPayload;
|
|
1506
|
+
exports.listKeys = listKeys;
|
|
1507
|
+
exports.pack = pack;
|
|
1508
|
+
exports.present = present;
|
|
1509
|
+
exports.presentSync = presentSync;
|
|
1510
|
+
exports.presentableKeys = presentableKeys;
|
|
1511
|
+
exports.presentableKeysSync = presentableKeysSync;
|
|
1512
|
+
exports.selectDisclosures = selectDisclosures;
|
|
1513
|
+
exports.splitSdJwt = splitSdJwt;
|
|
1514
|
+
exports.transformPresentationFrame = transformPresentationFrame;
|
|
1515
|
+
Object.defineProperty(exports, "uint8ArrayToBase64Url", {
|
|
1516
|
+
enumerable: true,
|
|
1517
|
+
get: function() {
|
|
1518
|
+
return _owf_identity_common.uint8ArrayToBase64Url;
|
|
1519
|
+
}
|
|
1520
|
+
});
|
|
1521
|
+
exports.unpack = unpack;
|
|
1522
|
+
exports.unpackObj = unpackObj;
|
|
1523
|
+
exports.unpackSync = unpackSync;
|
|
1524
|
+
exports.validateJwtPayload = validateJwtPayload;
|
|
1525
|
+
|
|
1526
|
+
//# sourceMappingURL=index.cjs.map
|