unjwt 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -2
- package/dist/THIRD-PARTY-LICENSES.md +63 -0
- package/dist/_chunks/_internal.mjs +88 -0
- package/dist/_chunks/index.d.mts +401 -41
- package/dist/_chunks/jwe.d.mts +82 -0
- package/dist/_chunks/jwe.mjs +407 -0
- package/dist/_chunks/{_crypto.mjs → jwk.mjs} +637 -26
- package/dist/_chunks/jws.d.mts +111 -0
- package/dist/_chunks/jws.mjs +264 -2031
- package/dist/_chunks/libs/rou3.mjs +5 -0
- package/dist/_chunks/libs/unsecure.d.mts +94 -0
- package/dist/_chunks/libs/unsecure.mjs +144 -0
- package/dist/_chunks/utils.mjs +222 -211
- package/dist/adapters/h3v1/index.d.mts +9 -9
- package/dist/adapters/h3v1/index.mjs +13 -75
- package/dist/adapters/h3v2/index.d.mts +9 -9
- package/dist/adapters/h3v2/index.mjs +14 -38
- package/dist/core/jwe.d.mts +3 -40
- package/dist/core/jwe.mjs +3 -161
- package/dist/core/jwk.d.mts +202 -46
- package/dist/core/jwk.mjs +3 -686
- package/dist/core/jws.d.mts +3 -31
- package/dist/core/jws.mjs +3 -104
- package/dist/core/utils/index.d.mts +74 -60
- package/dist/core/utils/index.mjs +3 -2
- package/dist/index.d.mts +6 -7
- package/dist/index.mjs +5 -5
- package/package.json +24 -22
- package/dist/_chunks/error.d.mts +0 -67
- package/dist/_chunks/jwk.d.mts +0 -708
- package/dist/core/_crypto/index.d.mts +0 -71
- package/dist/core/_crypto/index.mjs +0 -2
package/dist/_chunks/jws.mjs
CHANGED
|
@@ -1,2028 +1,283 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
function isJWTError(error, code) {
|
|
30
|
-
if (!(error instanceof JWTError)) return false;
|
|
31
|
-
return code === void 0 || error.code === code;
|
|
32
|
-
}
|
|
33
|
-
//#endregion
|
|
34
|
-
//#region src/core/utils/jwt.ts
|
|
35
|
-
/**
|
|
36
|
-
* Apply default typ/cty semantics shared by JWS & JWE.
|
|
37
|
-
* - If typ is undefined and payload is a non-Uint8Array object, set typ="JWT".
|
|
38
|
-
* - If typ is "JWT" and payload is a non-Uint8Array object, ensure cty defaults to "application/json".
|
|
39
|
-
*/
|
|
40
|
-
function applyTypCtyDefaults(header, payload) {
|
|
41
|
-
const isObjectPayload = typeof payload === "object" && payload !== null && !(payload instanceof Uint8Array);
|
|
42
|
-
if (header.typ === void 0 && isObjectPayload) header.typ = "JWT";
|
|
43
|
-
if (header.typ?.toLowerCase().includes("jwt") && isObjectPayload) header.cty ||= "application/json";
|
|
44
|
-
return header;
|
|
45
|
-
}
|
|
46
|
-
/** Returns true when headers indicate JSON content. */
|
|
47
|
-
function isJWTContent(header) {
|
|
48
|
-
if (!header) return false;
|
|
49
|
-
if (header.typ?.toLowerCase().includes("jwt")) return true;
|
|
50
|
-
const cty = header.cty?.toLowerCase();
|
|
51
|
-
return cty === "json" || cty === "application/json" || !!cty && cty.endsWith("+json");
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Given a decoded string and JOSE headers, return an object if JSON, otherwise the string.
|
|
55
|
-
*/
|
|
56
|
-
function decodeMaybeJWTString(decodedString, header) {
|
|
57
|
-
if (isJWTContent(header)) {
|
|
58
|
-
if (decodedString.startsWith("{") && decodedString.endsWith("}") || decodedString.startsWith("[") && decodedString.endsWith("]")) try {
|
|
59
|
-
return sanitizeObject(JSON.parse(decodedString));
|
|
60
|
-
} catch {}
|
|
61
|
-
}
|
|
62
|
-
return decodedString;
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Decode a payload that is represented as raw bytes into T | string, honoring forceUint8Array and headers.
|
|
66
|
-
*/
|
|
67
|
-
function decodePayloadFromBytes(bytes, header, forceUint8Array) {
|
|
68
|
-
if (forceUint8Array) return bytes;
|
|
69
|
-
return decodeMaybeJWTString(textDecoder.decode(bytes), header);
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Decode a payload that is a Base64URL segment into T | string | Uint8Array based on flags and headers.
|
|
73
|
-
*/
|
|
74
|
-
function decodePayloadFromB64UrlSegment(payloadEncoded, header, forceUint8Array) {
|
|
75
|
-
if (forceUint8Array) return base64UrlDecode(payloadEncoded, false);
|
|
76
|
-
return decodeMaybeJWTString(base64UrlDecode(payloadEncoded), header);
|
|
77
|
-
}
|
|
78
|
-
/** Convert plaintext input to bytes, shared by JWS & JWE when preparing payload. */
|
|
79
|
-
function getPlaintextBytes(payload) {
|
|
80
|
-
if (payload instanceof Uint8Array) return payload;
|
|
81
|
-
if (typeof payload === "string") return textEncoder.encode(payload);
|
|
82
|
-
if (typeof payload === "object" && payload !== null) return textEncoder.encode(JSON.stringify(payload));
|
|
83
|
-
throw new TypeError("Plaintext must be a string, Uint8Array, or a JSON-serializable object.");
|
|
84
|
-
}
|
|
85
|
-
const TIME_CONSTANTS = Object.freeze({
|
|
86
|
-
s: 1,
|
|
87
|
-
second: 1,
|
|
88
|
-
seconds: 1,
|
|
89
|
-
m: 60,
|
|
90
|
-
minute: 60,
|
|
91
|
-
minutes: 60,
|
|
92
|
-
h: 3600,
|
|
93
|
-
hour: 3600,
|
|
94
|
-
hours: 3600,
|
|
95
|
-
D: 86400,
|
|
96
|
-
day: 86400,
|
|
97
|
-
days: 86400,
|
|
98
|
-
W: 604800,
|
|
99
|
-
week: 604800,
|
|
100
|
-
weeks: 604800,
|
|
101
|
-
M: 2592e3,
|
|
102
|
-
month: 2592e3,
|
|
103
|
-
months: 2592e3,
|
|
104
|
-
Y: 31536e3,
|
|
105
|
-
year: 31536e3,
|
|
106
|
-
years: 31536e3
|
|
107
|
-
});
|
|
108
|
-
const EXPIRES_IN_REGEX = /^(\d+)(s|second|seconds|m|minute|minutes|h|hour|hours|D|day|days|W|week|weeks|M|month|months|Y|year|years)?$/;
|
|
109
|
-
function computeExpiresInSeconds(expiresIn) {
|
|
110
|
-
if (typeof expiresIn === "number") {
|
|
111
|
-
if (!Number.isInteger(expiresIn) || expiresIn <= 0) throw new TypeError("If 'expiresIn' is a number, it must be a positive integer representing seconds.");
|
|
112
|
-
return expiresIn;
|
|
113
|
-
}
|
|
114
|
-
if (typeof expiresIn === "string") {
|
|
115
|
-
const match = expiresIn.match(EXPIRES_IN_REGEX);
|
|
116
|
-
if (!match || !match[1]) throw new TypeError("Invalid 'expiresIn' format. Must be a positive integer or a string like '10m', '2h', '7D', etc.");
|
|
117
|
-
return Number.parseInt(match[1], 10) * TIME_CONSTANTS[match[2] || "s"];
|
|
118
|
-
}
|
|
119
|
-
throw new TypeError("'expiresIn' must be a number or a string representing time duration.");
|
|
120
|
-
}
|
|
121
|
-
const computeMaxTokenAgeSeconds = computeExpiresInSeconds;
|
|
122
|
-
/** Optionally compute iat/exp when signing JWTs. */
|
|
123
|
-
function computeJwtTimeClaims(payload, headerTyp, expiresIn, currentDate = /* @__PURE__ */ new Date()) {
|
|
124
|
-
if (expiresIn === void 0 || !headerTyp?.toLowerCase().includes("jwt") || !(payload && typeof payload === "object") || payload instanceof Uint8Array || payload.exp) return;
|
|
125
|
-
const now = Math.round(currentDate.getTime() / 1e3);
|
|
126
|
-
const claims = { ...payload };
|
|
127
|
-
claims.iat ||= now;
|
|
128
|
-
claims.exp = claims.iat + computeExpiresInSeconds(expiresIn);
|
|
129
|
-
return claims;
|
|
130
|
-
}
|
|
131
|
-
/** Validate JWT claims under JWS verification rules (or custom JWE implementations). */
|
|
132
|
-
function validateJwtClaims(jwtClaims, options = {}) {
|
|
133
|
-
const clockTolerance = options.clockTolerance ?? 0;
|
|
134
|
-
const currentTime = Math.round((options.currentDate ?? /* @__PURE__ */ new Date()).getTime() / 1e3);
|
|
135
|
-
const allRequiredClaims = new Set(options.requiredClaims || []);
|
|
136
|
-
const missingClaims = /* @__PURE__ */ new Set();
|
|
137
|
-
if (options.issuer) allRequiredClaims.add("iss");
|
|
138
|
-
if (options.audience) allRequiredClaims.add("aud");
|
|
139
|
-
if (options.subject) allRequiredClaims.add("sub");
|
|
140
|
-
if (options.maxTokenAge) allRequiredClaims.add("iat");
|
|
141
|
-
for (const claimName of allRequiredClaims) if (!(claimName in jwtClaims)) missingClaims.add(claimName);
|
|
142
|
-
if (missingClaims.size > 0) throw new JWTError(`Missing required JWT Claims: ${[...missingClaims].join(", ")}`, "ERR_JWT_CLAIM_MISSING");
|
|
143
|
-
if (options.issuer) {
|
|
144
|
-
const expectedIssuers = maybeArray(options.issuer);
|
|
145
|
-
if (!jwtClaims.iss || !expectedIssuers.includes(jwtClaims.iss)) throw new JWTError(`Invalid JWT "iss" (Issuer) Claim: Expected ${expectedIssuers.join(" or ")}, got ${jwtClaims.iss}`, "ERR_JWT_CLAIM_INVALID");
|
|
146
|
-
}
|
|
147
|
-
if (options.subject && jwtClaims.sub !== options.subject) throw new JWTError(`Invalid JWT "sub" (Subject) Claim: Expected ${options.subject}, got ${jwtClaims.sub}`, "ERR_JWT_CLAIM_INVALID");
|
|
148
|
-
if (options.audience) {
|
|
149
|
-
const expectedAudiences = maybeArray(options.audience);
|
|
150
|
-
const claimAudience = maybeArray(jwtClaims.aud || []);
|
|
151
|
-
if (!claimAudience.some((aud) => expectedAudiences.includes(aud))) throw new JWTError(`Invalid JWT "aud" (Audience) Claim: Expected ${expectedAudiences.join(" or ")}, got ${claimAudience.join(", ")}`, "ERR_JWT_CLAIM_INVALID");
|
|
152
|
-
}
|
|
153
|
-
if (typeof jwtClaims.nbf === "number" && jwtClaims.nbf > currentTime + clockTolerance) throw new JWTError(`JWT "nbf" (Not Before) Claim validation failed: Token is not yet valid (nbf: ${(/* @__PURE__ */ new Date(jwtClaims.nbf * 1e3)).toISOString()})`, "ERR_JWT_NBF");
|
|
154
|
-
if (typeof jwtClaims.exp === "number" && jwtClaims.exp <= currentTime - clockTolerance) throw new JWTError(`JWT "exp" (Expiration Time) Claim validation failed: Token has expired (exp: ${(/* @__PURE__ */ new Date(jwtClaims.exp * 1e3)).toISOString()})`, "ERR_JWT_EXPIRED", {
|
|
155
|
-
jti: jwtClaims.jti,
|
|
156
|
-
iat: jwtClaims.iat,
|
|
157
|
-
exp: jwtClaims.exp
|
|
158
|
-
});
|
|
159
|
-
if (options.maxTokenAge) {
|
|
160
|
-
if (typeof jwtClaims.iat !== "number") throw new JWTError("JWT \"iat\" (Issued At) Claim must be a number when maxTokenAge is set.", "ERR_JWT_CLAIM_INVALID");
|
|
161
|
-
if (jwtClaims.iat > currentTime + clockTolerance) throw new JWTError(`JWT "iat" (Issued At) Claim validation failed: Token was issued in the future (iat: ${(/* @__PURE__ */ new Date(jwtClaims.iat * 1e3)).toISOString()})`, "ERR_JWT_CLAIM_INVALID");
|
|
162
|
-
if (jwtClaims.iat < currentTime - computeMaxTokenAgeSeconds(options.maxTokenAge) - clockTolerance) throw new JWTError(`JWT "iat" (Issued At) Claim validation failed: Token is too old (maxTokenAge: ${options.maxTokenAge}s, iat: ${(/* @__PURE__ */ new Date(jwtClaims.iat * 1e3)).toISOString()})`, "ERR_JWT_EXPIRED", {
|
|
163
|
-
jti: jwtClaims.jti,
|
|
164
|
-
iat: jwtClaims.iat,
|
|
165
|
-
exp: jwtClaims.exp
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
const BASE_HEADER_PARAMS = [
|
|
170
|
-
"alg",
|
|
171
|
-
"typ",
|
|
172
|
-
"cty",
|
|
173
|
-
"kid",
|
|
174
|
-
"jwk",
|
|
175
|
-
"jku",
|
|
176
|
-
"x5c",
|
|
177
|
-
"x5t",
|
|
178
|
-
"x5u"
|
|
179
|
-
];
|
|
180
|
-
/** Validate critical headers in JWS semantics. */
|
|
181
|
-
function validateCriticalHeadersJWS(protectedHeader, recognizedHeaders = []) {
|
|
182
|
-
if (!protectedHeader.crit) return;
|
|
183
|
-
const missingHeaderParams = /* @__PURE__ */ new Set();
|
|
184
|
-
const recognizedParams = new Set([
|
|
185
|
-
...recognizedHeaders,
|
|
186
|
-
...BASE_HEADER_PARAMS,
|
|
187
|
-
"b64"
|
|
188
|
-
]);
|
|
189
|
-
for (const param of protectedHeader.crit) {
|
|
190
|
-
if (recognizedParams.has(param) && (param in protectedHeader || param === "b64")) continue;
|
|
191
|
-
missingHeaderParams.add(param);
|
|
192
|
-
}
|
|
193
|
-
if (missingHeaderParams.size > 0) throw new JWTError(`Missing critical header parameters: ${[...missingHeaderParams].join(", ")}`, "ERR_JWS_INVALID");
|
|
194
|
-
}
|
|
195
|
-
/** Validate critical headers in JWE semantics. */
|
|
196
|
-
function validateCriticalHeadersJWE(protectedHeader, recognizedHeaders) {
|
|
197
|
-
if (protectedHeader.crit && !recognizedHeaders) throw new JWTError(`Unprocessed critical header parameters: ${protectedHeader.crit.join(", ")}`, "ERR_JWE_INVALID");
|
|
198
|
-
if (!protectedHeader.crit) return;
|
|
199
|
-
const understoodParams = new Set([
|
|
200
|
-
...recognizedHeaders || [],
|
|
201
|
-
...BASE_HEADER_PARAMS,
|
|
202
|
-
"enc",
|
|
203
|
-
"iv",
|
|
204
|
-
"tag",
|
|
205
|
-
"p2s",
|
|
206
|
-
"p2c",
|
|
207
|
-
"epk",
|
|
208
|
-
"apu",
|
|
209
|
-
"apv"
|
|
210
|
-
]);
|
|
211
|
-
for (const critParam of protectedHeader.crit) {
|
|
212
|
-
if (!Object.prototype.hasOwnProperty.call(protectedHeader, critParam)) throw new JWTError(`Critical header parameter "${critParam}" listed in "crit" but not present in the protected header.`, "ERR_JWE_INVALID");
|
|
213
|
-
if (!understoodParams.has(critParam)) throw new JWTError(`Unprocessed critical header parameters: ${critParam}`, "ERR_JWE_INVALID");
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
//#endregion
|
|
217
|
-
//#region src/core/utils/sanitize.ts
|
|
218
|
-
/**
|
|
219
|
-
* Originally derived from unsecure@0.0.4
|
|
220
|
-
* Licensed under the MIT License.
|
|
221
|
-
*
|
|
222
|
-
* Source: https://github.com/sandros94/unsecure/blob/913ed5580c551153a19154b4916c67a8efb2be85/src/sanitize.ts
|
|
223
|
-
*/
|
|
224
|
-
/**
|
|
225
|
-
* Returns a deep structural copy of `obj` with prototype-pollution vectors
|
|
226
|
-
* stripped at every level (`__proto__`, `prototype`, `constructor`).
|
|
227
|
-
*
|
|
228
|
-
* The input is never modified. Arrays are copied element-by-element;
|
|
229
|
-
* nested objects are recursed with a WeakSet cycle guard.
|
|
230
|
-
*/
|
|
231
|
-
function sanitizeObject(obj) {
|
|
232
|
-
if (!obj || typeof obj !== "object") return obj;
|
|
233
|
-
return _sanitizeCopy(obj, /* @__PURE__ */ new WeakSet());
|
|
234
|
-
}
|
|
235
|
-
function _sanitizeCopy(current, seen) {
|
|
236
|
-
seen.add(current);
|
|
237
|
-
if (Array.isArray(current)) return current.map((item) => item && typeof item === "object" && !seen.has(item) ? _sanitizeCopy(item, seen) : item);
|
|
238
|
-
const result = {};
|
|
239
|
-
for (const [key, value] of Object.entries(current)) {
|
|
240
|
-
if (key === "__proto__" || key === "prototype" || key === "constructor") continue;
|
|
241
|
-
result[key] = value && typeof value === "object" && !seen.has(value) ? _sanitizeCopy(value, seen) : value;
|
|
242
|
-
}
|
|
243
|
-
return result;
|
|
244
|
-
}
|
|
245
|
-
//#endregion
|
|
246
|
-
//#region src/core/utils/index.ts
|
|
247
|
-
const textEncoder = /* @__PURE__ */ new TextEncoder();
|
|
248
|
-
const textDecoder = /* @__PURE__ */ new TextDecoder();
|
|
249
|
-
const _Buffer = /* @__PURE__ */ (() => {
|
|
250
|
-
try {
|
|
251
|
-
return globalThis.Buffer;
|
|
252
|
-
} catch {
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
})();
|
|
256
|
-
const _hasBuffer = typeof _Buffer?.from === "function";
|
|
257
|
-
const _hasToBase64 = !_hasBuffer && typeof Uint8Array.prototype.toBase64 === "function";
|
|
258
|
-
const _hasFromBase64 = !_hasBuffer && typeof Uint8Array.fromBase64 === "function";
|
|
259
|
-
const _b64UrlEncOpts = /* @__PURE__ */ Object.freeze({
|
|
260
|
-
alphabet: "base64url",
|
|
261
|
-
omitPadding: true
|
|
262
|
-
});
|
|
263
|
-
const _b64UrlDecOpts = /* @__PURE__ */ Object.freeze({ alphabet: "base64url" });
|
|
264
|
-
function _toBuffer(data) {
|
|
265
|
-
return data instanceof Uint8Array ? _Buffer.from(data.buffer, data.byteOffset, data.byteLength) : _Buffer.from(data);
|
|
266
|
-
}
|
|
267
|
-
function base64Encode(data) {
|
|
268
|
-
if (_hasBuffer) return _toBuffer(data).toString("base64");
|
|
269
|
-
const encodedData = data instanceof Uint8Array ? data : textEncoder.encode(data);
|
|
270
|
-
if (_hasToBase64) return encodedData.toBase64();
|
|
271
|
-
return btoa(String.fromCodePoint(...encodedData));
|
|
272
|
-
}
|
|
273
|
-
function base64UrlEncode(data) {
|
|
274
|
-
if (_hasBuffer) return _toBuffer(data).toString("base64url");
|
|
275
|
-
const encodedData = data instanceof Uint8Array ? data : textEncoder.encode(data);
|
|
276
|
-
if (_hasToBase64) return encodedData.toBase64(_b64UrlEncOpts);
|
|
277
|
-
return btoa(String.fromCodePoint(...encodedData)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
278
|
-
}
|
|
279
|
-
function base64Decode(str, toString) {
|
|
280
|
-
const decodeToString = toString !== false;
|
|
281
|
-
if (!str) return decodeToString ? "" : new Uint8Array(0);
|
|
282
|
-
if (_hasBuffer) {
|
|
283
|
-
const buf = _Buffer.from(str, "base64");
|
|
284
|
-
return decodeToString ? buf.toString("utf8") : new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
285
|
-
}
|
|
286
|
-
const data = _hasFromBase64 ? Uint8Array.fromBase64(str) : Uint8Array.from(atob(str), (b) => b.codePointAt(0));
|
|
287
|
-
return decodeToString ? textDecoder.decode(data) : data;
|
|
288
|
-
}
|
|
289
|
-
function base64UrlDecode(str, toString) {
|
|
290
|
-
const decodeToString = toString !== false;
|
|
291
|
-
if (!str) return decodeToString ? "" : new Uint8Array(0);
|
|
292
|
-
if (_hasBuffer) {
|
|
293
|
-
const buf = _Buffer.from(str, "base64url");
|
|
294
|
-
return decodeToString ? buf.toString("utf8") : new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
295
|
-
}
|
|
296
|
-
let data;
|
|
297
|
-
if (_hasFromBase64) data = Uint8Array.fromBase64(str, _b64UrlDecOpts);
|
|
298
|
-
else {
|
|
299
|
-
str = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
300
|
-
while (str.length % 4) str += "=";
|
|
301
|
-
data = Uint8Array.from(atob(str), (b) => b.codePointAt(0));
|
|
302
|
-
}
|
|
303
|
-
return decodeToString ? textDecoder.decode(data) : data;
|
|
304
|
-
}
|
|
305
|
-
function randomBytes(length) {
|
|
306
|
-
return crypto.getRandomValues(new Uint8Array(length));
|
|
307
|
-
}
|
|
308
|
-
/**
|
|
309
|
-
* Concatenates multiple Uint8Arrays
|
|
310
|
-
* @param arrays Arrays to concatenate
|
|
311
|
-
* @returns Concatenated array
|
|
312
|
-
*/
|
|
313
|
-
function concatUint8Arrays(...arrays) {
|
|
314
|
-
const totalLength = arrays.reduce((length, arr) => length + arr.length, 0);
|
|
315
|
-
const result = new Uint8Array(totalLength);
|
|
316
|
-
let offset = 0;
|
|
317
|
-
for (const arr of arrays) {
|
|
318
|
-
result.set(arr, offset);
|
|
319
|
-
offset += arr.length;
|
|
320
|
-
}
|
|
321
|
-
return result;
|
|
322
|
-
}
|
|
323
|
-
function maybeArray(item) {
|
|
324
|
-
return Array.isArray(item) ? item : [item];
|
|
325
|
-
}
|
|
326
|
-
function isJWK(key) {
|
|
327
|
-
return typeof key === "object" && key !== null && "kty" in key && typeof key.kty === "string";
|
|
328
|
-
}
|
|
329
|
-
function isJWKSet(key) {
|
|
330
|
-
return key && typeof key === "object" && "keys" in key && Array.isArray(key.keys);
|
|
331
|
-
}
|
|
332
|
-
function assertCryptoKey(key) {
|
|
333
|
-
if (!isCryptoKey(key)) throw new Error("CryptoKey instance expected");
|
|
334
|
-
}
|
|
335
|
-
function isCryptoKey(key) {
|
|
336
|
-
return key?.[Symbol.toStringTag] === "CryptoKey";
|
|
337
|
-
}
|
|
338
|
-
const isCryptoKeyPair = (key) => key && typeof key === "object" && isCryptoKey(key.publicKey) && isCryptoKey(key.privateKey);
|
|
339
|
-
/** Returns true if the JWK is a symmetric (oct) key. */
|
|
340
|
-
function isSymmetricJWK(key) {
|
|
341
|
-
return isJWK(key) && key.kty === "oct" && typeof key.k === "string";
|
|
342
|
-
}
|
|
343
|
-
/** Returns true if the JWK is an asymmetric (RSA, EC, OKP) key. */
|
|
344
|
-
function isAsymmetricJWK(key) {
|
|
345
|
-
return !isSymmetricJWK(key);
|
|
346
|
-
}
|
|
347
|
-
/**
|
|
348
|
-
* Type guard that checks if the provided JWK contains private key material.
|
|
349
|
-
* It relies purely on the presence of private components (e.g. "d"), not on the alg value.
|
|
350
|
-
*/
|
|
351
|
-
function isPrivateJWK(key) {
|
|
352
|
-
if (!isJWK(key)) return false;
|
|
353
|
-
if (key.kty === "EC") return typeof key.d === "string";
|
|
354
|
-
if (key.kty === "OKP") return typeof key.d === "string";
|
|
355
|
-
if (key.kty === "RSA") return typeof key.d === "string";
|
|
356
|
-
return false;
|
|
357
|
-
}
|
|
358
|
-
/**
|
|
359
|
-
* Type guard that checks if the provided JWK is a public (asymmetric) key.
|
|
360
|
-
* This checks for the presence of public components and absence of private material.
|
|
361
|
-
*/
|
|
362
|
-
function isPublicJWK(key) {
|
|
363
|
-
if (!isJWK(key)) return false;
|
|
364
|
-
if (key.kty === "EC") {
|
|
365
|
-
const ec = key;
|
|
366
|
-
return typeof ec.x === "string" && typeof ec.y === "string" && typeof ec.d !== "string";
|
|
367
|
-
}
|
|
368
|
-
if (key.kty === "OKP") {
|
|
369
|
-
const okp = key;
|
|
370
|
-
return typeof okp.x === "string" && typeof okp.d !== "string";
|
|
371
|
-
}
|
|
372
|
-
if (key.kty === "RSA") {
|
|
373
|
-
const rsa = key;
|
|
374
|
-
return typeof rsa.n === "string" && typeof rsa.e === "string" && typeof rsa.d !== "string";
|
|
375
|
-
}
|
|
376
|
-
return false;
|
|
377
|
-
}
|
|
378
|
-
//#endregion
|
|
379
|
-
//#region src/core/_crypto/_sign-verify.ts
|
|
380
|
-
async function sign$1(alg, key, data) {
|
|
381
|
-
checkSigCryptoKey(key, alg, "sign");
|
|
382
|
-
const signature = await crypto.subtle.sign(subtleAlgorithm(alg, key.algorithm), key, data);
|
|
383
|
-
return new Uint8Array(signature);
|
|
384
|
-
}
|
|
385
|
-
async function verify$1(alg, key, signature, data) {
|
|
386
|
-
checkSigCryptoKey(key, alg, "verify");
|
|
387
|
-
try {
|
|
388
|
-
return await crypto.subtle.verify(subtleAlgorithm(alg, key.algorithm), key, signature, data);
|
|
389
|
-
} catch {
|
|
390
|
-
return false;
|
|
1
|
+
import { C as validateCriticalHeadersJWS, D as JWTError, b as getPlaintextBytes, f as applyTypCtyDefaults, h as computeJwtTimeClaims, o as isJWK, s as isJWKSet } from "./utils.mjs";
|
|
2
|
+
import { a as base64UrlEncode, i as base64UrlDecode, o as textDecoder, s as textEncoder, u as sanitizeObjectCopy } from "./libs/unsecure.mjs";
|
|
3
|
+
import { T as verify$1, h as importKey, p as getJWKsFromSet, w as sign$1 } from "./jwk.mjs";
|
|
4
|
+
import { a as decodeJWSPayload, c as resolveSigningKey, i as checkAlgAllowed, l as validateJwtClaimsIfJsonPayload, n as JWS_ALG_CTX, o as decodeProtectedHeader, r as buildJWKSetFilter } from "./_internal.mjs";
|
|
5
|
+
async function signMulti(payload, signers, options = {}) {
|
|
6
|
+
if (!Array.isArray(signers) || signers.length === 0) throw new JWTError("signMulti requires at least one signer.", "ERR_JWS_INVALID");
|
|
7
|
+
const resolvedAlgs = signers.map((s, i) => _resolveSignerAlg(s.key, i));
|
|
8
|
+
const b64 = _resolveB64(signers);
|
|
9
|
+
const payloadBytes = getPlaintextBytes(computeJwtTimeClaims(payload, options) || payload);
|
|
10
|
+
const payloadEncoded = b64 ? base64UrlEncode(payloadBytes) : textDecoder.decode(payloadBytes);
|
|
11
|
+
const wireSignatures = [];
|
|
12
|
+
for (let i = 0; i < signers.length; i++) {
|
|
13
|
+
const signer = signers[i];
|
|
14
|
+
const alg = resolvedAlgs[i];
|
|
15
|
+
const protectedHeader = _buildSignerProtectedHeader(signer, alg, payload, b64);
|
|
16
|
+
const unprotectedHeader = sanitizeObjectCopy(signer.unprotectedHeader);
|
|
17
|
+
_assertDisjointHeaders(protectedHeader, unprotectedHeader);
|
|
18
|
+
const protectedHeaderEncoded = base64UrlEncode(JSON.stringify(protectedHeader));
|
|
19
|
+
const signingInputBytes = textEncoder.encode(`${protectedHeaderEncoded}.${payloadEncoded}`);
|
|
20
|
+
const wireSig = {
|
|
21
|
+
protected: protectedHeaderEncoded,
|
|
22
|
+
signature: base64UrlEncode(await sign$1(alg, await resolveSigningKey(alg, await importKey(signer.key, {
|
|
23
|
+
alg,
|
|
24
|
+
expect: "private"
|
|
25
|
+
}), "sign"), signingInputBytes))
|
|
26
|
+
};
|
|
27
|
+
if (unprotectedHeader && Object.keys(unprotectedHeader).length > 0) wireSig.header = unprotectedHeader;
|
|
28
|
+
wireSignatures.push(wireSig);
|
|
391
29
|
}
|
|
30
|
+
return {
|
|
31
|
+
payload: payloadEncoded,
|
|
32
|
+
signatures: wireSignatures
|
|
33
|
+
};
|
|
392
34
|
}
|
|
393
|
-
function
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
35
|
+
async function verifyMulti(jws, keyOrLookup, options = {}) {
|
|
36
|
+
const general = _normalizeSerialization(jws);
|
|
37
|
+
if (typeof general.payload !== "string" || !Array.isArray(general.signatures) || general.signatures.length === 0) throw new JWTError("Invalid JWS JSON Serialization: missing payload or signatures.", "ERR_JWS_INVALID_SERIALIZATION");
|
|
38
|
+
let lastError;
|
|
39
|
+
let cryptoAttempted = false;
|
|
40
|
+
for (let i = 0; i < general.signatures.length; i++) {
|
|
41
|
+
const wireSig = general.signatures[i];
|
|
42
|
+
if (typeof wireSig.signature !== "string") {
|
|
43
|
+
lastError = new JWTError(`Signature[${i}] is missing the "signature" field.`, "ERR_JWS_INVALID");
|
|
44
|
+
continue;
|
|
402
45
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
break;
|
|
46
|
+
let protectedHeader;
|
|
47
|
+
try {
|
|
48
|
+
protectedHeader = decodeProtectedHeader(wireSig.protected, "JWS");
|
|
49
|
+
} catch (err) {
|
|
50
|
+
lastError = err instanceof JWTError ? err : void 0;
|
|
51
|
+
continue;
|
|
410
52
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
if (!isAlgorithm$3(key.algorithm, "RSA-PSS")) throw unusable$4("RSA-PSS");
|
|
415
|
-
const expected = Number.parseInt(alg.slice(2), 10);
|
|
416
|
-
if (getHashLength$1(key.algorithm.hash) !== expected) throw unusable$4(`SHA-${expected}`, "algorithm.hash");
|
|
417
|
-
break;
|
|
53
|
+
if (!protectedHeader.alg) {
|
|
54
|
+
lastError = new JWTError(`Invalid JWS: Signature[${i}] protected header must have an "alg" property.`, "ERR_JWS_INVALID");
|
|
55
|
+
continue;
|
|
418
56
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
if (!isAlgorithm$3(key.algorithm, "ECDSA")) throw unusable$4("ECDSA");
|
|
427
|
-
const expected = getNamedCurve$1(alg);
|
|
428
|
-
if (key.algorithm.namedCurve !== expected) throw unusable$4(expected, "algorithm.namedCurve");
|
|
429
|
-
break;
|
|
57
|
+
const unprotectedHeader = sanitizeObjectCopy(wireSig.header);
|
|
58
|
+
_assertDisjointHeaders(protectedHeader, unprotectedHeader);
|
|
59
|
+
const effective = _mergeHeaders(protectedHeader, unprotectedHeader);
|
|
60
|
+
const alg = protectedHeader.alg;
|
|
61
|
+
if (options.typ && protectedHeader.typ !== options.typ) {
|
|
62
|
+
lastError = new JWTError(`Invalid JWS: "typ" Header Parameter mismatch on signature[${i}]. Expected "${options.typ}", got "${protectedHeader.typ}".`, "ERR_JWS_INVALID");
|
|
63
|
+
continue;
|
|
430
64
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
const hash = `SHA-${alg.slice(-3)}`;
|
|
437
|
-
switch (alg) {
|
|
438
|
-
case "HS256":
|
|
439
|
-
case "HS384":
|
|
440
|
-
case "HS512": return {
|
|
441
|
-
hash,
|
|
442
|
-
name: "HMAC"
|
|
443
|
-
};
|
|
444
|
-
case "PS256":
|
|
445
|
-
case "PS384":
|
|
446
|
-
case "PS512": return {
|
|
447
|
-
hash,
|
|
448
|
-
name: "RSA-PSS",
|
|
449
|
-
saltLength: Number.parseInt(alg.slice(-3), 10) >> 3
|
|
450
|
-
};
|
|
451
|
-
case "RS256":
|
|
452
|
-
case "RS384":
|
|
453
|
-
case "RS512": return {
|
|
454
|
-
hash,
|
|
455
|
-
name: "RSASSA-PKCS1-v1_5"
|
|
456
|
-
};
|
|
457
|
-
case "ES256":
|
|
458
|
-
case "ES384":
|
|
459
|
-
case "ES512": return {
|
|
460
|
-
hash,
|
|
461
|
-
name: "ECDSA",
|
|
462
|
-
namedCurve: algorithm.namedCurve
|
|
463
|
-
};
|
|
464
|
-
case "Ed25519":
|
|
465
|
-
case "EdDSA": return { name: "Ed25519" };
|
|
466
|
-
default: throw new Error(`alg ${alg} is not supported either by JOSE or your javascript runtime`);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
function unusable$4(name, prop = "algorithm.name") {
|
|
470
|
-
return /* @__PURE__ */ new TypeError(`CryptoKey does not support this operation, its ${prop} must be ${name}`);
|
|
471
|
-
}
|
|
472
|
-
function isAlgorithm$3(algorithm, name) {
|
|
473
|
-
return algorithm.name === name;
|
|
474
|
-
}
|
|
475
|
-
function getHashLength$1(hash) {
|
|
476
|
-
return Number.parseInt(hash.name.slice(4), 10);
|
|
477
|
-
}
|
|
478
|
-
function getNamedCurve$1(alg) {
|
|
479
|
-
switch (alg) {
|
|
480
|
-
case "ES256": return "P-256";
|
|
481
|
-
case "ES384": return "P-384";
|
|
482
|
-
case "ES512": return "P-521";
|
|
483
|
-
default: throw new Error("unreachable");
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
function checkUsage$4(key, usage) {
|
|
487
|
-
if (usage && !key.usages.includes(usage)) throw new TypeError(`CryptoKey does not support this operation, its usages must include ${usage}.`);
|
|
488
|
-
}
|
|
489
|
-
//#endregion
|
|
490
|
-
//#region src/core/_crypto/_aes.ts
|
|
491
|
-
const IV_BIT_LENGTHS = {
|
|
492
|
-
A128GCM: 96,
|
|
493
|
-
A128GCMKW: 96,
|
|
494
|
-
A192GCM: 96,
|
|
495
|
-
A192GCMKW: 96,
|
|
496
|
-
A256GCM: 96,
|
|
497
|
-
A256GCMKW: 96,
|
|
498
|
-
"A128CBC-HS256": 128,
|
|
499
|
-
"A192CBC-HS384": 128,
|
|
500
|
-
"A256CBC-HS512": 128
|
|
501
|
-
};
|
|
502
|
-
const CEK_BIT_LENGTHS = {
|
|
503
|
-
A128GCM: 128,
|
|
504
|
-
A192GCM: 192,
|
|
505
|
-
A256GCM: 256,
|
|
506
|
-
"A128CBC-HS256": 256,
|
|
507
|
-
"A192CBC-HS384": 384,
|
|
508
|
-
"A256CBC-HS512": 512
|
|
509
|
-
};
|
|
510
|
-
function bitLengthIV(alg) {
|
|
511
|
-
const length = IV_BIT_LENGTHS[alg];
|
|
512
|
-
if (length === void 0) throw new Error(`Unsupported JWE Algorithm: ${alg}`);
|
|
513
|
-
return length;
|
|
514
|
-
}
|
|
515
|
-
function generateIV(alg) {
|
|
516
|
-
return crypto.getRandomValues(new Uint8Array(bitLengthIV(alg) >> 3));
|
|
517
|
-
}
|
|
518
|
-
function checkIvLength(enc, iv) {
|
|
519
|
-
if (iv.length << 3 !== bitLengthIV(enc)) throw new Error("Invalid Initialization Vector length");
|
|
520
|
-
}
|
|
521
|
-
function bitLengthCEK(alg) {
|
|
522
|
-
const length = CEK_BIT_LENGTHS[alg];
|
|
523
|
-
if (length === void 0) throw new Error(`Unsupported JWE Algorithm: ${alg}`);
|
|
524
|
-
return length;
|
|
525
|
-
}
|
|
526
|
-
function generateCEK(alg) {
|
|
527
|
-
return crypto.getRandomValues(new Uint8Array(bitLengthCEK(alg) >> 3));
|
|
528
|
-
}
|
|
529
|
-
function checkCEKLength(cek, expected) {
|
|
530
|
-
const actual = cek.byteLength << 3;
|
|
531
|
-
if (actual !== expected) throw new Error(`Invalid Content Encryption Key length. Expected ${expected} bits, got ${actual} bits`);
|
|
532
|
-
}
|
|
533
|
-
function checkAESCryptoKey(key, alg, usage) {
|
|
534
|
-
switch (alg) {
|
|
535
|
-
case "A128GCM":
|
|
536
|
-
case "A192GCM":
|
|
537
|
-
case "A256GCM": {
|
|
538
|
-
if (!isAlgorithm$2(key.algorithm, "AES-GCM")) throw unusable$3("AES-GCM");
|
|
539
|
-
const expected = Number.parseInt(alg.slice(1, 4), 10);
|
|
540
|
-
if (key.algorithm.length !== expected) throw unusable$3(expected, "algorithm.length");
|
|
541
|
-
break;
|
|
65
|
+
const rawKeyMaterial = typeof keyOrLookup === "function" ? await keyOrLookup(effective, wireSig.protected ?? "") : keyOrLookup;
|
|
66
|
+
const algError = checkAlgAllowed(alg, rawKeyMaterial, options.algorithms, JWS_ALG_CTX);
|
|
67
|
+
if (algError) {
|
|
68
|
+
lastError = algError;
|
|
69
|
+
continue;
|
|
542
70
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
if (
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
default: throw new TypeError("CryptoKey does not support this operation");
|
|
552
|
-
}
|
|
553
|
-
checkUsage$3(key, usage);
|
|
554
|
-
}
|
|
555
|
-
async function aesKwWrap(alg, key, cek) {
|
|
556
|
-
const cryptoKey = await getAESKWCryptoKey(key, alg, { usage: "wrapKey" });
|
|
557
|
-
checkAESKWKeySize(cryptoKey, alg);
|
|
558
|
-
const cryptoKeyCek = await crypto.subtle.importKey("raw", cek, {
|
|
559
|
-
hash: "SHA-256",
|
|
560
|
-
name: "HMAC"
|
|
561
|
-
}, true, ["sign"]);
|
|
562
|
-
return new Uint8Array(await crypto.subtle.wrapKey("raw", cryptoKeyCek, cryptoKey, "AES-KW"));
|
|
563
|
-
}
|
|
564
|
-
async function aesKwUnwrap(alg, key, encryptedKey) {
|
|
565
|
-
const cryptoKey = await getAESKWCryptoKey(key, alg, { usage: "unwrapKey" });
|
|
566
|
-
checkAESKWKeySize(cryptoKey, alg);
|
|
567
|
-
const cryptoKeyCek = await crypto.subtle.unwrapKey("raw", encryptedKey, cryptoKey, "AES-KW", {
|
|
568
|
-
hash: "SHA-256",
|
|
569
|
-
name: "HMAC"
|
|
570
|
-
}, true, ["sign"]);
|
|
571
|
-
return new Uint8Array(await crypto.subtle.exportKey("raw", cryptoKeyCek));
|
|
572
|
-
}
|
|
573
|
-
async function gcmkwEncrypt(alg, key, plaintext, iv) {
|
|
574
|
-
const wrapped = await encrypt$1(alg.slice(0, 7), plaintext, key, iv, new Uint8Array(0));
|
|
575
|
-
return {
|
|
576
|
-
encryptedKey: wrapped.ciphertext,
|
|
577
|
-
iv: base64UrlEncode(wrapped.iv),
|
|
578
|
-
tag: base64UrlEncode(wrapped.tag)
|
|
579
|
-
};
|
|
580
|
-
}
|
|
581
|
-
async function gcmkwDecrypt(alg, key, ciphertext, iv, tag) {
|
|
582
|
-
return decrypt$1(alg.slice(0, 7), key, ciphertext, iv, tag, new Uint8Array(0));
|
|
583
|
-
}
|
|
584
|
-
async function encrypt$1(enc, plaintext, cek, iv, aad) {
|
|
585
|
-
if (!isCryptoKey(cek) && !(cek instanceof Uint8Array)) throw new TypeError("Key must be one of type: CryptoKey or Uint8Array");
|
|
586
|
-
if (iv) checkIvLength(enc, iv);
|
|
587
|
-
else iv = generateIV(enc);
|
|
588
|
-
switch (enc) {
|
|
589
|
-
case "A128CBC-HS256":
|
|
590
|
-
case "A192CBC-HS384":
|
|
591
|
-
case "A256CBC-HS512":
|
|
592
|
-
if (cek instanceof Uint8Array) checkCEKLength(cek, Number.parseInt(enc.slice(-3), 10));
|
|
593
|
-
return cbcEncrypt(enc, plaintext, cek, iv, aad);
|
|
594
|
-
case "A128GCM":
|
|
595
|
-
case "A192GCM":
|
|
596
|
-
case "A256GCM":
|
|
597
|
-
if (cek instanceof Uint8Array) checkCEKLength(cek, Number.parseInt(enc.slice(1, 4), 10));
|
|
598
|
-
return gcmEncrypt(enc, plaintext, cek, iv, aad);
|
|
599
|
-
default: throw new Error("Unsupported JWE Content Encryption Algorithm");
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
async function decrypt$1(enc, cek, ciphertext, iv, tag, aad) {
|
|
603
|
-
if (!isCryptoKey(cek) && !(cek instanceof Uint8Array)) throw new TypeError("Key must be one of type: CryptoKey or Uint8Array");
|
|
604
|
-
if (!iv) throw new Error("JWE Initialization Vector missing");
|
|
605
|
-
if (!tag) throw new Error("JWE Authentication Tag missing");
|
|
606
|
-
checkIvLength(enc, iv);
|
|
607
|
-
switch (enc) {
|
|
608
|
-
case "A128CBC-HS256":
|
|
609
|
-
case "A192CBC-HS384":
|
|
610
|
-
case "A256CBC-HS512":
|
|
611
|
-
if (cek instanceof Uint8Array) checkCEKLength(cek, Number.parseInt(enc.slice(-3), 10));
|
|
612
|
-
return cbcDecrypt(enc, cek, ciphertext, iv, tag, aad);
|
|
613
|
-
case "A128GCM":
|
|
614
|
-
case "A192GCM":
|
|
615
|
-
case "A256GCM":
|
|
616
|
-
if (cek instanceof Uint8Array) checkCEKLength(cek, Number.parseInt(enc.slice(1, 4), 10));
|
|
617
|
-
return gcmDecrypt(enc, cek, ciphertext, iv, tag, aad);
|
|
618
|
-
default: throw new Error("Unsupported JWE Content Encryption Algorithm");
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
function unusable$3(name, prop = "algorithm.name") {
|
|
622
|
-
return /* @__PURE__ */ new TypeError(`CryptoKey does not support this operation, its ${prop} must be ${name}`);
|
|
623
|
-
}
|
|
624
|
-
function isAlgorithm$2(algorithm, name) {
|
|
625
|
-
return algorithm.name === name;
|
|
626
|
-
}
|
|
627
|
-
function checkUsage$3(key, usage) {
|
|
628
|
-
if (usage && !key.usages.includes(usage)) throw new TypeError(`CryptoKey does not support this operation, its usages must include ${usage}.`);
|
|
629
|
-
}
|
|
630
|
-
async function getAESKWCryptoKey(key, alg, options = {}) {
|
|
631
|
-
const { extractable = true, usage } = options;
|
|
632
|
-
if (key instanceof Uint8Array) return crypto.subtle.importKey("raw", key, "AES-KW", extractable, [usage ?? "wrapKey"]);
|
|
633
|
-
checkAESCryptoKey(key, alg, usage);
|
|
634
|
-
return key;
|
|
635
|
-
}
|
|
636
|
-
function checkAESKWKeySize(key, alg) {
|
|
637
|
-
if (key.algorithm.length !== Number.parseInt(alg.slice(1, 4), 10)) throw new TypeError(`Invalid key size for alg: ${alg}`);
|
|
638
|
-
}
|
|
639
|
-
async function importCBCKeys(enc, cek, usage) {
|
|
640
|
-
const keySize = Number.parseInt(enc.slice(1, 4), 10);
|
|
641
|
-
const [encKey, macKey] = await Promise.all([crypto.subtle.importKey("raw", cek.subarray(keySize >> 3), "AES-CBC", false, [usage]), crypto.subtle.importKey("raw", cek.subarray(0, keySize >> 3), {
|
|
642
|
-
hash: `SHA-${keySize << 1}`,
|
|
643
|
-
name: "HMAC"
|
|
644
|
-
}, false, ["sign"])]);
|
|
645
|
-
return {
|
|
646
|
-
encKey,
|
|
647
|
-
macKey,
|
|
648
|
-
keySize
|
|
649
|
-
};
|
|
650
|
-
}
|
|
651
|
-
function cbcMacData(aad, iv, ciphertext) {
|
|
652
|
-
return concatUint8Arrays(aad, iv, ciphertext, uint64be(aad.length << 3));
|
|
653
|
-
}
|
|
654
|
-
async function cbcEncrypt(enc, plaintext, cek, iv, aad) {
|
|
655
|
-
if (!(cek instanceof Uint8Array)) throw new TypeError("CBC key must be of type Uint8Array");
|
|
656
|
-
const { encKey, macKey, keySize } = await importCBCKeys(enc, cek, "encrypt");
|
|
657
|
-
const ciphertext = new Uint8Array(await crypto.subtle.encrypt({
|
|
658
|
-
iv,
|
|
659
|
-
name: "AES-CBC"
|
|
660
|
-
}, encKey, plaintext));
|
|
661
|
-
const macData = cbcMacData(aad, iv, ciphertext);
|
|
662
|
-
return {
|
|
663
|
-
ciphertext,
|
|
664
|
-
tag: new Uint8Array((await crypto.subtle.sign("HMAC", macKey, macData)).slice(0, keySize >> 3)),
|
|
665
|
-
iv
|
|
666
|
-
};
|
|
667
|
-
}
|
|
668
|
-
async function gcmEncrypt(enc, plaintext, cek, iv, aad) {
|
|
669
|
-
let encKey;
|
|
670
|
-
if (cek instanceof Uint8Array) encKey = await crypto.subtle.importKey("raw", cek, "AES-GCM", false, ["encrypt"]);
|
|
671
|
-
else {
|
|
672
|
-
checkAESCryptoKey(cek, enc, "encrypt");
|
|
673
|
-
encKey = cek;
|
|
674
|
-
}
|
|
675
|
-
const encrypted = new Uint8Array(await crypto.subtle.encrypt({
|
|
676
|
-
additionalData: aad,
|
|
677
|
-
iv,
|
|
678
|
-
name: "AES-GCM",
|
|
679
|
-
tagLength: 128
|
|
680
|
-
}, encKey, plaintext));
|
|
681
|
-
const tag = encrypted.subarray(-16);
|
|
682
|
-
return {
|
|
683
|
-
ciphertext: encrypted.subarray(0, -16),
|
|
684
|
-
tag,
|
|
685
|
-
iv
|
|
686
|
-
};
|
|
687
|
-
}
|
|
688
|
-
async function cbcDecrypt(enc, cek, ciphertext, iv, tag, aad) {
|
|
689
|
-
if (!(cek instanceof Uint8Array)) throw new TypeError("CBC key must be of type Uint8Array");
|
|
690
|
-
const { encKey, macKey, keySize } = await importCBCKeys(enc, cek, "decrypt");
|
|
691
|
-
const macData = cbcMacData(aad, iv, ciphertext);
|
|
692
|
-
const expectedTag = new Uint8Array((await crypto.subtle.sign("HMAC", macKey, macData)).slice(0, keySize >> 3));
|
|
693
|
-
let macCheckPassed;
|
|
694
|
-
try {
|
|
695
|
-
macCheckPassed = await timingSafeEqual(tag, expectedTag);
|
|
696
|
-
} catch {}
|
|
697
|
-
if (!macCheckPassed) throw new Error("JWE Decryption Failed");
|
|
698
|
-
let plaintext;
|
|
699
|
-
try {
|
|
700
|
-
plaintext = new Uint8Array(await crypto.subtle.decrypt({
|
|
701
|
-
iv,
|
|
702
|
-
name: "AES-CBC"
|
|
703
|
-
}, encKey, ciphertext));
|
|
704
|
-
} catch {}
|
|
705
|
-
if (!plaintext) throw new Error("JWE Decryption Failed");
|
|
706
|
-
return plaintext;
|
|
707
|
-
}
|
|
708
|
-
async function gcmDecrypt(enc, cek, ciphertext, iv, tag, aad) {
|
|
709
|
-
let encKey;
|
|
710
|
-
if (cek instanceof Uint8Array) encKey = await crypto.subtle.importKey("raw", cek, "AES-GCM", false, ["decrypt"]);
|
|
711
|
-
else {
|
|
712
|
-
checkAESCryptoKey(cek, enc, "decrypt");
|
|
713
|
-
encKey = cek;
|
|
714
|
-
}
|
|
715
|
-
try {
|
|
716
|
-
return new Uint8Array(await crypto.subtle.decrypt({
|
|
717
|
-
additionalData: aad,
|
|
718
|
-
iv,
|
|
719
|
-
name: "AES-GCM",
|
|
720
|
-
tagLength: 128
|
|
721
|
-
}, encKey, concatUint8Arrays(ciphertext, tag)));
|
|
722
|
-
} catch {
|
|
723
|
-
throw new Error("JWE Decryption Failed");
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
let _timingSafeKey;
|
|
727
|
-
function getTimingSafeKey() {
|
|
728
|
-
if (!_timingSafeKey) _timingSafeKey = crypto.subtle.generateKey({
|
|
729
|
-
name: "HMAC",
|
|
730
|
-
hash: "SHA-256"
|
|
731
|
-
}, false, ["sign"]);
|
|
732
|
-
return _timingSafeKey;
|
|
733
|
-
}
|
|
734
|
-
async function timingSafeEqual(a, b) {
|
|
735
|
-
if (!(a instanceof Uint8Array)) throw new TypeError("First argument must be a buffer");
|
|
736
|
-
if (!(b instanceof Uint8Array)) throw new TypeError("Second argument must be a buffer");
|
|
737
|
-
const algorithm = {
|
|
738
|
-
name: "HMAC",
|
|
739
|
-
hash: "SHA-256"
|
|
740
|
-
};
|
|
741
|
-
const key = await getTimingSafeKey();
|
|
742
|
-
const aHmac = new Uint8Array(await crypto.subtle.sign(algorithm, key, a));
|
|
743
|
-
const bHmac = new Uint8Array(await crypto.subtle.sign(algorithm, key, b));
|
|
744
|
-
let out = 0;
|
|
745
|
-
let i = -1;
|
|
746
|
-
while (++i < 32) out |= aHmac[i] ^ bHmac[i];
|
|
747
|
-
return out === 0;
|
|
748
|
-
}
|
|
749
|
-
function writeUInt32BE$1(buf, value, offset) {
|
|
750
|
-
if (value < 0 || value >= 2 ** 32) throw new RangeError(`value must be >= 0 and <= ${2 ** 32 - 1}. Received ${value}`);
|
|
751
|
-
buf.set([
|
|
752
|
-
value >>> 24,
|
|
753
|
-
value >>> 16,
|
|
754
|
-
value >>> 8,
|
|
755
|
-
value & 255
|
|
756
|
-
], offset);
|
|
757
|
-
}
|
|
758
|
-
function uint64be(value) {
|
|
759
|
-
const high = Math.floor(value / 2 ** 32);
|
|
760
|
-
const low = value % 2 ** 32;
|
|
761
|
-
const buf = new Uint8Array(8);
|
|
762
|
-
writeUInt32BE$1(buf, high, 0);
|
|
763
|
-
writeUInt32BE$1(buf, low, 4);
|
|
764
|
-
return buf;
|
|
765
|
-
}
|
|
766
|
-
//#endregion
|
|
767
|
-
//#region src/core/_crypto/_key-codec.ts
|
|
768
|
-
async function jwkTokey(jwk) {
|
|
769
|
-
if (!jwk.alg) throw new TypeError("\"alg\" argument is required when \"jwk.alg\" is not present");
|
|
770
|
-
const { algorithm, keyUsages } = subtleMapping(jwk);
|
|
771
|
-
return crypto.subtle.importKey("jwk", jwk, algorithm, jwk.ext ?? (jwk.d ? false : true), jwk.key_ops ?? keyUsages);
|
|
772
|
-
}
|
|
773
|
-
async function keyToJWK(key) {
|
|
774
|
-
if (key instanceof Uint8Array) return {
|
|
775
|
-
kty: "oct",
|
|
776
|
-
k: base64UrlEncode(key)
|
|
777
|
-
};
|
|
778
|
-
if (!isCryptoKey(key)) throw new TypeError("Key must be one of type: CryptoKey or Uint8Array");
|
|
779
|
-
if (!key.extractable) throw new TypeError("non-extractable CryptoKey cannot be exported as a JWK");
|
|
780
|
-
return await crypto.subtle.exportKey("jwk", key);
|
|
781
|
-
}
|
|
782
|
-
function subtleMapping(jwk) {
|
|
783
|
-
let algorithm;
|
|
784
|
-
let keyUsages;
|
|
785
|
-
switch (jwk.kty) {
|
|
786
|
-
case "RSA":
|
|
787
|
-
switch (jwk.alg) {
|
|
788
|
-
case "PS256":
|
|
789
|
-
case "PS384":
|
|
790
|
-
case "PS512":
|
|
791
|
-
algorithm = {
|
|
792
|
-
name: "RSA-PSS",
|
|
793
|
-
hash: `SHA-${jwk.alg.slice(-3)}`
|
|
794
|
-
};
|
|
795
|
-
keyUsages = jwk.d ? ["sign"] : ["verify"];
|
|
796
|
-
break;
|
|
797
|
-
case "RS256":
|
|
798
|
-
case "RS384":
|
|
799
|
-
case "RS512":
|
|
800
|
-
algorithm = {
|
|
801
|
-
name: "RSASSA-PKCS1-v1_5",
|
|
802
|
-
hash: `SHA-${jwk.alg.slice(-3)}`
|
|
803
|
-
};
|
|
804
|
-
keyUsages = jwk.d ? ["sign"] : ["verify"];
|
|
805
|
-
break;
|
|
806
|
-
case "RSA-OAEP":
|
|
807
|
-
case "RSA-OAEP-256":
|
|
808
|
-
case "RSA-OAEP-384":
|
|
809
|
-
case "RSA-OAEP-512":
|
|
810
|
-
algorithm = {
|
|
811
|
-
name: "RSA-OAEP",
|
|
812
|
-
hash: `SHA-${Number.parseInt(jwk.alg.slice(-3), 10) || 1}`
|
|
813
|
-
};
|
|
814
|
-
keyUsages = jwk.d ? ["decrypt", "unwrapKey"] : ["encrypt", "wrapKey"];
|
|
815
|
-
break;
|
|
816
|
-
default: throw new Error("Invalid or unsupported JWK \"alg\" (Algorithm) Parameter value");
|
|
817
|
-
}
|
|
818
|
-
break;
|
|
819
|
-
case "EC":
|
|
820
|
-
switch (jwk.alg) {
|
|
821
|
-
case "ES256":
|
|
822
|
-
algorithm = {
|
|
823
|
-
name: "ECDSA",
|
|
824
|
-
namedCurve: "P-256"
|
|
825
|
-
};
|
|
826
|
-
keyUsages = jwk.d ? ["sign"] : ["verify"];
|
|
827
|
-
break;
|
|
828
|
-
case "ES384":
|
|
829
|
-
algorithm = {
|
|
830
|
-
name: "ECDSA",
|
|
831
|
-
namedCurve: "P-384"
|
|
832
|
-
};
|
|
833
|
-
keyUsages = jwk.d ? ["sign"] : ["verify"];
|
|
834
|
-
break;
|
|
835
|
-
case "ES512":
|
|
836
|
-
algorithm = {
|
|
837
|
-
name: "ECDSA",
|
|
838
|
-
namedCurve: "P-521"
|
|
839
|
-
};
|
|
840
|
-
keyUsages = jwk.d ? ["sign"] : ["verify"];
|
|
841
|
-
break;
|
|
842
|
-
case "ECDH-ES":
|
|
843
|
-
case "ECDH-ES+A128KW":
|
|
844
|
-
case "ECDH-ES+A192KW":
|
|
845
|
-
case "ECDH-ES+A256KW":
|
|
846
|
-
algorithm = {
|
|
847
|
-
name: "ECDH",
|
|
848
|
-
namedCurve: jwk.crv
|
|
849
|
-
};
|
|
850
|
-
keyUsages = jwk.d ? ["deriveBits"] : [];
|
|
851
|
-
break;
|
|
852
|
-
default: throw new Error("Invalid or unsupported JWK \"alg\" (Algorithm) Parameter value");
|
|
853
|
-
}
|
|
854
|
-
break;
|
|
855
|
-
case "OKP":
|
|
856
|
-
switch (jwk.alg) {
|
|
857
|
-
case "Ed25519":
|
|
858
|
-
case "EdDSA":
|
|
859
|
-
algorithm = { name: "Ed25519" };
|
|
860
|
-
keyUsages = jwk.d ? ["sign"] : ["verify"];
|
|
861
|
-
break;
|
|
862
|
-
case "ECDH-ES":
|
|
863
|
-
case "ECDH-ES+A128KW":
|
|
864
|
-
case "ECDH-ES+A192KW":
|
|
865
|
-
case "ECDH-ES+A256KW":
|
|
866
|
-
algorithm = { name: jwk.crv };
|
|
867
|
-
keyUsages = jwk.d ? ["deriveBits"] : [];
|
|
868
|
-
break;
|
|
869
|
-
default: throw new Error("Invalid or unsupported JWK \"alg\" (Algorithm) Parameter value");
|
|
870
|
-
}
|
|
871
|
-
break;
|
|
872
|
-
default: throw new Error("Invalid or unsupported JWK \"kty\" (Key Type) Parameter value");
|
|
873
|
-
}
|
|
874
|
-
return {
|
|
875
|
-
algorithm,
|
|
876
|
-
keyUsages
|
|
877
|
-
};
|
|
878
|
-
}
|
|
879
|
-
//#endregion
|
|
880
|
-
//#region src/core/_crypto/_rsa.ts
|
|
881
|
-
async function encryptRSAES(alg, key, cek) {
|
|
882
|
-
checkRSACryptoKey(key, alg, "encrypt");
|
|
883
|
-
checkRSAModulusLength(alg, key);
|
|
884
|
-
return new Uint8Array(await crypto.subtle.encrypt("RSA-OAEP", key, cek));
|
|
885
|
-
}
|
|
886
|
-
async function decryptRSAES(alg, key, encryptedKey) {
|
|
887
|
-
checkRSACryptoKey(key, alg, "decrypt");
|
|
888
|
-
checkRSAModulusLength(alg, key);
|
|
889
|
-
return new Uint8Array(await crypto.subtle.decrypt("RSA-OAEP", key, encryptedKey));
|
|
890
|
-
}
|
|
891
|
-
function checkRSACryptoKey(key, alg, usage) {
|
|
892
|
-
if (!isAlgorithm$1(key.algorithm, "RSA-OAEP")) throw unusable$2("RSA-OAEP");
|
|
893
|
-
const expected = Number.parseInt(alg.slice(9), 10) || 1;
|
|
894
|
-
if (getHashLength(key.algorithm.hash) !== expected) throw unusable$2(`SHA-${expected}`, "algorithm.hash");
|
|
895
|
-
checkUsage$2(key, usage);
|
|
896
|
-
}
|
|
897
|
-
function checkRSAModulusLength(alg, key) {
|
|
898
|
-
if (alg.startsWith("RS") || alg.startsWith("PS") || alg.startsWith("RSA")) {
|
|
899
|
-
const { modulusLength } = key.algorithm;
|
|
900
|
-
if (typeof modulusLength !== "number" || modulusLength < 2048) throw new TypeError(`${alg} requires key modulusLength to be 2048 bits or larger`);
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
function unusable$2(name, prop = "algorithm.name") {
|
|
904
|
-
return /* @__PURE__ */ new TypeError(`CryptoKey does not support this operation, its ${prop} must be ${name}`);
|
|
905
|
-
}
|
|
906
|
-
function isAlgorithm$1(algorithm, name) {
|
|
907
|
-
return algorithm.name === name;
|
|
908
|
-
}
|
|
909
|
-
function getHashLength(hash) {
|
|
910
|
-
return Number.parseInt(hash.name.slice(4), 10);
|
|
911
|
-
}
|
|
912
|
-
function checkUsage$2(key, usage) {
|
|
913
|
-
if (usage && !key.usages.includes(usage)) throw new TypeError(`CryptoKey does not support this operation, its usages must include ${usage}.`);
|
|
914
|
-
}
|
|
915
|
-
//#endregion
|
|
916
|
-
//#region src/core/_crypto/_ecdh.ts
|
|
917
|
-
async function deriveECDHESKey(publicKey, privateKey, algorithm, keyLength, apu = new Uint8Array(0), apv = new Uint8Array(0)) {
|
|
918
|
-
checkECDHCryptoKey(publicKey, "ECDH");
|
|
919
|
-
checkECDHCryptoKey(privateKey, "ECDH", "deriveBits");
|
|
920
|
-
const value = concatUint8Arrays(lengthAndInput(textEncoder.encode(algorithm)), lengthAndInput(apu), lengthAndInput(apv), uint32be(keyLength));
|
|
921
|
-
const length = publicKey.algorithm.name === "X25519" ? 256 : Math.ceil(Number.parseInt(publicKey.algorithm.namedCurve.slice(-3), 10) / 8) << 3;
|
|
922
|
-
return concatKdf(new Uint8Array(await crypto.subtle.deriveBits({
|
|
923
|
-
name: publicKey.algorithm.name,
|
|
924
|
-
public: publicKey
|
|
925
|
-
}, privateKey, length)), keyLength, value);
|
|
926
|
-
}
|
|
927
|
-
function isECDHKeyAllowed(key) {
|
|
928
|
-
switch (key.algorithm.namedCurve) {
|
|
929
|
-
case "P-256":
|
|
930
|
-
case "P-384":
|
|
931
|
-
case "P-521": return true;
|
|
932
|
-
default: return key.algorithm.name === "X25519";
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
|
-
function checkECDHCryptoKey(key, alg, usage) {
|
|
936
|
-
switch (key.algorithm.name) {
|
|
937
|
-
case "ECDH":
|
|
938
|
-
case "X25519": break;
|
|
939
|
-
default: throw unusable$1("ECDH or X25519");
|
|
940
|
-
}
|
|
941
|
-
checkUsage$1(key, usage);
|
|
942
|
-
}
|
|
943
|
-
function unusable$1(name, prop = "algorithm.name") {
|
|
944
|
-
return /* @__PURE__ */ new TypeError(`CryptoKey does not support this operation, its ${prop} must be ${name}`);
|
|
945
|
-
}
|
|
946
|
-
function checkUsage$1(key, usage) {
|
|
947
|
-
if (usage && !key.usages.includes(usage)) throw new TypeError(`CryptoKey does not support this operation, its usages must include ${usage}.`);
|
|
948
|
-
}
|
|
949
|
-
async function concatKdf(secret, bits, value) {
|
|
950
|
-
const iterations = Math.ceil((bits >> 3) / 32);
|
|
951
|
-
const res = new Uint8Array(iterations * 32);
|
|
952
|
-
for (let iter = 0; iter < iterations; iter++) {
|
|
953
|
-
const buf = new Uint8Array(4 + secret.length + value.length);
|
|
954
|
-
buf.set(uint32be(iter + 1));
|
|
955
|
-
buf.set(secret, 4);
|
|
956
|
-
buf.set(value, 4 + secret.length);
|
|
957
|
-
res.set(await digest("sha256", buf), iter * 32);
|
|
958
|
-
}
|
|
959
|
-
return res.slice(0, bits >> 3);
|
|
960
|
-
}
|
|
961
|
-
function lengthAndInput(input) {
|
|
962
|
-
return concatUint8Arrays(uint32be(input.length), input);
|
|
963
|
-
}
|
|
964
|
-
async function digest(algorithm, data) {
|
|
965
|
-
return new Uint8Array(await crypto.subtle.digest(`SHA-${algorithm.slice(-3)}`, data));
|
|
966
|
-
}
|
|
967
|
-
function writeUInt32BE(buf, value, offset) {
|
|
968
|
-
if (value < 0 || value >= 2 ** 32) throw new RangeError(`value must be >= 0 and <= ${2 ** 32 - 1}. Received ${value}`);
|
|
969
|
-
buf.set([
|
|
970
|
-
value >>> 24,
|
|
971
|
-
value >>> 16,
|
|
972
|
-
value >>> 8,
|
|
973
|
-
value & 255
|
|
974
|
-
], offset);
|
|
975
|
-
}
|
|
976
|
-
function uint32be(value) {
|
|
977
|
-
const buf = new Uint8Array(4);
|
|
978
|
-
writeUInt32BE(buf, value, 0);
|
|
979
|
-
return buf;
|
|
980
|
-
}
|
|
981
|
-
//#endregion
|
|
982
|
-
//#region src/core/_crypto/_pbes2.ts
|
|
983
|
-
async function pbes2Wrap(alg, key, cek, p2c, p2s) {
|
|
984
|
-
const derived = await deriveKey(p2s, alg, p2c, key);
|
|
985
|
-
return {
|
|
986
|
-
encryptedKey: await aesKwWrap(alg.slice(-6), derived, cek),
|
|
987
|
-
p2c,
|
|
988
|
-
p2s: base64UrlEncode(p2s)
|
|
989
|
-
};
|
|
990
|
-
}
|
|
991
|
-
async function pbes2Unwrap(alg, key, encryptedKey, p2c, p2s) {
|
|
992
|
-
const derived = await deriveKey(p2s, alg, p2c, key);
|
|
993
|
-
return aesKwUnwrap(alg.slice(-6), derived, encryptedKey);
|
|
994
|
-
}
|
|
995
|
-
async function deriveKey(p2s, alg, p2c, key) {
|
|
996
|
-
if (!(p2s instanceof Uint8Array) || p2s.length < 8) throw new Error("PBES2 Salt Input must be 8 or more octets");
|
|
997
|
-
if (p2c === void 0 || p2c < 1) throw new Error("PBES2 Iteration Count Parameter must be a positive integer");
|
|
998
|
-
const salt = concatUint8Arrays(textEncoder.encode(alg), new Uint8Array([0]), p2s);
|
|
999
|
-
const keylen = Number.parseInt(alg.slice(13, 16), 10);
|
|
1000
|
-
const subtleAlg = {
|
|
1001
|
-
hash: `SHA-${alg.slice(8, 11)}`,
|
|
1002
|
-
iterations: p2c,
|
|
1003
|
-
name: "PBKDF2",
|
|
1004
|
-
salt
|
|
1005
|
-
};
|
|
1006
|
-
const cryptoKey = await getPBKDF2CryptoKey(key, alg);
|
|
1007
|
-
return new Uint8Array(await crypto.subtle.deriveBits(subtleAlg, cryptoKey, keylen));
|
|
1008
|
-
}
|
|
1009
|
-
function checkPBES2CryptoKey(key, alg, usage) {
|
|
1010
|
-
if (!isAlgorithm(key.algorithm, "PBKDF2")) throw unusable("PBKDF2");
|
|
1011
|
-
checkUsage(key, usage);
|
|
1012
|
-
}
|
|
1013
|
-
function unusable(name, prop = "algorithm.name") {
|
|
1014
|
-
return /* @__PURE__ */ new TypeError(`CryptoKey does not support this operation, its ${prop} must be ${name}`);
|
|
1015
|
-
}
|
|
1016
|
-
function isAlgorithm(algorithm, name) {
|
|
1017
|
-
return algorithm.name === name;
|
|
1018
|
-
}
|
|
1019
|
-
function checkUsage(key, usage) {
|
|
1020
|
-
if (usage && !key.usages.includes(usage)) throw new TypeError(`CryptoKey does not support this operation, its usages must include ${usage}.`);
|
|
1021
|
-
}
|
|
1022
|
-
async function getPBKDF2CryptoKey(key, alg, options = {}) {
|
|
1023
|
-
const { extractable = false, usage = "deriveBits" } = options;
|
|
1024
|
-
if (key instanceof Uint8Array) return crypto.subtle.importKey("raw", key, "PBKDF2", extractable, [usage]);
|
|
1025
|
-
checkPBES2CryptoKey(key, alg, usage);
|
|
1026
|
-
return key;
|
|
1027
|
-
}
|
|
1028
|
-
//#endregion
|
|
1029
|
-
//#region src/core/_crypto/_key-encryption.ts
|
|
1030
|
-
const PUBLIC_EPK_FIELDS = new Set([
|
|
1031
|
-
"kty",
|
|
1032
|
-
"crv",
|
|
1033
|
-
"x",
|
|
1034
|
-
"y",
|
|
1035
|
-
"kid",
|
|
1036
|
-
"use",
|
|
1037
|
-
"key_ops",
|
|
1038
|
-
"alg",
|
|
1039
|
-
"x5c",
|
|
1040
|
-
"x5t",
|
|
1041
|
-
"x5t#S256",
|
|
1042
|
-
"x5u"
|
|
1043
|
-
]);
|
|
1044
|
-
async function encryptKey(alg, enc, key, providedCek, providedParameters = {}) {
|
|
1045
|
-
let encryptedKey;
|
|
1046
|
-
let parameters = {};
|
|
1047
|
-
let cek;
|
|
1048
|
-
switch (alg) {
|
|
1049
|
-
case "dir":
|
|
1050
|
-
cek = key;
|
|
1051
|
-
break;
|
|
1052
|
-
case "ECDH-ES":
|
|
1053
|
-
case "ECDH-ES+A128KW":
|
|
1054
|
-
case "ECDH-ES+A192KW":
|
|
1055
|
-
case "ECDH-ES+A256KW": {
|
|
1056
|
-
assertCryptoKey(key);
|
|
1057
|
-
if (!isECDHKeyAllowed(key)) throw new Error("ECDH with the provided key is not allowed or not supported by your javascript runtime");
|
|
1058
|
-
const { apu, apv, epk: providedEpk, epkPrivateKey } = providedParameters;
|
|
1059
|
-
if (providedEpk && !epkPrivateKey) throw new TypeError("ECDH-ES custom ephemeral key material must include the matching private key.");
|
|
1060
|
-
let ephemeralPrivateKey;
|
|
1061
|
-
let epkHeader;
|
|
1062
|
-
if (epkPrivateKey) {
|
|
1063
|
-
ephemeralPrivateKey = await ensurePrivateKey(alg, epkPrivateKey);
|
|
1064
|
-
epkHeader = await exportPublicJwkFrom(alg, providedEpk ?? epkPrivateKey);
|
|
1065
|
-
} else {
|
|
1066
|
-
if (providedEpk) throw new TypeError("ECDH-ES custom ephemeral public key requires a matching private key.");
|
|
1067
|
-
const generated = await crypto.subtle.generateKey(key.algorithm, true, ["deriveBits"]);
|
|
1068
|
-
ephemeralPrivateKey = generated.privateKey;
|
|
1069
|
-
epkHeader = await exportPublicJwkFrom(alg, generated.publicKey);
|
|
1070
|
-
}
|
|
1071
|
-
const sharedSecret = await deriveECDHESKey(key, ephemeralPrivateKey, alg === "ECDH-ES" ? enc : alg, alg === "ECDH-ES" ? bitLengthCEK(enc) : Number.parseInt(alg.slice(-5, -2), 10), apu, apv);
|
|
1072
|
-
parameters = { epk: epkHeader };
|
|
1073
|
-
if (apu) parameters.apu = base64UrlEncode(apu);
|
|
1074
|
-
if (apv) parameters.apv = base64UrlEncode(apv);
|
|
1075
|
-
if (alg === "ECDH-ES") {
|
|
1076
|
-
cek = sharedSecret;
|
|
1077
|
-
break;
|
|
71
|
+
if (options.strictSignerMatch && !_signerKeyMatches(effective, rawKeyMaterial)) continue;
|
|
72
|
+
const signatureBytes = base64UrlDecode(wireSig.signature, { returnAs: "uint8array" });
|
|
73
|
+
const signingInputBytes = textEncoder.encode(`${wireSig.protected ?? ""}.${general.payload}`);
|
|
74
|
+
cryptoAttempted = true;
|
|
75
|
+
try {
|
|
76
|
+
if (!await _verifyWithKey(alg, rawKeyMaterial, signatureBytes, signingInputBytes, effective.kid)) {
|
|
77
|
+
lastError = new JWTError("JWS signature verification failed.", "ERR_JWS_SIGNATURE_INVALID");
|
|
78
|
+
continue;
|
|
1078
79
|
}
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
case "PBES2-HS512+A256KW": {
|
|
1094
|
-
cek = providedCek || generateCEK(enc);
|
|
1095
|
-
const { p2c, p2s } = providedParameters;
|
|
1096
|
-
({encryptedKey, ...parameters} = await pbes2Wrap(alg, key, cek, p2c, p2s));
|
|
1097
|
-
break;
|
|
1098
|
-
}
|
|
1099
|
-
case "A128KW":
|
|
1100
|
-
case "A192KW":
|
|
1101
|
-
case "A256KW":
|
|
1102
|
-
cek = providedCek || generateCEK(enc);
|
|
1103
|
-
encryptedKey = await aesKwWrap(alg, key, cek);
|
|
1104
|
-
break;
|
|
1105
|
-
case "A128GCMKW":
|
|
1106
|
-
case "A192GCMKW":
|
|
1107
|
-
case "A256GCMKW": {
|
|
1108
|
-
cek = providedCek || generateCEK(enc);
|
|
1109
|
-
const { iv } = providedParameters;
|
|
1110
|
-
({encryptedKey, ...parameters} = await gcmkwEncrypt(alg, key, cek, iv));
|
|
1111
|
-
break;
|
|
80
|
+
const useB64 = protectedHeader.b64 !== false;
|
|
81
|
+
const payload = decodeJWSPayload(general.payload, useB64, options.forceUint8Array);
|
|
82
|
+
validateCriticalHeadersJWS(protectedHeader, options.recognizedHeaders);
|
|
83
|
+
validateJwtClaimsIfJsonPayload(payload, options);
|
|
84
|
+
const result = {
|
|
85
|
+
payload,
|
|
86
|
+
protectedHeader,
|
|
87
|
+
signerIndex: i
|
|
88
|
+
};
|
|
89
|
+
if (unprotectedHeader) result.signerHeader = unprotectedHeader;
|
|
90
|
+
return result;
|
|
91
|
+
} catch (err) {
|
|
92
|
+
if (err instanceof JWTError && err.code !== "ERR_JWS_SIGNATURE_INVALID" && err.code !== "ERR_JWK_KEY_NOT_FOUND") throw err;
|
|
93
|
+
lastError = err instanceof JWTError ? err : void 0;
|
|
1112
94
|
}
|
|
1113
|
-
default: throw new Error("Invalid or unsupported \"alg\" (JWE Algorithm) header value");
|
|
1114
|
-
}
|
|
1115
|
-
return {
|
|
1116
|
-
cek,
|
|
1117
|
-
encryptedKey,
|
|
1118
|
-
parameters
|
|
1119
|
-
};
|
|
1120
|
-
}
|
|
1121
|
-
async function normalizeKey(key, alg) {
|
|
1122
|
-
if (key instanceof Uint8Array) return key;
|
|
1123
|
-
if (isCryptoKey(key)) return key;
|
|
1124
|
-
if (isJWK(key)) {
|
|
1125
|
-
if ("k" in key && key.k) return base64UrlDecode(key.k, false);
|
|
1126
|
-
return jwkTokey(key.alg ? key : {
|
|
1127
|
-
...key,
|
|
1128
|
-
alg
|
|
1129
|
-
});
|
|
1130
95
|
}
|
|
1131
|
-
throw new
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
96
|
+
if (options.strictSignerMatch && !cryptoAttempted) throw new JWTError("No signature matched the provided key under strictSignerMatch.", "ERR_JWS_NO_MATCHING_SIGNER");
|
|
97
|
+
throw lastError ?? new JWTError("JWS signature verification failed.", "ERR_JWS_SIGNATURE_INVALID");
|
|
98
|
+
}
|
|
99
|
+
async function verifyMultiAll(jws, keyResolver, options = {}) {
|
|
100
|
+
const general = _normalizeSerialization(jws);
|
|
101
|
+
if (typeof general.payload !== "string" || !Array.isArray(general.signatures) || general.signatures.length === 0) throw new JWTError("Invalid JWS JSON Serialization: missing payload or signatures.", "ERR_JWS_INVALID_SERIALIZATION");
|
|
102
|
+
const outcomes = [];
|
|
103
|
+
for (let i = 0; i < general.signatures.length; i++) outcomes.push(await _verifyOneSignature(general.payload, general.signatures[i], i, keyResolver, options));
|
|
104
|
+
return outcomes;
|
|
105
|
+
}
|
|
106
|
+
function generalToFlattenedJWS(jws) {
|
|
107
|
+
if (jws.signatures.length !== 1) throw new JWTError(`Flattened JWS Serialization requires exactly one signature, got ${jws.signatures.length}.`, "ERR_JWS_INVALID_SERIALIZATION");
|
|
108
|
+
const [signature] = jws.signatures;
|
|
109
|
+
const flattened = {
|
|
110
|
+
payload: jws.payload,
|
|
111
|
+
signature: signature.signature
|
|
1146
112
|
};
|
|
1147
|
-
if (
|
|
1148
|
-
|
|
1149
|
-
return
|
|
113
|
+
if (signature.protected !== void 0) flattened.protected = signature.protected;
|
|
114
|
+
if (signature.header !== void 0) flattened.header = signature.header;
|
|
115
|
+
return flattened;
|
|
116
|
+
}
|
|
117
|
+
function _resolveSignerAlg(key, index) {
|
|
118
|
+
if (!isJWK(key)) throw new JWTError(`Signer[${index}] key is not a JWK.`, "ERR_JWS_SIGNER_ALG_INFERENCE");
|
|
119
|
+
const alg = key.alg;
|
|
120
|
+
if (!alg) throw new JWTError(`Cannot infer "alg" for signer[${index}]: the JWK has no "alg" property.`, "ERR_JWS_SIGNER_ALG_INFERENCE");
|
|
121
|
+
if (alg === "none") throw new JWTError("\"none\" is not a valid signing algorithm", "ERR_JWS_ALG_NOT_ALLOWED");
|
|
122
|
+
return alg;
|
|
123
|
+
}
|
|
124
|
+
function _resolveB64(signers) {
|
|
125
|
+
let decided;
|
|
126
|
+
for (let i = 0; i < signers.length; i++) {
|
|
127
|
+
const raw = signers[i].protectedHeader?.b64;
|
|
128
|
+
if (raw === void 0) continue;
|
|
129
|
+
if (decided === void 0) decided = raw;
|
|
130
|
+
else if (decided !== raw) throw new JWTError(`Inconsistent "b64" across signers (RFC 7797 §3 requires all signers use the same b64 value).`, "ERR_JWS_B64_INCONSISTENT");
|
|
131
|
+
}
|
|
132
|
+
return decided ?? true;
|
|
133
|
+
}
|
|
134
|
+
function _buildSignerProtectedHeader(signer, alg, payload, b64) {
|
|
135
|
+
const userHeader = sanitizeObjectCopy(signer.protectedHeader);
|
|
136
|
+
const header = {};
|
|
137
|
+
if (signer.key.kid) header.kid = signer.key.kid;
|
|
138
|
+
if (userHeader) Object.assign(header, userHeader);
|
|
139
|
+
header.alg = alg;
|
|
140
|
+
applyTypCtyDefaults(header, payload);
|
|
141
|
+
if (!b64) header.b64 = false;
|
|
142
|
+
else delete header.b64;
|
|
143
|
+
return header;
|
|
1150
144
|
}
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
if (normalized.type !== "private") throw new TypeError("ECDH-ES ephemeral private key must have type \"private\".");
|
|
1155
|
-
return normalized;
|
|
145
|
+
function _assertDisjointHeaders(protectedHeader, unprotectedHeader) {
|
|
146
|
+
if (!protectedHeader || !unprotectedHeader) return;
|
|
147
|
+
for (const k of Object.keys(unprotectedHeader)) if (k in protectedHeader) throw new JWTError(`JWS header parameter "${k}" appears in both protected and unprotected headers of a signature; they must be disjoint (RFC 7515 §7.2.1).`, "ERR_JWS_HEADER_PARAMS_NOT_DISJOINT");
|
|
1156
148
|
}
|
|
1157
|
-
|
|
1158
|
-
if (
|
|
1159
|
-
return toPublicEpkJwk(await keyToJWK(value));
|
|
1160
|
-
} catch (error_) {
|
|
1161
|
-
throw new TypeError("ECDH-ES ephemeral CryptoKey must be extractable to export as JWK.", { cause: error_ instanceof Error ? error_ : void 0 });
|
|
1162
|
-
}
|
|
1163
|
-
if (isJWK(value) && isAsymmetricJWK(value) && value.kty === "EC") return toPublicEpkJwk(value);
|
|
1164
|
-
const normalized = await normalizeKey(value, alg);
|
|
1165
|
-
if (!(normalized instanceof CryptoKey)) throw new TypeError("ECDH-ES ephemeral public key must be a CryptoKey or convertible JWK.");
|
|
1166
|
-
return exportPublicJwkFrom(alg, normalized);
|
|
1167
|
-
}
|
|
1168
|
-
//#endregion
|
|
1169
|
-
//#region src/core/_crypto/_pem.ts
|
|
1170
|
-
const toSPKI = (key) => {
|
|
1171
|
-
return genericExport("public", "spki", key);
|
|
1172
|
-
};
|
|
1173
|
-
const toPKCS8 = (key) => {
|
|
1174
|
-
return genericExport("private", "pkcs8", key);
|
|
1175
|
-
};
|
|
1176
|
-
const fromPKCS8 = (pem, alg, options) => {
|
|
1177
|
-
return genericImport(/(?:-----(?:BEGIN|END) PRIVATE KEY-----|\s)/g, "pkcs8", pem, alg, options);
|
|
1178
|
-
};
|
|
1179
|
-
const fromSPKI = (pem, alg, options) => {
|
|
1180
|
-
return genericImport(/(?:-----(?:BEGIN|END) PUBLIC KEY-----|\s)/g, "spki", pem, alg, options);
|
|
1181
|
-
};
|
|
1182
|
-
const fromX509 = (pem, alg, options) => {
|
|
1183
|
-
let spki;
|
|
1184
|
-
try {
|
|
1185
|
-
spki = getSPKI(pem);
|
|
1186
|
-
} catch (error_) {
|
|
1187
|
-
throw new TypeError("Failed to parse the X.509 certificate", { cause: error_ });
|
|
1188
|
-
}
|
|
1189
|
-
return fromSPKI(spki, alg, options);
|
|
1190
|
-
};
|
|
1191
|
-
const formatPEM = (b64, descriptor) => {
|
|
1192
|
-
return `-----BEGIN ${descriptor}-----\n${(b64.match(/.{1,64}/g) || []).join("\n")}\n-----END ${descriptor}-----`;
|
|
1193
|
-
};
|
|
1194
|
-
const genericExport = async (keyType, keyFormat, key) => {
|
|
1195
|
-
if (!isCryptoKey(key)) throw new TypeError(`Key must be ${key} of type CryptoKey`);
|
|
1196
|
-
if (!key.extractable) throw new TypeError("CryptoKey is not extractable");
|
|
1197
|
-
if (key.type !== keyType) throw new TypeError(`key is not a ${keyType} key`);
|
|
1198
|
-
return formatPEM(base64Encode(new Uint8Array(await crypto.subtle.exportKey(keyFormat, key))), `${keyType.toUpperCase()} KEY`);
|
|
1199
|
-
};
|
|
1200
|
-
const genericImport = async (replace, keyFormat, pem, alg, options) => {
|
|
1201
|
-
let algorithm;
|
|
1202
|
-
let keyUsages;
|
|
1203
|
-
const keyData = base64Decode(pem.replace(replace, ""), false);
|
|
1204
|
-
const isPublic = keyFormat === "spki";
|
|
1205
|
-
switch (alg) {
|
|
1206
|
-
case "PS256":
|
|
1207
|
-
case "PS384":
|
|
1208
|
-
case "PS512":
|
|
1209
|
-
algorithm = {
|
|
1210
|
-
name: "RSA-PSS",
|
|
1211
|
-
hash: `SHA-${alg.slice(-3)}`
|
|
1212
|
-
};
|
|
1213
|
-
keyUsages = isPublic ? ["verify"] : ["sign"];
|
|
1214
|
-
break;
|
|
1215
|
-
case "RS256":
|
|
1216
|
-
case "RS384":
|
|
1217
|
-
case "RS512":
|
|
1218
|
-
algorithm = {
|
|
1219
|
-
name: "RSASSA-PKCS1-v1_5",
|
|
1220
|
-
hash: `SHA-${alg.slice(-3)}`
|
|
1221
|
-
};
|
|
1222
|
-
keyUsages = isPublic ? ["verify"] : ["sign"];
|
|
1223
|
-
break;
|
|
1224
|
-
case "RSA-OAEP":
|
|
1225
|
-
case "RSA-OAEP-256":
|
|
1226
|
-
case "RSA-OAEP-384":
|
|
1227
|
-
case "RSA-OAEP-512":
|
|
1228
|
-
algorithm = {
|
|
1229
|
-
name: "RSA-OAEP",
|
|
1230
|
-
hash: `SHA-${Number.parseInt(alg.slice(-3), 10) || 1}`
|
|
1231
|
-
};
|
|
1232
|
-
keyUsages = isPublic ? ["encrypt", "wrapKey"] : ["decrypt", "unwrapKey"];
|
|
1233
|
-
break;
|
|
1234
|
-
case "ES256":
|
|
1235
|
-
algorithm = {
|
|
1236
|
-
name: "ECDSA",
|
|
1237
|
-
namedCurve: "P-256"
|
|
1238
|
-
};
|
|
1239
|
-
keyUsages = isPublic ? ["verify"] : ["sign"];
|
|
1240
|
-
break;
|
|
1241
|
-
case "ES384":
|
|
1242
|
-
algorithm = {
|
|
1243
|
-
name: "ECDSA",
|
|
1244
|
-
namedCurve: "P-384"
|
|
1245
|
-
};
|
|
1246
|
-
keyUsages = isPublic ? ["verify"] : ["sign"];
|
|
1247
|
-
break;
|
|
1248
|
-
case "ES512":
|
|
1249
|
-
algorithm = {
|
|
1250
|
-
name: "ECDSA",
|
|
1251
|
-
namedCurve: "P-521"
|
|
1252
|
-
};
|
|
1253
|
-
keyUsages = isPublic ? ["verify"] : ["sign"];
|
|
1254
|
-
break;
|
|
1255
|
-
case "ECDH-ES":
|
|
1256
|
-
case "ECDH-ES+A128KW":
|
|
1257
|
-
case "ECDH-ES+A192KW":
|
|
1258
|
-
case "ECDH-ES+A256KW": {
|
|
1259
|
-
const namedCurve = getNamedCurve(keyData);
|
|
1260
|
-
algorithm = namedCurve?.startsWith("P-") ? {
|
|
1261
|
-
name: "ECDH",
|
|
1262
|
-
namedCurve
|
|
1263
|
-
} : { name: "X25519" };
|
|
1264
|
-
keyUsages = isPublic ? [] : ["deriveBits"];
|
|
1265
|
-
break;
|
|
1266
|
-
}
|
|
1267
|
-
case "Ed25519":
|
|
1268
|
-
case "EdDSA":
|
|
1269
|
-
algorithm = { name: "Ed25519" };
|
|
1270
|
-
keyUsages = isPublic ? ["verify"] : ["sign"];
|
|
1271
|
-
break;
|
|
1272
|
-
default: throw new Error("Invalid or unsupported \"alg\" (Algorithm) value");
|
|
1273
|
-
}
|
|
1274
|
-
return crypto.subtle.importKey(keyFormat, keyData, algorithm, options?.extractable ?? (isPublic ? true : false), keyUsages);
|
|
1275
|
-
};
|
|
1276
|
-
const findOid = (keyData, oid, from = 0) => {
|
|
1277
|
-
if (from === 0) {
|
|
1278
|
-
oid.unshift(oid.length);
|
|
1279
|
-
oid.unshift(6);
|
|
1280
|
-
}
|
|
1281
|
-
const i = keyData.indexOf(oid[0], from);
|
|
1282
|
-
if (i === -1) return false;
|
|
1283
|
-
const sub = keyData.subarray(i, i + oid.length);
|
|
1284
|
-
if (sub.length !== oid.length) return false;
|
|
1285
|
-
return sub.every((value, index) => value === oid[index]) || findOid(keyData, oid, i + 1);
|
|
1286
|
-
};
|
|
1287
|
-
const getNamedCurve = (keyData) => {
|
|
1288
|
-
switch (true) {
|
|
1289
|
-
case findOid(keyData, [
|
|
1290
|
-
42,
|
|
1291
|
-
134,
|
|
1292
|
-
72,
|
|
1293
|
-
206,
|
|
1294
|
-
61,
|
|
1295
|
-
3,
|
|
1296
|
-
1,
|
|
1297
|
-
7
|
|
1298
|
-
]): return "P-256";
|
|
1299
|
-
case findOid(keyData, [
|
|
1300
|
-
43,
|
|
1301
|
-
129,
|
|
1302
|
-
4,
|
|
1303
|
-
0,
|
|
1304
|
-
34
|
|
1305
|
-
]): return "P-384";
|
|
1306
|
-
case findOid(keyData, [
|
|
1307
|
-
43,
|
|
1308
|
-
129,
|
|
1309
|
-
4,
|
|
1310
|
-
0,
|
|
1311
|
-
35
|
|
1312
|
-
]): return "P-521";
|
|
1313
|
-
default: return;
|
|
1314
|
-
}
|
|
1315
|
-
};
|
|
1316
|
-
const getElement = (seq) => {
|
|
1317
|
-
const result = [];
|
|
1318
|
-
let next = 0;
|
|
1319
|
-
while (next < seq.length) {
|
|
1320
|
-
const nextPart = parseElement(seq.subarray(next));
|
|
1321
|
-
result.push(nextPart);
|
|
1322
|
-
next += nextPart.byteLength;
|
|
1323
|
-
}
|
|
1324
|
-
return result;
|
|
1325
|
-
};
|
|
1326
|
-
const parseElement = (bytes) => {
|
|
1327
|
-
let position = 0;
|
|
1328
|
-
let tag = bytes[0] & 31;
|
|
1329
|
-
position++;
|
|
1330
|
-
if (tag === 31) {
|
|
1331
|
-
tag = 0;
|
|
1332
|
-
while (bytes[position] >= 128) {
|
|
1333
|
-
tag = tag * 128 + bytes[position] - 128;
|
|
1334
|
-
position++;
|
|
1335
|
-
}
|
|
1336
|
-
tag = tag * 128 + bytes[position] - 128;
|
|
1337
|
-
position++;
|
|
1338
|
-
}
|
|
1339
|
-
let length = 0;
|
|
1340
|
-
if (bytes[position] < 128) {
|
|
1341
|
-
length = bytes[position];
|
|
1342
|
-
position++;
|
|
1343
|
-
} else if (length === 128) {
|
|
1344
|
-
length = 0;
|
|
1345
|
-
while (bytes[position + length] !== 0 || bytes[position + length + 1] !== 0) {
|
|
1346
|
-
if (length > bytes.byteLength) throw new TypeError("invalid indefinite form length");
|
|
1347
|
-
length++;
|
|
1348
|
-
}
|
|
1349
|
-
const byteLength = position + length + 2;
|
|
1350
|
-
return {
|
|
1351
|
-
byteLength,
|
|
1352
|
-
contents: bytes.subarray(position, position + length),
|
|
1353
|
-
raw: bytes.subarray(0, byteLength)
|
|
1354
|
-
};
|
|
1355
|
-
} else {
|
|
1356
|
-
const numberOfDigits = bytes[position] & 127;
|
|
1357
|
-
position++;
|
|
1358
|
-
length = 0;
|
|
1359
|
-
for (let i = 0; i < numberOfDigits; i++) {
|
|
1360
|
-
length = length * 256 + bytes[position];
|
|
1361
|
-
position++;
|
|
1362
|
-
}
|
|
1363
|
-
}
|
|
1364
|
-
const byteLength = position + length;
|
|
149
|
+
function _mergeHeaders(protectedHeader, unprotectedHeader) {
|
|
150
|
+
if (!unprotectedHeader) return protectedHeader;
|
|
1365
151
|
return {
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
raw: bytes.subarray(0, byteLength)
|
|
152
|
+
...protectedHeader,
|
|
153
|
+
...unprotectedHeader
|
|
1369
154
|
};
|
|
1370
|
-
};
|
|
1371
|
-
const spkiFromX509 = (buf) => {
|
|
1372
|
-
const tbsCertificate = getElement(getElement(parseElement(buf).contents)[0].contents);
|
|
1373
|
-
return base64Encode(tbsCertificate[tbsCertificate[0].raw[0] === 160 ? 6 : 5].raw);
|
|
1374
|
-
};
|
|
1375
|
-
let createPublicKey;
|
|
1376
|
-
const getSPKI = (x509) => {
|
|
1377
|
-
try {
|
|
1378
|
-
createPublicKey ??= globalThis.process?.getBuiltinModule?.("node:crypto")?.createPublicKey;
|
|
1379
|
-
} catch {
|
|
1380
|
-
createPublicKey = 0;
|
|
1381
|
-
}
|
|
1382
|
-
if (createPublicKey) try {
|
|
1383
|
-
return new createPublicKey(x509).export({
|
|
1384
|
-
format: "pem",
|
|
1385
|
-
type: "spki"
|
|
1386
|
-
});
|
|
1387
|
-
} catch {}
|
|
1388
|
-
return formatPEM(spkiFromX509(base64Decode(x509.replace(/(?:-----(?:BEGIN|END) CERTIFICATE-----|\s)/g, ""), false)), "PUBLIC KEY");
|
|
1389
|
-
};
|
|
1390
|
-
//#endregion
|
|
1391
|
-
//#region src/core/jwk.ts
|
|
1392
|
-
async function generateKey(alg, options = {}) {
|
|
1393
|
-
const exportToJWK = options.toJWK !== void 0 && options.toJWK !== false;
|
|
1394
|
-
const defaultExtractable = options.extractable !== false;
|
|
1395
|
-
if (alg === "A128CBC-HS256" || alg === "A192CBC-HS384" || alg === "A256CBC-HS512") {
|
|
1396
|
-
const keyBytes = randomBytes(bitLengthCEK(alg) >> 3);
|
|
1397
|
-
if (exportToJWK) return keyToJWK(keyBytes);
|
|
1398
|
-
return keyBytes;
|
|
1399
|
-
}
|
|
1400
|
-
const { algorithm, keyUsages } = getGenerateKeyParams(alg, options);
|
|
1401
|
-
const key = await crypto.subtle.generateKey(algorithm, defaultExtractable, keyUsages);
|
|
1402
|
-
if (exportToJWK) if (key instanceof CryptoKey) return exportKey(key, { alg });
|
|
1403
|
-
else {
|
|
1404
|
-
const [publicKey, privateKey] = await Promise.all([exportKey(key.publicKey, { alg }), exportKey(key.privateKey, { alg })]);
|
|
1405
|
-
return {
|
|
1406
|
-
privateKey,
|
|
1407
|
-
publicKey
|
|
1408
|
-
};
|
|
1409
|
-
}
|
|
1410
|
-
return key;
|
|
1411
155
|
}
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
const extraParams = {
|
|
1422
|
-
kid: typeof jwkParams?.kid === "string" ? jwkParams.kid : crypto.randomUUID(),
|
|
1423
|
-
...jwkParams
|
|
1424
|
-
};
|
|
1425
|
-
const result = await generateKey(alg, {
|
|
1426
|
-
...options,
|
|
1427
|
-
toJWK: true
|
|
1428
|
-
});
|
|
1429
|
-
if (result && typeof result === "object" && "privateKey" in result && "publicKey" in result) {
|
|
1430
|
-
const pair = result;
|
|
156
|
+
function _normalizeSerialization(input) {
|
|
157
|
+
if (!input || typeof input !== "object") throw new JWTError("Invalid JWS JSON Serialization: expected an object.", "ERR_JWS_INVALID_SERIALIZATION");
|
|
158
|
+
const obj = input;
|
|
159
|
+
if (Array.isArray(obj.signatures)) return obj;
|
|
160
|
+
if ("signature" in obj) {
|
|
161
|
+
const flattened = obj;
|
|
162
|
+
const signature = { signature: flattened.signature };
|
|
163
|
+
if (flattened.protected !== void 0) signature.protected = flattened.protected;
|
|
164
|
+
if (flattened.header !== void 0) signature.header = flattened.header;
|
|
1431
165
|
return {
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
...pair.privateKey
|
|
1435
|
-
},
|
|
1436
|
-
publicKey: {
|
|
1437
|
-
...extraParams,
|
|
1438
|
-
...pair.publicKey
|
|
1439
|
-
}
|
|
166
|
+
payload: flattened.payload,
|
|
167
|
+
signatures: [signature]
|
|
1440
168
|
};
|
|
1441
169
|
}
|
|
1442
|
-
|
|
1443
|
-
...extraParams,
|
|
1444
|
-
...result
|
|
1445
|
-
};
|
|
170
|
+
throw new JWTError("Invalid JWS JSON Serialization: missing both signatures[] and flattened signature.", "ERR_JWS_INVALID_SERIALIZATION");
|
|
1446
171
|
}
|
|
1447
|
-
|
|
1448
|
-
const
|
|
1449
|
-
|
|
1450
|
-
if (
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
const wrappingAlg = alg.slice(-6);
|
|
1454
|
-
const finalUsages = keyUsage ?? ["wrapKey", "unwrapKey"];
|
|
1455
|
-
const finalExtractable = exportToJWK ? true : extractable === true;
|
|
1456
|
-
const derivedKey = await crypto.subtle.importKey("raw", derivedBytes, { name: "AES-KW" }, finalExtractable, finalUsages);
|
|
1457
|
-
if (exportToJWK) return {
|
|
1458
|
-
...await keyToJWK(derivedKey),
|
|
1459
|
-
alg: wrappingAlg,
|
|
1460
|
-
kty: "oct"
|
|
1461
|
-
};
|
|
1462
|
-
return derivedKey;
|
|
1463
|
-
}
|
|
1464
|
-
/**
|
|
1465
|
-
* Derives a Json Web Key (JWK) from a password using PBKDF2 as specified by PBES2 algorithms.
|
|
1466
|
-
*
|
|
1467
|
-
* @param password The password to derive the key from (string or Uint8Array).
|
|
1468
|
-
* @param alg The PBES2 algorithm identifier (e.g., "PBES2-HS256+A128KW").
|
|
1469
|
-
* @param jwkParams Optional partial JWK to merge with the generated key, allowing overrides.
|
|
1470
|
-
* @param options Configuration options including salt and iterations.
|
|
1471
|
-
* @returns A Promise resolving to the derived JWK (JWK_oct).
|
|
1472
|
-
*/
|
|
1473
|
-
async function deriveJWKFromPassword(password, alg, options, jwkParams) {
|
|
1474
|
-
const result = await deriveKeyFromPassword(password, alg, {
|
|
1475
|
-
...options,
|
|
1476
|
-
toJWK: true
|
|
1477
|
-
});
|
|
1478
|
-
return jwkParams ? {
|
|
1479
|
-
...jwkParams,
|
|
1480
|
-
...result
|
|
1481
|
-
} : result;
|
|
1482
|
-
}
|
|
1483
|
-
/**
|
|
1484
|
-
* Default JWK import cache backed by a WeakMap.
|
|
1485
|
-
*
|
|
1486
|
-
* The outer WeakMap is keyed by the JWK object reference — a cache hit only
|
|
1487
|
-
* occurs when the exact same object variable is passed to {@link importKey}.
|
|
1488
|
-
* Reconstructing a structurally identical JWK (e.g. `{ ...jwk }`) will miss
|
|
1489
|
-
* the cache.
|
|
1490
|
-
*
|
|
1491
|
-
* The inner `Record<string, CryptoKey>` uses a plain object rather than `Map`
|
|
1492
|
-
* because typical entries have 1–2 algorithm strings per key. V8 applies
|
|
1493
|
-
* hidden-class optimisation to small plain objects, making property access
|
|
1494
|
-
* faster than `Map.get()` at this cardinality.
|
|
1495
|
-
*/
|
|
1496
|
-
var WeakMapJWKCache = class {
|
|
1497
|
-
_map = /* @__PURE__ */ new WeakMap();
|
|
1498
|
-
get(jwk, alg) {
|
|
1499
|
-
return this._map.get(jwk)?.[alg];
|
|
1500
|
-
}
|
|
1501
|
-
set(jwk, alg, key) {
|
|
1502
|
-
let entry = this._map.get(jwk);
|
|
1503
|
-
if (!entry) this._map.set(jwk, { [alg]: key });
|
|
1504
|
-
else entry[alg] = key;
|
|
1505
|
-
}
|
|
1506
|
-
};
|
|
1507
|
-
let _activeCache = new WeakMapJWKCache();
|
|
1508
|
-
async function importKey(key, algOrOptions) {
|
|
1509
|
-
const alg = typeof algOrOptions === "string" ? algOrOptions : void 0;
|
|
1510
|
-
if (typeof key === "string") key = textEncoder.encode(key);
|
|
1511
|
-
if (key instanceof Uint8Array) return key;
|
|
1512
|
-
if (isCryptoKey(key)) return key;
|
|
1513
|
-
if (isJWK(key)) if ("k" in key && typeof key.k === "string") {
|
|
1514
|
-
const rawBytes = base64UrlDecode(key.k, false);
|
|
1515
|
-
if (typeof algOrOptions === "object" && algOrOptions.asCryptoKey) {
|
|
1516
|
-
const { algorithm, usage, extractable = false } = algOrOptions;
|
|
1517
|
-
return crypto.subtle.importKey("raw", rawBytes, algorithm, extractable, usage);
|
|
1518
|
-
}
|
|
1519
|
-
return rawBytes;
|
|
1520
|
-
} else {
|
|
1521
|
-
if (!key.alg && !alg) throw new TypeError("Algorithm must be provided when importing non-oct JWK");
|
|
1522
|
-
const effectiveAlg = key.alg || alg;
|
|
1523
|
-
if (_activeCache) {
|
|
1524
|
-
const cached = _activeCache.get(key, effectiveAlg);
|
|
1525
|
-
if (cached) return cached;
|
|
1526
|
-
}
|
|
1527
|
-
const cryptoKey = await jwkTokey(key.alg ? key : {
|
|
1528
|
-
...key,
|
|
1529
|
-
alg
|
|
1530
|
-
});
|
|
1531
|
-
if (_activeCache) _activeCache.set(key, effectiveAlg, cryptoKey);
|
|
1532
|
-
return cryptoKey;
|
|
1533
|
-
}
|
|
1534
|
-
throw new JWTError("Invalid key type provided to importKey", "ERR_JWK_INVALID");
|
|
1535
|
-
}
|
|
1536
|
-
/**
|
|
1537
|
-
* Exports a CryptoKey to a JWK (JSON Web Key) format.
|
|
1538
|
-
*
|
|
1539
|
-
* @param key The CryptoKey to export.
|
|
1540
|
-
* @param jwk Optional partial JWK to merge with the exported key, allowing overrides.
|
|
1541
|
-
* @returns A Promise resolving to the exported JWK.
|
|
1542
|
-
*/
|
|
1543
|
-
async function exportKey(key, jwk) {
|
|
1544
|
-
const exportedJwk = await keyToJWK(key);
|
|
1545
|
-
if (jwk) return {
|
|
1546
|
-
...sanitizeObject(jwk),
|
|
1547
|
-
...exportedJwk
|
|
1548
|
-
};
|
|
1549
|
-
return exportedJwk;
|
|
1550
|
-
}
|
|
1551
|
-
async function resolveWrappingKey(alg, key) {
|
|
1552
|
-
if (alg.startsWith("PBES2") && (typeof key === "string" || key instanceof Uint8Array)) return typeof key === "string" ? textEncoder.encode(key) : key;
|
|
1553
|
-
if (typeof key === "string") throw new TypeError(`Key must be CryptoKey, JWK, or Uint8Array for ${alg}`);
|
|
1554
|
-
return importKey(key, alg);
|
|
1555
|
-
}
|
|
1556
|
-
async function unwrapKey(alg, wrappedKey, unwrappingKey, options = {}) {
|
|
1557
|
-
const returnRaw = options.format === "raw";
|
|
1558
|
-
const defaultExtractable = options.extractable !== false;
|
|
1559
|
-
const importedUnwrappingKey = isCryptoKey(unwrappingKey) ? unwrappingKey : await resolveWrappingKey(alg, unwrappingKey);
|
|
1560
|
-
let unwrappedCekBytes;
|
|
1561
|
-
switch (alg) {
|
|
1562
|
-
case "dir":
|
|
1563
|
-
if (importedUnwrappingKey instanceof Uint8Array) unwrappedCekBytes = importedUnwrappingKey;
|
|
1564
|
-
else {
|
|
1565
|
-
if (!returnRaw) return importedUnwrappingKey;
|
|
1566
|
-
unwrappedCekBytes = new Uint8Array(await crypto.subtle.exportKey("raw", importedUnwrappingKey));
|
|
1567
|
-
}
|
|
1568
|
-
break;
|
|
1569
|
-
case "A128KW":
|
|
1570
|
-
case "A192KW":
|
|
1571
|
-
case "A256KW":
|
|
1572
|
-
unwrappedCekBytes = await aesKwUnwrap(alg, importedUnwrappingKey, wrappedKey);
|
|
1573
|
-
break;
|
|
1574
|
-
case "PBES2-HS256+A128KW":
|
|
1575
|
-
case "PBES2-HS384+A192KW":
|
|
1576
|
-
case "PBES2-HS512+A256KW": {
|
|
1577
|
-
const { p2s, p2c } = options;
|
|
1578
|
-
if (!p2s || typeof p2c !== "number") throw new Error("PBES2 requires 'p2s' (salt) and 'p2c' (count) options");
|
|
1579
|
-
unwrappedCekBytes = await pbes2Unwrap(alg, importedUnwrappingKey, wrappedKey, p2c, typeof p2s === "string" ? base64UrlDecode(p2s, false) : p2s);
|
|
1580
|
-
break;
|
|
1581
|
-
}
|
|
1582
|
-
case "A128GCMKW":
|
|
1583
|
-
case "A192GCMKW":
|
|
1584
|
-
case "A256GCMKW":
|
|
1585
|
-
if (!options.iv || !options.tag) throw new Error("AES-GCMKW requires 'iv' and 'tag' options for unwrapping");
|
|
1586
|
-
unwrappedCekBytes = await gcmkwDecrypt(alg, importedUnwrappingKey, wrappedKey, typeof options.iv === "string" ? base64UrlDecode(options.iv, false) : options.iv, typeof options.tag === "string" ? base64UrlDecode(options.tag, false) : options.tag);
|
|
1587
|
-
break;
|
|
1588
|
-
case "RSA-OAEP":
|
|
1589
|
-
case "RSA-OAEP-256":
|
|
1590
|
-
case "RSA-OAEP-384":
|
|
1591
|
-
case "RSA-OAEP-512": {
|
|
1592
|
-
if (!(importedUnwrappingKey instanceof CryptoKey)) throw new TypeError("RSA-OAEP requires the unwrapping key to be provided as a CryptoKey");
|
|
1593
|
-
unwrappedCekBytes = await decryptRSAES(alg, importedUnwrappingKey, wrappedKey);
|
|
1594
|
-
if (returnRaw) break;
|
|
1595
|
-
const inferredAlgorithm = options.unwrappedKeyAlgorithm || inferAesImportAlgorithm(options.enc, unwrappedCekBytes.length);
|
|
1596
|
-
if (!inferredAlgorithm) throw new Error("Unable to infer algorithm for RSA-OAEP unwrapped key. Provide \"unwrappedKeyAlgorithm\" in options.");
|
|
1597
|
-
const keyUsages = options.keyUsage || ["encrypt", "decrypt"];
|
|
1598
|
-
return await crypto.subtle.importKey("raw", unwrappedCekBytes, inferredAlgorithm, defaultExtractable, keyUsages);
|
|
1599
|
-
}
|
|
1600
|
-
case "ECDH-ES":
|
|
1601
|
-
case "ECDH-ES+A128KW":
|
|
1602
|
-
case "ECDH-ES+A192KW":
|
|
1603
|
-
case "ECDH-ES+A256KW": {
|
|
1604
|
-
if (!options.epk) throw new Error("ECDH-ES requires 'epk' (Ephemeral Public Key) option");
|
|
1605
|
-
if (!(importedUnwrappingKey instanceof CryptoKey)) throw new TypeError("ECDH-ES requires the unwrapping key to be a CryptoKey");
|
|
1606
|
-
if (!isECDHKeyAllowed(importedUnwrappingKey)) throw new Error("ECDH with the provided key is not allowed or not supported");
|
|
1607
|
-
const epkCandidate = options.epk instanceof CryptoKey ? options.epk : await normalizeKey(options.epk, alg);
|
|
1608
|
-
if (!(epkCandidate instanceof CryptoKey)) throw new TypeError("Failed to normalize ECDH ephemeral public key");
|
|
1609
|
-
const apuBytes = typeof options.apu === "string" ? base64UrlDecode(options.apu, false) : options.apu ?? new Uint8Array(0);
|
|
1610
|
-
const apvBytes = typeof options.apv === "string" ? base64UrlDecode(options.apv, false) : options.apv ?? new Uint8Array(0);
|
|
1611
|
-
const infoAlg = alg === "ECDH-ES" ? options.enc : alg;
|
|
1612
|
-
if (!infoAlg) throw new Error("ECDH-ES requires content encryption algorithm ('enc') to derive the shared secret");
|
|
1613
|
-
const sharedSecret = await deriveECDHESKey(epkCandidate, importedUnwrappingKey, infoAlg, alg === "ECDH-ES" ? bitLengthCEK(infoAlg) : Number.parseInt(alg.slice(-5, -2), 10), apuBytes, apvBytes);
|
|
1614
|
-
if (alg === "ECDH-ES") unwrappedCekBytes = sharedSecret;
|
|
1615
|
-
else {
|
|
1616
|
-
const kwAlg = alg.slice(-6);
|
|
1617
|
-
if (!(wrappedKey instanceof Uint8Array) || wrappedKey.length === 0) throw new Error("ECDH-ES key agreement with key wrapping requires an encrypted key");
|
|
1618
|
-
unwrappedCekBytes = await aesKwUnwrap(kwAlg, sharedSecret, wrappedKey);
|
|
1619
|
-
}
|
|
1620
|
-
break;
|
|
1621
|
-
}
|
|
1622
|
-
default: throw new Error(`Unsupported key unwrapping algorithm: ${alg}`);
|
|
172
|
+
function _signerKeyMatches(effectiveHeader, keyMaterial) {
|
|
173
|
+
const headerKid = effectiveHeader.kid;
|
|
174
|
+
if (isJWKSet(keyMaterial)) return keyMaterial.keys.some((k) => !headerKid || k.kid === headerKid);
|
|
175
|
+
if (isJWK(keyMaterial)) {
|
|
176
|
+
if (headerKid && keyMaterial.kid && headerKid !== keyMaterial.kid) return false;
|
|
177
|
+
return true;
|
|
1623
178
|
}
|
|
1624
|
-
|
|
1625
|
-
return await crypto.subtle.importKey("raw", unwrappedCekBytes, options.unwrappedKeyAlgorithm || { name: "AES-GCM" }, defaultExtractable, options.keyUsage || ["encrypt", "decrypt"]);
|
|
179
|
+
return !headerKid;
|
|
1626
180
|
}
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
const extractable = options?.extractable !== false;
|
|
1640
|
-
let cryptoKey;
|
|
1641
|
-
switch (pemType) {
|
|
1642
|
-
case "pkcs8":
|
|
1643
|
-
cryptoKey = await fromPKCS8(pem, alg, { extractable });
|
|
1644
|
-
break;
|
|
1645
|
-
case "spki":
|
|
1646
|
-
cryptoKey = await fromSPKI(pem, alg, { extractable });
|
|
1647
|
-
break;
|
|
1648
|
-
case "x509":
|
|
1649
|
-
cryptoKey = await fromX509(pem, alg, { extractable });
|
|
1650
|
-
break;
|
|
1651
|
-
default: throw new TypeError(`Unsupported PEM type: ${pemType}`);
|
|
181
|
+
async function _verifyWithKey(alg, rawKeyMaterial, signatureBytes, signingInputBytes, headerKid) {
|
|
182
|
+
if (isJWKSet(rawKeyMaterial)) {
|
|
183
|
+
const candidates = getJWKsFromSet(rawKeyMaterial, buildJWKSetFilter({
|
|
184
|
+
kid: headerKid,
|
|
185
|
+
alg
|
|
186
|
+
}));
|
|
187
|
+
if (candidates.length === 0) throw new JWTError("No key found in JWK Set.", "ERR_JWK_KEY_NOT_FOUND");
|
|
188
|
+
for (const candidate of candidates) if (await verify$1(alg, await resolveSigningKey(alg, await importKey(candidate, {
|
|
189
|
+
alg,
|
|
190
|
+
expect: "public"
|
|
191
|
+
}), "verify"), signatureBytes, signingInputBytes)) return true;
|
|
192
|
+
return false;
|
|
1652
193
|
}
|
|
1653
|
-
return
|
|
194
|
+
return verify$1(alg, await resolveSigningKey(alg, await importKey(rawKeyMaterial, {
|
|
1654
195
|
alg,
|
|
1655
|
-
|
|
1656
|
-
});
|
|
1657
|
-
}
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
* required to correctly convert it to a CryptoKey first.
|
|
1665
|
-
* @returns A Promise resolving to the PEM-encoded key string.
|
|
1666
|
-
*/
|
|
1667
|
-
async function exportToPEM(jwk, pemFormat, algForCryptoKeyImport) {
|
|
1668
|
-
if (jwk.kty === "oct") throw new TypeError("Octet (symmetric) JWKs (kty: 'oct') cannot be exported to PKCS8 or SPKI PEM formats.");
|
|
1669
|
-
const effectiveAlg = jwk.alg || algForCryptoKeyImport;
|
|
1670
|
-
if (!effectiveAlg && (jwk.kty === "RSA" || jwk.kty === "EC" || jwk.kty === "OKP")) throw new TypeError("Algorithm (alg) must be provided in the JWK or as a parameter for converting this JWK type to a CryptoKey.");
|
|
1671
|
-
const cryptoKeyCandidate = await importKey({
|
|
1672
|
-
...jwk,
|
|
1673
|
-
ext: true
|
|
1674
|
-
}, effectiveAlg);
|
|
1675
|
-
if (!isCryptoKey(cryptoKeyCandidate)) throw new Error("Failed to convert JWK to a CryptoKey instance suitable for PEM export.");
|
|
1676
|
-
const cryptoKey = cryptoKeyCandidate;
|
|
1677
|
-
switch (pemFormat) {
|
|
1678
|
-
case "pkcs8":
|
|
1679
|
-
if (cryptoKey.type !== "private") throw new TypeError(`Only 'private' type CryptoKeys can be exported to PKCS8 PEM format. Key type is '${cryptoKey.type}'.`);
|
|
1680
|
-
return toPKCS8(cryptoKey);
|
|
1681
|
-
case "spki":
|
|
1682
|
-
if (cryptoKey.type !== "public") throw new TypeError(`Only 'public' type CryptoKeys can be exported to SPKI PEM format. Key type is '${cryptoKey.type}'.`);
|
|
1683
|
-
return toSPKI(cryptoKey);
|
|
1684
|
-
default: throw new TypeError(`Unsupported PEM format: ${pemFormat}`);
|
|
1685
|
-
}
|
|
1686
|
-
}
|
|
1687
|
-
/**
|
|
1688
|
-
* Returns all JWKs from a JWK Set, optionally narrowed by a predicate.
|
|
1689
|
-
*
|
|
1690
|
-
* Useful for multi-key verification retry, key rotation tooling, and
|
|
1691
|
-
* constructing multi-recipient JWE JSON Serialization structures.
|
|
1692
|
-
*
|
|
1693
|
-
* @param jwkSet The JWK Set to search.
|
|
1694
|
-
* @param filter Optional predicate `(jwk: JWK) => boolean`. Returns all keys when omitted.
|
|
1695
|
-
* @returns An array of matching JWKs.
|
|
1696
|
-
*/
|
|
1697
|
-
function getJWKsFromSet(jwkSet, filter) {
|
|
1698
|
-
if (!jwkSet || !isJWKSet(jwkSet)) throw new TypeError("Invalid JWK Set provided");
|
|
1699
|
-
if (!filter) return [...jwkSet.keys];
|
|
1700
|
-
return jwkSet.keys.filter(filter);
|
|
1701
|
-
}
|
|
1702
|
-
function getGenerateKeyParams(alg, options) {
|
|
1703
|
-
let algorithm;
|
|
1704
|
-
let keyUsages;
|
|
1705
|
-
const defaultKeyUsage = options?.keyUsage;
|
|
1706
|
-
switch (alg) {
|
|
1707
|
-
case "HS256":
|
|
1708
|
-
case "HS384":
|
|
1709
|
-
case "HS512":
|
|
1710
|
-
algorithm = {
|
|
1711
|
-
name: "HMAC",
|
|
1712
|
-
hash: `SHA-${alg.slice(2)}`
|
|
1713
|
-
};
|
|
1714
|
-
keyUsages = defaultKeyUsage ?? ["sign", "verify"];
|
|
1715
|
-
break;
|
|
1716
|
-
case "RS256":
|
|
1717
|
-
case "RS384":
|
|
1718
|
-
case "RS512":
|
|
1719
|
-
algorithm = {
|
|
1720
|
-
name: "RSASSA-PKCS1-v1_5",
|
|
1721
|
-
modulusLength: options?.modulusLength ?? 2048,
|
|
1722
|
-
publicExponent: options?.publicExponent ?? new Uint8Array([
|
|
1723
|
-
1,
|
|
1724
|
-
0,
|
|
1725
|
-
1
|
|
1726
|
-
]),
|
|
1727
|
-
hash: `SHA-${alg.slice(2)}`
|
|
1728
|
-
};
|
|
1729
|
-
keyUsages = defaultKeyUsage ?? ["sign", "verify"];
|
|
1730
|
-
break;
|
|
1731
|
-
case "PS256":
|
|
1732
|
-
case "PS384":
|
|
1733
|
-
case "PS512":
|
|
1734
|
-
algorithm = {
|
|
1735
|
-
name: "RSA-PSS",
|
|
1736
|
-
modulusLength: options?.modulusLength ?? 2048,
|
|
1737
|
-
publicExponent: options?.publicExponent ?? new Uint8Array([
|
|
1738
|
-
1,
|
|
1739
|
-
0,
|
|
1740
|
-
1
|
|
1741
|
-
]),
|
|
1742
|
-
hash: `SHA-${alg.slice(2)}`
|
|
1743
|
-
};
|
|
1744
|
-
keyUsages = defaultKeyUsage ?? ["sign", "verify"];
|
|
1745
|
-
break;
|
|
1746
|
-
case "RSA-OAEP":
|
|
1747
|
-
case "RSA-OAEP-256":
|
|
1748
|
-
case "RSA-OAEP-384":
|
|
1749
|
-
case "RSA-OAEP-512":
|
|
1750
|
-
algorithm = {
|
|
1751
|
-
name: "RSA-OAEP",
|
|
1752
|
-
modulusLength: options?.modulusLength ?? 2048,
|
|
1753
|
-
publicExponent: options?.publicExponent ?? new Uint8Array([
|
|
1754
|
-
1,
|
|
1755
|
-
0,
|
|
1756
|
-
1
|
|
1757
|
-
]),
|
|
1758
|
-
hash: `SHA-${Number.parseInt(alg.slice(9), 10) || 1}`
|
|
1759
|
-
};
|
|
1760
|
-
keyUsages = defaultKeyUsage ?? [
|
|
1761
|
-
"encrypt",
|
|
1762
|
-
"decrypt",
|
|
1763
|
-
"wrapKey",
|
|
1764
|
-
"unwrapKey"
|
|
1765
|
-
];
|
|
1766
|
-
break;
|
|
1767
|
-
case "A128KW":
|
|
1768
|
-
case "A192KW":
|
|
1769
|
-
case "A256KW":
|
|
1770
|
-
algorithm = {
|
|
1771
|
-
name: "AES-KW",
|
|
1772
|
-
length: Number.parseInt(alg.slice(1, 4), 10)
|
|
1773
|
-
};
|
|
1774
|
-
keyUsages = defaultKeyUsage ?? ["wrapKey", "unwrapKey"];
|
|
1775
|
-
break;
|
|
1776
|
-
case "A128GCM":
|
|
1777
|
-
case "A192GCM":
|
|
1778
|
-
case "A256GCM":
|
|
1779
|
-
case "A128GCMKW":
|
|
1780
|
-
case "A192GCMKW":
|
|
1781
|
-
case "A256GCMKW": {
|
|
1782
|
-
const baseAlg = alg.replace("KW", "");
|
|
1783
|
-
algorithm = {
|
|
1784
|
-
name: "AES-GCM",
|
|
1785
|
-
length: Number.parseInt(baseAlg.slice(1, 4), 10)
|
|
1786
|
-
};
|
|
1787
|
-
keyUsages = defaultKeyUsage ?? (alg.endsWith("KW") ? ["wrapKey", "unwrapKey"] : ["encrypt", "decrypt"]);
|
|
1788
|
-
break;
|
|
1789
|
-
}
|
|
1790
|
-
case "ES256":
|
|
1791
|
-
case "ES384":
|
|
1792
|
-
case "ES512":
|
|
1793
|
-
algorithm = {
|
|
1794
|
-
name: "ECDSA",
|
|
1795
|
-
namedCurve: `P-${alg.slice(2)}`
|
|
1796
|
-
};
|
|
1797
|
-
keyUsages = defaultKeyUsage ?? ["sign", "verify"];
|
|
1798
|
-
break;
|
|
1799
|
-
case "Ed25519":
|
|
1800
|
-
keyUsages = defaultKeyUsage ?? ["sign", "verify"];
|
|
1801
|
-
algorithm = { name: "Ed25519" };
|
|
1802
|
-
break;
|
|
1803
|
-
case "EdDSA": {
|
|
1804
|
-
keyUsages = defaultKeyUsage ?? ["sign", "verify"];
|
|
1805
|
-
const namedCurve = options?.namedCurve ?? "Ed25519";
|
|
1806
|
-
switch (namedCurve) {
|
|
1807
|
-
case "Ed25519":
|
|
1808
|
-
case "Ed448":
|
|
1809
|
-
algorithm = { name: namedCurve };
|
|
1810
|
-
break;
|
|
1811
|
-
default: throw new Error("Unsupported namedCurve provided. Supported values are: Ed25519 and Ed448");
|
|
1812
|
-
}
|
|
1813
|
-
break;
|
|
1814
|
-
}
|
|
1815
|
-
case "ECDH-ES":
|
|
1816
|
-
case "ECDH-ES+A128KW":
|
|
1817
|
-
case "ECDH-ES+A192KW":
|
|
1818
|
-
case "ECDH-ES+A256KW": {
|
|
1819
|
-
keyUsages = ["deriveBits"];
|
|
1820
|
-
const namedCurve = options?.namedCurve ?? "P-256";
|
|
1821
|
-
switch (namedCurve) {
|
|
1822
|
-
case "P-256":
|
|
1823
|
-
case "P-384":
|
|
1824
|
-
case "P-521":
|
|
1825
|
-
algorithm = {
|
|
1826
|
-
name: "ECDH",
|
|
1827
|
-
namedCurve
|
|
1828
|
-
};
|
|
1829
|
-
break;
|
|
1830
|
-
case "X25519":
|
|
1831
|
-
algorithm = { name: "X25519" };
|
|
1832
|
-
break;
|
|
1833
|
-
default: throw new Error("Unsupported namedCurve provided. Supported values are: P-256, P-384, P-521 and X25519");
|
|
1834
|
-
}
|
|
1835
|
-
break;
|
|
1836
|
-
}
|
|
1837
|
-
default: throw new Error(`Unsupported or invalid algorithm for key generation: ${alg}`);
|
|
1838
|
-
}
|
|
1839
|
-
return {
|
|
1840
|
-
algorithm,
|
|
1841
|
-
keyUsages
|
|
1842
|
-
};
|
|
1843
|
-
}
|
|
1844
|
-
function inferAesImportAlgorithm(enc, cekLengthBytes) {
|
|
1845
|
-
const bitLength = cekLengthBytes << 3;
|
|
1846
|
-
if (enc) {
|
|
1847
|
-
if (enc.includes("GCM")) return {
|
|
1848
|
-
name: "AES-GCM",
|
|
1849
|
-
length: Number.parseInt(enc.slice(1, 4), 10)
|
|
196
|
+
expect: "public"
|
|
197
|
+
}), "verify"), signatureBytes, signingInputBytes);
|
|
198
|
+
}
|
|
199
|
+
async function _verifyOneSignature(payload, wireSig, index, keyResolver, options) {
|
|
200
|
+
const fail = (error, protectedHeader, signerHeader) => {
|
|
201
|
+
const outcome = {
|
|
202
|
+
signerIndex: index,
|
|
203
|
+
verified: false,
|
|
204
|
+
error
|
|
1850
205
|
};
|
|
1851
|
-
if (
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
name: "AES-GCM",
|
|
1855
|
-
length: bitLength
|
|
206
|
+
if (protectedHeader) outcome.protectedHeader = protectedHeader;
|
|
207
|
+
if (signerHeader) outcome.signerHeader = signerHeader;
|
|
208
|
+
return outcome;
|
|
1856
209
|
};
|
|
1857
|
-
}
|
|
1858
|
-
//#endregion
|
|
1859
|
-
//#region src/core/jwe.ts
|
|
1860
|
-
async function encrypt(payload, key, options = {}) {
|
|
1861
|
-
const { protectedHeader: userHeader, cek: providedCek, contentEncryptionIV: providedContentIV, keyManagementIV, p2s, p2c, ecdh } = options;
|
|
1862
|
-
let { alg, enc } = options;
|
|
1863
|
-
if (!alg) {
|
|
1864
|
-
if (typeof key === "string" || key instanceof Uint8Array) alg = "PBES2-HS256+A128KW";
|
|
1865
|
-
else if (isJWK(key)) alg = key.alg === "A128GCM" || key.alg === "A192GCM" || key.alg === "A256GCM" ? `${key.alg}KW` : key.alg;
|
|
1866
|
-
}
|
|
1867
|
-
if (!alg) throw new TypeError("JWE \"alg\" (Key Management Algorithm) must be provided in options or inferable from the key");
|
|
1868
|
-
if (!enc) if (alg === "dir") {
|
|
1869
|
-
enc = isJWK(key) && "enc" in key ? key.enc : void 0;
|
|
1870
|
-
if (!enc) throw new TypeError("JWE \"enc\" must be provided when alg is \"dir\"");
|
|
1871
|
-
} else enc = isJWK(key) && "enc" in key ? key.enc : "A128GCM";
|
|
1872
|
-
const jweKeyManagementParams = {};
|
|
1873
|
-
if (keyManagementIV) jweKeyManagementParams.iv = keyManagementIV;
|
|
1874
|
-
if (p2s) jweKeyManagementParams.p2s = p2s;
|
|
1875
|
-
else if (alg?.startsWith("PBES2")) jweKeyManagementParams.p2s = randomBytes(16);
|
|
1876
|
-
if (p2c) jweKeyManagementParams.p2c = p2c;
|
|
1877
|
-
else if (alg?.startsWith("PBES2")) jweKeyManagementParams.p2c = 6e5;
|
|
1878
|
-
if (ecdh?.partyUInfo) jweKeyManagementParams.apu = ecdh.partyUInfo;
|
|
1879
|
-
if (ecdh?.partyVInfo) jweKeyManagementParams.apv = ecdh.partyVInfo;
|
|
1880
|
-
if (ecdh?.ephemeralKey) {
|
|
1881
|
-
const { epk, epkPrivateKey } = parseEphemeralKey(ecdh.ephemeralKey);
|
|
1882
|
-
jweKeyManagementParams.epk = epk;
|
|
1883
|
-
jweKeyManagementParams.epkPrivateKey = epkPrivateKey;
|
|
1884
|
-
}
|
|
1885
|
-
const wrappingKeyMaterial = await importKey(key, alg);
|
|
1886
|
-
const { cek: finalCek, encryptedKey: jweEncryptedKey, parameters: keyManagementHeaderParams } = await encryptKey(alg, enc, wrappingKeyMaterial, providedCek, jweKeyManagementParams);
|
|
1887
|
-
const protectedHeader = _buildJWEHeader(alg, enc, key, userHeader, keyManagementHeaderParams, payload);
|
|
1888
|
-
const plaintextBytes = getPlaintextBytes(computeJwtTimeClaims(payload, protectedHeader.typ, options.expiresIn, options.currentDate) || payload);
|
|
1889
|
-
const protectedHeaderEncoded = base64UrlEncode(JSON.stringify(protectedHeader));
|
|
1890
|
-
const aadBytes = textEncoder.encode(protectedHeaderEncoded);
|
|
1891
|
-
const contentIVBytes = providedContentIV ?? generateIV(enc);
|
|
1892
|
-
const { ciphertext: contentCiphertext, tag: contentAuthTag, iv: actualContentIV } = await encrypt$1(enc, plaintextBytes, finalCek, contentIVBytes, aadBytes);
|
|
1893
|
-
if (!actualContentIV) throw new JWTError("Content encryption IV was not generated or returned.", "ERR_JWE_INVALID");
|
|
1894
|
-
if (!contentAuthTag) throw new JWTError("Content encryption auth tag was not generated or returned.", "ERR_JWE_INVALID");
|
|
1895
|
-
return [
|
|
1896
|
-
protectedHeaderEncoded,
|
|
1897
|
-
jweEncryptedKey ? base64UrlEncode(jweEncryptedKey) : "",
|
|
1898
|
-
base64UrlEncode(actualContentIV),
|
|
1899
|
-
base64UrlEncode(contentCiphertext),
|
|
1900
|
-
base64UrlEncode(contentAuthTag)
|
|
1901
|
-
].join(".");
|
|
1902
|
-
}
|
|
1903
|
-
async function decrypt(jwe, key, options = {}) {
|
|
1904
|
-
const parts = jwe.split(".");
|
|
1905
|
-
if (parts.length !== 5) throw new JWTError("Invalid JWE: Must contain five sections (RFC7516, section-3).", "ERR_JWE_INVALID");
|
|
1906
|
-
const [protectedHeaderEncoded, encryptedKeyEncoded, ivEncoded, ciphertextEncoded, authTagEncoded] = parts;
|
|
210
|
+
if (typeof wireSig.signature !== "string") return fail(new JWTError(`Signature[${index}] is missing the "signature" field.`, "ERR_JWS_INVALID"));
|
|
1907
211
|
let protectedHeader;
|
|
1908
212
|
try {
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
throw new JWTError("Invalid JWE: Protected header could not be decoded.", "ERR_JWE_INVALID");
|
|
213
|
+
protectedHeader = decodeProtectedHeader(wireSig.protected, "JWS");
|
|
214
|
+
} catch (err) {
|
|
215
|
+
return fail(_asJWTError(err, "ERR_JWS_INVALID"));
|
|
1913
216
|
}
|
|
1914
|
-
if (!protectedHeader
|
|
217
|
+
if (!protectedHeader.alg) return fail(new JWTError(`Invalid JWS: Signature[${index}] protected header must have an "alg" property.`, "ERR_JWS_INVALID"), protectedHeader);
|
|
218
|
+
const unprotectedHeader = sanitizeObjectCopy(wireSig.header);
|
|
219
|
+
try {
|
|
220
|
+
_assertDisjointHeaders(protectedHeader, unprotectedHeader);
|
|
221
|
+
} catch (err) {
|
|
222
|
+
return fail(_asJWTError(err, "ERR_JWS_INVALID"), protectedHeader, unprotectedHeader);
|
|
223
|
+
}
|
|
224
|
+
const effective = _mergeHeaders(protectedHeader, unprotectedHeader);
|
|
1915
225
|
const alg = protectedHeader.alg;
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
const
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
if (protectedHeader.p2c) unwrapKeyOpts.p2c = protectedHeader.p2c;
|
|
1933
|
-
if (protectedHeader.epk) unwrapKeyOpts.epk = protectedHeader.epk;
|
|
1934
|
-
if (protectedHeader.apu) unwrapKeyOpts.apu = protectedHeader.apu;
|
|
1935
|
-
if (protectedHeader.apv) unwrapKeyOpts.apv = protectedHeader.apv;
|
|
1936
|
-
if (options?.unwrappedKeyAlgorithm) unwrapKeyOpts.unwrappedKeyAlgorithm = options.unwrappedKeyAlgorithm;
|
|
1937
|
-
if (options?.extractable) unwrapKeyOpts.extractable = options.extractable;
|
|
1938
|
-
const aadBytes = textEncoder.encode(protectedHeaderEncoded);
|
|
1939
|
-
let cek;
|
|
1940
|
-
let plaintextBytes;
|
|
1941
|
-
if (isJWKSet(rawKeyMaterial)) {
|
|
1942
|
-
const candidates = getJWKsFromSet(rawKeyMaterial, _buildJWESetFilter(protectedHeader));
|
|
1943
|
-
if (candidates.length === 0) throw new JWTError(`No key found in JWK Set${protectedHeader.kid ? ` with kid "${protectedHeader.kid}"` : ""}.`, "ERR_JWK_KEY_NOT_FOUND");
|
|
1944
|
-
let decrypted = false;
|
|
1945
|
-
for (const candidate of candidates) try {
|
|
1946
|
-
const candidateCek = await unwrapKey(alg, encryptedKeyBytes, await importKey(candidate, alg), unwrapKeyOpts);
|
|
1947
|
-
plaintextBytes = await decrypt$1(enc, candidateCek, ciphertextBytes, contentIVBytes, contentAuthTagBytes, aadBytes);
|
|
1948
|
-
cek = candidateCek;
|
|
1949
|
-
decrypted = true;
|
|
1950
|
-
break;
|
|
1951
|
-
} catch {}
|
|
1952
|
-
if (!decrypted) throw new JWTError("JWE decryption failed.", "ERR_JWE_DECRYPTION_FAILED");
|
|
1953
|
-
} else {
|
|
1954
|
-
const unwrappingKey = await importKey(rawKeyMaterial, alg);
|
|
1955
|
-
try {
|
|
1956
|
-
cek = await unwrapKey(alg, encryptedKeyBytes, unwrappingKey, unwrapKeyOpts);
|
|
1957
|
-
plaintextBytes = await decrypt$1(enc, cek, ciphertextBytes, contentIVBytes, contentAuthTagBytes, aadBytes);
|
|
1958
|
-
} catch (error_) {
|
|
1959
|
-
if (error_ instanceof JWTError) throw error_;
|
|
1960
|
-
throw new JWTError("JWE decryption failed.", "ERR_JWE_DECRYPTION_FAILED", error_);
|
|
1961
|
-
}
|
|
226
|
+
if (options.typ && protectedHeader.typ !== options.typ) return fail(new JWTError(`Invalid JWS: "typ" Header Parameter mismatch on signature[${index}]. Expected "${options.typ}", got "${protectedHeader.typ}".`, "ERR_JWS_INVALID"), protectedHeader, unprotectedHeader);
|
|
227
|
+
let rawKeyMaterial;
|
|
228
|
+
try {
|
|
229
|
+
rawKeyMaterial = await keyResolver(effective, wireSig.protected ?? "");
|
|
230
|
+
} catch (err) {
|
|
231
|
+
return fail(_asJWTError(err, "ERR_JWK_KEY_NOT_FOUND", `Key resolver failed for signature[${index}]`), protectedHeader, unprotectedHeader);
|
|
232
|
+
}
|
|
233
|
+
const algError = checkAlgAllowed(alg, rawKeyMaterial, options.algorithms, JWS_ALG_CTX);
|
|
234
|
+
if (algError) return fail(algError, protectedHeader, unprotectedHeader);
|
|
235
|
+
const signatureBytes = base64UrlDecode(wireSig.signature, { returnAs: "uint8array" });
|
|
236
|
+
const signingInputBytes = textEncoder.encode(`${wireSig.protected ?? ""}.${payload}`);
|
|
237
|
+
let verified;
|
|
238
|
+
try {
|
|
239
|
+
verified = await _verifyWithKey(alg, rawKeyMaterial, signatureBytes, signingInputBytes, effective.kid);
|
|
240
|
+
} catch (err) {
|
|
241
|
+
return fail(_asJWTError(err, "ERR_JWS_SIGNATURE_INVALID"), protectedHeader, unprotectedHeader);
|
|
1962
242
|
}
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
protectedHeader
|
|
1969
|
-
};
|
|
1970
|
-
if (options.returnCek) {
|
|
1971
|
-
result.cek = isCryptoKey(cek) ? new Uint8Array(await crypto.subtle.exportKey("raw", cek)) : cek;
|
|
1972
|
-
result.aad = aadBytes;
|
|
243
|
+
if (!verified) return fail(new JWTError(`JWS signature[${index}] verification failed.`, "ERR_JWS_SIGNATURE_INVALID"), protectedHeader, unprotectedHeader);
|
|
244
|
+
let decodedPayload;
|
|
245
|
+
try {
|
|
246
|
+
decodedPayload = decodeJWSPayload(payload, protectedHeader.b64 !== false, options.forceUint8Array);
|
|
247
|
+
} catch (err) {
|
|
248
|
+
return fail(_asJWTError(err, "ERR_JWS_INVALID"), protectedHeader, unprotectedHeader);
|
|
1973
249
|
}
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
epk = candidate.publicKey;
|
|
1986
|
-
epkPrivateKey = candidate.privateKey;
|
|
1987
|
-
} else if (isCryptoKey(ephemeralKey)) {
|
|
1988
|
-
if (ephemeralKey.type !== "private") throw new TypeError("ECDH-ES custom ephemeral CryptoKey must include private key material.");
|
|
1989
|
-
epk = ephemeralKey;
|
|
1990
|
-
epkPrivateKey = ephemeralKey;
|
|
1991
|
-
} else if (isJWK(ephemeralKey)) {
|
|
1992
|
-
if (!("d" in ephemeralKey) || typeof ephemeralKey.d !== "string") throw new TypeError("ECDH-ES custom ephemeral JWK must include private parameter \"d\".");
|
|
1993
|
-
epk = ephemeralKey;
|
|
1994
|
-
epkPrivateKey = ephemeralKey;
|
|
1995
|
-
} else throw new TypeError("Unsupported ECDH-ES ephemeral key material provided in options.");
|
|
1996
|
-
return {
|
|
1997
|
-
epk,
|
|
1998
|
-
epkPrivateKey
|
|
250
|
+
try {
|
|
251
|
+
validateCriticalHeadersJWS(protectedHeader, options.recognizedHeaders);
|
|
252
|
+
validateJwtClaimsIfJsonPayload(decodedPayload, options);
|
|
253
|
+
} catch (err) {
|
|
254
|
+
return fail(_asJWTError(err, "ERR_JWT_CLAIM_INVALID"), protectedHeader, unprotectedHeader);
|
|
255
|
+
}
|
|
256
|
+
const outcome = {
|
|
257
|
+
signerIndex: index,
|
|
258
|
+
verified: true,
|
|
259
|
+
payload: decodedPayload,
|
|
260
|
+
protectedHeader
|
|
1999
261
|
};
|
|
262
|
+
if (unprotectedHeader) outcome.signerHeader = unprotectedHeader;
|
|
263
|
+
return outcome;
|
|
2000
264
|
}
|
|
2001
|
-
function
|
|
2002
|
-
|
|
2003
|
-
return (
|
|
265
|
+
function _asJWTError(err, fallbackCode, fallbackMessage) {
|
|
266
|
+
if (err instanceof JWTError) return err;
|
|
267
|
+
return new JWTError(fallbackMessage ?? (err instanceof Error ? err.message : typeof err === "string" ? err : "Unknown error"), fallbackCode, err);
|
|
2004
268
|
}
|
|
2005
|
-
function _buildJWEHeader(alg, enc, key, userHeader, keyManagementParams, payload) {
|
|
2006
|
-
const safeHeader = sanitizeObject(userHeader);
|
|
2007
|
-
return applyTypCtyDefaults({
|
|
2008
|
-
...isJWK(key) && key.kid ? { kid: key.kid } : {},
|
|
2009
|
-
...safeHeader,
|
|
2010
|
-
...keyManagementParams,
|
|
2011
|
-
alg,
|
|
2012
|
-
enc
|
|
2013
|
-
}, payload);
|
|
2014
|
-
}
|
|
2015
|
-
//#endregion
|
|
2016
|
-
//#region src/core/jws.ts
|
|
2017
269
|
async function sign(payload, key, options = {}) {
|
|
2018
270
|
const { protectedHeader: userHeader } = options;
|
|
2019
271
|
let { alg } = options;
|
|
2020
272
|
if (!alg) if (isJWK(key) && key.alg) alg = key.alg;
|
|
2021
|
-
else throw new
|
|
273
|
+
else throw new JWTError("JWS \"alg\" (Algorithm) must be provided in options", "ERR_JWS_ALG_MISSING");
|
|
2022
274
|
if (alg === "none") throw new JWTError("\"none\" is not a valid signing algorithm", "ERR_JWS_ALG_NOT_ALLOWED");
|
|
2023
|
-
const signingKey = await
|
|
275
|
+
const signingKey = await resolveSigningKey(alg, await importKey(key, {
|
|
276
|
+
alg,
|
|
277
|
+
expect: "private"
|
|
278
|
+
}), "sign");
|
|
2024
279
|
const protectedHeader = _buildJWSHeader(alg, key, userHeader, payload);
|
|
2025
|
-
const computedPayload = computeJwtTimeClaims(payload,
|
|
280
|
+
const computedPayload = computeJwtTimeClaims(payload, options);
|
|
2026
281
|
const protectedHeaderEncoded = base64UrlEncode(JSON.stringify(protectedHeader));
|
|
2027
282
|
let payloadBytes;
|
|
2028
283
|
if (payload instanceof Uint8Array) payloadBytes = payload;
|
|
@@ -2037,80 +292,58 @@ async function verify(jws, key, options = {}) {
|
|
|
2037
292
|
const parts = jws.split(".");
|
|
2038
293
|
if (parts.length !== 3) throw new JWTError("Invalid JWS: Must contain three parts separated by dots.", "ERR_JWS_INVALID");
|
|
2039
294
|
const [protectedHeaderEncoded, payloadEncoded, signatureEncoded] = parts;
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
const protectedHeaderString = base64UrlDecode(protectedHeaderEncoded);
|
|
2043
|
-
protectedHeader = sanitizeObject(JSON.parse(protectedHeaderString));
|
|
2044
|
-
} catch {
|
|
2045
|
-
throw new JWTError("Invalid JWS: Protected header could not be decoded.", "ERR_JWS_INVALID");
|
|
2046
|
-
}
|
|
2047
|
-
if (!protectedHeader || typeof protectedHeader !== "object" || !protectedHeader.alg) throw new JWTError("Invalid JWS: Protected header must be an object with an \"alg\" property.", "ERR_JWS_INVALID");
|
|
295
|
+
const protectedHeader = decodeProtectedHeader(protectedHeaderEncoded, "JWS");
|
|
296
|
+
if (!protectedHeader.alg) throw new JWTError("Invalid JWS: Protected header must be an object with an \"alg\" property.", "ERR_JWS_INVALID");
|
|
2048
297
|
const alg = protectedHeader.alg;
|
|
2049
|
-
if (options.algorithms && !options.algorithms.includes(alg)) throw new JWTError(`Algorithm not allowed: ${alg}`, "ERR_JWS_ALG_NOT_ALLOWED");
|
|
2050
298
|
if (options.typ && protectedHeader.typ !== options.typ) throw new JWTError(`Invalid JWS: "typ" (Type) Header Parameter mismatch. Expected "${options.typ}", got "${protectedHeader.typ}".`, "ERR_JWS_INVALID");
|
|
2051
299
|
let signatureBytes;
|
|
2052
300
|
try {
|
|
2053
|
-
signatureBytes = base64UrlDecode(signatureEncoded,
|
|
301
|
+
signatureBytes = base64UrlDecode(signatureEncoded, { returnAs: "uint8array" });
|
|
2054
302
|
} catch {
|
|
2055
303
|
throw new JWTError("Invalid JWS: Signature could not be decoded.", "ERR_JWS_INVALID");
|
|
2056
304
|
}
|
|
2057
305
|
const keyInput = typeof key === "function" ? await key(protectedHeader, jws) : key;
|
|
306
|
+
const algError = checkAlgAllowed(alg, keyInput, options.algorithms, JWS_ALG_CTX);
|
|
307
|
+
if (algError) throw algError;
|
|
2058
308
|
const signingInputBytes = textEncoder.encode(`${protectedHeaderEncoded}.${payloadEncoded}`);
|
|
2059
309
|
if (isJWKSet(keyInput)) {
|
|
2060
|
-
const candidates = getJWKsFromSet(keyInput,
|
|
310
|
+
const candidates = getJWKsFromSet(keyInput, buildJWKSetFilter(protectedHeader));
|
|
2061
311
|
if (candidates.length === 0) throw new JWTError(`No key found in JWK Set${protectedHeader.kid ? ` with kid "${protectedHeader.kid}"` : ""}.`, "ERR_JWK_KEY_NOT_FOUND");
|
|
2062
312
|
let verified = false;
|
|
2063
|
-
for (const candidate of candidates)
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
313
|
+
for (const candidate of candidates) if (await verify$1(alg, await resolveSigningKey(alg, await importKey(candidate, {
|
|
314
|
+
alg,
|
|
315
|
+
expect: "public"
|
|
316
|
+
}), "verify"), signatureBytes, signingInputBytes)) {
|
|
317
|
+
verified = true;
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
2069
320
|
if (!verified) throw new JWTError("JWS signature verification failed.", "ERR_JWS_SIGNATURE_INVALID");
|
|
2070
|
-
} else if (!await verify$1(alg, await
|
|
321
|
+
} else if (!await verify$1(alg, await resolveSigningKey(alg, await importKey(keyInput, {
|
|
322
|
+
alg,
|
|
323
|
+
expect: "public"
|
|
324
|
+
}), "verify"), signatureBytes, signingInputBytes)) throw new JWTError("JWS signature verification failed.", "ERR_JWS_SIGNATURE_INVALID");
|
|
2071
325
|
const useB64 = protectedHeader.b64 !== false;
|
|
2072
326
|
let payload;
|
|
2073
327
|
try {
|
|
2074
|
-
payload =
|
|
328
|
+
payload = decodeJWSPayload(payloadEncoded, useB64, options.forceUint8Array);
|
|
2075
329
|
} catch (error_) {
|
|
2076
330
|
throw new JWTError(`Invalid JWS: Payload decoding failed (${error_ instanceof Error ? error_.message : error_})`, "ERR_JWS_INVALID");
|
|
2077
331
|
}
|
|
2078
332
|
validateCriticalHeadersJWS(protectedHeader, options.recognizedHeaders);
|
|
2079
|
-
|
|
333
|
+
validateJwtClaimsIfJsonPayload(payload, options);
|
|
2080
334
|
return {
|
|
2081
335
|
payload,
|
|
2082
336
|
protectedHeader
|
|
2083
337
|
};
|
|
2084
338
|
}
|
|
2085
|
-
async function _resolveSigningKey(alg, key, usage) {
|
|
2086
|
-
if (key instanceof Uint8Array) {
|
|
2087
|
-
const minBytes = Number.parseInt(alg.slice(2), 10) / 8;
|
|
2088
|
-
if (key.length < minBytes) throw new JWTError(`${alg} requires a key of at least ${minBytes} bytes`, "ERR_JWK_INVALID");
|
|
2089
|
-
return crypto.subtle.importKey("raw", key, {
|
|
2090
|
-
name: "HMAC",
|
|
2091
|
-
hash: `SHA-${alg.slice(-3)}`
|
|
2092
|
-
}, false, [usage]);
|
|
2093
|
-
}
|
|
2094
|
-
if (alg.startsWith("RS") || alg.startsWith("PS")) {
|
|
2095
|
-
const { modulusLength } = key.algorithm;
|
|
2096
|
-
if (typeof modulusLength !== "number" || modulusLength < 2048) throw new JWTError(`${alg} requires a key modulusLength of at least 2048 bits`, "ERR_JWK_INVALID");
|
|
2097
|
-
}
|
|
2098
|
-
return key;
|
|
2099
|
-
}
|
|
2100
|
-
function _buildJWSSetFilter(header) {
|
|
2101
|
-
const { kid, alg } = header;
|
|
2102
|
-
return (k) => (!kid || k.kid === kid) && (!k.alg || k.alg === alg);
|
|
2103
|
-
}
|
|
2104
339
|
function _buildJWSHeader(alg, key, userHeader, payload) {
|
|
2105
|
-
const safeHeader =
|
|
2106
|
-
const header = {
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
};
|
|
340
|
+
const safeHeader = sanitizeObjectCopy(userHeader);
|
|
341
|
+
const header = {};
|
|
342
|
+
if (isJWK(key) && key.kid) header.kid = key.kid;
|
|
343
|
+
if (safeHeader) Object.assign(header, safeHeader);
|
|
344
|
+
header.alg = alg;
|
|
2111
345
|
applyTypCtyDefaults(header, payload);
|
|
2112
346
|
if (header.b64 === true) delete header.b64;
|
|
2113
347
|
return header;
|
|
2114
348
|
}
|
|
2115
|
-
|
|
2116
|
-
export { deriveJWKFromPassword as a, importFromPEM as c, isSymmetricJWK as d, computeExpiresInSeconds as f, encrypt as i, isPrivateJWK as l, isJWTError as m, verify as n, exportToPEM as o, JWTError as p, decrypt as r, generateJWK as s, sign as t, isPublicJWK as u };
|
|
349
|
+
export { verifyMulti as a, signMulti as i, verify as n, verifyMultiAll as o, generalToFlattenedJWS as r, sign as t };
|