unjwt 0.8.3 → 0.8.5
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 +3 -9
- package/dist/_chunks/algorithms.mjs +1541 -0
- package/dist/_chunks/{_codec-jwe.d.mts → codec-jwe.d.mts} +40 -9
- package/dist/_chunks/{_codec-jws.mjs → codec-jws.mjs} +148 -95
- package/dist/_chunks/cookie.mjs +50 -0
- package/dist/_chunks/digest.mjs +10 -3
- package/dist/_chunks/error.d.mts +103 -0
- package/dist/_chunks/export.mjs +97 -0
- package/dist/_chunks/index.d.mts +5 -103
- package/dist/_chunks/index2.d.mts +206 -12
- package/dist/_chunks/index3.d.mts +12 -109
- package/dist/_chunks/{jwe.d.mts → index4.d.mts} +3 -1
- package/dist/_chunks/{jws.d.mts → index5.d.mts} +6 -2
- package/dist/_chunks/index6.d.mts +113 -0
- package/dist/_chunks/jwe.mjs +208 -74
- package/dist/_chunks/jwk.mjs +214 -2237
- package/dist/_chunks/jws.mjs +289 -202
- package/dist/_chunks/jwt.d.mts +2 -1
- package/dist/_chunks/{_jwt-internal.mjs → jwt.mjs} +124 -68
- package/dist/_chunks/libs/unsecure.d.mts +35 -25
- package/dist/_chunks/libs/unsecure.mjs +148 -302
- package/dist/_chunks/{_internal.d.mts → limits.d.mts} +1 -1
- package/dist/_chunks/payload.mjs +23 -0
- package/dist/_chunks/wrap.mjs +209 -0
- package/dist/adapters/elysia/index.d.mts +4 -4
- package/dist/adapters/elysia/index.mjs +5 -3
- package/dist/adapters/h3/index.d.mts +13 -13
- package/dist/adapters/h3/index.mjs +8 -10
- package/dist/adapters/h3v1/index.d.mts +5 -5
- package/dist/adapters/h3v1/index.mjs +6 -8
- package/dist/core/internal.mjs +1 -1
- package/dist/core/{jwe.d.mts → jwe/index.d.mts} +5 -4
- package/dist/core/jwe/index.mjs +5 -0
- package/dist/core/jwk/index.d.mts +6 -0
- package/dist/core/jwk/index.mjs +7 -0
- package/dist/core/{jws.d.mts → jws/index.d.mts} +5 -4
- package/dist/core/jws/index.mjs +5 -0
- package/dist/dpop/index.d.mts +73 -0
- package/dist/dpop/index.mjs +296 -0
- package/dist/error.d.mts +3 -0
- package/dist/error.mjs +20 -0
- package/dist/index.d.mts +10 -9
- package/dist/index.mjs +7 -3
- package/dist/session/index.d.mts +4 -4
- package/dist/session/index.mjs +6 -46
- package/dist/utils/index.d.mts +7 -0
- package/dist/{core/utils → utils}/index.mjs +4 -3
- package/package.json +22 -10
- package/dist/core/jwe.mjs +0 -5
- package/dist/core/jwk.d.mts +0 -207
- package/dist/core/jwk.mjs +0 -4
- package/dist/core/jws.mjs +0 -5
- package/dist/core/utils/index.d.mts +0 -6
- /package/dist/_chunks/{_cookie-attributes.d.mts → cookie-attributes.d.mts} +0 -0
|
@@ -0,0 +1,1541 @@
|
|
|
1
|
+
import { safeJsonParse, sanitizeObjectCopy, textEncoder } from "./libs/unsecure.mjs";
|
|
2
|
+
import { JWTError } from "../error.mjs";
|
|
3
|
+
import { assertClaimValidationOptions, assertCritShape, assertCryptoKey, assertPlainObject, assertStringList, base64Parse, base64Stringify, base64UrlParse, base64UrlStringify, concatUint8Arrays, hasSymmetricSecret, isCryptoKey, isCryptoKeyPair, isJWK, isJWKSet, isPlainObject, isPublicJWK, quoteTokenValue, uint32be } from "./jwt.mjs";
|
|
4
|
+
import { ECDSA_CURVES, aesBitsOf, checkKeyUsage, cryptoKeyCurve, digestUrl, hashBitsOf, inferJWSAllowedAlgorithms, innerKeyWrapAlg, isAlgorithm, isContentEncryptionAlgorithm, isECDHESAlgorithm, isPBES2Algorithm, isSameAlgorithm, jwkAlgServes, jwsHashOf, lengthOf, modulusLengthOf, namedCurveOf, pbes2HashOf, rsaOaepHashOf, shaBitsOf, unusableKey } from "./digest.mjs";
|
|
5
|
+
function serializeProtectedHeader(header) {
|
|
6
|
+
try {
|
|
7
|
+
return JSON.stringify(header);
|
|
8
|
+
} catch (error) {
|
|
9
|
+
throw new JWTError("The protected header is not JSON-serializable.", "ERR_INVALID_OPTION", error);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function assertProducerCrit(header, family) {
|
|
13
|
+
const crit = header.crit;
|
|
14
|
+
if (crit === void 0) return;
|
|
15
|
+
assertCritShape(crit, {
|
|
16
|
+
family,
|
|
17
|
+
code: "ERR_INVALID_OPTION"
|
|
18
|
+
});
|
|
19
|
+
for (const param of crit) {
|
|
20
|
+
if (Object.hasOwn(header, param)) continue;
|
|
21
|
+
throw new JWTError(`"crit" lists "${param}", which the protected header does not carry.`, "ERR_INVALID_OPTION");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const PROTECTED_ONLY_PARAMS = {
|
|
25
|
+
JWS: ["crit", "b64"],
|
|
26
|
+
JWE: ["crit"]
|
|
27
|
+
};
|
|
28
|
+
function assertProtectedOnlyOption(header, options) {
|
|
29
|
+
if (!header) return;
|
|
30
|
+
const { tier, family } = options;
|
|
31
|
+
for (const param of PROTECTED_ONLY_PARAMS[family]) {
|
|
32
|
+
if (!Object.hasOwn(header, param)) continue;
|
|
33
|
+
throw new JWTError(`${family} "${param}" must be in the protected header, not the ${tier} header.`, "ERR_INVALID_OPTION");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function assertProducerKeyHeaders(header, options) {
|
|
37
|
+
if (!header) return;
|
|
38
|
+
const { tier, family } = options;
|
|
39
|
+
if (Object.hasOwn(header, "jwk") && !isPublicJWK(Reflect.get(header, "jwk"))) throw new JWTError(`${family} "jwk" in the ${tier} header must be a public JWK.`, "ERR_INVALID_OPTION");
|
|
40
|
+
if (Object.hasOwn(header, "epk")) throw new JWTError(`${family} "epk" is not a header option, use "ecdh.ephemeralKey".`, "ERR_INVALID_OPTION");
|
|
41
|
+
}
|
|
42
|
+
function assertProducerConfirmation(payload) {
|
|
43
|
+
if (typeof payload !== "object" || payload === null) return;
|
|
44
|
+
const cnf = Reflect.get(payload, "cnf");
|
|
45
|
+
if (typeof cnf !== "object" || cnf === null || !Object.hasOwn(cnf, "jwk")) return;
|
|
46
|
+
if (!isPublicJWK(Reflect.get(cnf, "jwk"))) throw new JWTError("Invalid JWT \"cnf\": \"jwk\" must be a public JWK.", "ERR_JWT_CLAIM_INVALID");
|
|
47
|
+
}
|
|
48
|
+
function assertOptionEntry(entry, options) {
|
|
49
|
+
const { label, objects } = options;
|
|
50
|
+
assertPlainObject(entry, label);
|
|
51
|
+
for (const member of objects) if (entry[member] !== void 0) assertPlainObject(entry[member], `${label}.${member}`);
|
|
52
|
+
}
|
|
53
|
+
function assertConsumerOptions(options) {
|
|
54
|
+
assertOptionEntry(options, {
|
|
55
|
+
label: "options",
|
|
56
|
+
objects: ["limits"]
|
|
57
|
+
});
|
|
58
|
+
const typ = options.typ;
|
|
59
|
+
if (typ !== void 0 && (typeof typ !== "string" || typ === "")) throw new JWTError("\"typ\" must be a non-empty string.", "ERR_INVALID_OPTION");
|
|
60
|
+
assertStringList(options.algorithms, "algorithms");
|
|
61
|
+
assertStringList(options.encryptionAlgorithms, "encryptionAlgorithms");
|
|
62
|
+
assertStringList(options.recognizedHeaders, "recognizedHeaders");
|
|
63
|
+
assertClaimValidationOptions(options);
|
|
64
|
+
}
|
|
65
|
+
function assertCompactToken(token, family) {
|
|
66
|
+
if (typeof token !== "string") throw new JWTError(`Invalid ${family}: token must be a string.`, `ERR_${family}_INVALID`);
|
|
67
|
+
}
|
|
68
|
+
function assertSerializationObject(value, family) {
|
|
69
|
+
if (!isPlainObject(value)) throw new JWTError(`Invalid ${family} JSON Serialization: expected an object.`, `ERR_${family}_INVALID_SERIALIZATION`);
|
|
70
|
+
}
|
|
71
|
+
function checkSerializationEntry(entry, options) {
|
|
72
|
+
const { family, label, objects, strings } = options;
|
|
73
|
+
const code = `ERR_${family}_INVALID`;
|
|
74
|
+
if (!isPlainObject(entry)) return new JWTError(`Invalid ${family}: ${label} must be a JSON object.`, code);
|
|
75
|
+
for (const member of objects) if (entry[member] !== void 0 && !isPlainObject(entry[member])) return new JWTError(`Invalid ${family}: ${label} "${member}" must be a JSON object.`, code);
|
|
76
|
+
for (const member of strings) if (entry[member] !== void 0 && typeof entry[member] !== "string") return new JWTError(`Invalid ${family}: ${label} "${member}" must be a string.`, code);
|
|
77
|
+
}
|
|
78
|
+
function typMatches(expected, actual) {
|
|
79
|
+
if (typeof actual !== "string") return false;
|
|
80
|
+
return normalizeTyp(expected) === normalizeTyp(actual);
|
|
81
|
+
}
|
|
82
|
+
function checkProtectedTyp(expected, protectedHeader, options) {
|
|
83
|
+
if (expected === void 0) return void 0;
|
|
84
|
+
const actual = protectedHeader.typ;
|
|
85
|
+
if (typMatches(expected, actual)) return void 0;
|
|
86
|
+
const { family, label } = options;
|
|
87
|
+
const where = label ? ` on ${label}` : "";
|
|
88
|
+
const message = actual === void 0 ? `Invalid ${family}: the protected header${where} carries no "typ", expected "${expected}".` : `Invalid ${family}: "typ" mismatch${where}, expected "${expected}".`;
|
|
89
|
+
return new JWTError(message, `ERR_${family}_INVALID`);
|
|
90
|
+
}
|
|
91
|
+
function normalizeTyp(typ) {
|
|
92
|
+
const lowered = typ.toLowerCase();
|
|
93
|
+
return lowered.includes("/") ? lowered : `application/${lowered}`;
|
|
94
|
+
}
|
|
95
|
+
function decodeProtectedHeader(encoded, family) {
|
|
96
|
+
let parsed = {};
|
|
97
|
+
if (encoded) {
|
|
98
|
+
try {
|
|
99
|
+
parsed = safeJsonParse(base64UrlParse(encoded));
|
|
100
|
+
} catch (error) {
|
|
101
|
+
throw new JWTError(`Invalid ${family}: Protected header could not be decoded.`, `ERR_${family}_INVALID`, error);
|
|
102
|
+
}
|
|
103
|
+
if (!isPlainObject(parsed)) throw new JWTError(`Invalid ${family}: Protected header must be a JSON object.`, `ERR_${family}_INVALID`);
|
|
104
|
+
}
|
|
105
|
+
return parsed;
|
|
106
|
+
}
|
|
107
|
+
function decodeSegment(encoded, options) {
|
|
108
|
+
try {
|
|
109
|
+
return base64UrlParse(encoded, { returnAs: "uint8array" });
|
|
110
|
+
} catch (error) {
|
|
111
|
+
throw new JWTError(`Invalid ${options.family}: ${options.what} could not be decoded.`, `ERR_${options.family}_INVALID`, error);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const HASHED_SIGNATURE_ALGORITHMS = /* @__PURE__ */ Object.freeze({
|
|
115
|
+
HS256: "HMAC",
|
|
116
|
+
HS384: "HMAC",
|
|
117
|
+
HS512: "HMAC",
|
|
118
|
+
RS256: "RSASSA-PKCS1-v1_5",
|
|
119
|
+
RS384: "RSASSA-PKCS1-v1_5",
|
|
120
|
+
RS512: "RSASSA-PKCS1-v1_5",
|
|
121
|
+
PS256: "RSA-PSS",
|
|
122
|
+
PS384: "RSA-PSS",
|
|
123
|
+
PS512: "RSA-PSS"
|
|
124
|
+
});
|
|
125
|
+
async function sign(alg, key, data) {
|
|
126
|
+
checkSigCryptoKey(key, alg, "sign");
|
|
127
|
+
const signature = await crypto.subtle.sign(subtleAlgorithm(alg, key.algorithm), key, data);
|
|
128
|
+
return new Uint8Array(signature);
|
|
129
|
+
}
|
|
130
|
+
async function verify(alg, key, signature, data) {
|
|
131
|
+
checkSigCryptoKey(key, alg, "verify");
|
|
132
|
+
try {
|
|
133
|
+
return await crypto.subtle.verify(subtleAlgorithm(alg, key.algorithm), key, signature, data);
|
|
134
|
+
} catch {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function checkSigCryptoKey(key, alg, usage) {
|
|
139
|
+
switch (alg) {
|
|
140
|
+
case "HS256":
|
|
141
|
+
case "HS384":
|
|
142
|
+
case "HS512":
|
|
143
|
+
case "RS256":
|
|
144
|
+
case "RS384":
|
|
145
|
+
case "RS512":
|
|
146
|
+
case "PS256":
|
|
147
|
+
case "PS384":
|
|
148
|
+
case "PS512": {
|
|
149
|
+
const name = HASHED_SIGNATURE_ALGORITHMS[alg];
|
|
150
|
+
if (!isAlgorithm(key.algorithm, name)) throw unusableKey(name);
|
|
151
|
+
const expected = shaBitsOf(jwsHashOf(alg));
|
|
152
|
+
if (hashBitsOf(key.algorithm.hash) !== expected) throw unusableKey(`SHA-${expected}`, "algorithm.hash");
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
case "Ed25519":
|
|
156
|
+
case "EdDSA":
|
|
157
|
+
if (!isAlgorithm(key.algorithm, "Ed25519")) throw unusableKey("Ed25519");
|
|
158
|
+
break;
|
|
159
|
+
case "ES256":
|
|
160
|
+
case "ES384":
|
|
161
|
+
case "ES512": {
|
|
162
|
+
if (!isAlgorithm(key.algorithm, "ECDSA")) throw unusableKey("ECDSA");
|
|
163
|
+
const expected = ECDSA_CURVES[alg];
|
|
164
|
+
if (key.algorithm.namedCurve !== expected) throw unusableKey(expected, "algorithm.namedCurve");
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
checkKeyUsage(key, usage);
|
|
169
|
+
}
|
|
170
|
+
function subtleAlgorithm(alg, algorithm) {
|
|
171
|
+
switch (alg) {
|
|
172
|
+
case "HS256":
|
|
173
|
+
case "HS384":
|
|
174
|
+
case "HS512": return {
|
|
175
|
+
hash: jwsHashOf(alg),
|
|
176
|
+
name: "HMAC"
|
|
177
|
+
};
|
|
178
|
+
case "PS256":
|
|
179
|
+
case "PS384":
|
|
180
|
+
case "PS512": {
|
|
181
|
+
const hash = jwsHashOf(alg);
|
|
182
|
+
return {
|
|
183
|
+
hash,
|
|
184
|
+
name: "RSA-PSS",
|
|
185
|
+
saltLength: shaBitsOf(hash) >> 3
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
case "RS256":
|
|
189
|
+
case "RS384":
|
|
190
|
+
case "RS512": return {
|
|
191
|
+
hash: jwsHashOf(alg),
|
|
192
|
+
name: "RSASSA-PKCS1-v1_5"
|
|
193
|
+
};
|
|
194
|
+
case "ES256":
|
|
195
|
+
case "ES384":
|
|
196
|
+
case "ES512": return {
|
|
197
|
+
hash: jwsHashOf(alg),
|
|
198
|
+
name: "ECDSA",
|
|
199
|
+
namedCurve: namedCurveOf(algorithm)
|
|
200
|
+
};
|
|
201
|
+
case "Ed25519":
|
|
202
|
+
case "EdDSA": return { name: "Ed25519" };
|
|
203
|
+
default: return unsupportedSignatureAlgorithm(alg);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function unsupportedSignatureAlgorithm(alg) {
|
|
207
|
+
throw new JWTError(`alg ${String(alg)} is not supported either by JOSE or your javascript runtime`, "ERR_JWS_ALG_NOT_ALLOWED");
|
|
208
|
+
}
|
|
209
|
+
const IV_BIT_LENGTHS = /* @__PURE__ */ Object.freeze({
|
|
210
|
+
"A128GCM": 96,
|
|
211
|
+
"A128GCMKW": 96,
|
|
212
|
+
"A192GCM": 96,
|
|
213
|
+
"A192GCMKW": 96,
|
|
214
|
+
"A256GCM": 96,
|
|
215
|
+
"A256GCMKW": 96,
|
|
216
|
+
"A128CBC-HS256": 128,
|
|
217
|
+
"A192CBC-HS384": 128,
|
|
218
|
+
"A256CBC-HS512": 128
|
|
219
|
+
});
|
|
220
|
+
const CEK_BIT_LENGTHS = /* @__PURE__ */ Object.freeze({
|
|
221
|
+
"A128GCM": 128,
|
|
222
|
+
"A192GCM": 192,
|
|
223
|
+
"A256GCM": 256,
|
|
224
|
+
"A128CBC-HS256": 256,
|
|
225
|
+
"A192CBC-HS384": 384,
|
|
226
|
+
"A256CBC-HS512": 512
|
|
227
|
+
});
|
|
228
|
+
function bitLengthIV(alg) {
|
|
229
|
+
return IV_BIT_LENGTHS[alg];
|
|
230
|
+
}
|
|
231
|
+
function generateIV(alg) {
|
|
232
|
+
return crypto.getRandomValues(new Uint8Array(bitLengthIV(alg) >> 3));
|
|
233
|
+
}
|
|
234
|
+
function checkIvLength(enc, iv) {
|
|
235
|
+
if (iv.length << 3 !== bitLengthIV(enc)) throw new JWTError("Invalid Initialization Vector length", "ERR_JWE_INVALID");
|
|
236
|
+
}
|
|
237
|
+
const TAG_BIT_LENGTHS = /* @__PURE__ */ Object.freeze({
|
|
238
|
+
"A128GCM": 128,
|
|
239
|
+
"A128GCMKW": 128,
|
|
240
|
+
"A192GCM": 128,
|
|
241
|
+
"A192GCMKW": 128,
|
|
242
|
+
"A256GCM": 128,
|
|
243
|
+
"A256GCMKW": 128,
|
|
244
|
+
"A128CBC-HS256": 128,
|
|
245
|
+
"A192CBC-HS384": 192,
|
|
246
|
+
"A256CBC-HS512": 256
|
|
247
|
+
});
|
|
248
|
+
function bitLengthTag(alg) {
|
|
249
|
+
return TAG_BIT_LENGTHS[alg];
|
|
250
|
+
}
|
|
251
|
+
function checkTagLength(enc, tag) {
|
|
252
|
+
if (tag.length << 3 !== bitLengthTag(enc)) throw new JWTError("Invalid Authentication Tag length", "ERR_JWE_INVALID");
|
|
253
|
+
}
|
|
254
|
+
function bitLengthCEK(enc) {
|
|
255
|
+
return CEK_BIT_LENGTHS[enc];
|
|
256
|
+
}
|
|
257
|
+
function generateCEK(alg) {
|
|
258
|
+
return crypto.getRandomValues(new Uint8Array(bitLengthCEK(alg) >> 3));
|
|
259
|
+
}
|
|
260
|
+
function checkCEKLength(cek, expected) {
|
|
261
|
+
const actual = cek.byteLength << 3;
|
|
262
|
+
if (actual !== expected) throw new JWTError(`Invalid Content Encryption Key length, expected ${expected} bits (got ${actual})`, "ERR_INVALID_OPTION");
|
|
263
|
+
}
|
|
264
|
+
function assertOctKeyLength(bytes, alg) {
|
|
265
|
+
if (!alg) return;
|
|
266
|
+
if (alg === "HS256" || alg === "HS384" || alg === "HS512") {
|
|
267
|
+
const minBytes = shaBitsOf(jwsHashOf(alg)) >> 3;
|
|
268
|
+
if (bytes.length < minBytes) throw new JWTError(`${alg} requires a key of at least ${minBytes} bytes (got ${bytes.length})`, "ERR_JWK_INVALID");
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
const exactBits = isKeyWrapAlgorithm(alg) ? aesBitsOf(alg) : isContentEncryption(alg) ? CEK_BIT_LENGTHS[alg] : void 0;
|
|
272
|
+
if (exactBits !== void 0 && bytes.length << 3 !== exactBits) throw new JWTError(`${alg} requires a key of exactly ${exactBits >> 3} bytes (got ${bytes.length})`, "ERR_JWK_INVALID");
|
|
273
|
+
}
|
|
274
|
+
function checkAESCryptoKey(key, alg, usage) {
|
|
275
|
+
const name = alg.endsWith("KW") ? "AES-KW" : "AES-GCM";
|
|
276
|
+
if (!isAlgorithm(key.algorithm, name)) throw unusableKey(name);
|
|
277
|
+
const expected = aesBitsOf(alg);
|
|
278
|
+
if (key.algorithm.length !== expected) throw unusableKey(expected, "algorithm.length");
|
|
279
|
+
checkKeyUsage(key, usage);
|
|
280
|
+
}
|
|
281
|
+
async function aesKwWrap(alg, key, cek) {
|
|
282
|
+
const cryptoKey = await getAESKWCryptoKey(key, alg, { usage: "wrapKey" });
|
|
283
|
+
checkAESKWKeySize(cryptoKey, alg);
|
|
284
|
+
const cryptoKeyCek = await crypto.subtle.importKey("raw", cek, {
|
|
285
|
+
hash: "SHA-256",
|
|
286
|
+
name: "HMAC"
|
|
287
|
+
}, true, ["sign"]);
|
|
288
|
+
return new Uint8Array(await crypto.subtle.wrapKey("raw", cryptoKeyCek, cryptoKey, "AES-KW"));
|
|
289
|
+
}
|
|
290
|
+
async function aesKwUnwrap(alg, key, encryptedKey) {
|
|
291
|
+
const cryptoKey = await getAESKWCryptoKey(key, alg, { usage: "unwrapKey" });
|
|
292
|
+
checkAESKWKeySize(cryptoKey, alg);
|
|
293
|
+
const cryptoKeyCek = await crypto.subtle.unwrapKey("raw", encryptedKey, cryptoKey, "AES-KW", {
|
|
294
|
+
hash: "SHA-256",
|
|
295
|
+
name: "HMAC"
|
|
296
|
+
}, true, ["sign"]);
|
|
297
|
+
return new Uint8Array(await crypto.subtle.exportKey("raw", cryptoKeyCek));
|
|
298
|
+
}
|
|
299
|
+
async function gcmkwEncrypt(alg, key, plaintext, iv) {
|
|
300
|
+
const wrapped = await encrypt(gcmOf(alg), plaintext, key, iv, /* @__PURE__ */ new Uint8Array(0));
|
|
301
|
+
return {
|
|
302
|
+
encryptedKey: wrapped.ciphertext,
|
|
303
|
+
iv: base64UrlStringify(wrapped.iv),
|
|
304
|
+
tag: base64UrlStringify(wrapped.tag)
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
async function gcmkwDecrypt(alg, key, ciphertext, iv, tag) {
|
|
308
|
+
return decrypt(gcmOf(alg), key, ciphertext, iv, tag, /* @__PURE__ */ new Uint8Array(0));
|
|
309
|
+
}
|
|
310
|
+
async function encrypt(enc, plaintext, cek, iv, aad) {
|
|
311
|
+
if (!isCryptoKey(cek) && !(cek instanceof Uint8Array)) throw new JWTError("Key must be one of type: CryptoKey or Uint8Array", "ERR_JWK_INVALID");
|
|
312
|
+
if (iv) checkIvLength(enc, iv);
|
|
313
|
+
else iv = generateIV(enc);
|
|
314
|
+
switch (enc) {
|
|
315
|
+
case "A128CBC-HS256":
|
|
316
|
+
case "A192CBC-HS384":
|
|
317
|
+
case "A256CBC-HS512":
|
|
318
|
+
if (cek instanceof Uint8Array) checkCEKLength(cek, bitLengthCEK(enc));
|
|
319
|
+
return cbcEncrypt(enc, plaintext, cek, iv, aad);
|
|
320
|
+
case "A128GCM":
|
|
321
|
+
case "A192GCM":
|
|
322
|
+
case "A256GCM":
|
|
323
|
+
if (cek instanceof Uint8Array) checkCEKLength(cek, bitLengthCEK(enc));
|
|
324
|
+
return gcmEncrypt(enc, plaintext, cek, iv, aad);
|
|
325
|
+
default: return unsupportedContentEncryption(enc);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
async function decrypt(enc, cek, ciphertext, iv, tag, aad) {
|
|
329
|
+
if (!isCryptoKey(cek) && !(cek instanceof Uint8Array)) throw new JWTError("Key must be one of type: CryptoKey or Uint8Array", "ERR_JWK_INVALID");
|
|
330
|
+
if (!iv) throw new JWTError("JWE Initialization Vector missing", "ERR_JWE_INVALID");
|
|
331
|
+
if (!tag) throw new JWTError("JWE Authentication Tag missing", "ERR_JWE_INVALID");
|
|
332
|
+
checkIvLength(enc, iv);
|
|
333
|
+
checkTagLength(enc, tag);
|
|
334
|
+
switch (enc) {
|
|
335
|
+
case "A128CBC-HS256":
|
|
336
|
+
case "A192CBC-HS384":
|
|
337
|
+
case "A256CBC-HS512":
|
|
338
|
+
if (cek instanceof Uint8Array) checkCEKLength(cek, bitLengthCEK(enc));
|
|
339
|
+
return cbcDecrypt(enc, cek, ciphertext, iv, tag, aad);
|
|
340
|
+
case "A128GCM":
|
|
341
|
+
case "A192GCM":
|
|
342
|
+
case "A256GCM":
|
|
343
|
+
if (cek instanceof Uint8Array) checkCEKLength(cek, bitLengthCEK(enc));
|
|
344
|
+
return gcmDecrypt(enc, cek, ciphertext, iv, tag, aad);
|
|
345
|
+
default: return unsupportedContentEncryption(enc);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
async function getAESKWCryptoKey(key, alg, options = {}) {
|
|
349
|
+
const { extractable = true, usage } = options;
|
|
350
|
+
if (key instanceof Uint8Array) return crypto.subtle.importKey("raw", key, "AES-KW", extractable, [usage ?? "wrapKey"]);
|
|
351
|
+
checkAESCryptoKey(key, alg, usage);
|
|
352
|
+
return key;
|
|
353
|
+
}
|
|
354
|
+
function checkAESKWKeySize(key, alg) {
|
|
355
|
+
if (lengthOf(key.algorithm) !== aesBitsOf(alg)) throw new JWTError(`Invalid key size for alg: ${alg}`, "ERR_JWK_INVALID");
|
|
356
|
+
}
|
|
357
|
+
async function importCBCKeys(enc, cek, usage) {
|
|
358
|
+
const keySize = aesBitsOf(enc);
|
|
359
|
+
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), {
|
|
360
|
+
hash: `SHA-${keySize << 1}`,
|
|
361
|
+
name: "HMAC"
|
|
362
|
+
}, false, ["sign"])]);
|
|
363
|
+
return {
|
|
364
|
+
encKey,
|
|
365
|
+
macKey,
|
|
366
|
+
keySize
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
function cbcMacData(aad, iv, ciphertext) {
|
|
370
|
+
return concatUint8Arrays(aad, iv, ciphertext, uint64be(aad.length << 3));
|
|
371
|
+
}
|
|
372
|
+
async function cbcEncrypt(enc, plaintext, cek, iv, aad) {
|
|
373
|
+
if (!(cek instanceof Uint8Array)) throw new JWTError("CBC key must be of type Uint8Array", "ERR_JWK_INVALID");
|
|
374
|
+
const { encKey, macKey, keySize } = await importCBCKeys(enc, cek, "encrypt");
|
|
375
|
+
const ciphertext = new Uint8Array(await crypto.subtle.encrypt({
|
|
376
|
+
iv,
|
|
377
|
+
name: "AES-CBC"
|
|
378
|
+
}, encKey, plaintext));
|
|
379
|
+
const macData = cbcMacData(aad, iv, ciphertext);
|
|
380
|
+
return {
|
|
381
|
+
ciphertext,
|
|
382
|
+
tag: new Uint8Array((await crypto.subtle.sign("HMAC", macKey, macData)).slice(0, keySize >> 3)),
|
|
383
|
+
iv
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
async function gcmEncrypt(enc, plaintext, cek, iv, aad) {
|
|
387
|
+
let encKey;
|
|
388
|
+
if (cek instanceof Uint8Array) encKey = await crypto.subtle.importKey("raw", cek, "AES-GCM", false, ["encrypt"]);
|
|
389
|
+
else {
|
|
390
|
+
checkAESCryptoKey(cek, enc, "encrypt");
|
|
391
|
+
encKey = cek;
|
|
392
|
+
}
|
|
393
|
+
const encrypted = new Uint8Array(await crypto.subtle.encrypt({
|
|
394
|
+
additionalData: aad,
|
|
395
|
+
iv,
|
|
396
|
+
name: "AES-GCM",
|
|
397
|
+
tagLength: 128
|
|
398
|
+
}, encKey, plaintext));
|
|
399
|
+
const tag = encrypted.subarray(-16);
|
|
400
|
+
return {
|
|
401
|
+
ciphertext: encrypted.subarray(0, -16),
|
|
402
|
+
tag,
|
|
403
|
+
iv
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
async function cbcDecrypt(enc, cek, ciphertext, iv, tag, aad) {
|
|
407
|
+
if (!(cek instanceof Uint8Array)) throw new JWTError("CBC key must be of type Uint8Array", "ERR_JWK_INVALID");
|
|
408
|
+
const { encKey, macKey, keySize } = await importCBCKeys(enc, cek, "decrypt");
|
|
409
|
+
const macData = cbcMacData(aad, iv, ciphertext);
|
|
410
|
+
const expectedTag = new Uint8Array((await crypto.subtle.sign("HMAC", macKey, macData)).slice(0, keySize >> 3));
|
|
411
|
+
let macCheckPassed;
|
|
412
|
+
try {
|
|
413
|
+
macCheckPassed = await timingSafeEqual(tag, expectedTag);
|
|
414
|
+
} catch {}
|
|
415
|
+
if (!macCheckPassed) throw new JWTError("JWE Decryption Failed", "ERR_JWE_DECRYPTION_FAILED");
|
|
416
|
+
let plaintext;
|
|
417
|
+
try {
|
|
418
|
+
plaintext = new Uint8Array(await crypto.subtle.decrypt({
|
|
419
|
+
iv,
|
|
420
|
+
name: "AES-CBC"
|
|
421
|
+
}, encKey, ciphertext));
|
|
422
|
+
} catch {}
|
|
423
|
+
if (!plaintext) throw new JWTError("JWE Decryption Failed", "ERR_JWE_DECRYPTION_FAILED");
|
|
424
|
+
return plaintext;
|
|
425
|
+
}
|
|
426
|
+
async function gcmDecrypt(enc, cek, ciphertext, iv, tag, aad) {
|
|
427
|
+
let encKey;
|
|
428
|
+
if (cek instanceof Uint8Array) encKey = await crypto.subtle.importKey("raw", cek, "AES-GCM", false, ["decrypt"]);
|
|
429
|
+
else {
|
|
430
|
+
checkAESCryptoKey(cek, enc, "decrypt");
|
|
431
|
+
encKey = cek;
|
|
432
|
+
}
|
|
433
|
+
try {
|
|
434
|
+
return new Uint8Array(await crypto.subtle.decrypt({
|
|
435
|
+
additionalData: aad,
|
|
436
|
+
iv,
|
|
437
|
+
name: "AES-GCM",
|
|
438
|
+
tagLength: 128
|
|
439
|
+
}, encKey, concatUint8Arrays(ciphertext, tag)));
|
|
440
|
+
} catch {
|
|
441
|
+
throw new JWTError("JWE Decryption Failed", "ERR_JWE_DECRYPTION_FAILED");
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
let _timingSafeKey;
|
|
445
|
+
function getTimingSafeKey() {
|
|
446
|
+
if (!_timingSafeKey) _timingSafeKey = crypto.subtle.generateKey({
|
|
447
|
+
name: "HMAC",
|
|
448
|
+
hash: "SHA-256"
|
|
449
|
+
}, false, ["sign"]);
|
|
450
|
+
return _timingSafeKey;
|
|
451
|
+
}
|
|
452
|
+
async function timingSafeEqual(a, b) {
|
|
453
|
+
if (!(a instanceof Uint8Array)) throw new JWTError("First argument must be a buffer", "ERR_INVALID_OPTION");
|
|
454
|
+
if (!(b instanceof Uint8Array)) throw new JWTError("Second argument must be a buffer", "ERR_INVALID_OPTION");
|
|
455
|
+
const algorithm = {
|
|
456
|
+
name: "HMAC",
|
|
457
|
+
hash: "SHA-256"
|
|
458
|
+
};
|
|
459
|
+
const key = await getTimingSafeKey();
|
|
460
|
+
const aHmac = new Uint8Array(await crypto.subtle.sign(algorithm, key, a));
|
|
461
|
+
const bHmac = new Uint8Array(await crypto.subtle.sign(algorithm, key, b));
|
|
462
|
+
let out = 0;
|
|
463
|
+
let i = -1;
|
|
464
|
+
while (++i < 32) out |= aHmac[i] ^ bHmac[i];
|
|
465
|
+
return out === 0;
|
|
466
|
+
}
|
|
467
|
+
function uint64be(value) {
|
|
468
|
+
return concatUint8Arrays(uint32be(Math.floor(value / 2 ** 32)), uint32be(value % 2 ** 32));
|
|
469
|
+
}
|
|
470
|
+
function gcmOf(alg) {
|
|
471
|
+
return alg.slice(0, 7);
|
|
472
|
+
}
|
|
473
|
+
function isKeyWrapAlgorithm(alg) {
|
|
474
|
+
return alg.startsWith("A") && alg.endsWith("KW");
|
|
475
|
+
}
|
|
476
|
+
function isContentEncryption(alg) {
|
|
477
|
+
return Object.hasOwn(CEK_BIT_LENGTHS, alg);
|
|
478
|
+
}
|
|
479
|
+
function unsupportedContentEncryption(enc) {
|
|
480
|
+
throw new JWTError(`Unsupported JWE Content Encryption Algorithm: ${quoteTokenValue(String(enc))}`, "ERR_JWE_INVALID");
|
|
481
|
+
}
|
|
482
|
+
async function jwkTokey(jwk, options = {}) {
|
|
483
|
+
const alg = jwk.alg ?? options.alg;
|
|
484
|
+
if (!alg) throw new JWTError("\"alg\" argument is required when \"jwk.alg\" is not present", "ERR_JWK_INVALID");
|
|
485
|
+
const { algorithm, keyUsages } = subtleMapping(jwk, alg);
|
|
486
|
+
const material = {
|
|
487
|
+
...jwk,
|
|
488
|
+
alg
|
|
489
|
+
};
|
|
490
|
+
const isPrivate = "d" in jwk && jwk.d !== void 0;
|
|
491
|
+
const extractable = jwk.ext === false ? false : options.extractable ?? !isPrivate;
|
|
492
|
+
try {
|
|
493
|
+
return await crypto.subtle.importKey("jwk", material, algorithm, extractable, permittedUsages(jwk, keyUsages));
|
|
494
|
+
} catch (error) {
|
|
495
|
+
if (error instanceof JWTError) throw error;
|
|
496
|
+
throw new JWTError(`Key is not usable for ${quoteTokenValue(alg)}.`, "ERR_JWK_INVALID", error);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
async function keyToJWK(key) {
|
|
500
|
+
let jwk;
|
|
501
|
+
if (key instanceof Uint8Array) jwk = {
|
|
502
|
+
kty: "oct",
|
|
503
|
+
k: base64UrlStringify(key)
|
|
504
|
+
};
|
|
505
|
+
else {
|
|
506
|
+
if (!isCryptoKey(key)) throw new JWTError("Key must be one of type: CryptoKey or Uint8Array", "ERR_JWK_INVALID");
|
|
507
|
+
if (!key.extractable) throw new JWTError("non-extractable CryptoKey cannot be exported as a JWK", "ERR_JWK_INVALID");
|
|
508
|
+
const { ext: _ext, key_ops: _keyOps, ...exported } = await crypto.subtle.exportKey("jwk", key);
|
|
509
|
+
jwk = exported;
|
|
510
|
+
}
|
|
511
|
+
return jwk;
|
|
512
|
+
}
|
|
513
|
+
function permittedUsages(jwk, keyUsages) {
|
|
514
|
+
const keyOps = jwk.key_ops;
|
|
515
|
+
if (!keyOps || keyUsages.length === 0) return keyUsages;
|
|
516
|
+
const permitted = keyUsages.filter((usage) => keyOps.includes(usage));
|
|
517
|
+
if (permitted.length === 0) throw new JWTError(`JWK "key_ops" does not permit ${keyUsages.join(", ")}.`, "ERR_JWK_INVALID");
|
|
518
|
+
return permitted;
|
|
519
|
+
}
|
|
520
|
+
function subtleMapping(jwk, alg) {
|
|
521
|
+
let algorithm;
|
|
522
|
+
let keyUsages;
|
|
523
|
+
switch (jwk.kty) {
|
|
524
|
+
case "RSA":
|
|
525
|
+
switch (alg) {
|
|
526
|
+
case "PS256":
|
|
527
|
+
case "PS384":
|
|
528
|
+
case "PS512":
|
|
529
|
+
algorithm = {
|
|
530
|
+
name: "RSA-PSS",
|
|
531
|
+
hash: jwsHashOf(alg)
|
|
532
|
+
};
|
|
533
|
+
keyUsages = hasPrivateMember(jwk) ? ["sign"] : ["verify"];
|
|
534
|
+
break;
|
|
535
|
+
case "RS256":
|
|
536
|
+
case "RS384":
|
|
537
|
+
case "RS512":
|
|
538
|
+
algorithm = {
|
|
539
|
+
name: "RSASSA-PKCS1-v1_5",
|
|
540
|
+
hash: jwsHashOf(alg)
|
|
541
|
+
};
|
|
542
|
+
keyUsages = hasPrivateMember(jwk) ? ["sign"] : ["verify"];
|
|
543
|
+
break;
|
|
544
|
+
case "RSA-OAEP":
|
|
545
|
+
case "RSA-OAEP-256":
|
|
546
|
+
case "RSA-OAEP-384":
|
|
547
|
+
case "RSA-OAEP-512":
|
|
548
|
+
algorithm = {
|
|
549
|
+
name: "RSA-OAEP",
|
|
550
|
+
hash: rsaOaepHashOf(alg)
|
|
551
|
+
};
|
|
552
|
+
keyUsages = hasPrivateMember(jwk) ? ["decrypt", "unwrapKey"] : ["encrypt", "wrapKey"];
|
|
553
|
+
break;
|
|
554
|
+
default: throw new JWTError(`Invalid JWK "alg" ${quoteTokenValue(alg)}.`, "ERR_JWK_INVALID");
|
|
555
|
+
}
|
|
556
|
+
break;
|
|
557
|
+
case "EC":
|
|
558
|
+
switch (alg) {
|
|
559
|
+
case "ES256":
|
|
560
|
+
case "ES384":
|
|
561
|
+
case "ES512":
|
|
562
|
+
algorithm = {
|
|
563
|
+
name: "ECDSA",
|
|
564
|
+
namedCurve: ECDSA_CURVES[alg]
|
|
565
|
+
};
|
|
566
|
+
keyUsages = hasPrivateMember(jwk) ? ["sign"] : ["verify"];
|
|
567
|
+
break;
|
|
568
|
+
case "ECDH-ES":
|
|
569
|
+
case "ECDH-ES+A128KW":
|
|
570
|
+
case "ECDH-ES+A192KW":
|
|
571
|
+
case "ECDH-ES+A256KW":
|
|
572
|
+
algorithm = {
|
|
573
|
+
name: "ECDH",
|
|
574
|
+
namedCurve: requireCurve(jwk, [
|
|
575
|
+
"P-256",
|
|
576
|
+
"P-384",
|
|
577
|
+
"P-521"
|
|
578
|
+
])
|
|
579
|
+
};
|
|
580
|
+
keyUsages = hasPrivateMember(jwk) ? ["deriveBits"] : [];
|
|
581
|
+
break;
|
|
582
|
+
default: throw new JWTError(`Invalid JWK "alg" ${quoteTokenValue(alg)}.`, "ERR_JWK_INVALID");
|
|
583
|
+
}
|
|
584
|
+
break;
|
|
585
|
+
case "OKP":
|
|
586
|
+
switch (alg) {
|
|
587
|
+
case "Ed25519":
|
|
588
|
+
case "EdDSA":
|
|
589
|
+
algorithm = { name: requireCurve(jwk, ["Ed25519"]) };
|
|
590
|
+
keyUsages = hasPrivateMember(jwk) ? ["sign"] : ["verify"];
|
|
591
|
+
break;
|
|
592
|
+
case "ECDH-ES":
|
|
593
|
+
case "ECDH-ES+A128KW":
|
|
594
|
+
case "ECDH-ES+A192KW":
|
|
595
|
+
case "ECDH-ES+A256KW":
|
|
596
|
+
algorithm = { name: requireCurve(jwk, ["X25519"]) };
|
|
597
|
+
keyUsages = hasPrivateMember(jwk) ? ["deriveBits"] : [];
|
|
598
|
+
break;
|
|
599
|
+
default: throw new JWTError(`Invalid JWK "alg" ${quoteTokenValue(alg)}.`, "ERR_JWK_INVALID");
|
|
600
|
+
}
|
|
601
|
+
break;
|
|
602
|
+
default: throw new JWTError(`Invalid JWK "kty" ${quoteTokenValue(jwk.kty)}.`, "ERR_JWK_INVALID");
|
|
603
|
+
}
|
|
604
|
+
return {
|
|
605
|
+
algorithm,
|
|
606
|
+
keyUsages
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
function requireCurve(jwk, allowed) {
|
|
610
|
+
const crv = "crv" in jwk ? jwk.crv : void 0;
|
|
611
|
+
if (typeof crv !== "string" || !allowed.includes(crv)) throw new JWTError(`Invalid JWK "crv" ${quoteTokenValue(crv)}, expected ${allowed.join(", ")}.`, "ERR_JWK_INVALID");
|
|
612
|
+
return crv;
|
|
613
|
+
}
|
|
614
|
+
function hasPrivateMember(jwk) {
|
|
615
|
+
return "d" in jwk && Boolean(jwk.d);
|
|
616
|
+
}
|
|
617
|
+
async function encryptRSAES(alg, key, cek) {
|
|
618
|
+
checkRSACryptoKey(key, alg, "encrypt");
|
|
619
|
+
checkRSAModulusLength(alg, key);
|
|
620
|
+
return new Uint8Array(await crypto.subtle.encrypt("RSA-OAEP", key, cek));
|
|
621
|
+
}
|
|
622
|
+
async function decryptRSAES(alg, key, encryptedKey) {
|
|
623
|
+
checkRSACryptoKey(key, alg, "decrypt");
|
|
624
|
+
checkRSAModulusLength(alg, key);
|
|
625
|
+
return new Uint8Array(await crypto.subtle.decrypt("RSA-OAEP", key, encryptedKey));
|
|
626
|
+
}
|
|
627
|
+
function checkRSACryptoKey(key, alg, usage) {
|
|
628
|
+
if (!isAlgorithm(key.algorithm, "RSA-OAEP")) throw unusableKey("RSA-OAEP");
|
|
629
|
+
const expected = shaBitsOf(rsaOaepHashOf(alg));
|
|
630
|
+
if (hashBitsOf(key.algorithm.hash) !== expected) throw unusableKey(`SHA-${expected}`, "algorithm.hash");
|
|
631
|
+
checkKeyUsage(key, usage);
|
|
632
|
+
}
|
|
633
|
+
function checkRSAModulusLength(alg, key) {
|
|
634
|
+
const modulusLength = modulusLengthOf(key.algorithm);
|
|
635
|
+
if (typeof modulusLength !== "number" || modulusLength < 2048) throw new JWTError(`${alg} requires key modulusLength to be 2048 bits or larger`, "ERR_JWK_INVALID");
|
|
636
|
+
}
|
|
637
|
+
async function deriveECDHESKey(publicKey, privateKey, algorithm, keyLength, apu = /* @__PURE__ */ new Uint8Array(0), apv = /* @__PURE__ */ new Uint8Array(0)) {
|
|
638
|
+
checkECDHCryptoKey(publicKey);
|
|
639
|
+
checkECDHCryptoKey(privateKey, "deriveBits");
|
|
640
|
+
const value = concatUint8Arrays(lengthAndInput(textEncoder.encode(algorithm)), lengthAndInput(apu), lengthAndInput(apv), uint32be(keyLength));
|
|
641
|
+
const length = publicKey.algorithm.name === "X25519" ? 256 : Math.ceil(Number.parseInt((namedCurveOf(publicKey.algorithm) ?? "").slice(-3), 10) / 8) << 3;
|
|
642
|
+
return concatKdf(new Uint8Array(await crypto.subtle.deriveBits({
|
|
643
|
+
name: publicKey.algorithm.name,
|
|
644
|
+
public: publicKey
|
|
645
|
+
}, privateKey, length)), keyLength, value);
|
|
646
|
+
}
|
|
647
|
+
function ecdhDerivedBits(alg, enc) {
|
|
648
|
+
if (alg !== "ECDH-ES") return aesBitsOf(innerKeyWrapAlg(alg));
|
|
649
|
+
if (enc) return bitLengthCEK(enc);
|
|
650
|
+
throw new JWTError("ECDH-ES requires \"enc\" to derive the key.", "ERR_JWE_INVALID");
|
|
651
|
+
}
|
|
652
|
+
function generateEphemeralKeyPair(recipientKey) {
|
|
653
|
+
return crypto.subtle.generateKey(recipientKey.algorithm, true, ["deriveBits"]);
|
|
654
|
+
}
|
|
655
|
+
function assertECDHKeyAllowed(key) {
|
|
656
|
+
switch (namedCurveOf(key.algorithm) ?? key.algorithm.name) {
|
|
657
|
+
case "P-256":
|
|
658
|
+
case "P-384":
|
|
659
|
+
case "P-521":
|
|
660
|
+
case "X25519": return;
|
|
661
|
+
default: throw new JWTError("ECDH with the provided key is not allowed or not supported", "ERR_JWK_INVALID");
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
function checkECDHCryptoKey(key, usage) {
|
|
665
|
+
switch (key.algorithm.name) {
|
|
666
|
+
case "ECDH":
|
|
667
|
+
case "X25519": break;
|
|
668
|
+
default: throw unusableKey("ECDH or X25519");
|
|
669
|
+
}
|
|
670
|
+
checkKeyUsage(key, usage);
|
|
671
|
+
}
|
|
672
|
+
async function concatKdf(secret, bits, value) {
|
|
673
|
+
const iterations = Math.ceil((bits >> 3) / 32);
|
|
674
|
+
const res = new Uint8Array(iterations * 32);
|
|
675
|
+
for (let iter = 0; iter < iterations; iter++) {
|
|
676
|
+
const buf = new Uint8Array(4 + secret.length + value.length);
|
|
677
|
+
buf.set(uint32be(iter + 1));
|
|
678
|
+
buf.set(secret, 4);
|
|
679
|
+
buf.set(value, 4 + secret.length);
|
|
680
|
+
res.set(await digest("sha256", buf), iter * 32);
|
|
681
|
+
}
|
|
682
|
+
return res.slice(0, bits >> 3);
|
|
683
|
+
}
|
|
684
|
+
function lengthAndInput(input) {
|
|
685
|
+
return concatUint8Arrays(uint32be(input.length), input);
|
|
686
|
+
}
|
|
687
|
+
async function digest(algorithm, data) {
|
|
688
|
+
return new Uint8Array(await crypto.subtle.digest(`SHA-${algorithm.slice(-3)}`, data));
|
|
689
|
+
}
|
|
690
|
+
const DEFAULT_PBES2_MIN_ITERATIONS = 1e3;
|
|
691
|
+
const DEFAULT_PBES2_ITERATIONS = 6e5;
|
|
692
|
+
const DEFAULT_PBES2_MAX_ITERATIONS = DEFAULT_PBES2_ITERATIONS;
|
|
693
|
+
function assertPBES2Params(params) {
|
|
694
|
+
const { p2s, p2c } = params;
|
|
695
|
+
if (p2s !== void 0) assertPBES2Salt(p2s, "\"p2s\"");
|
|
696
|
+
if (p2c === void 0) return;
|
|
697
|
+
assertPBES2Count(p2c, "\"p2c\"");
|
|
698
|
+
if (p2c > 6e5) throw new JWTError(`"p2c" must be between ${DEFAULT_PBES2_MIN_ITERATIONS} and ${DEFAULT_PBES2_MAX_ITERATIONS} iterations (got ${p2c}).`, "ERR_INVALID_OPTION");
|
|
699
|
+
}
|
|
700
|
+
function assertPBES2Salt(salt, name) {
|
|
701
|
+
if (salt instanceof Uint8Array && salt.length >= 8) return;
|
|
702
|
+
throw new JWTError(`${name} must be 8 or more octets.`, "ERR_INVALID_OPTION");
|
|
703
|
+
}
|
|
704
|
+
function assertPBES2Count(count, name) {
|
|
705
|
+
if (typeof count !== "number" || !Number.isInteger(count) || count < 1) throw new JWTError(`${name} must be a positive integer.`, "ERR_INVALID_OPTION");
|
|
706
|
+
if (count < 1e3) throw new JWTError(`${name} must be at least ${DEFAULT_PBES2_MIN_ITERATIONS} iterations (got ${count}).`, "ERR_INVALID_OPTION");
|
|
707
|
+
}
|
|
708
|
+
async function pbes2Wrap(alg, key, cek, p2c, p2s) {
|
|
709
|
+
const derived = await deriveKey(p2s, alg, p2c, key);
|
|
710
|
+
return {
|
|
711
|
+
encryptedKey: await aesKwWrap(innerKeyWrapAlg(alg), derived, cek),
|
|
712
|
+
p2c,
|
|
713
|
+
p2s: base64UrlStringify(p2s)
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
function assertPBES2Iterations(p2c, bounds) {
|
|
717
|
+
const { minIterations = DEFAULT_PBES2_MIN_ITERATIONS, maxIterations = DEFAULT_PBES2_MAX_ITERATIONS } = bounds;
|
|
718
|
+
if (p2c < minIterations) throw new JWTError(`PBES2 "p2c" below the minimum of ${minIterations} iterations (got ${p2c}).`, "ERR_JWE_INVALID");
|
|
719
|
+
if (p2c > maxIterations) throw new JWTError(`PBES2 "p2c" above the maximum of ${maxIterations} iterations (got ${p2c}).`, "ERR_JWE_INVALID");
|
|
720
|
+
}
|
|
721
|
+
async function pbes2Unwrap(alg, key, encryptedKey, p2c, p2s, minIterations, maxIterations) {
|
|
722
|
+
assertPBES2Iterations(p2c, {
|
|
723
|
+
minIterations,
|
|
724
|
+
maxIterations
|
|
725
|
+
});
|
|
726
|
+
const derived = await deriveKey(p2s, alg, p2c, key);
|
|
727
|
+
return aesKwUnwrap(innerKeyWrapAlg(alg), derived, encryptedKey);
|
|
728
|
+
}
|
|
729
|
+
async function deriveKey(p2s, alg, p2c, key) {
|
|
730
|
+
assertPBES2Salt(p2s, "\"p2s\"");
|
|
731
|
+
if (p2c === void 0 || p2c < 1) throw new JWTError("PBES2 Iteration Count Parameter must be a positive integer", "ERR_INVALID_OPTION");
|
|
732
|
+
const salt = concatUint8Arrays(textEncoder.encode(alg), new Uint8Array([0]), p2s);
|
|
733
|
+
const keylen = aesBitsOf(innerKeyWrapAlg(alg));
|
|
734
|
+
const subtleAlg = {
|
|
735
|
+
hash: pbes2HashOf(alg),
|
|
736
|
+
iterations: p2c,
|
|
737
|
+
name: "PBKDF2",
|
|
738
|
+
salt
|
|
739
|
+
};
|
|
740
|
+
const cryptoKey = await getPBKDF2CryptoKey(key, alg);
|
|
741
|
+
return new Uint8Array(await crypto.subtle.deriveBits(subtleAlg, cryptoKey, keylen));
|
|
742
|
+
}
|
|
743
|
+
function checkPBES2CryptoKey(key, alg, usage) {
|
|
744
|
+
if (!isAlgorithm(key.algorithm, "PBKDF2")) throw unusableKey("PBKDF2");
|
|
745
|
+
checkKeyUsage(key, usage);
|
|
746
|
+
}
|
|
747
|
+
async function getPBKDF2CryptoKey(key, alg, options = {}) {
|
|
748
|
+
const { extractable = false, usage = "deriveBits" } = options;
|
|
749
|
+
if (key instanceof Uint8Array) return crypto.subtle.importKey("raw", key, "PBKDF2", extractable, [usage]);
|
|
750
|
+
checkPBES2CryptoKey(key, alg, usage);
|
|
751
|
+
return key;
|
|
752
|
+
}
|
|
753
|
+
async function encryptKey(alg, enc, key, providedCek, providedParameters = {}) {
|
|
754
|
+
let encryptedKey;
|
|
755
|
+
let parameters = {};
|
|
756
|
+
let cek;
|
|
757
|
+
switch (alg) {
|
|
758
|
+
case "dir":
|
|
759
|
+
cek = key;
|
|
760
|
+
break;
|
|
761
|
+
case "ECDH-ES":
|
|
762
|
+
case "ECDH-ES+A128KW":
|
|
763
|
+
case "ECDH-ES+A192KW":
|
|
764
|
+
case "ECDH-ES+A256KW":
|
|
765
|
+
assertCryptoKey(key);
|
|
766
|
+
({cek, encryptedKey, parameters} = await ecdhesWrap(alg, {
|
|
767
|
+
enc,
|
|
768
|
+
recipientKey: key,
|
|
769
|
+
cek: providedCek,
|
|
770
|
+
parameters: providedParameters
|
|
771
|
+
}));
|
|
772
|
+
break;
|
|
773
|
+
case "RSA-OAEP":
|
|
774
|
+
case "RSA-OAEP-256":
|
|
775
|
+
case "RSA-OAEP-384":
|
|
776
|
+
case "RSA-OAEP-512":
|
|
777
|
+
cek = providedCek || generateCEK(enc);
|
|
778
|
+
assertCryptoKey(key);
|
|
779
|
+
encryptedKey = await encryptRSAES(alg, key, cek);
|
|
780
|
+
break;
|
|
781
|
+
case "PBES2-HS256+A128KW":
|
|
782
|
+
case "PBES2-HS384+A192KW":
|
|
783
|
+
case "PBES2-HS512+A256KW": {
|
|
784
|
+
cek = providedCek || generateCEK(enc);
|
|
785
|
+
const { p2c, p2s } = providedParameters;
|
|
786
|
+
if (p2c === void 0 || p2s === void 0) throw new JWTError(`${alg} requires "p2c" and "p2s"`, "ERR_JWE_INVALID");
|
|
787
|
+
({encryptedKey, ...parameters} = await pbes2Wrap(alg, key, cek, p2c, p2s));
|
|
788
|
+
break;
|
|
789
|
+
}
|
|
790
|
+
case "A128KW":
|
|
791
|
+
case "A192KW":
|
|
792
|
+
case "A256KW":
|
|
793
|
+
cek = providedCek || generateCEK(enc);
|
|
794
|
+
encryptedKey = await aesKwWrap(alg, key, cek);
|
|
795
|
+
break;
|
|
796
|
+
case "A128GCMKW":
|
|
797
|
+
case "A192GCMKW":
|
|
798
|
+
case "A256GCMKW": {
|
|
799
|
+
cek = providedCek || generateCEK(enc);
|
|
800
|
+
const { iv } = providedParameters;
|
|
801
|
+
({encryptedKey, ...parameters} = await gcmkwEncrypt(alg, key, cek, iv));
|
|
802
|
+
break;
|
|
803
|
+
}
|
|
804
|
+
default: throw new JWTError(`Unsupported JWE "alg" ${quoteTokenValue(alg)}`, "ERR_JWE_ALG_NOT_ALLOWED");
|
|
805
|
+
}
|
|
806
|
+
return {
|
|
807
|
+
cek,
|
|
808
|
+
encryptedKey,
|
|
809
|
+
parameters
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
async function ecdhesWrap(alg, options) {
|
|
813
|
+
const { enc, recipientKey } = options;
|
|
814
|
+
const { apu, apv, epk: providedEpk, epkPrivateKey } = options.parameters;
|
|
815
|
+
assertECDHKeyAllowed(recipientKey);
|
|
816
|
+
if (providedEpk && !epkPrivateKey) throw new JWTError("ECDH-ES custom ephemeral key material must include the matching private key.", "ERR_JWK_INVALID");
|
|
817
|
+
let ephemeralPrivateKey;
|
|
818
|
+
let epk;
|
|
819
|
+
if (epkPrivateKey) {
|
|
820
|
+
ephemeralPrivateKey = await ensurePrivateKey(alg, epkPrivateKey);
|
|
821
|
+
assertEphemeralCurve(ephemeralPrivateKey, recipientKey);
|
|
822
|
+
epk = await exportPublicJwkFrom(providedEpk ?? epkPrivateKey);
|
|
823
|
+
} else {
|
|
824
|
+
const generated = await generateEphemeralKeyPair(recipientKey);
|
|
825
|
+
ephemeralPrivateKey = generated.privateKey;
|
|
826
|
+
epk = await exportPublicJwkFrom(generated.publicKey);
|
|
827
|
+
}
|
|
828
|
+
const kdfAlg = alg === "ECDH-ES" ? contentAlgorithm(enc) : alg;
|
|
829
|
+
const sharedSecret = await deriveECDHESKey(recipientKey, ephemeralPrivateKey, kdfAlg, ecdhDerivedBits(alg, enc), apu, apv);
|
|
830
|
+
const parameters = { epk };
|
|
831
|
+
if (apu) parameters.apu = base64UrlStringify(apu);
|
|
832
|
+
if (apv) parameters.apv = base64UrlStringify(apv);
|
|
833
|
+
if (alg === "ECDH-ES") return {
|
|
834
|
+
cek: sharedSecret,
|
|
835
|
+
parameters
|
|
836
|
+
};
|
|
837
|
+
const cek = options.cek ?? generateCEK(contentAlgorithm(enc));
|
|
838
|
+
return {
|
|
839
|
+
cek,
|
|
840
|
+
encryptedKey: await aesKwWrap(innerKeyWrapAlg(alg), sharedSecret, cek),
|
|
841
|
+
parameters
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
async function normalizeEpk(epk, options) {
|
|
845
|
+
const { alg } = options;
|
|
846
|
+
const recipientCurve = cryptoKeyCurve(options.recipientKey);
|
|
847
|
+
if (isCryptoKey(epk)) {
|
|
848
|
+
if (epk.type !== "public") throw invalidEpk("must not contain private key material.");
|
|
849
|
+
assertEpkCurve(cryptoKeyCurve(epk), recipientCurve);
|
|
850
|
+
return epk;
|
|
851
|
+
}
|
|
852
|
+
if (!isJWK(epk) || epk.kty !== "EC" && epk.kty !== "OKP") throw invalidEpk("must be an EC or OKP public key.");
|
|
853
|
+
if ("d" in epk && epk.d !== void 0) throw invalidEpk("must not contain private key material.");
|
|
854
|
+
const crv = epk.crv;
|
|
855
|
+
const allowed = epk.kty === "EC" ? EC_ECDH_CURVES : OKP_ECDH_CURVES;
|
|
856
|
+
if (typeof crv !== "string" || !allowed.has(crv)) throw invalidEpk(`curve ${quoteTokenValue(crv)} is not supported for ${options.alg}.`);
|
|
857
|
+
assertEpkCurve(crv, recipientCurve);
|
|
858
|
+
const x = epk.x;
|
|
859
|
+
const y = epk.kty === "EC" ? epk.y : void 0;
|
|
860
|
+
if (typeof x !== "string" || epk.kty === "EC" && typeof y !== "string") throw invalidEpk("is missing its public coordinates.");
|
|
861
|
+
return jwkTokey(epk.kty === "EC" && typeof y === "string" ? {
|
|
862
|
+
kty: "EC",
|
|
863
|
+
crv,
|
|
864
|
+
x,
|
|
865
|
+
y,
|
|
866
|
+
alg
|
|
867
|
+
} : {
|
|
868
|
+
kty: "OKP",
|
|
869
|
+
crv,
|
|
870
|
+
x,
|
|
871
|
+
alg
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
const EC_ECDH_CURVES = /* @__PURE__ */ new Set([
|
|
875
|
+
"P-256",
|
|
876
|
+
"P-384",
|
|
877
|
+
"P-521"
|
|
878
|
+
]);
|
|
879
|
+
const OKP_ECDH_CURVES = /* @__PURE__ */ new Set(["X25519"]);
|
|
880
|
+
function contentAlgorithm(enc) {
|
|
881
|
+
if (enc) return enc;
|
|
882
|
+
throw new JWTError("ECDH-ES requires \"enc\" to derive the key.", "ERR_JWE_INVALID");
|
|
883
|
+
}
|
|
884
|
+
function assertEphemeralCurve(ephemeralKey, recipientKey) {
|
|
885
|
+
assertECDHKeyAllowed(ephemeralKey);
|
|
886
|
+
const ephemeralCurve = cryptoKeyCurve(ephemeralKey);
|
|
887
|
+
const recipientCurve = cryptoKeyCurve(recipientKey);
|
|
888
|
+
if (ephemeralCurve === recipientCurve) return;
|
|
889
|
+
throw new JWTError(`ECDH-ES ephemeral key curve ${quoteTokenValue(ephemeralCurve)} does not match recipient curve ${quoteTokenValue(recipientCurve)}.`, "ERR_JWK_INVALID");
|
|
890
|
+
}
|
|
891
|
+
function invalidEpk(detail) {
|
|
892
|
+
return new JWTError(`Invalid JWE: "epk" ${detail}`, "ERR_JWE_INVALID");
|
|
893
|
+
}
|
|
894
|
+
function assertEpkCurve(epkCurve, recipientCurve) {
|
|
895
|
+
if (!recipientCurve || !epkCurve || epkCurve === recipientCurve) return;
|
|
896
|
+
throw invalidEpk(`curve "${epkCurve}" does not match recipient curve "${recipientCurve}".`);
|
|
897
|
+
}
|
|
898
|
+
function toPublicEpkJwk(jwk) {
|
|
899
|
+
const { kty } = jwk;
|
|
900
|
+
const crv = "crv" in jwk ? jwk.crv : void 0;
|
|
901
|
+
const x = "x" in jwk ? jwk.x : void 0;
|
|
902
|
+
if (kty !== "EC" && kty !== "OKP") throw new JWTError("ECDH-ES ephemeral key JWK \"kty\" must be \"EC\" or \"OKP\".", "ERR_JWK_INVALID");
|
|
903
|
+
if (typeof crv !== "string") throw new JWTError("ECDH-ES ephemeral key JWK must include the \"crv\" parameter.", "ERR_JWK_INVALID");
|
|
904
|
+
if (typeof x !== "string") throw new JWTError("ECDH-ES ephemeral key JWK must include the \"x\" coordinate.", "ERR_JWK_INVALID");
|
|
905
|
+
if (kty === "OKP") return {
|
|
906
|
+
kty,
|
|
907
|
+
crv,
|
|
908
|
+
x
|
|
909
|
+
};
|
|
910
|
+
const y = "y" in jwk ? jwk.y : void 0;
|
|
911
|
+
if (typeof y !== "string") throw new JWTError("ECDH-ES EC ephemeral key JWK must include both \"x\" and \"y\" coordinates.", "ERR_JWK_INVALID");
|
|
912
|
+
return {
|
|
913
|
+
kty,
|
|
914
|
+
crv,
|
|
915
|
+
x,
|
|
916
|
+
y
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
async function ensurePrivateKey(alg, value) {
|
|
920
|
+
const normalized = isCryptoKey(value) ? value : await jwkTokey(value, { alg });
|
|
921
|
+
if (normalized.type !== "private") throw new JWTError("ECDH-ES ephemeral private key must have type \"private\".", "ERR_JWK_INVALID");
|
|
922
|
+
return normalized;
|
|
923
|
+
}
|
|
924
|
+
async function exportPublicJwkFrom(value) {
|
|
925
|
+
if (!isCryptoKey(value)) return toPublicEpkJwk(value);
|
|
926
|
+
try {
|
|
927
|
+
return toPublicEpkJwk(await keyToJWK(value));
|
|
928
|
+
} catch (error_) {
|
|
929
|
+
throw new JWTError("ECDH-ES ephemeral CryptoKey must be extractable to export as JWK.", "ERR_JWK_INVALID", error_);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
const toSPKI = (key) => {
|
|
933
|
+
return genericExport("public", "spki", key);
|
|
934
|
+
};
|
|
935
|
+
const toPKCS8 = (key) => {
|
|
936
|
+
return genericExport("private", "pkcs8", key);
|
|
937
|
+
};
|
|
938
|
+
const fromPKCS8 = (pem, alg, options) => {
|
|
939
|
+
return genericImport("PRIVATE KEY", "pkcs8", pem, alg, options);
|
|
940
|
+
};
|
|
941
|
+
const fromSPKI = (pem, alg, options) => {
|
|
942
|
+
return genericImport("PUBLIC KEY", "spki", pem, alg, options);
|
|
943
|
+
};
|
|
944
|
+
const fromX509 = (pem, alg, options) => {
|
|
945
|
+
let spki;
|
|
946
|
+
try {
|
|
947
|
+
spki = getSPKI(pem);
|
|
948
|
+
} catch (error_) {
|
|
949
|
+
throw new JWTError("Failed to parse the X.509 certificate.", "ERR_JWK_INVALID", error_);
|
|
950
|
+
}
|
|
951
|
+
return fromSPKI(spki, alg, options);
|
|
952
|
+
};
|
|
953
|
+
const formatPEM = (b64, descriptor) => {
|
|
954
|
+
return `-----BEGIN ${descriptor}-----\n${(b64.match(/.{1,64}/g) || []).join("\n")}\n-----END ${descriptor}-----`;
|
|
955
|
+
};
|
|
956
|
+
const pemBody = (pem, descriptor) => {
|
|
957
|
+
const begin = `-----BEGIN ${descriptor}-----`;
|
|
958
|
+
const end = `-----END ${descriptor}-----`;
|
|
959
|
+
const start = pem.indexOf(begin);
|
|
960
|
+
if (start === -1) {
|
|
961
|
+
const found = pem.match(/-----BEGIN [^-]+-----/)?.[0];
|
|
962
|
+
throw new JWTError(found ? `PEM label mismatch: expected "${begin}", got "${found}".` : `PEM label missing: expected "${begin}".`, "ERR_JWK_INVALID");
|
|
963
|
+
}
|
|
964
|
+
const bodyStart = start + begin.length;
|
|
965
|
+
const bodyEnd = pem.indexOf(end, bodyStart);
|
|
966
|
+
if (bodyEnd === -1) throw new JWTError(`PEM label missing: expected "${end}".`, "ERR_JWK_INVALID");
|
|
967
|
+
if (pem.includes("-----BEGIN", bodyStart) || pem.slice(bodyEnd + end.length).trim() !== "") throw new JWTError(`PEM must contain exactly one ${descriptor} block, with nothing after it.`, "ERR_JWK_INVALID");
|
|
968
|
+
return pem.slice(bodyStart, bodyEnd).replaceAll(/\s/g, "");
|
|
969
|
+
};
|
|
970
|
+
const genericExport = async (keyType, keyFormat, key) => {
|
|
971
|
+
if (!isCryptoKey(key)) throw new JWTError("Key must be of type CryptoKey", "ERR_INVALID_OPTION");
|
|
972
|
+
if (!key.extractable) throw new JWTError("CryptoKey is not extractable", "ERR_JWK_INVALID");
|
|
973
|
+
if (key.type !== keyType) throw new JWTError(`key is not a ${keyType} key`, "ERR_JWK_INVALID");
|
|
974
|
+
return formatPEM(base64Stringify(new Uint8Array(await crypto.subtle.exportKey(keyFormat, key))), `${keyType.toUpperCase()} KEY`);
|
|
975
|
+
};
|
|
976
|
+
const genericImport = async (descriptor, keyFormat, pem, alg, options) => {
|
|
977
|
+
let algorithm;
|
|
978
|
+
let keyUsages;
|
|
979
|
+
const body = pemBody(pem, descriptor);
|
|
980
|
+
let keyData;
|
|
981
|
+
try {
|
|
982
|
+
keyData = base64Parse(body, { returnAs: "uint8array" });
|
|
983
|
+
} catch (error) {
|
|
984
|
+
throw new JWTError("Invalid PEM: body could not be decoded.", "ERR_JWK_INVALID", error);
|
|
985
|
+
}
|
|
986
|
+
const isPublic = keyFormat === "spki";
|
|
987
|
+
switch (alg) {
|
|
988
|
+
case "PS256":
|
|
989
|
+
case "PS384":
|
|
990
|
+
case "PS512":
|
|
991
|
+
algorithm = {
|
|
992
|
+
name: "RSA-PSS",
|
|
993
|
+
hash: jwsHashOf(alg)
|
|
994
|
+
};
|
|
995
|
+
keyUsages = isPublic ? ["verify"] : ["sign"];
|
|
996
|
+
break;
|
|
997
|
+
case "RS256":
|
|
998
|
+
case "RS384":
|
|
999
|
+
case "RS512":
|
|
1000
|
+
algorithm = {
|
|
1001
|
+
name: "RSASSA-PKCS1-v1_5",
|
|
1002
|
+
hash: jwsHashOf(alg)
|
|
1003
|
+
};
|
|
1004
|
+
keyUsages = isPublic ? ["verify"] : ["sign"];
|
|
1005
|
+
break;
|
|
1006
|
+
case "RSA-OAEP":
|
|
1007
|
+
case "RSA-OAEP-256":
|
|
1008
|
+
case "RSA-OAEP-384":
|
|
1009
|
+
case "RSA-OAEP-512":
|
|
1010
|
+
algorithm = {
|
|
1011
|
+
name: "RSA-OAEP",
|
|
1012
|
+
hash: rsaOaepHashOf(alg)
|
|
1013
|
+
};
|
|
1014
|
+
keyUsages = isPublic ? ["encrypt", "wrapKey"] : ["decrypt", "unwrapKey"];
|
|
1015
|
+
break;
|
|
1016
|
+
case "ES256":
|
|
1017
|
+
case "ES384":
|
|
1018
|
+
case "ES512":
|
|
1019
|
+
algorithm = {
|
|
1020
|
+
name: "ECDSA",
|
|
1021
|
+
namedCurve: ECDSA_CURVES[alg]
|
|
1022
|
+
};
|
|
1023
|
+
keyUsages = isPublic ? ["verify"] : ["sign"];
|
|
1024
|
+
break;
|
|
1025
|
+
case "ECDH-ES":
|
|
1026
|
+
case "ECDH-ES+A128KW":
|
|
1027
|
+
case "ECDH-ES+A192KW":
|
|
1028
|
+
case "ECDH-ES+A256KW": {
|
|
1029
|
+
const namedCurve = getNamedCurve(keyData);
|
|
1030
|
+
if (namedCurve) algorithm = {
|
|
1031
|
+
name: "ECDH",
|
|
1032
|
+
namedCurve
|
|
1033
|
+
};
|
|
1034
|
+
else algorithm = { name: requireOKPCurve(keyData, "X25519") };
|
|
1035
|
+
keyUsages = isPublic ? [] : ["deriveBits"];
|
|
1036
|
+
break;
|
|
1037
|
+
}
|
|
1038
|
+
case "Ed25519":
|
|
1039
|
+
case "EdDSA":
|
|
1040
|
+
algorithm = { name: requireOKPCurve(keyData, "Ed25519") };
|
|
1041
|
+
keyUsages = isPublic ? ["verify"] : ["sign"];
|
|
1042
|
+
break;
|
|
1043
|
+
default: throw new JWTError("Invalid or unsupported \"alg\" value", "ERR_JWK_UNSUPPORTED");
|
|
1044
|
+
}
|
|
1045
|
+
try {
|
|
1046
|
+
return await crypto.subtle.importKey(keyFormat, keyData, algorithm, options?.extractable ?? isPublic, keyUsages);
|
|
1047
|
+
} catch (error) {
|
|
1048
|
+
throw new JWTError(`Invalid PEM: the body could not be imported as "${alg}".`, "ERR_JWK_INVALID", error);
|
|
1049
|
+
}
|
|
1050
|
+
};
|
|
1051
|
+
const findOid = (keyData, oid) => {
|
|
1052
|
+
const needle = [
|
|
1053
|
+
6,
|
|
1054
|
+
oid.length,
|
|
1055
|
+
...oid
|
|
1056
|
+
];
|
|
1057
|
+
const last = keyData.length - needle.length;
|
|
1058
|
+
for (let i = keyData.indexOf(needle[0]); i !== -1 && i <= last; i = keyData.indexOf(needle[0], i + 1)) if (needle.every((byte, index) => keyData[i + index] === byte)) return true;
|
|
1059
|
+
return false;
|
|
1060
|
+
};
|
|
1061
|
+
const OKP_CURVE_OIDS = {
|
|
1062
|
+
X25519: [
|
|
1063
|
+
43,
|
|
1064
|
+
101,
|
|
1065
|
+
110
|
|
1066
|
+
],
|
|
1067
|
+
Ed25519: [
|
|
1068
|
+
43,
|
|
1069
|
+
101,
|
|
1070
|
+
112
|
|
1071
|
+
]
|
|
1072
|
+
};
|
|
1073
|
+
const requireOKPCurve = (keyData, crv) => {
|
|
1074
|
+
if (findOid(keyData, OKP_CURVE_OIDS[crv])) return crv;
|
|
1075
|
+
throw new JWTError(`Invalid PEM: unsupported curve, expected the ${crv} OID.`, "ERR_JWK_INVALID");
|
|
1076
|
+
};
|
|
1077
|
+
const getNamedCurve = (keyData) => {
|
|
1078
|
+
switch (true) {
|
|
1079
|
+
case findOid(keyData, [
|
|
1080
|
+
42,
|
|
1081
|
+
134,
|
|
1082
|
+
72,
|
|
1083
|
+
206,
|
|
1084
|
+
61,
|
|
1085
|
+
3,
|
|
1086
|
+
1,
|
|
1087
|
+
7
|
|
1088
|
+
]): return "P-256";
|
|
1089
|
+
case findOid(keyData, [
|
|
1090
|
+
43,
|
|
1091
|
+
129,
|
|
1092
|
+
4,
|
|
1093
|
+
0,
|
|
1094
|
+
34
|
|
1095
|
+
]): return "P-384";
|
|
1096
|
+
case findOid(keyData, [
|
|
1097
|
+
43,
|
|
1098
|
+
129,
|
|
1099
|
+
4,
|
|
1100
|
+
0,
|
|
1101
|
+
35
|
|
1102
|
+
]): return "P-521";
|
|
1103
|
+
default: return;
|
|
1104
|
+
}
|
|
1105
|
+
};
|
|
1106
|
+
const getElement = (seq) => {
|
|
1107
|
+
const result = [];
|
|
1108
|
+
let next = 0;
|
|
1109
|
+
while (next < seq.length) {
|
|
1110
|
+
const nextPart = parseElement(seq.subarray(next));
|
|
1111
|
+
if (nextPart.byteLength <= 0) throw new JWTError("Invalid DER: zero-length element", "ERR_JWK_INVALID");
|
|
1112
|
+
result.push(nextPart);
|
|
1113
|
+
next += nextPart.byteLength;
|
|
1114
|
+
}
|
|
1115
|
+
return result;
|
|
1116
|
+
};
|
|
1117
|
+
const parseElement = (bytes) => {
|
|
1118
|
+
let position = 0;
|
|
1119
|
+
const at = (index) => {
|
|
1120
|
+
const byte = bytes[index];
|
|
1121
|
+
if (byte === void 0) throw new JWTError("Invalid DER: element is truncated", "ERR_JWK_INVALID");
|
|
1122
|
+
return byte;
|
|
1123
|
+
};
|
|
1124
|
+
let tag = at(0) & 31;
|
|
1125
|
+
position++;
|
|
1126
|
+
if (tag === 31) {
|
|
1127
|
+
tag = 0;
|
|
1128
|
+
while (at(position) >= 128) {
|
|
1129
|
+
tag = tag * 128 + at(position) - 128;
|
|
1130
|
+
position++;
|
|
1131
|
+
}
|
|
1132
|
+
tag = tag * 128 + at(position) - 128;
|
|
1133
|
+
position++;
|
|
1134
|
+
}
|
|
1135
|
+
let length = 0;
|
|
1136
|
+
if (at(position) < 128) {
|
|
1137
|
+
length = at(position);
|
|
1138
|
+
position++;
|
|
1139
|
+
} else if (at(position) === 128) {
|
|
1140
|
+
length = 0;
|
|
1141
|
+
while (position + length + 1 < bytes.byteLength) {
|
|
1142
|
+
if (at(position + length) === 0 && at(position + length + 1) === 0) break;
|
|
1143
|
+
length++;
|
|
1144
|
+
}
|
|
1145
|
+
if (position + length + 1 >= bytes.byteLength) throw new JWTError("Invalid DER: invalid indefinite form length", "ERR_JWK_INVALID");
|
|
1146
|
+
const byteLength = position + length + 2;
|
|
1147
|
+
return {
|
|
1148
|
+
byteLength,
|
|
1149
|
+
contents: bytes.subarray(position, position + length),
|
|
1150
|
+
raw: bytes.subarray(0, byteLength)
|
|
1151
|
+
};
|
|
1152
|
+
} else {
|
|
1153
|
+
const numberOfDigits = at(position) & 127;
|
|
1154
|
+
position++;
|
|
1155
|
+
length = 0;
|
|
1156
|
+
for (let i = 0; i < numberOfDigits; i++) {
|
|
1157
|
+
length = length * 256 + at(position);
|
|
1158
|
+
position++;
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
const byteLength = position + length;
|
|
1162
|
+
if (byteLength > bytes.byteLength) throw new JWTError("Invalid DER: element length exceeds the available bytes", "ERR_JWK_INVALID");
|
|
1163
|
+
return {
|
|
1164
|
+
byteLength,
|
|
1165
|
+
contents: bytes.subarray(position, byteLength),
|
|
1166
|
+
raw: bytes.subarray(0, byteLength)
|
|
1167
|
+
};
|
|
1168
|
+
};
|
|
1169
|
+
const spkiFromX509 = (buf) => {
|
|
1170
|
+
const tbs = getElement(parseElement(buf).contents)[0];
|
|
1171
|
+
if (!tbs) throw new JWTError("Invalid X.509 certificate: missing tbsCertificate", "ERR_JWK_INVALID");
|
|
1172
|
+
const tbsCertificate = getElement(tbs.contents);
|
|
1173
|
+
const first = tbsCertificate[0];
|
|
1174
|
+
if (!first) throw new JWTError("Invalid X.509 certificate: empty tbsCertificate", "ERR_JWK_INVALID");
|
|
1175
|
+
const spki = tbsCertificate[first.raw[0] === 160 ? 6 : 5];
|
|
1176
|
+
if (!spki) throw new JWTError("Invalid X.509 certificate: missing subjectPublicKeyInfo", "ERR_JWK_INVALID");
|
|
1177
|
+
return base64Stringify(spki.raw);
|
|
1178
|
+
};
|
|
1179
|
+
let createPublicKey;
|
|
1180
|
+
const getSPKI = (x509) => {
|
|
1181
|
+
try {
|
|
1182
|
+
createPublicKey ??= globalThis.process?.getBuiltinModule?.("node:crypto")?.createPublicKey;
|
|
1183
|
+
} catch {
|
|
1184
|
+
createPublicKey = 0;
|
|
1185
|
+
}
|
|
1186
|
+
const body = pemBody(x509, "CERTIFICATE");
|
|
1187
|
+
if (createPublicKey) try {
|
|
1188
|
+
return new createPublicKey(x509).export({
|
|
1189
|
+
format: "pem",
|
|
1190
|
+
type: "spki"
|
|
1191
|
+
});
|
|
1192
|
+
} catch {}
|
|
1193
|
+
let raw;
|
|
1194
|
+
try {
|
|
1195
|
+
raw = base64Parse(body, { returnAs: "uint8array" });
|
|
1196
|
+
} catch (error) {
|
|
1197
|
+
throw new JWTError("Invalid X.509 certificate: body could not be decoded.", "ERR_JWK_INVALID", error);
|
|
1198
|
+
}
|
|
1199
|
+
return formatPEM(spkiFromX509(raw), "PUBLIC KEY");
|
|
1200
|
+
};
|
|
1201
|
+
const THUMBPRINT_MEMBERS = /* @__PURE__ */ Object.freeze({
|
|
1202
|
+
EC: [
|
|
1203
|
+
"crv",
|
|
1204
|
+
"kty",
|
|
1205
|
+
"x",
|
|
1206
|
+
"y"
|
|
1207
|
+
],
|
|
1208
|
+
RSA: [
|
|
1209
|
+
"e",
|
|
1210
|
+
"kty",
|
|
1211
|
+
"n"
|
|
1212
|
+
],
|
|
1213
|
+
OKP: [
|
|
1214
|
+
"crv",
|
|
1215
|
+
"kty",
|
|
1216
|
+
"x"
|
|
1217
|
+
],
|
|
1218
|
+
oct: ["k", "kty"]
|
|
1219
|
+
});
|
|
1220
|
+
const THUMBPRINT_HASHES = /* @__PURE__ */ new Set([
|
|
1221
|
+
"SHA-256",
|
|
1222
|
+
"SHA-384",
|
|
1223
|
+
"SHA-512"
|
|
1224
|
+
]);
|
|
1225
|
+
async function thumbprint(jwk, options = {}) {
|
|
1226
|
+
assertPlainObject(jwk, "jwk");
|
|
1227
|
+
assertPlainObject(options, "options");
|
|
1228
|
+
const hash = options.hash ?? "SHA-256";
|
|
1229
|
+
if (!THUMBPRINT_HASHES.has(hash)) throw new JWTError("\"hash\" must be \"SHA-256\", \"SHA-384\" or \"SHA-512\".", "ERR_INVALID_OPTION");
|
|
1230
|
+
const kty = jwk.kty;
|
|
1231
|
+
const members = typeof kty === "string" && Object.hasOwn(THUMBPRINT_MEMBERS, kty) ? THUMBPRINT_MEMBERS[kty] : void 0;
|
|
1232
|
+
if (!members) throw new JWTError(`JWK thumbprint is not defined for kty "${String(kty)}".`, "ERR_JWK_INVALID");
|
|
1233
|
+
const canonical = members.map((name) => {
|
|
1234
|
+
const value = Reflect.get(jwk, name);
|
|
1235
|
+
if (typeof value !== "string" || value.length === 0) throw new JWTError(`JWK thumbprint requires the member "${name}" on an ${jwk.kty} key.`, "ERR_JWK_INVALID");
|
|
1236
|
+
return `${JSON.stringify(name)}:${JSON.stringify(value)}`;
|
|
1237
|
+
});
|
|
1238
|
+
return digestUrl(`{${canonical.join(",")}}`, hash);
|
|
1239
|
+
}
|
|
1240
|
+
const MAX_PARTY_INFO_BYTES = 1024;
|
|
1241
|
+
const DEFAULT_JOSE_LIMITS = /* @__PURE__ */ Object.freeze({
|
|
1242
|
+
signatures: 16,
|
|
1243
|
+
recipients: 16,
|
|
1244
|
+
keyCandidates: 8,
|
|
1245
|
+
pbes2Iterations: 4 * DEFAULT_PBES2_ITERATIONS
|
|
1246
|
+
});
|
|
1247
|
+
function resolveLimit(limits, name) {
|
|
1248
|
+
const value = limits?.[name];
|
|
1249
|
+
if (value === void 0) return DEFAULT_JOSE_LIMITS[name];
|
|
1250
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 1) throw new JWTError(`"limits.${name}" must be a positive integer.`, "ERR_INVALID_OPTION");
|
|
1251
|
+
return value;
|
|
1252
|
+
}
|
|
1253
|
+
function assertEntryLimit(count, limit, options) {
|
|
1254
|
+
if (count <= limit) return;
|
|
1255
|
+
throw new JWTError(`Invalid ${options.family}: ${count} ${options.what} exceed the limit of ${limit}.`, `ERR_${options.family}_INVALID`);
|
|
1256
|
+
}
|
|
1257
|
+
function assertCandidateLimit(count, limit) {
|
|
1258
|
+
if (count <= limit) return;
|
|
1259
|
+
throw new JWTError(`${count} candidate keys exceed the limit of ${limit}, narrow them with a "kid".`, "ERR_JWK_KEY_NOT_FOUND");
|
|
1260
|
+
}
|
|
1261
|
+
function assertJWKSetCandidates(candidates, options) {
|
|
1262
|
+
if (candidates.length === 0) {
|
|
1263
|
+
const { kid } = options;
|
|
1264
|
+
throw new JWTError(`No key found in JWK Set${kid ? ` with kid ${quoteTokenValue(kid)}` : ""}.`, "ERR_JWK_KEY_NOT_FOUND");
|
|
1265
|
+
}
|
|
1266
|
+
assertCandidateLimit(candidates.length, options.limit);
|
|
1267
|
+
}
|
|
1268
|
+
function asKeyLookupError(error, what) {
|
|
1269
|
+
return asJWTError(error, {
|
|
1270
|
+
code: "ERR_JWK_KEY_NOT_FOUND",
|
|
1271
|
+
message: `Key lookup failed for ${what}.`
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1274
|
+
function asJWTError(error, options) {
|
|
1275
|
+
if (error instanceof JWTError) return error;
|
|
1276
|
+
return new JWTError(options.message, options.code, error);
|
|
1277
|
+
}
|
|
1278
|
+
function copyJsonObject(value, options) {
|
|
1279
|
+
try {
|
|
1280
|
+
return sanitizeObjectCopy(value);
|
|
1281
|
+
} catch (error) {
|
|
1282
|
+
throw asJWTError(error, options);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
function asDecryptionError(error) {
|
|
1286
|
+
return asJWTError(error, {
|
|
1287
|
+
code: "ERR_JWE_DECRYPTION_FAILED",
|
|
1288
|
+
message: "JWE decryption failed."
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
function assertProducerPartyInfo(name, value) {
|
|
1292
|
+
if (!(value instanceof Uint8Array)) throw new JWTError(`"${name === "apu" ? "partyUInfo" : "partyVInfo"}" (${name}) must be a Uint8Array.`, "ERR_INVALID_OPTION");
|
|
1293
|
+
if (value.length > 1024) throw new JWTError(`"${name === "apu" ? "partyUInfo" : "partyVInfo"}" (${name}) must not exceed ${MAX_PARTY_INFO_BYTES} bytes (got ${value.length}).`, "ERR_INVALID_OPTION");
|
|
1294
|
+
}
|
|
1295
|
+
const JWK_SET_KEY_OPS = /* @__PURE__ */ Object.freeze({
|
|
1296
|
+
sig: ["verify"],
|
|
1297
|
+
enc: [
|
|
1298
|
+
"unwrapKey",
|
|
1299
|
+
"decrypt",
|
|
1300
|
+
"deriveBits",
|
|
1301
|
+
"deriveKey"
|
|
1302
|
+
]
|
|
1303
|
+
});
|
|
1304
|
+
function buildJWKSetFilter(header, options) {
|
|
1305
|
+
const kid = header?.kid;
|
|
1306
|
+
const alg = header?.alg;
|
|
1307
|
+
const { op, allowSymmetric } = options;
|
|
1308
|
+
const use = op;
|
|
1309
|
+
const keyOps = JWK_SET_KEY_OPS[op];
|
|
1310
|
+
return (k) => {
|
|
1311
|
+
if (!isJWK(k)) return false;
|
|
1312
|
+
if (!allowSymmetric && hasSymmetricSecret(k)) return false;
|
|
1313
|
+
if (kid && k.kid !== kid) return false;
|
|
1314
|
+
if (k.alg && !(alg && jwkAlgServes({
|
|
1315
|
+
kty: k.kty,
|
|
1316
|
+
alg: k.alg
|
|
1317
|
+
}, alg))) return false;
|
|
1318
|
+
if (k.use !== void 0 && k.use !== use) return false;
|
|
1319
|
+
const ops = k.key_ops;
|
|
1320
|
+
if (ops !== void 0 && !(Array.isArray(ops) && ops.some((o) => keyOps.includes(o)))) return false;
|
|
1321
|
+
return true;
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
function parseEphemeralKey(ephemeralKey) {
|
|
1325
|
+
if (isCryptoKeyPair(ephemeralKey)) return {
|
|
1326
|
+
epk: ephemeralKey.publicKey,
|
|
1327
|
+
epkPrivateKey: ephemeralKey.privateKey
|
|
1328
|
+
};
|
|
1329
|
+
if (typeof ephemeralKey === "object" && ephemeralKey !== null && "publicKey" in ephemeralKey && "privateKey" in ephemeralKey) {
|
|
1330
|
+
if (!ephemeralKey.publicKey || !ephemeralKey.privateKey) throw new JWTError("ECDH-ES custom ephemeral key must include both publicKey and privateKey.", "ERR_JWK_INVALID");
|
|
1331
|
+
return {
|
|
1332
|
+
epk: ephemeralKey.publicKey,
|
|
1333
|
+
epkPrivateKey: ephemeralKey.privateKey
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
if (isCryptoKey(ephemeralKey)) {
|
|
1337
|
+
if (ephemeralKey.type !== "private") throw new JWTError("ECDH-ES custom ephemeral CryptoKey must include private key material.", "ERR_JWK_INVALID");
|
|
1338
|
+
return {
|
|
1339
|
+
epk: ephemeralKey,
|
|
1340
|
+
epkPrivateKey: ephemeralKey
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
if (isJWK(ephemeralKey)) {
|
|
1344
|
+
if (!("d" in ephemeralKey) || typeof ephemeralKey.d !== "string") throw new JWTError("ECDH-ES custom ephemeral JWK must include private parameter \"d\".", "ERR_JWK_INVALID");
|
|
1345
|
+
return {
|
|
1346
|
+
epk: ephemeralKey,
|
|
1347
|
+
epkPrivateKey: ephemeralKey
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
throw new JWTError("Unsupported ECDH-ES ephemeral key material provided.", "ERR_JWK_INVALID");
|
|
1351
|
+
}
|
|
1352
|
+
function buildECDHAgreementParams(ecdh) {
|
|
1353
|
+
const params = {};
|
|
1354
|
+
if (!ecdh) return params;
|
|
1355
|
+
const { partyUInfo, partyVInfo, ephemeralKey } = ecdh;
|
|
1356
|
+
if (partyUInfo !== void 0) {
|
|
1357
|
+
assertProducerPartyInfo("apu", partyUInfo);
|
|
1358
|
+
if (partyUInfo.length > 0) params.apu = partyUInfo;
|
|
1359
|
+
}
|
|
1360
|
+
if (partyVInfo !== void 0) {
|
|
1361
|
+
assertProducerPartyInfo("apv", partyVInfo);
|
|
1362
|
+
if (partyVInfo.length > 0) params.apv = partyVInfo;
|
|
1363
|
+
}
|
|
1364
|
+
if (ephemeralKey) {
|
|
1365
|
+
const { epk, epkPrivateKey } = parseEphemeralKey(ephemeralKey);
|
|
1366
|
+
params.epk = epk;
|
|
1367
|
+
params.epkPrivateKey = epkPrivateKey;
|
|
1368
|
+
}
|
|
1369
|
+
return params;
|
|
1370
|
+
}
|
|
1371
|
+
function stringKeyError() {
|
|
1372
|
+
return new JWTError("Key must be a JWK, CryptoKey or Uint8Array, not a string.", "ERR_JWK_INVALID");
|
|
1373
|
+
}
|
|
1374
|
+
function resolvePasswordKey(alg, key) {
|
|
1375
|
+
if (typeof key !== "string") return key;
|
|
1376
|
+
if (!isPBES2Algorithm(alg)) throw stringKeyError();
|
|
1377
|
+
return textEncoder.encode(key);
|
|
1378
|
+
}
|
|
1379
|
+
async function resolveKeyInput(key, options) {
|
|
1380
|
+
if (typeof key !== "function") return key;
|
|
1381
|
+
const { header, token, what } = options;
|
|
1382
|
+
let resolved;
|
|
1383
|
+
try {
|
|
1384
|
+
resolved = await key(header, token);
|
|
1385
|
+
} catch (error) {
|
|
1386
|
+
throw asKeyLookupError(error, what);
|
|
1387
|
+
}
|
|
1388
|
+
if (resolved === void 0 || resolved === null) throw new JWTError(`Key lookup returned no key for ${what}.`, "ERR_JWK_KEY_NOT_FOUND");
|
|
1389
|
+
return resolved;
|
|
1390
|
+
}
|
|
1391
|
+
function assertDirectKeyRole(key, enc) {
|
|
1392
|
+
if (!isJWK(key) || typeof key.alg !== "string" || key.alg === "dir") return;
|
|
1393
|
+
if (enc !== void 0 && key.alg === enc) return;
|
|
1394
|
+
throw new JWTError(`JWK "alg" is "${key.alg}" but "dir" requires "dir" or the token's "enc".`, "ERR_JWK_INVALID");
|
|
1395
|
+
}
|
|
1396
|
+
var WeakMapJWKCache = class {
|
|
1397
|
+
_map = /* @__PURE__ */ new WeakMap();
|
|
1398
|
+
get(jwk, alg) {
|
|
1399
|
+
return this._map.get(jwk)?.[alg];
|
|
1400
|
+
}
|
|
1401
|
+
set(jwk, alg, key) {
|
|
1402
|
+
let entry = this._map.get(jwk);
|
|
1403
|
+
if (!entry) this._map.set(jwk, { [alg]: key });
|
|
1404
|
+
else entry[alg] = key;
|
|
1405
|
+
}
|
|
1406
|
+
};
|
|
1407
|
+
let _activeCache = new WeakMapJWKCache();
|
|
1408
|
+
function configureJWKCache(cache) {
|
|
1409
|
+
_activeCache = cache;
|
|
1410
|
+
}
|
|
1411
|
+
function clearJWKCache() {
|
|
1412
|
+
_activeCache = new WeakMapJWKCache();
|
|
1413
|
+
}
|
|
1414
|
+
async function importKey(key, algOrOptions) {
|
|
1415
|
+
const alg = typeof algOrOptions === "string" ? algOrOptions : algOrOptions?.alg ?? void 0;
|
|
1416
|
+
const expect = typeof algOrOptions === "object" ? algOrOptions.expect ?? void 0 : void 0;
|
|
1417
|
+
const extractable = typeof algOrOptions === "object" && !algOrOptions.asCryptoKey ? algOrOptions.extractable : void 0;
|
|
1418
|
+
if (typeof key === "string") throw stringKeyError();
|
|
1419
|
+
if (key instanceof Uint8Array) return key;
|
|
1420
|
+
if (isCryptoKey(key)) {
|
|
1421
|
+
assertIntentOnCryptoKey(key, expect);
|
|
1422
|
+
return key;
|
|
1423
|
+
}
|
|
1424
|
+
if (isJWK(key)) {
|
|
1425
|
+
if (key.kty !== "oct" && Object.hasOwn(key, "k")) throw new JWTError(`JWK "k" is defined only for "oct" keys, not "${key.kty}".`, "ERR_JWK_INVALID");
|
|
1426
|
+
assertJWKAlgAuthority(key, alg);
|
|
1427
|
+
if (key.kty === "oct" && typeof key.k === "string") {
|
|
1428
|
+
const rawBytes = decodeSegment(key.k, {
|
|
1429
|
+
family: "JWK",
|
|
1430
|
+
what: "\"k\""
|
|
1431
|
+
});
|
|
1432
|
+
assertOctKeyLength(rawBytes, alg ?? key.alg);
|
|
1433
|
+
if (typeof algOrOptions === "object" && algOrOptions.asCryptoKey) {
|
|
1434
|
+
const { algorithm, usage, extractable: rawExtractable = false } = algOrOptions;
|
|
1435
|
+
if (!algorithm || !usage) throw new JWTError("asCryptoKey option requires `algorithm` and `usage` to be provided.", "ERR_JWK_INVALID");
|
|
1436
|
+
if (typeof algorithm === "object" && algorithm.name === "HMAC") {
|
|
1437
|
+
const hash = "hash" in algorithm ? algorithm.hash : void 0;
|
|
1438
|
+
const hashName = typeof hash === "string" ? hash : typeof hash === "object" && hash !== null && "name" in hash && typeof hash.name === "string" ? hash.name : void 0;
|
|
1439
|
+
const minBytes = Number.parseInt(hashName?.slice(-3) ?? "", 10) / 8;
|
|
1440
|
+
if (minBytes && rawBytes.length < minBytes) throw new JWTError(`HMAC with ${hashName} requires a key of at least ${minBytes} bytes (got ${rawBytes.length})`, "ERR_JWK_INVALID");
|
|
1441
|
+
}
|
|
1442
|
+
return crypto.subtle.importKey("raw", rawBytes, algorithm, rawExtractable, usage);
|
|
1443
|
+
}
|
|
1444
|
+
return rawBytes;
|
|
1445
|
+
} else {
|
|
1446
|
+
const effectiveAlg = key.alg || alg;
|
|
1447
|
+
if (!effectiveAlg) throw new JWTError("Algorithm must be provided when importing non-oct JWK", "ERR_JWK_INVALID");
|
|
1448
|
+
assertIntentOnJWK(key, expect);
|
|
1449
|
+
const cache = extractable === void 0 ? _activeCache : false;
|
|
1450
|
+
if (cache) {
|
|
1451
|
+
const cached = await cache.get(key, effectiveAlg);
|
|
1452
|
+
if (cached) return cached;
|
|
1453
|
+
}
|
|
1454
|
+
const cryptoKey = await jwkTokey(key, {
|
|
1455
|
+
extractable,
|
|
1456
|
+
alg
|
|
1457
|
+
});
|
|
1458
|
+
if (cache) await cache.set(key, effectiveAlg, cryptoKey);
|
|
1459
|
+
return cryptoKey;
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
throw new JWTError("Invalid key type provided to importKey", "ERR_JWK_INVALID");
|
|
1463
|
+
}
|
|
1464
|
+
function assertJWKAlgAuthority(jwk, alg) {
|
|
1465
|
+
if (!alg || !jwk.alg || jwkAlgServes({
|
|
1466
|
+
kty: jwk.kty,
|
|
1467
|
+
alg: jwk.alg
|
|
1468
|
+
}, alg)) return;
|
|
1469
|
+
throw new JWTError(`JWK "alg" is "${jwk.alg}" but the operation requires "${alg}".`, "ERR_JWK_INVALID");
|
|
1470
|
+
}
|
|
1471
|
+
function assertIntentOnJWK(jwk, expect) {
|
|
1472
|
+
if (expect === void 0 || jwk.kty === "oct") return;
|
|
1473
|
+
const hasPrivateScalar = "d" in jwk && typeof jwk.d === "string";
|
|
1474
|
+
if (expect === "public" && hasPrivateScalar) throw new JWTError("Private JWK passed where a public key is expected.", "ERR_JWK_INVALID");
|
|
1475
|
+
if (expect === "private" && !hasPrivateScalar) throw new JWTError("Public JWK passed where a private key is expected.", "ERR_JWK_INVALID");
|
|
1476
|
+
}
|
|
1477
|
+
function assertIntentOnCryptoKey(key, expect) {
|
|
1478
|
+
if (expect === void 0 || key.type === "secret") return;
|
|
1479
|
+
if (expect === "public" && key.type === "private") throw new JWTError("Private CryptoKey passed where a public key is expected.", "ERR_JWK_INVALID");
|
|
1480
|
+
if (expect === "private" && key.type === "public") throw new JWTError("Public CryptoKey passed where a private key is expected.", "ERR_JWK_INVALID");
|
|
1481
|
+
}
|
|
1482
|
+
function getJWKsFromSet(jwkSet, filter) {
|
|
1483
|
+
if (!jwkSet || !isJWKSet(jwkSet)) throw new JWTError("Invalid JWK Set provided", "ERR_JWK_INVALID");
|
|
1484
|
+
const keys = jwkSet.keys.filter((key) => isJWK(key));
|
|
1485
|
+
return filter ? keys.filter(filter) : keys;
|
|
1486
|
+
}
|
|
1487
|
+
const JWS_ALG_CTX = /* @__PURE__ */ Object.freeze({
|
|
1488
|
+
infer: inferJWSAllowedAlgorithms,
|
|
1489
|
+
octRoles: false,
|
|
1490
|
+
errorCode: "ERR_JWS_ALG_NOT_ALLOWED",
|
|
1491
|
+
label: "Algorithm",
|
|
1492
|
+
op: "sig"
|
|
1493
|
+
});
|
|
1494
|
+
function checkAlgAllowed(alg, key, options) {
|
|
1495
|
+
const { ctx, algorithms, allowSymmetric } = options;
|
|
1496
|
+
if (algorithms !== void 0) return algorithms.includes(alg) ? void 0 : notAllowed(alg, ctx);
|
|
1497
|
+
if (isJWK(key) && typeof key.alg === "string" && (!ctx.octRoles || key.kty !== "oct")) return isSameAlgorithm(key.alg, alg) ? void 0 : notAllowed(alg, ctx);
|
|
1498
|
+
const inferred = ctx.infer(key, { allowSymmetric });
|
|
1499
|
+
if (!inferred) return new JWTError("Cannot infer allowed algorithms from this key, pass \"algorithms\".", ctx.errorCode);
|
|
1500
|
+
return inferred.includes(alg) ? void 0 : notAllowed(alg, ctx);
|
|
1501
|
+
}
|
|
1502
|
+
function notAllowed(alg, ctx) {
|
|
1503
|
+
return new JWTError(`${ctx.label} not allowed: ${alg}`, ctx.errorCode);
|
|
1504
|
+
}
|
|
1505
|
+
function keyMatchesHeader(header, keyMaterial, options) {
|
|
1506
|
+
const { ctx, allowSymmetric } = options;
|
|
1507
|
+
if (isJWKSet(keyMaterial)) {
|
|
1508
|
+
const candidates = keyMaterial.keys.filter(buildJWKSetFilter(header, {
|
|
1509
|
+
op: ctx.op,
|
|
1510
|
+
allowSymmetric
|
|
1511
|
+
}));
|
|
1512
|
+
return candidates.length === 0 || candidates.some((k) => keyServesHeaderAlg(header, k, ctx));
|
|
1513
|
+
}
|
|
1514
|
+
if (isJWK(keyMaterial)) return keyServesHeaderAlg(header, keyMaterial, ctx);
|
|
1515
|
+
if (isCryptoKey(keyMaterial)) return keyServesHeaderAlg(header, keyMaterial, ctx);
|
|
1516
|
+
return true;
|
|
1517
|
+
}
|
|
1518
|
+
function checkJWEEncSupported(enc) {
|
|
1519
|
+
if (typeof enc === "string" && isContentEncryptionAlgorithm(enc)) return void 0;
|
|
1520
|
+
return new JWTError(`Invalid JWE: unsupported "enc" ${quoteTokenValue(enc)}`, "ERR_JWE_INVALID");
|
|
1521
|
+
}
|
|
1522
|
+
function assertJWEEncSupported(enc) {
|
|
1523
|
+
const error = checkJWEEncSupported(enc);
|
|
1524
|
+
if (error) throw error;
|
|
1525
|
+
}
|
|
1526
|
+
function checkAlgSupported(alg, options) {
|
|
1527
|
+
const { supported, code, label, role } = options;
|
|
1528
|
+
if (alg === "none") return new JWTError(`"none" is not a valid ${role}`, code);
|
|
1529
|
+
if (!supported(alg)) return new JWTError(`${label} not supported: ${quoteTokenValue(alg)}`, code);
|
|
1530
|
+
}
|
|
1531
|
+
function keyServesHeaderAlg(header, key, ctx) {
|
|
1532
|
+
const { alg } = header;
|
|
1533
|
+
if (!alg) return false;
|
|
1534
|
+
const inferred = ctx.infer(key);
|
|
1535
|
+
if (inferred && !inferred.includes(alg)) return false;
|
|
1536
|
+
if (!isECDHESAlgorithm(alg)) return true;
|
|
1537
|
+
const epkCurve = header.epk?.crv;
|
|
1538
|
+
const keyCurve = isCryptoKey(key) ? cryptoKeyCurve(key) : "crv" in key ? key.crv : void 0;
|
|
1539
|
+
return !epkCurve || !keyCurve || epkCurve === keyCurve;
|
|
1540
|
+
}
|
|
1541
|
+
export { DEFAULT_JOSE_LIMITS, DEFAULT_PBES2_ITERATIONS, JWS_ALG_CTX, MAX_PARTY_INFO_BYTES, WeakMapJWKCache, aesKwUnwrap, aesKwWrap, asDecryptionError, asJWTError, asKeyLookupError, assertCompactToken, assertConsumerOptions, assertDirectKeyRole, assertECDHKeyAllowed, assertEntryLimit, assertJWEEncSupported, assertJWKSetCandidates, assertOctKeyLength, assertOptionEntry, assertPBES2Count, assertPBES2Iterations, assertPBES2Params, assertPBES2Salt, assertProducerConfirmation, assertProducerCrit, assertProducerKeyHeaders, assertProtectedOnlyOption, assertSerializationObject, bitLengthCEK, bitLengthIV, bitLengthTag, buildECDHAgreementParams, buildJWKSetFilter, checkAlgAllowed, checkAlgSupported, checkJWEEncSupported, checkProtectedTyp, checkSerializationEntry, clearJWKCache, configureJWKCache, copyJsonObject, decodeProtectedHeader, decodeSegment, decrypt, decryptRSAES, deriveECDHESKey, deriveKey, ecdhDerivedBits, ecdhesWrap, encrypt, encryptKey, encryptRSAES, fromPKCS8, fromSPKI, fromX509, gcmkwDecrypt, gcmkwEncrypt, generateCEK, generateIV, getJWKsFromSet, importKey, jwkTokey, keyMatchesHeader, keyToJWK, normalizeEpk, pbes2Unwrap, pbes2Wrap, resolveKeyInput, resolveLimit, resolvePasswordKey, serializeProtectedHeader, sign, stringKeyError, thumbprint, toPKCS8, toSPKI, verify };
|