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
package/dist/_chunks/jwk.mjs
CHANGED
|
@@ -1,2115 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { JWTError
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
const HASHED_SIGNATURE_ALGORITHMS = /* @__PURE__ */ Object.freeze({
|
|
10
|
-
HS256: "HMAC",
|
|
11
|
-
HS384: "HMAC",
|
|
12
|
-
HS512: "HMAC",
|
|
13
|
-
RS256: "RSASSA-PKCS1-v1_5",
|
|
14
|
-
RS384: "RSASSA-PKCS1-v1_5",
|
|
15
|
-
RS512: "RSASSA-PKCS1-v1_5",
|
|
16
|
-
PS256: "RSA-PSS",
|
|
17
|
-
PS384: "RSA-PSS",
|
|
18
|
-
PS512: "RSA-PSS"
|
|
19
|
-
});
|
|
20
|
-
async function sign(alg, key, data) {
|
|
21
|
-
checkSigCryptoKey(key, alg, "sign");
|
|
22
|
-
const signature = await crypto.subtle.sign(subtleAlgorithm(alg, key.algorithm), key, data);
|
|
23
|
-
return new Uint8Array(signature);
|
|
24
|
-
}
|
|
25
|
-
async function verify(alg, key, signature, data) {
|
|
26
|
-
checkSigCryptoKey(key, alg, "verify");
|
|
27
|
-
try {
|
|
28
|
-
return await crypto.subtle.verify(subtleAlgorithm(alg, key.algorithm), key, signature, data);
|
|
29
|
-
} catch {
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
function checkSigCryptoKey(key, alg, usage) {
|
|
34
|
-
switch (alg) {
|
|
35
|
-
case "HS256":
|
|
36
|
-
case "HS384":
|
|
37
|
-
case "HS512":
|
|
38
|
-
case "RS256":
|
|
39
|
-
case "RS384":
|
|
40
|
-
case "RS512":
|
|
41
|
-
case "PS256":
|
|
42
|
-
case "PS384":
|
|
43
|
-
case "PS512": {
|
|
44
|
-
const name = HASHED_SIGNATURE_ALGORITHMS[alg];
|
|
45
|
-
if (!isAlgorithm(key.algorithm, name)) throw unusableKey(name);
|
|
46
|
-
const expected = shaBitsOf(jwsHashOf(alg));
|
|
47
|
-
if (hashBitsOf(key.algorithm.hash) !== expected) throw unusableKey(`SHA-${expected}`, "algorithm.hash");
|
|
48
|
-
break;
|
|
49
|
-
}
|
|
50
|
-
case "Ed25519":
|
|
51
|
-
case "EdDSA":
|
|
52
|
-
if (!isAlgorithm(key.algorithm, "Ed25519")) throw unusableKey("Ed25519");
|
|
53
|
-
break;
|
|
54
|
-
case "ES256":
|
|
55
|
-
case "ES384":
|
|
56
|
-
case "ES512": {
|
|
57
|
-
if (!isAlgorithm(key.algorithm, "ECDSA")) throw unusableKey("ECDSA");
|
|
58
|
-
const expected = ECDSA_CURVES[alg];
|
|
59
|
-
if (key.algorithm.namedCurve !== expected) throw unusableKey(expected, "algorithm.namedCurve");
|
|
60
|
-
break;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
checkKeyUsage(key, usage);
|
|
64
|
-
}
|
|
65
|
-
function subtleAlgorithm(alg, algorithm) {
|
|
66
|
-
switch (alg) {
|
|
67
|
-
case "HS256":
|
|
68
|
-
case "HS384":
|
|
69
|
-
case "HS512": return {
|
|
70
|
-
hash: jwsHashOf(alg),
|
|
71
|
-
name: "HMAC"
|
|
72
|
-
};
|
|
73
|
-
case "PS256":
|
|
74
|
-
case "PS384":
|
|
75
|
-
case "PS512": {
|
|
76
|
-
const hash = jwsHashOf(alg);
|
|
77
|
-
return {
|
|
78
|
-
hash,
|
|
79
|
-
name: "RSA-PSS",
|
|
80
|
-
saltLength: shaBitsOf(hash) >> 3
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
case "RS256":
|
|
84
|
-
case "RS384":
|
|
85
|
-
case "RS512": return {
|
|
86
|
-
hash: jwsHashOf(alg),
|
|
87
|
-
name: "RSASSA-PKCS1-v1_5"
|
|
88
|
-
};
|
|
89
|
-
case "ES256":
|
|
90
|
-
case "ES384":
|
|
91
|
-
case "ES512": return {
|
|
92
|
-
hash: jwsHashOf(alg),
|
|
93
|
-
name: "ECDSA",
|
|
94
|
-
namedCurve: namedCurveOf(algorithm)
|
|
95
|
-
};
|
|
96
|
-
case "Ed25519":
|
|
97
|
-
case "EdDSA": return { name: "Ed25519" };
|
|
98
|
-
default: return unsupportedSignatureAlgorithm(alg);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
function unsupportedSignatureAlgorithm(alg) {
|
|
102
|
-
throw new JWTError(`alg ${String(alg)} is not supported either by JOSE or your javascript runtime`, "ERR_JWS_ALG_NOT_ALLOWED");
|
|
103
|
-
}
|
|
104
|
-
const IV_BIT_LENGTHS = /* @__PURE__ */ Object.freeze({
|
|
105
|
-
"A128GCM": 96,
|
|
106
|
-
"A128GCMKW": 96,
|
|
107
|
-
"A192GCM": 96,
|
|
108
|
-
"A192GCMKW": 96,
|
|
109
|
-
"A256GCM": 96,
|
|
110
|
-
"A256GCMKW": 96,
|
|
111
|
-
"A128CBC-HS256": 128,
|
|
112
|
-
"A192CBC-HS384": 128,
|
|
113
|
-
"A256CBC-HS512": 128
|
|
114
|
-
});
|
|
115
|
-
const CEK_BIT_LENGTHS = /* @__PURE__ */ Object.freeze({
|
|
116
|
-
"A128GCM": 128,
|
|
117
|
-
"A192GCM": 192,
|
|
118
|
-
"A256GCM": 256,
|
|
119
|
-
"A128CBC-HS256": 256,
|
|
120
|
-
"A192CBC-HS384": 384,
|
|
121
|
-
"A256CBC-HS512": 512
|
|
122
|
-
});
|
|
123
|
-
function bitLengthIV(alg) {
|
|
124
|
-
return IV_BIT_LENGTHS[alg];
|
|
125
|
-
}
|
|
126
|
-
function generateIV(alg) {
|
|
127
|
-
return crypto.getRandomValues(new Uint8Array(bitLengthIV(alg) >> 3));
|
|
128
|
-
}
|
|
129
|
-
function checkIvLength(enc, iv) {
|
|
130
|
-
if (iv.length << 3 !== bitLengthIV(enc)) throw new JWTError("Invalid Initialization Vector length", "ERR_JWE_INVALID");
|
|
131
|
-
}
|
|
132
|
-
const TAG_BIT_LENGTHS = /* @__PURE__ */ Object.freeze({
|
|
133
|
-
"A128GCM": 128,
|
|
134
|
-
"A128GCMKW": 128,
|
|
135
|
-
"A192GCM": 128,
|
|
136
|
-
"A192GCMKW": 128,
|
|
137
|
-
"A256GCM": 128,
|
|
138
|
-
"A256GCMKW": 128,
|
|
139
|
-
"A128CBC-HS256": 128,
|
|
140
|
-
"A192CBC-HS384": 192,
|
|
141
|
-
"A256CBC-HS512": 256
|
|
142
|
-
});
|
|
143
|
-
function bitLengthTag(alg) {
|
|
144
|
-
return TAG_BIT_LENGTHS[alg];
|
|
145
|
-
}
|
|
146
|
-
function checkTagLength(enc, tag) {
|
|
147
|
-
if (tag.length << 3 !== bitLengthTag(enc)) throw new JWTError("Invalid Authentication Tag length", "ERR_JWE_INVALID");
|
|
148
|
-
}
|
|
149
|
-
function bitLengthCEK(enc) {
|
|
150
|
-
return CEK_BIT_LENGTHS[enc];
|
|
151
|
-
}
|
|
152
|
-
function generateCEK(alg) {
|
|
153
|
-
return crypto.getRandomValues(new Uint8Array(bitLengthCEK(alg) >> 3));
|
|
154
|
-
}
|
|
155
|
-
function checkCEKLength(cek, expected) {
|
|
156
|
-
const actual = cek.byteLength << 3;
|
|
157
|
-
if (actual !== expected) throw new JWTError(`Invalid Content Encryption Key length. Expected ${expected} bits, got ${actual} bits`, "ERR_INVALID_OPTION");
|
|
158
|
-
}
|
|
159
|
-
function assertOctKeyLength(bytes, alg) {
|
|
160
|
-
if (!alg) return;
|
|
161
|
-
if (alg === "HS256" || alg === "HS384" || alg === "HS512") {
|
|
162
|
-
const minBytes = shaBitsOf(jwsHashOf(alg)) >> 3;
|
|
163
|
-
if (bytes.length < minBytes) throw new JWTError(`${alg} requires a key of at least ${minBytes} bytes (got ${bytes.length})`, "ERR_JWK_INVALID");
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
const exactBits = isKeyWrapAlgorithm(alg) ? aesBitsOf(alg) : isContentEncryption(alg) ? CEK_BIT_LENGTHS[alg] : void 0;
|
|
167
|
-
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");
|
|
168
|
-
}
|
|
169
|
-
function checkAESCryptoKey(key, alg, usage) {
|
|
170
|
-
const name = alg.endsWith("KW") ? "AES-KW" : "AES-GCM";
|
|
171
|
-
if (!isAlgorithm(key.algorithm, name)) throw unusableKey(name);
|
|
172
|
-
const expected = aesBitsOf(alg);
|
|
173
|
-
if (key.algorithm.length !== expected) throw unusableKey(expected, "algorithm.length");
|
|
174
|
-
checkKeyUsage(key, usage);
|
|
175
|
-
}
|
|
176
|
-
async function aesKwWrap(alg, key, cek) {
|
|
177
|
-
const cryptoKey = await getAESKWCryptoKey(key, alg, { usage: "wrapKey" });
|
|
178
|
-
checkAESKWKeySize(cryptoKey, alg);
|
|
179
|
-
const cryptoKeyCek = await crypto.subtle.importKey("raw", cek, {
|
|
180
|
-
hash: "SHA-256",
|
|
181
|
-
name: "HMAC"
|
|
182
|
-
}, true, ["sign"]);
|
|
183
|
-
return new Uint8Array(await crypto.subtle.wrapKey("raw", cryptoKeyCek, cryptoKey, "AES-KW"));
|
|
184
|
-
}
|
|
185
|
-
async function aesKwUnwrap(alg, key, encryptedKey) {
|
|
186
|
-
const cryptoKey = await getAESKWCryptoKey(key, alg, { usage: "unwrapKey" });
|
|
187
|
-
checkAESKWKeySize(cryptoKey, alg);
|
|
188
|
-
const cryptoKeyCek = await crypto.subtle.unwrapKey("raw", encryptedKey, cryptoKey, "AES-KW", {
|
|
189
|
-
hash: "SHA-256",
|
|
190
|
-
name: "HMAC"
|
|
191
|
-
}, true, ["sign"]);
|
|
192
|
-
return new Uint8Array(await crypto.subtle.exportKey("raw", cryptoKeyCek));
|
|
193
|
-
}
|
|
194
|
-
async function gcmkwEncrypt(alg, key, plaintext, iv) {
|
|
195
|
-
const wrapped = await encrypt(gcmOf(alg), plaintext, key, iv, /* @__PURE__ */ new Uint8Array(0));
|
|
196
|
-
return {
|
|
197
|
-
encryptedKey: wrapped.ciphertext,
|
|
198
|
-
iv: base64UrlStringify(wrapped.iv),
|
|
199
|
-
tag: base64UrlStringify(wrapped.tag)
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
async function gcmkwDecrypt(alg, key, ciphertext, iv, tag) {
|
|
203
|
-
return decrypt(gcmOf(alg), key, ciphertext, iv, tag, /* @__PURE__ */ new Uint8Array(0));
|
|
204
|
-
}
|
|
205
|
-
async function encrypt(enc, plaintext, cek, iv, aad) {
|
|
206
|
-
if (!isCryptoKey(cek) && !(cek instanceof Uint8Array)) throw new JWTError("Key must be one of type: CryptoKey or Uint8Array", "ERR_JWK_INVALID");
|
|
207
|
-
if (iv) checkIvLength(enc, iv);
|
|
208
|
-
else iv = generateIV(enc);
|
|
209
|
-
switch (enc) {
|
|
210
|
-
case "A128CBC-HS256":
|
|
211
|
-
case "A192CBC-HS384":
|
|
212
|
-
case "A256CBC-HS512":
|
|
213
|
-
if (cek instanceof Uint8Array) checkCEKLength(cek, bitLengthCEK(enc));
|
|
214
|
-
return cbcEncrypt(enc, plaintext, cek, iv, aad);
|
|
215
|
-
case "A128GCM":
|
|
216
|
-
case "A192GCM":
|
|
217
|
-
case "A256GCM":
|
|
218
|
-
if (cek instanceof Uint8Array) checkCEKLength(cek, bitLengthCEK(enc));
|
|
219
|
-
return gcmEncrypt(enc, plaintext, cek, iv, aad);
|
|
220
|
-
default: return unsupportedContentEncryption(enc);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
async function decrypt(enc, cek, ciphertext, iv, tag, aad) {
|
|
224
|
-
if (!isCryptoKey(cek) && !(cek instanceof Uint8Array)) throw new JWTError("Key must be one of type: CryptoKey or Uint8Array", "ERR_JWK_INVALID");
|
|
225
|
-
if (!iv) throw new JWTError("JWE Initialization Vector missing", "ERR_JWE_INVALID");
|
|
226
|
-
if (!tag) throw new JWTError("JWE Authentication Tag missing", "ERR_JWE_INVALID");
|
|
227
|
-
checkIvLength(enc, iv);
|
|
228
|
-
checkTagLength(enc, tag);
|
|
229
|
-
switch (enc) {
|
|
230
|
-
case "A128CBC-HS256":
|
|
231
|
-
case "A192CBC-HS384":
|
|
232
|
-
case "A256CBC-HS512":
|
|
233
|
-
if (cek instanceof Uint8Array) checkCEKLength(cek, bitLengthCEK(enc));
|
|
234
|
-
return cbcDecrypt(enc, cek, ciphertext, iv, tag, aad);
|
|
235
|
-
case "A128GCM":
|
|
236
|
-
case "A192GCM":
|
|
237
|
-
case "A256GCM":
|
|
238
|
-
if (cek instanceof Uint8Array) checkCEKLength(cek, bitLengthCEK(enc));
|
|
239
|
-
return gcmDecrypt(enc, cek, ciphertext, iv, tag, aad);
|
|
240
|
-
default: return unsupportedContentEncryption(enc);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
async function getAESKWCryptoKey(key, alg, options = {}) {
|
|
244
|
-
const { extractable = true, usage } = options;
|
|
245
|
-
if (key instanceof Uint8Array) return crypto.subtle.importKey("raw", key, "AES-KW", extractable, [usage ?? "wrapKey"]);
|
|
246
|
-
checkAESCryptoKey(key, alg, usage);
|
|
247
|
-
return key;
|
|
248
|
-
}
|
|
249
|
-
function checkAESKWKeySize(key, alg) {
|
|
250
|
-
if (lengthOf(key.algorithm) !== aesBitsOf(alg)) throw new JWTError(`Invalid key size for alg: ${alg}`, "ERR_JWK_INVALID");
|
|
251
|
-
}
|
|
252
|
-
async function importCBCKeys(enc, cek, usage) {
|
|
253
|
-
const keySize = aesBitsOf(enc);
|
|
254
|
-
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), {
|
|
255
|
-
hash: `SHA-${keySize << 1}`,
|
|
256
|
-
name: "HMAC"
|
|
257
|
-
}, false, ["sign"])]);
|
|
258
|
-
return {
|
|
259
|
-
encKey,
|
|
260
|
-
macKey,
|
|
261
|
-
keySize
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
function cbcMacData(aad, iv, ciphertext) {
|
|
265
|
-
return concatUint8Arrays(aad, iv, ciphertext, uint64be(aad.length << 3));
|
|
266
|
-
}
|
|
267
|
-
async function cbcEncrypt(enc, plaintext, cek, iv, aad) {
|
|
268
|
-
if (!(cek instanceof Uint8Array)) throw new JWTError("CBC key must be of type Uint8Array", "ERR_JWK_INVALID");
|
|
269
|
-
const { encKey, macKey, keySize } = await importCBCKeys(enc, cek, "encrypt");
|
|
270
|
-
const ciphertext = new Uint8Array(await crypto.subtle.encrypt({
|
|
271
|
-
iv,
|
|
272
|
-
name: "AES-CBC"
|
|
273
|
-
}, encKey, plaintext));
|
|
274
|
-
const macData = cbcMacData(aad, iv, ciphertext);
|
|
275
|
-
return {
|
|
276
|
-
ciphertext,
|
|
277
|
-
tag: new Uint8Array((await crypto.subtle.sign("HMAC", macKey, macData)).slice(0, keySize >> 3)),
|
|
278
|
-
iv
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
async function gcmEncrypt(enc, plaintext, cek, iv, aad) {
|
|
282
|
-
let encKey;
|
|
283
|
-
if (cek instanceof Uint8Array) encKey = await crypto.subtle.importKey("raw", cek, "AES-GCM", false, ["encrypt"]);
|
|
284
|
-
else {
|
|
285
|
-
checkAESCryptoKey(cek, enc, "encrypt");
|
|
286
|
-
encKey = cek;
|
|
287
|
-
}
|
|
288
|
-
const encrypted = new Uint8Array(await crypto.subtle.encrypt({
|
|
289
|
-
additionalData: aad,
|
|
290
|
-
iv,
|
|
291
|
-
name: "AES-GCM",
|
|
292
|
-
tagLength: 128
|
|
293
|
-
}, encKey, plaintext));
|
|
294
|
-
const tag = encrypted.subarray(-16);
|
|
295
|
-
return {
|
|
296
|
-
ciphertext: encrypted.subarray(0, -16),
|
|
297
|
-
tag,
|
|
298
|
-
iv
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
async function cbcDecrypt(enc, cek, ciphertext, iv, tag, aad) {
|
|
302
|
-
if (!(cek instanceof Uint8Array)) throw new JWTError("CBC key must be of type Uint8Array", "ERR_JWK_INVALID");
|
|
303
|
-
const { encKey, macKey, keySize } = await importCBCKeys(enc, cek, "decrypt");
|
|
304
|
-
const macData = cbcMacData(aad, iv, ciphertext);
|
|
305
|
-
const expectedTag = new Uint8Array((await crypto.subtle.sign("HMAC", macKey, macData)).slice(0, keySize >> 3));
|
|
306
|
-
let macCheckPassed;
|
|
307
|
-
try {
|
|
308
|
-
macCheckPassed = await timingSafeEqual(tag, expectedTag);
|
|
309
|
-
} catch {}
|
|
310
|
-
if (!macCheckPassed) throw new JWTError("JWE Decryption Failed", "ERR_JWE_DECRYPTION_FAILED");
|
|
311
|
-
let plaintext;
|
|
312
|
-
try {
|
|
313
|
-
plaintext = new Uint8Array(await crypto.subtle.decrypt({
|
|
314
|
-
iv,
|
|
315
|
-
name: "AES-CBC"
|
|
316
|
-
}, encKey, ciphertext));
|
|
317
|
-
} catch {}
|
|
318
|
-
if (!plaintext) throw new JWTError("JWE Decryption Failed", "ERR_JWE_DECRYPTION_FAILED");
|
|
319
|
-
return plaintext;
|
|
320
|
-
}
|
|
321
|
-
async function gcmDecrypt(enc, cek, ciphertext, iv, tag, aad) {
|
|
322
|
-
let encKey;
|
|
323
|
-
if (cek instanceof Uint8Array) encKey = await crypto.subtle.importKey("raw", cek, "AES-GCM", false, ["decrypt"]);
|
|
324
|
-
else {
|
|
325
|
-
checkAESCryptoKey(cek, enc, "decrypt");
|
|
326
|
-
encKey = cek;
|
|
327
|
-
}
|
|
328
|
-
try {
|
|
329
|
-
return new Uint8Array(await crypto.subtle.decrypt({
|
|
330
|
-
additionalData: aad,
|
|
331
|
-
iv,
|
|
332
|
-
name: "AES-GCM",
|
|
333
|
-
tagLength: 128
|
|
334
|
-
}, encKey, concatUint8Arrays(ciphertext, tag)));
|
|
335
|
-
} catch {
|
|
336
|
-
throw new JWTError("JWE Decryption Failed", "ERR_JWE_DECRYPTION_FAILED");
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
let _timingSafeKey;
|
|
340
|
-
function getTimingSafeKey() {
|
|
341
|
-
if (!_timingSafeKey) _timingSafeKey = crypto.subtle.generateKey({
|
|
342
|
-
name: "HMAC",
|
|
343
|
-
hash: "SHA-256"
|
|
344
|
-
}, false, ["sign"]);
|
|
345
|
-
return _timingSafeKey;
|
|
346
|
-
}
|
|
347
|
-
async function timingSafeEqual(a, b) {
|
|
348
|
-
if (!(a instanceof Uint8Array)) throw new JWTError("First argument must be a buffer", "ERR_INVALID_OPTION");
|
|
349
|
-
if (!(b instanceof Uint8Array)) throw new JWTError("Second argument must be a buffer", "ERR_INVALID_OPTION");
|
|
350
|
-
const algorithm = {
|
|
351
|
-
name: "HMAC",
|
|
352
|
-
hash: "SHA-256"
|
|
353
|
-
};
|
|
354
|
-
const key = await getTimingSafeKey();
|
|
355
|
-
const aHmac = new Uint8Array(await crypto.subtle.sign(algorithm, key, a));
|
|
356
|
-
const bHmac = new Uint8Array(await crypto.subtle.sign(algorithm, key, b));
|
|
357
|
-
let out = 0;
|
|
358
|
-
let i = -1;
|
|
359
|
-
while (++i < 32) out |= aHmac[i] ^ bHmac[i];
|
|
360
|
-
return out === 0;
|
|
361
|
-
}
|
|
362
|
-
function uint64be(value) {
|
|
363
|
-
return concatUint8Arrays(uint32be(Math.floor(value / 2 ** 32)), uint32be(value % 2 ** 32));
|
|
364
|
-
}
|
|
365
|
-
function gcmOf(alg) {
|
|
366
|
-
return alg.slice(0, 7);
|
|
367
|
-
}
|
|
368
|
-
function isKeyWrapAlgorithm(alg) {
|
|
369
|
-
return alg.startsWith("A") && alg.endsWith("KW");
|
|
370
|
-
}
|
|
371
|
-
function isContentEncryption(alg) {
|
|
372
|
-
return Object.hasOwn(CEK_BIT_LENGTHS, alg);
|
|
373
|
-
}
|
|
374
|
-
function unsupportedContentEncryption(enc) {
|
|
375
|
-
throw new JWTError(`Unsupported JWE Content Encryption Algorithm: ${quoteTokenValue(String(enc))}`, "ERR_JWE_INVALID");
|
|
376
|
-
}
|
|
377
|
-
async function jwkTokey(jwk, options = {}) {
|
|
378
|
-
const alg = jwk.alg ?? options.alg;
|
|
379
|
-
if (!alg) throw new JWTError("\"alg\" argument is required when \"jwk.alg\" is not present", "ERR_JWK_INVALID");
|
|
380
|
-
const { algorithm, keyUsages } = subtleMapping(jwk, alg);
|
|
381
|
-
const material = {
|
|
382
|
-
...jwk,
|
|
383
|
-
alg
|
|
384
|
-
};
|
|
385
|
-
const isPrivate = "d" in jwk && jwk.d !== void 0;
|
|
386
|
-
const extractable = jwk.ext === false ? false : options.extractable ?? !isPrivate;
|
|
387
|
-
try {
|
|
388
|
-
return await crypto.subtle.importKey("jwk", material, algorithm, extractable, permittedUsages(jwk, keyUsages));
|
|
389
|
-
} catch (error) {
|
|
390
|
-
if (error instanceof JWTError) throw error;
|
|
391
|
-
throw new JWTError(`Key is not usable for ${quoteTokenValue(alg)}.`, "ERR_JWK_INVALID", error);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
async function keyToJWK(key) {
|
|
395
|
-
let jwk;
|
|
396
|
-
if (key instanceof Uint8Array) jwk = {
|
|
397
|
-
kty: "oct",
|
|
398
|
-
k: base64UrlStringify(key)
|
|
399
|
-
};
|
|
400
|
-
else {
|
|
401
|
-
if (!isCryptoKey(key)) throw new JWTError("Key must be one of type: CryptoKey or Uint8Array", "ERR_JWK_INVALID");
|
|
402
|
-
if (!key.extractable) throw new JWTError("non-extractable CryptoKey cannot be exported as a JWK", "ERR_JWK_INVALID");
|
|
403
|
-
const { ext: _ext, key_ops: _keyOps, ...exported } = await crypto.subtle.exportKey("jwk", key);
|
|
404
|
-
jwk = exported;
|
|
405
|
-
}
|
|
406
|
-
return jwk;
|
|
407
|
-
}
|
|
408
|
-
function permittedUsages(jwk, keyUsages) {
|
|
409
|
-
const keyOps = jwk.key_ops;
|
|
410
|
-
if (!keyOps || keyUsages.length === 0) return keyUsages;
|
|
411
|
-
const permitted = keyUsages.filter((usage) => keyOps.includes(usage));
|
|
412
|
-
if (permitted.length === 0) throw new JWTError(`JWK "key_ops" does not permit ${keyUsages.join(", ")}.`, "ERR_JWK_INVALID");
|
|
413
|
-
return permitted;
|
|
414
|
-
}
|
|
415
|
-
function subtleMapping(jwk, alg) {
|
|
416
|
-
let algorithm;
|
|
417
|
-
let keyUsages;
|
|
418
|
-
switch (jwk.kty) {
|
|
419
|
-
case "RSA":
|
|
420
|
-
switch (alg) {
|
|
421
|
-
case "PS256":
|
|
422
|
-
case "PS384":
|
|
423
|
-
case "PS512":
|
|
424
|
-
algorithm = {
|
|
425
|
-
name: "RSA-PSS",
|
|
426
|
-
hash: jwsHashOf(alg)
|
|
427
|
-
};
|
|
428
|
-
keyUsages = hasPrivateMember(jwk) ? ["sign"] : ["verify"];
|
|
429
|
-
break;
|
|
430
|
-
case "RS256":
|
|
431
|
-
case "RS384":
|
|
432
|
-
case "RS512":
|
|
433
|
-
algorithm = {
|
|
434
|
-
name: "RSASSA-PKCS1-v1_5",
|
|
435
|
-
hash: jwsHashOf(alg)
|
|
436
|
-
};
|
|
437
|
-
keyUsages = hasPrivateMember(jwk) ? ["sign"] : ["verify"];
|
|
438
|
-
break;
|
|
439
|
-
case "RSA-OAEP":
|
|
440
|
-
case "RSA-OAEP-256":
|
|
441
|
-
case "RSA-OAEP-384":
|
|
442
|
-
case "RSA-OAEP-512":
|
|
443
|
-
algorithm = {
|
|
444
|
-
name: "RSA-OAEP",
|
|
445
|
-
hash: rsaOaepHashOf(alg)
|
|
446
|
-
};
|
|
447
|
-
keyUsages = hasPrivateMember(jwk) ? ["decrypt", "unwrapKey"] : ["encrypt", "wrapKey"];
|
|
448
|
-
break;
|
|
449
|
-
default: throw new JWTError(`Invalid or unsupported JWK "alg" (Algorithm) Parameter value: ${quoteTokenValue(alg)}.`, "ERR_JWK_INVALID");
|
|
450
|
-
}
|
|
451
|
-
break;
|
|
452
|
-
case "EC":
|
|
453
|
-
switch (alg) {
|
|
454
|
-
case "ES256":
|
|
455
|
-
case "ES384":
|
|
456
|
-
case "ES512":
|
|
457
|
-
algorithm = {
|
|
458
|
-
name: "ECDSA",
|
|
459
|
-
namedCurve: ECDSA_CURVES[alg]
|
|
460
|
-
};
|
|
461
|
-
keyUsages = hasPrivateMember(jwk) ? ["sign"] : ["verify"];
|
|
462
|
-
break;
|
|
463
|
-
case "ECDH-ES":
|
|
464
|
-
case "ECDH-ES+A128KW":
|
|
465
|
-
case "ECDH-ES+A192KW":
|
|
466
|
-
case "ECDH-ES+A256KW":
|
|
467
|
-
algorithm = {
|
|
468
|
-
name: "ECDH",
|
|
469
|
-
namedCurve: requireCurve(jwk, [
|
|
470
|
-
"P-256",
|
|
471
|
-
"P-384",
|
|
472
|
-
"P-521"
|
|
473
|
-
])
|
|
474
|
-
};
|
|
475
|
-
keyUsages = hasPrivateMember(jwk) ? ["deriveBits"] : [];
|
|
476
|
-
break;
|
|
477
|
-
default: throw new JWTError(`Invalid or unsupported JWK "alg" (Algorithm) Parameter value: ${quoteTokenValue(alg)}.`, "ERR_JWK_INVALID");
|
|
478
|
-
}
|
|
479
|
-
break;
|
|
480
|
-
case "OKP":
|
|
481
|
-
switch (alg) {
|
|
482
|
-
case "Ed25519":
|
|
483
|
-
case "EdDSA":
|
|
484
|
-
algorithm = { name: requireCurve(jwk, ["Ed25519"]) };
|
|
485
|
-
keyUsages = hasPrivateMember(jwk) ? ["sign"] : ["verify"];
|
|
486
|
-
break;
|
|
487
|
-
case "ECDH-ES":
|
|
488
|
-
case "ECDH-ES+A128KW":
|
|
489
|
-
case "ECDH-ES+A192KW":
|
|
490
|
-
case "ECDH-ES+A256KW":
|
|
491
|
-
algorithm = { name: requireCurve(jwk, ["X25519"]) };
|
|
492
|
-
keyUsages = hasPrivateMember(jwk) ? ["deriveBits"] : [];
|
|
493
|
-
break;
|
|
494
|
-
default: throw new JWTError(`Invalid or unsupported JWK "alg" (Algorithm) Parameter value: ${quoteTokenValue(alg)}.`, "ERR_JWK_INVALID");
|
|
495
|
-
}
|
|
496
|
-
break;
|
|
497
|
-
default: throw new JWTError(`Invalid or unsupported JWK "kty" (Key Type) Parameter value: ${quoteTokenValue(jwk.kty)}.`, "ERR_JWK_INVALID");
|
|
498
|
-
}
|
|
499
|
-
return {
|
|
500
|
-
algorithm,
|
|
501
|
-
keyUsages
|
|
502
|
-
};
|
|
503
|
-
}
|
|
504
|
-
function requireCurve(jwk, allowed) {
|
|
505
|
-
const crv = "crv" in jwk ? jwk.crv : void 0;
|
|
506
|
-
if (typeof crv !== "string" || !allowed.includes(crv)) throw new JWTError(`Invalid or unsupported JWK "crv" (Curve) Parameter value: ${quoteTokenValue(crv)}. Expected one of: ${allowed.join(", ")}.`, "ERR_JWK_INVALID");
|
|
507
|
-
return crv;
|
|
508
|
-
}
|
|
509
|
-
function hasPrivateMember(jwk) {
|
|
510
|
-
return "d" in jwk && Boolean(jwk.d);
|
|
511
|
-
}
|
|
512
|
-
async function encryptRSAES(alg, key, cek) {
|
|
513
|
-
checkRSACryptoKey(key, alg, "encrypt");
|
|
514
|
-
checkRSAModulusLength(alg, key);
|
|
515
|
-
return new Uint8Array(await crypto.subtle.encrypt("RSA-OAEP", key, cek));
|
|
516
|
-
}
|
|
517
|
-
async function decryptRSAES(alg, key, encryptedKey) {
|
|
518
|
-
checkRSACryptoKey(key, alg, "decrypt");
|
|
519
|
-
checkRSAModulusLength(alg, key);
|
|
520
|
-
return new Uint8Array(await crypto.subtle.decrypt("RSA-OAEP", key, encryptedKey));
|
|
521
|
-
}
|
|
522
|
-
function checkRSACryptoKey(key, alg, usage) {
|
|
523
|
-
if (!isAlgorithm(key.algorithm, "RSA-OAEP")) throw unusableKey("RSA-OAEP");
|
|
524
|
-
const expected = shaBitsOf(rsaOaepHashOf(alg));
|
|
525
|
-
if (hashBitsOf(key.algorithm.hash) !== expected) throw unusableKey(`SHA-${expected}`, "algorithm.hash");
|
|
526
|
-
checkKeyUsage(key, usage);
|
|
527
|
-
}
|
|
528
|
-
function checkRSAModulusLength(alg, key) {
|
|
529
|
-
const modulusLength = modulusLengthOf(key.algorithm);
|
|
530
|
-
if (typeof modulusLength !== "number" || modulusLength < 2048) throw new JWTError(`${alg} requires key modulusLength to be 2048 bits or larger`, "ERR_JWK_INVALID");
|
|
531
|
-
}
|
|
532
|
-
async function deriveECDHESKey(publicKey, privateKey, algorithm, keyLength, apu = /* @__PURE__ */ new Uint8Array(0), apv = /* @__PURE__ */ new Uint8Array(0)) {
|
|
533
|
-
checkECDHCryptoKey(publicKey);
|
|
534
|
-
checkECDHCryptoKey(privateKey, "deriveBits");
|
|
535
|
-
const value = concatUint8Arrays(lengthAndInput(textEncoder.encode(algorithm)), lengthAndInput(apu), lengthAndInput(apv), uint32be(keyLength));
|
|
536
|
-
const length = publicKey.algorithm.name === "X25519" ? 256 : Math.ceil(Number.parseInt((namedCurveOf(publicKey.algorithm) ?? "").slice(-3), 10) / 8) << 3;
|
|
537
|
-
return concatKdf(new Uint8Array(await crypto.subtle.deriveBits({
|
|
538
|
-
name: publicKey.algorithm.name,
|
|
539
|
-
public: publicKey
|
|
540
|
-
}, privateKey, length)), keyLength, value);
|
|
541
|
-
}
|
|
542
|
-
function ecdhDerivedBits(alg, enc) {
|
|
543
|
-
if (alg !== "ECDH-ES") return aesBitsOf(innerKeyWrapAlg(alg));
|
|
544
|
-
if (enc) return bitLengthCEK(enc);
|
|
545
|
-
throw new JWTError("ECDH-ES requires \"enc\", the content encryption algorithm the key is agreed for.", "ERR_JWE_INVALID");
|
|
546
|
-
}
|
|
547
|
-
function generateEphemeralKeyPair(recipientKey) {
|
|
548
|
-
return crypto.subtle.generateKey(recipientKey.algorithm, true, ["deriveBits"]);
|
|
549
|
-
}
|
|
550
|
-
function assertECDHKeyAllowed(key) {
|
|
551
|
-
switch (namedCurveOf(key.algorithm) ?? key.algorithm.name) {
|
|
552
|
-
case "P-256":
|
|
553
|
-
case "P-384":
|
|
554
|
-
case "P-521":
|
|
555
|
-
case "X25519": return;
|
|
556
|
-
default: throw new JWTError("ECDH with the provided key is not allowed or not supported", "ERR_JWK_INVALID");
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
function checkECDHCryptoKey(key, usage) {
|
|
560
|
-
switch (key.algorithm.name) {
|
|
561
|
-
case "ECDH":
|
|
562
|
-
case "X25519": break;
|
|
563
|
-
default: throw unusableKey("ECDH or X25519");
|
|
564
|
-
}
|
|
565
|
-
checkKeyUsage(key, usage);
|
|
566
|
-
}
|
|
567
|
-
async function concatKdf(secret, bits, value) {
|
|
568
|
-
const iterations = Math.ceil((bits >> 3) / 32);
|
|
569
|
-
const res = new Uint8Array(iterations * 32);
|
|
570
|
-
for (let iter = 0; iter < iterations; iter++) {
|
|
571
|
-
const buf = new Uint8Array(4 + secret.length + value.length);
|
|
572
|
-
buf.set(uint32be(iter + 1));
|
|
573
|
-
buf.set(secret, 4);
|
|
574
|
-
buf.set(value, 4 + secret.length);
|
|
575
|
-
res.set(await digest("sha256", buf), iter * 32);
|
|
576
|
-
}
|
|
577
|
-
return res.slice(0, bits >> 3);
|
|
578
|
-
}
|
|
579
|
-
function lengthAndInput(input) {
|
|
580
|
-
return concatUint8Arrays(uint32be(input.length), input);
|
|
581
|
-
}
|
|
582
|
-
async function digest(algorithm, data) {
|
|
583
|
-
return new Uint8Array(await crypto.subtle.digest(`SHA-${algorithm.slice(-3)}`, data));
|
|
584
|
-
}
|
|
585
|
-
const DEFAULT_PBES2_MIN_ITERATIONS = 1e3;
|
|
586
|
-
const DEFAULT_PBES2_ITERATIONS = 6e5;
|
|
587
|
-
const DEFAULT_PBES2_MAX_ITERATIONS = DEFAULT_PBES2_ITERATIONS;
|
|
588
|
-
function assertPBES2Params(params) {
|
|
589
|
-
const { p2s, p2c } = params;
|
|
590
|
-
if (p2s !== void 0) assertPBES2Salt(p2s, "\"p2s\"");
|
|
591
|
-
if (p2c === void 0) return;
|
|
592
|
-
assertPBES2Count(p2c, "\"p2c\"");
|
|
593
|
-
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");
|
|
594
|
-
}
|
|
595
|
-
function assertPBES2Salt(salt, name) {
|
|
596
|
-
if (salt instanceof Uint8Array && salt.length >= 8) return;
|
|
597
|
-
throw new JWTError(`${name} must be 8 or more octets (RFC 7518 §4.8.1.1).`, "ERR_INVALID_OPTION");
|
|
598
|
-
}
|
|
599
|
-
function assertPBES2Count(count, name) {
|
|
600
|
-
if (typeof count !== "number" || !Number.isInteger(count) || count < 1) throw new JWTError(`${name} must be a positive integer.`, "ERR_INVALID_OPTION");
|
|
601
|
-
if (count < 1e3) throw new JWTError(`${name} must be at least ${DEFAULT_PBES2_MIN_ITERATIONS} iterations (got ${count}).`, "ERR_INVALID_OPTION");
|
|
602
|
-
}
|
|
603
|
-
async function pbes2Wrap(alg, key, cek, p2c, p2s) {
|
|
604
|
-
const derived = await deriveKey(p2s, alg, p2c, key);
|
|
605
|
-
return {
|
|
606
|
-
encryptedKey: await aesKwWrap(innerKeyWrapAlg(alg), derived, cek),
|
|
607
|
-
p2c,
|
|
608
|
-
p2s: base64UrlStringify(p2s)
|
|
609
|
-
};
|
|
610
|
-
}
|
|
611
|
-
function assertPBES2Iterations(p2c, bounds) {
|
|
612
|
-
const { minIterations = DEFAULT_PBES2_MIN_ITERATIONS, maxIterations = DEFAULT_PBES2_MAX_ITERATIONS } = bounds;
|
|
613
|
-
if (p2c < minIterations) throw new JWTError(`PBES2 "p2c" below the minimum of ${minIterations} iterations (got ${p2c}).`, "ERR_JWE_INVALID");
|
|
614
|
-
if (p2c > maxIterations) throw new JWTError(`PBES2 "p2c" above the maximum of ${maxIterations} iterations (got ${p2c}).`, "ERR_JWE_INVALID");
|
|
615
|
-
}
|
|
616
|
-
async function pbes2Unwrap(alg, key, encryptedKey, p2c, p2s, minIterations, maxIterations) {
|
|
617
|
-
assertPBES2Iterations(p2c, {
|
|
618
|
-
minIterations,
|
|
619
|
-
maxIterations
|
|
620
|
-
});
|
|
621
|
-
const derived = await deriveKey(p2s, alg, p2c, key);
|
|
622
|
-
return aesKwUnwrap(innerKeyWrapAlg(alg), derived, encryptedKey);
|
|
623
|
-
}
|
|
624
|
-
async function deriveKey(p2s, alg, p2c, key) {
|
|
625
|
-
assertPBES2Salt(p2s, "\"p2s\"");
|
|
626
|
-
if (p2c === void 0 || p2c < 1) throw new JWTError("PBES2 Iteration Count Parameter must be a positive integer", "ERR_INVALID_OPTION");
|
|
627
|
-
const salt = concatUint8Arrays(textEncoder.encode(alg), new Uint8Array([0]), p2s);
|
|
628
|
-
const keylen = aesBitsOf(innerKeyWrapAlg(alg));
|
|
629
|
-
const subtleAlg = {
|
|
630
|
-
hash: pbes2HashOf(alg),
|
|
631
|
-
iterations: p2c,
|
|
632
|
-
name: "PBKDF2",
|
|
633
|
-
salt
|
|
634
|
-
};
|
|
635
|
-
const cryptoKey = await getPBKDF2CryptoKey(key, alg);
|
|
636
|
-
return new Uint8Array(await crypto.subtle.deriveBits(subtleAlg, cryptoKey, keylen));
|
|
637
|
-
}
|
|
638
|
-
function checkPBES2CryptoKey(key, alg, usage) {
|
|
639
|
-
if (!isAlgorithm(key.algorithm, "PBKDF2")) throw unusableKey("PBKDF2");
|
|
640
|
-
checkKeyUsage(key, usage);
|
|
641
|
-
}
|
|
642
|
-
async function getPBKDF2CryptoKey(key, alg, options = {}) {
|
|
643
|
-
const { extractable = false, usage = "deriveBits" } = options;
|
|
644
|
-
if (key instanceof Uint8Array) return crypto.subtle.importKey("raw", key, "PBKDF2", extractable, [usage]);
|
|
645
|
-
checkPBES2CryptoKey(key, alg, usage);
|
|
646
|
-
return key;
|
|
647
|
-
}
|
|
648
|
-
async function encryptKey(alg, enc, key, providedCek, providedParameters = {}) {
|
|
649
|
-
let encryptedKey;
|
|
650
|
-
let parameters = {};
|
|
651
|
-
let cek;
|
|
652
|
-
switch (alg) {
|
|
653
|
-
case "dir":
|
|
654
|
-
cek = key;
|
|
655
|
-
break;
|
|
656
|
-
case "ECDH-ES":
|
|
657
|
-
case "ECDH-ES+A128KW":
|
|
658
|
-
case "ECDH-ES+A192KW":
|
|
659
|
-
case "ECDH-ES+A256KW":
|
|
660
|
-
assertCryptoKey(key);
|
|
661
|
-
({cek, encryptedKey, parameters} = await ecdhesWrap(alg, {
|
|
662
|
-
enc,
|
|
663
|
-
recipientKey: key,
|
|
664
|
-
cek: providedCek,
|
|
665
|
-
parameters: providedParameters
|
|
666
|
-
}));
|
|
667
|
-
break;
|
|
668
|
-
case "RSA-OAEP":
|
|
669
|
-
case "RSA-OAEP-256":
|
|
670
|
-
case "RSA-OAEP-384":
|
|
671
|
-
case "RSA-OAEP-512":
|
|
672
|
-
cek = providedCek || generateCEK(enc);
|
|
673
|
-
assertCryptoKey(key);
|
|
674
|
-
encryptedKey = await encryptRSAES(alg, key, cek);
|
|
675
|
-
break;
|
|
676
|
-
case "PBES2-HS256+A128KW":
|
|
677
|
-
case "PBES2-HS384+A192KW":
|
|
678
|
-
case "PBES2-HS512+A256KW": {
|
|
679
|
-
cek = providedCek || generateCEK(enc);
|
|
680
|
-
const { p2c, p2s } = providedParameters;
|
|
681
|
-
if (p2c === void 0 || p2s === void 0) throw new JWTError(`${alg} requires "p2c" and "p2s"`, "ERR_JWE_INVALID");
|
|
682
|
-
({encryptedKey, ...parameters} = await pbes2Wrap(alg, key, cek, p2c, p2s));
|
|
683
|
-
break;
|
|
684
|
-
}
|
|
685
|
-
case "A128KW":
|
|
686
|
-
case "A192KW":
|
|
687
|
-
case "A256KW":
|
|
688
|
-
cek = providedCek || generateCEK(enc);
|
|
689
|
-
encryptedKey = await aesKwWrap(alg, key, cek);
|
|
690
|
-
break;
|
|
691
|
-
case "A128GCMKW":
|
|
692
|
-
case "A192GCMKW":
|
|
693
|
-
case "A256GCMKW": {
|
|
694
|
-
cek = providedCek || generateCEK(enc);
|
|
695
|
-
const { iv } = providedParameters;
|
|
696
|
-
({encryptedKey, ...parameters} = await gcmkwEncrypt(alg, key, cek, iv));
|
|
697
|
-
break;
|
|
698
|
-
}
|
|
699
|
-
default: throw new JWTError(`Invalid or unsupported "alg" (JWE Algorithm) header value: ${quoteTokenValue(alg)}`, "ERR_JWE_ALG_NOT_ALLOWED");
|
|
700
|
-
}
|
|
701
|
-
return {
|
|
702
|
-
cek,
|
|
703
|
-
encryptedKey,
|
|
704
|
-
parameters
|
|
705
|
-
};
|
|
706
|
-
}
|
|
707
|
-
async function ecdhesWrap(alg, options) {
|
|
708
|
-
const { enc, recipientKey } = options;
|
|
709
|
-
const { apu, apv, epk: providedEpk, epkPrivateKey } = options.parameters;
|
|
710
|
-
assertECDHKeyAllowed(recipientKey);
|
|
711
|
-
if (providedEpk && !epkPrivateKey) throw new JWTError("ECDH-ES custom ephemeral key material must include the matching private key.", "ERR_JWK_INVALID");
|
|
712
|
-
let ephemeralPrivateKey;
|
|
713
|
-
let epk;
|
|
714
|
-
if (epkPrivateKey) {
|
|
715
|
-
ephemeralPrivateKey = await ensurePrivateKey(alg, epkPrivateKey);
|
|
716
|
-
assertEphemeralCurve(ephemeralPrivateKey, recipientKey);
|
|
717
|
-
epk = await exportPublicJwkFrom(providedEpk ?? epkPrivateKey);
|
|
718
|
-
} else {
|
|
719
|
-
const generated = await generateEphemeralKeyPair(recipientKey);
|
|
720
|
-
ephemeralPrivateKey = generated.privateKey;
|
|
721
|
-
epk = await exportPublicJwkFrom(generated.publicKey);
|
|
722
|
-
}
|
|
723
|
-
const kdfAlg = alg === "ECDH-ES" ? contentAlgorithm(enc) : alg;
|
|
724
|
-
const sharedSecret = await deriveECDHESKey(recipientKey, ephemeralPrivateKey, kdfAlg, ecdhDerivedBits(alg, enc), apu, apv);
|
|
725
|
-
const parameters = { epk };
|
|
726
|
-
if (apu) parameters.apu = base64UrlStringify(apu);
|
|
727
|
-
if (apv) parameters.apv = base64UrlStringify(apv);
|
|
728
|
-
if (alg === "ECDH-ES") return {
|
|
729
|
-
cek: sharedSecret,
|
|
730
|
-
parameters
|
|
731
|
-
};
|
|
732
|
-
const cek = options.cek ?? generateCEK(contentAlgorithm(enc));
|
|
733
|
-
return {
|
|
734
|
-
cek,
|
|
735
|
-
encryptedKey: await aesKwWrap(innerKeyWrapAlg(alg), sharedSecret, cek),
|
|
736
|
-
parameters
|
|
737
|
-
};
|
|
738
|
-
}
|
|
739
|
-
async function normalizeEpk(epk, options) {
|
|
740
|
-
const { alg } = options;
|
|
741
|
-
const recipientCurve = cryptoKeyCurve(options.recipientKey);
|
|
742
|
-
if (isCryptoKey(epk)) {
|
|
743
|
-
if (epk.type !== "public") throw invalidEpk("must not contain private key material.");
|
|
744
|
-
assertEpkCurve(cryptoKeyCurve(epk), recipientCurve);
|
|
745
|
-
return epk;
|
|
746
|
-
}
|
|
747
|
-
if (!isJWK(epk) || epk.kty !== "EC" && epk.kty !== "OKP") throw invalidEpk("must be an EC or OKP public key.");
|
|
748
|
-
if ("d" in epk && epk.d !== void 0) throw invalidEpk("must not contain private key material.");
|
|
749
|
-
const crv = epk.crv;
|
|
750
|
-
const allowed = epk.kty === "EC" ? EC_ECDH_CURVES : OKP_ECDH_CURVES;
|
|
751
|
-
if (typeof crv !== "string" || !allowed.has(crv)) throw invalidEpk(`curve ${quoteTokenValue(crv)} is not supported for ${options.alg}.`);
|
|
752
|
-
assertEpkCurve(crv, recipientCurve);
|
|
753
|
-
const x = epk.x;
|
|
754
|
-
const y = epk.kty === "EC" ? epk.y : void 0;
|
|
755
|
-
if (typeof x !== "string" || epk.kty === "EC" && typeof y !== "string") throw invalidEpk("is missing its public coordinates.");
|
|
756
|
-
return jwkTokey(epk.kty === "EC" && typeof y === "string" ? {
|
|
757
|
-
kty: "EC",
|
|
758
|
-
crv,
|
|
759
|
-
x,
|
|
760
|
-
y,
|
|
761
|
-
alg
|
|
762
|
-
} : {
|
|
763
|
-
kty: "OKP",
|
|
764
|
-
crv,
|
|
765
|
-
x,
|
|
766
|
-
alg
|
|
767
|
-
});
|
|
768
|
-
}
|
|
769
|
-
const EC_ECDH_CURVES = /* @__PURE__ */ new Set([
|
|
770
|
-
"P-256",
|
|
771
|
-
"P-384",
|
|
772
|
-
"P-521"
|
|
773
|
-
]);
|
|
774
|
-
const OKP_ECDH_CURVES = /* @__PURE__ */ new Set(["X25519"]);
|
|
775
|
-
function contentAlgorithm(enc) {
|
|
776
|
-
if (enc) return enc;
|
|
777
|
-
throw new JWTError("ECDH-ES requires \"enc\", the content encryption algorithm the key is agreed for.", "ERR_JWE_INVALID");
|
|
778
|
-
}
|
|
779
|
-
function assertEphemeralCurve(ephemeralKey, recipientKey) {
|
|
780
|
-
assertECDHKeyAllowed(ephemeralKey);
|
|
781
|
-
const ephemeralCurve = cryptoKeyCurve(ephemeralKey);
|
|
782
|
-
const recipientCurve = cryptoKeyCurve(recipientKey);
|
|
783
|
-
if (ephemeralCurve === recipientCurve) return;
|
|
784
|
-
throw new JWTError(`ECDH-ES ephemeral key curve ${quoteTokenValue(ephemeralCurve)} does not match the recipient key curve ${quoteTokenValue(recipientCurve)}.`, "ERR_JWK_INVALID");
|
|
785
|
-
}
|
|
786
|
-
function invalidEpk(detail) {
|
|
787
|
-
return new JWTError(`Invalid JWE: "epk" ${detail}`, "ERR_JWE_INVALID");
|
|
788
|
-
}
|
|
789
|
-
function assertEpkCurve(epkCurve, recipientCurve) {
|
|
790
|
-
if (!recipientCurve || !epkCurve || epkCurve === recipientCurve) return;
|
|
791
|
-
throw invalidEpk(`curve "${epkCurve}" does not match the recipient key curve "${recipientCurve}".`);
|
|
792
|
-
}
|
|
793
|
-
function toPublicEpkJwk(jwk) {
|
|
794
|
-
const { kty } = jwk;
|
|
795
|
-
const crv = "crv" in jwk ? jwk.crv : void 0;
|
|
796
|
-
const x = "x" in jwk ? jwk.x : void 0;
|
|
797
|
-
if (kty !== "EC" && kty !== "OKP") throw new JWTError("ECDH-ES ephemeral key JWK \"kty\" must be \"EC\" or \"OKP\".", "ERR_JWK_INVALID");
|
|
798
|
-
if (typeof crv !== "string") throw new JWTError("ECDH-ES ephemeral key JWK must include the \"crv\" parameter.", "ERR_JWK_INVALID");
|
|
799
|
-
if (typeof x !== "string") throw new JWTError("ECDH-ES ephemeral key JWK must include the \"x\" coordinate.", "ERR_JWK_INVALID");
|
|
800
|
-
if (kty === "OKP") return {
|
|
801
|
-
kty,
|
|
802
|
-
crv,
|
|
803
|
-
x
|
|
804
|
-
};
|
|
805
|
-
const y = "y" in jwk ? jwk.y : void 0;
|
|
806
|
-
if (typeof y !== "string") throw new JWTError("ECDH-ES EC ephemeral key JWK must include both \"x\" and \"y\" coordinates.", "ERR_JWK_INVALID");
|
|
807
|
-
return {
|
|
808
|
-
kty,
|
|
809
|
-
crv,
|
|
810
|
-
x,
|
|
811
|
-
y
|
|
812
|
-
};
|
|
813
|
-
}
|
|
814
|
-
async function ensurePrivateKey(alg, value) {
|
|
815
|
-
const normalized = isCryptoKey(value) ? value : await jwkTokey(value, { alg });
|
|
816
|
-
if (normalized.type !== "private") throw new JWTError("ECDH-ES ephemeral private key must have type \"private\".", "ERR_JWK_INVALID");
|
|
817
|
-
return normalized;
|
|
818
|
-
}
|
|
819
|
-
async function exportPublicJwkFrom(value) {
|
|
820
|
-
if (!isCryptoKey(value)) return toPublicEpkJwk(value);
|
|
821
|
-
try {
|
|
822
|
-
return toPublicEpkJwk(await keyToJWK(value));
|
|
823
|
-
} catch (error_) {
|
|
824
|
-
throw new JWTError("ECDH-ES ephemeral CryptoKey must be extractable to export as JWK.", "ERR_JWK_INVALID", error_);
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
const toSPKI = (key) => {
|
|
828
|
-
return genericExport("public", "spki", key);
|
|
829
|
-
};
|
|
830
|
-
const toPKCS8 = (key) => {
|
|
831
|
-
return genericExport("private", "pkcs8", key);
|
|
832
|
-
};
|
|
833
|
-
const fromPKCS8 = (pem, alg, options) => {
|
|
834
|
-
return genericImport("PRIVATE KEY", "pkcs8", pem, alg, options);
|
|
835
|
-
};
|
|
836
|
-
const fromSPKI = (pem, alg, options) => {
|
|
837
|
-
return genericImport("PUBLIC KEY", "spki", pem, alg, options);
|
|
838
|
-
};
|
|
839
|
-
const fromX509 = (pem, alg, options) => {
|
|
840
|
-
let spki;
|
|
841
|
-
try {
|
|
842
|
-
spki = getSPKI(pem);
|
|
843
|
-
} catch (error_) {
|
|
844
|
-
throw new JWTError("Failed to parse the X.509 certificate.", "ERR_JWK_INVALID", error_);
|
|
845
|
-
}
|
|
846
|
-
return fromSPKI(spki, alg, options);
|
|
847
|
-
};
|
|
848
|
-
const formatPEM = (b64, descriptor) => {
|
|
849
|
-
return `-----BEGIN ${descriptor}-----\n${(b64.match(/.{1,64}/g) || []).join("\n")}\n-----END ${descriptor}-----`;
|
|
850
|
-
};
|
|
851
|
-
const pemBody = (pem, descriptor) => {
|
|
852
|
-
const begin = `-----BEGIN ${descriptor}-----`;
|
|
853
|
-
const end = `-----END ${descriptor}-----`;
|
|
854
|
-
const start = pem.indexOf(begin);
|
|
855
|
-
if (start === -1) {
|
|
856
|
-
const found = pem.match(/-----BEGIN [^-]+-----/)?.[0];
|
|
857
|
-
throw new JWTError(found ? `PEM label mismatch: expected "${begin}", got "${found}".` : `PEM label missing: expected "${begin}".`, "ERR_JWK_INVALID");
|
|
858
|
-
}
|
|
859
|
-
const bodyStart = start + begin.length;
|
|
860
|
-
const bodyEnd = pem.indexOf(end, bodyStart);
|
|
861
|
-
if (bodyEnd === -1) throw new JWTError(`PEM label missing: expected "${end}".`, "ERR_JWK_INVALID");
|
|
862
|
-
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");
|
|
863
|
-
return pem.slice(bodyStart, bodyEnd).replaceAll(/\s/g, "");
|
|
864
|
-
};
|
|
865
|
-
const genericExport = async (keyType, keyFormat, key) => {
|
|
866
|
-
if (!isCryptoKey(key)) throw new JWTError(`Key must be of type CryptoKey, got ${typeof key}`, "ERR_INVALID_OPTION");
|
|
867
|
-
if (!key.extractable) throw new JWTError("CryptoKey is not extractable", "ERR_JWK_INVALID");
|
|
868
|
-
if (key.type !== keyType) throw new JWTError(`key is not a ${keyType} key`, "ERR_JWK_INVALID");
|
|
869
|
-
return formatPEM(base64Stringify(new Uint8Array(await crypto.subtle.exportKey(keyFormat, key))), `${keyType.toUpperCase()} KEY`);
|
|
870
|
-
};
|
|
871
|
-
const genericImport = async (descriptor, keyFormat, pem, alg, options) => {
|
|
872
|
-
let algorithm;
|
|
873
|
-
let keyUsages;
|
|
874
|
-
const body = pemBody(pem, descriptor);
|
|
875
|
-
let keyData;
|
|
876
|
-
try {
|
|
877
|
-
keyData = base64Parse(body, { returnAs: "uint8array" });
|
|
878
|
-
} catch (error) {
|
|
879
|
-
throw new JWTError("Invalid PEM: body could not be decoded.", "ERR_JWK_INVALID", error);
|
|
880
|
-
}
|
|
881
|
-
const isPublic = keyFormat === "spki";
|
|
882
|
-
switch (alg) {
|
|
883
|
-
case "PS256":
|
|
884
|
-
case "PS384":
|
|
885
|
-
case "PS512":
|
|
886
|
-
algorithm = {
|
|
887
|
-
name: "RSA-PSS",
|
|
888
|
-
hash: jwsHashOf(alg)
|
|
889
|
-
};
|
|
890
|
-
keyUsages = isPublic ? ["verify"] : ["sign"];
|
|
891
|
-
break;
|
|
892
|
-
case "RS256":
|
|
893
|
-
case "RS384":
|
|
894
|
-
case "RS512":
|
|
895
|
-
algorithm = {
|
|
896
|
-
name: "RSASSA-PKCS1-v1_5",
|
|
897
|
-
hash: jwsHashOf(alg)
|
|
898
|
-
};
|
|
899
|
-
keyUsages = isPublic ? ["verify"] : ["sign"];
|
|
900
|
-
break;
|
|
901
|
-
case "RSA-OAEP":
|
|
902
|
-
case "RSA-OAEP-256":
|
|
903
|
-
case "RSA-OAEP-384":
|
|
904
|
-
case "RSA-OAEP-512":
|
|
905
|
-
algorithm = {
|
|
906
|
-
name: "RSA-OAEP",
|
|
907
|
-
hash: rsaOaepHashOf(alg)
|
|
908
|
-
};
|
|
909
|
-
keyUsages = isPublic ? ["encrypt", "wrapKey"] : ["decrypt", "unwrapKey"];
|
|
910
|
-
break;
|
|
911
|
-
case "ES256":
|
|
912
|
-
case "ES384":
|
|
913
|
-
case "ES512":
|
|
914
|
-
algorithm = {
|
|
915
|
-
name: "ECDSA",
|
|
916
|
-
namedCurve: ECDSA_CURVES[alg]
|
|
917
|
-
};
|
|
918
|
-
keyUsages = isPublic ? ["verify"] : ["sign"];
|
|
919
|
-
break;
|
|
920
|
-
case "ECDH-ES":
|
|
921
|
-
case "ECDH-ES+A128KW":
|
|
922
|
-
case "ECDH-ES+A192KW":
|
|
923
|
-
case "ECDH-ES+A256KW": {
|
|
924
|
-
const namedCurve = getNamedCurve(keyData);
|
|
925
|
-
if (namedCurve) algorithm = {
|
|
926
|
-
name: "ECDH",
|
|
927
|
-
namedCurve
|
|
928
|
-
};
|
|
929
|
-
else algorithm = { name: requireOKPCurve(keyData, "X25519") };
|
|
930
|
-
keyUsages = isPublic ? [] : ["deriveBits"];
|
|
931
|
-
break;
|
|
932
|
-
}
|
|
933
|
-
case "Ed25519":
|
|
934
|
-
case "EdDSA":
|
|
935
|
-
algorithm = { name: requireOKPCurve(keyData, "Ed25519") };
|
|
936
|
-
keyUsages = isPublic ? ["verify"] : ["sign"];
|
|
937
|
-
break;
|
|
938
|
-
default: throw new JWTError("Invalid or unsupported \"alg\" (Algorithm) value", "ERR_JWK_UNSUPPORTED");
|
|
939
|
-
}
|
|
940
|
-
try {
|
|
941
|
-
return await crypto.subtle.importKey(keyFormat, keyData, algorithm, options?.extractable ?? isPublic, keyUsages);
|
|
942
|
-
} catch (error) {
|
|
943
|
-
throw new JWTError(`Invalid PEM: the body could not be imported as "${alg}".`, "ERR_JWK_INVALID", error);
|
|
944
|
-
}
|
|
945
|
-
};
|
|
946
|
-
const findOid = (keyData, oid) => {
|
|
947
|
-
const needle = [
|
|
948
|
-
6,
|
|
949
|
-
oid.length,
|
|
950
|
-
...oid
|
|
951
|
-
];
|
|
952
|
-
const last = keyData.length - needle.length;
|
|
953
|
-
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;
|
|
954
|
-
return false;
|
|
955
|
-
};
|
|
956
|
-
const OKP_CURVE_OIDS = {
|
|
957
|
-
X25519: [
|
|
958
|
-
43,
|
|
959
|
-
101,
|
|
960
|
-
110
|
|
961
|
-
],
|
|
962
|
-
Ed25519: [
|
|
963
|
-
43,
|
|
964
|
-
101,
|
|
965
|
-
112
|
|
966
|
-
]
|
|
967
|
-
};
|
|
968
|
-
const requireOKPCurve = (keyData, crv) => {
|
|
969
|
-
if (findOid(keyData, OKP_CURVE_OIDS[crv])) return crv;
|
|
970
|
-
throw new JWTError(`Invalid PEM: unsupported curve, expected the ${crv} OID.`, "ERR_JWK_INVALID");
|
|
971
|
-
};
|
|
972
|
-
const getNamedCurve = (keyData) => {
|
|
973
|
-
switch (true) {
|
|
974
|
-
case findOid(keyData, [
|
|
975
|
-
42,
|
|
976
|
-
134,
|
|
977
|
-
72,
|
|
978
|
-
206,
|
|
979
|
-
61,
|
|
980
|
-
3,
|
|
981
|
-
1,
|
|
982
|
-
7
|
|
983
|
-
]): return "P-256";
|
|
984
|
-
case findOid(keyData, [
|
|
985
|
-
43,
|
|
986
|
-
129,
|
|
987
|
-
4,
|
|
988
|
-
0,
|
|
989
|
-
34
|
|
990
|
-
]): return "P-384";
|
|
991
|
-
case findOid(keyData, [
|
|
992
|
-
43,
|
|
993
|
-
129,
|
|
994
|
-
4,
|
|
995
|
-
0,
|
|
996
|
-
35
|
|
997
|
-
]): return "P-521";
|
|
998
|
-
default: return;
|
|
999
|
-
}
|
|
1000
|
-
};
|
|
1001
|
-
const getElement = (seq) => {
|
|
1002
|
-
const result = [];
|
|
1003
|
-
let next = 0;
|
|
1004
|
-
while (next < seq.length) {
|
|
1005
|
-
const nextPart = parseElement(seq.subarray(next));
|
|
1006
|
-
if (nextPart.byteLength <= 0) throw new JWTError("Invalid DER: zero-length element", "ERR_JWK_INVALID");
|
|
1007
|
-
result.push(nextPart);
|
|
1008
|
-
next += nextPart.byteLength;
|
|
1009
|
-
}
|
|
1010
|
-
return result;
|
|
1011
|
-
};
|
|
1012
|
-
const parseElement = (bytes) => {
|
|
1013
|
-
let position = 0;
|
|
1014
|
-
const at = (index) => {
|
|
1015
|
-
const byte = bytes[index];
|
|
1016
|
-
if (byte === void 0) throw new JWTError("Invalid DER: element is truncated", "ERR_JWK_INVALID");
|
|
1017
|
-
return byte;
|
|
1018
|
-
};
|
|
1019
|
-
let tag = at(0) & 31;
|
|
1020
|
-
position++;
|
|
1021
|
-
if (tag === 31) {
|
|
1022
|
-
tag = 0;
|
|
1023
|
-
while (at(position) >= 128) {
|
|
1024
|
-
tag = tag * 128 + at(position) - 128;
|
|
1025
|
-
position++;
|
|
1026
|
-
}
|
|
1027
|
-
tag = tag * 128 + at(position) - 128;
|
|
1028
|
-
position++;
|
|
1029
|
-
}
|
|
1030
|
-
let length = 0;
|
|
1031
|
-
if (at(position) < 128) {
|
|
1032
|
-
length = at(position);
|
|
1033
|
-
position++;
|
|
1034
|
-
} else if (at(position) === 128) {
|
|
1035
|
-
length = 0;
|
|
1036
|
-
while (position + length + 1 < bytes.byteLength) {
|
|
1037
|
-
if (at(position + length) === 0 && at(position + length + 1) === 0) break;
|
|
1038
|
-
length++;
|
|
1039
|
-
}
|
|
1040
|
-
if (position + length + 1 >= bytes.byteLength) throw new JWTError("Invalid DER: invalid indefinite form length", "ERR_JWK_INVALID");
|
|
1041
|
-
const byteLength = position + length + 2;
|
|
1042
|
-
return {
|
|
1043
|
-
byteLength,
|
|
1044
|
-
contents: bytes.subarray(position, position + length),
|
|
1045
|
-
raw: bytes.subarray(0, byteLength)
|
|
1046
|
-
};
|
|
1047
|
-
} else {
|
|
1048
|
-
const numberOfDigits = at(position) & 127;
|
|
1049
|
-
position++;
|
|
1050
|
-
length = 0;
|
|
1051
|
-
for (let i = 0; i < numberOfDigits; i++) {
|
|
1052
|
-
length = length * 256 + at(position);
|
|
1053
|
-
position++;
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
const byteLength = position + length;
|
|
1057
|
-
if (byteLength > bytes.byteLength) throw new JWTError("Invalid DER: element length exceeds the available bytes", "ERR_JWK_INVALID");
|
|
1058
|
-
return {
|
|
1059
|
-
byteLength,
|
|
1060
|
-
contents: bytes.subarray(position, byteLength),
|
|
1061
|
-
raw: bytes.subarray(0, byteLength)
|
|
1062
|
-
};
|
|
1063
|
-
};
|
|
1064
|
-
const spkiFromX509 = (buf) => {
|
|
1065
|
-
const tbs = getElement(parseElement(buf).contents)[0];
|
|
1066
|
-
if (!tbs) throw new JWTError("Invalid X.509 certificate: missing tbsCertificate", "ERR_JWK_INVALID");
|
|
1067
|
-
const tbsCertificate = getElement(tbs.contents);
|
|
1068
|
-
const first = tbsCertificate[0];
|
|
1069
|
-
if (!first) throw new JWTError("Invalid X.509 certificate: empty tbsCertificate", "ERR_JWK_INVALID");
|
|
1070
|
-
const spki = tbsCertificate[first.raw[0] === 160 ? 6 : 5];
|
|
1071
|
-
if (!spki) throw new JWTError("Invalid X.509 certificate: missing subjectPublicKeyInfo", "ERR_JWK_INVALID");
|
|
1072
|
-
return base64Stringify(spki.raw);
|
|
1073
|
-
};
|
|
1074
|
-
let createPublicKey;
|
|
1075
|
-
const getSPKI = (x509) => {
|
|
1076
|
-
try {
|
|
1077
|
-
createPublicKey ??= globalThis.process?.getBuiltinModule?.("node:crypto")?.createPublicKey;
|
|
1078
|
-
} catch {
|
|
1079
|
-
createPublicKey = 0;
|
|
1080
|
-
}
|
|
1081
|
-
const body = pemBody(x509, "CERTIFICATE");
|
|
1082
|
-
if (createPublicKey) try {
|
|
1083
|
-
return new createPublicKey(x509).export({
|
|
1084
|
-
format: "pem",
|
|
1085
|
-
type: "spki"
|
|
1086
|
-
});
|
|
1087
|
-
} catch {}
|
|
1088
|
-
let raw;
|
|
1089
|
-
try {
|
|
1090
|
-
raw = base64Parse(body, { returnAs: "uint8array" });
|
|
1091
|
-
} catch (error) {
|
|
1092
|
-
throw new JWTError("Invalid X.509 certificate: body could not be decoded.", "ERR_JWK_INVALID", error);
|
|
1093
|
-
}
|
|
1094
|
-
return formatPEM(spkiFromX509(raw), "PUBLIC KEY");
|
|
1095
|
-
};
|
|
1096
|
-
const THUMBPRINT_MEMBERS = /* @__PURE__ */ Object.freeze({
|
|
1097
|
-
EC: [
|
|
1098
|
-
"crv",
|
|
1099
|
-
"kty",
|
|
1100
|
-
"x",
|
|
1101
|
-
"y"
|
|
1102
|
-
],
|
|
1103
|
-
RSA: [
|
|
1104
|
-
"e",
|
|
1105
|
-
"kty",
|
|
1106
|
-
"n"
|
|
1107
|
-
],
|
|
1108
|
-
OKP: [
|
|
1109
|
-
"crv",
|
|
1110
|
-
"kty",
|
|
1111
|
-
"x"
|
|
1112
|
-
],
|
|
1113
|
-
oct: ["k", "kty"]
|
|
1114
|
-
});
|
|
1115
|
-
const THUMBPRINT_HASHES = /* @__PURE__ */ new Set([
|
|
1116
|
-
"SHA-256",
|
|
1117
|
-
"SHA-384",
|
|
1118
|
-
"SHA-512"
|
|
1119
|
-
]);
|
|
1120
|
-
async function thumbprint(jwk, options = {}) {
|
|
1121
|
-
assertPlainObject(jwk, "jwk");
|
|
1122
|
-
assertPlainObject(options, "options");
|
|
1123
|
-
const hash = options.hash ?? "SHA-256";
|
|
1124
|
-
if (!THUMBPRINT_HASHES.has(hash)) throw new JWTError("\"hash\" must be \"SHA-256\", \"SHA-384\" or \"SHA-512\".", "ERR_INVALID_OPTION");
|
|
1125
|
-
const kty = jwk.kty;
|
|
1126
|
-
const members = typeof kty === "string" && Object.hasOwn(THUMBPRINT_MEMBERS, kty) ? THUMBPRINT_MEMBERS[kty] : void 0;
|
|
1127
|
-
if (!members) throw new JWTError(`JWK thumbprint is not defined for kty "${String(kty)}".`, "ERR_JWK_INVALID");
|
|
1128
|
-
const canonical = members.map((name) => {
|
|
1129
|
-
const value = Reflect.get(jwk, name);
|
|
1130
|
-
if (typeof value !== "string" || value.length === 0) throw new JWTError(`JWK thumbprint requires the member "${name}" on an ${jwk.kty} key.`, "ERR_JWK_INVALID");
|
|
1131
|
-
return `${JSON.stringify(name)}:${JSON.stringify(value)}`;
|
|
1132
|
-
});
|
|
1133
|
-
return digestUrl(`{${canonical.join(",")}}`, hash);
|
|
1134
|
-
}
|
|
1135
|
-
const MAX_PARTY_INFO_BYTES = 1024;
|
|
1136
|
-
const DEFAULT_JOSE_LIMITS = /* @__PURE__ */ Object.freeze({
|
|
1137
|
-
signatures: 16,
|
|
1138
|
-
recipients: 16,
|
|
1139
|
-
keyCandidates: 8,
|
|
1140
|
-
pbes2Iterations: 4 * DEFAULT_PBES2_ITERATIONS
|
|
1141
|
-
});
|
|
1142
|
-
function resolveLimit(limits, name) {
|
|
1143
|
-
const value = limits?.[name];
|
|
1144
|
-
if (value === void 0) return DEFAULT_JOSE_LIMITS[name];
|
|
1145
|
-
if (typeof value !== "number" || !Number.isInteger(value) || value < 1) throw new JWTError(`"limits.${name}" must be a positive integer.`, "ERR_INVALID_OPTION");
|
|
1146
|
-
return value;
|
|
1147
|
-
}
|
|
1148
|
-
function assertEntryLimit(count, limit, options) {
|
|
1149
|
-
if (count <= limit) return;
|
|
1150
|
-
throw new JWTError(`Invalid ${options.family}: ${count} ${options.what} exceed the limit of ${limit}.`, `ERR_${options.family}_INVALID`);
|
|
1151
|
-
}
|
|
1152
|
-
function assertCandidateLimit(count, limit) {
|
|
1153
|
-
if (count <= limit) return;
|
|
1154
|
-
throw new JWTError(`${count} candidate keys exceed the limit of ${limit}; narrow the JWK Set with a "kid".`, "ERR_JWK_KEY_NOT_FOUND");
|
|
1155
|
-
}
|
|
1156
|
-
function assertJWKSetCandidates(candidates, options) {
|
|
1157
|
-
if (candidates.length === 0) {
|
|
1158
|
-
const { kid } = options;
|
|
1159
|
-
throw new JWTError(`No key found in JWK Set${kid ? ` with kid ${quoteTokenValue(kid)}` : ""}.`, "ERR_JWK_KEY_NOT_FOUND");
|
|
1160
|
-
}
|
|
1161
|
-
assertCandidateLimit(candidates.length, options.limit);
|
|
1162
|
-
}
|
|
1163
|
-
function createPBES2Budget(limit) {
|
|
1164
|
-
let remaining = limit;
|
|
1165
|
-
return { spend(attempt) {
|
|
1166
|
-
const { alg, p2c } = attempt;
|
|
1167
|
-
if (!alg.startsWith("PBES2")) return;
|
|
1168
|
-
if (typeof p2c !== "number" || !(p2c > 0)) return;
|
|
1169
|
-
assertPBES2Iterations(p2c, attempt);
|
|
1170
|
-
if (p2c > remaining) throw new JWTError(`PBES2 iteration budget of ${limit} exhausted.`, "ERR_JWE_INVALID");
|
|
1171
|
-
remaining -= p2c;
|
|
1172
|
-
} };
|
|
1173
|
-
}
|
|
1174
|
-
function serializeProtectedHeader(header) {
|
|
1175
|
-
try {
|
|
1176
|
-
return JSON.stringify(header);
|
|
1177
|
-
} catch (error) {
|
|
1178
|
-
throw new JWTError("The protected header is not JSON-serializable.", "ERR_INVALID_OPTION", error);
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
function assertProducerCrit(header, family) {
|
|
1182
|
-
const crit = header.crit;
|
|
1183
|
-
if (crit === void 0) return;
|
|
1184
|
-
assertCritShape(crit, {
|
|
1185
|
-
family,
|
|
1186
|
-
code: "ERR_INVALID_OPTION"
|
|
1187
|
-
});
|
|
1188
|
-
for (const param of crit) {
|
|
1189
|
-
if (Object.hasOwn(header, param)) continue;
|
|
1190
|
-
throw new JWTError(`"crit" lists "${param}", which the protected header does not carry.`, "ERR_INVALID_OPTION");
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
const PROTECTED_ONLY_PARAMS = {
|
|
1194
|
-
JWS: ["crit", "b64"],
|
|
1195
|
-
JWE: ["crit"]
|
|
1196
|
-
};
|
|
1197
|
-
function assertProtectedOnlyOption(header, options) {
|
|
1198
|
-
if (!header) return;
|
|
1199
|
-
const { tier, family } = options;
|
|
1200
|
-
for (const param of PROTECTED_ONLY_PARAMS[family]) {
|
|
1201
|
-
if (!Object.hasOwn(header, param)) continue;
|
|
1202
|
-
throw new JWTError(`${family} "${param}" must be integrity protected; move it from the ${tier} header to the protected header.`, "ERR_INVALID_OPTION");
|
|
1203
|
-
}
|
|
1204
|
-
}
|
|
1205
|
-
function assertProducerKeyHeaders(header, options) {
|
|
1206
|
-
if (!header) return;
|
|
1207
|
-
const { tier, family } = options;
|
|
1208
|
-
if (Object.hasOwn(header, "jwk") && !isPublicJWK(Reflect.get(header, "jwk"))) throw new JWTError(`${family} "jwk" in the ${tier} header must be a public JWK; pass the pair's publicKey half.`, "ERR_INVALID_OPTION");
|
|
1209
|
-
if (Object.hasOwn(header, "epk")) throw new JWTError(`${family} "epk" is written by the ECDH-ES key agreement; pass an ephemeral key through "ecdh.ephemeralKey" instead of the ${tier} header.`, "ERR_INVALID_OPTION");
|
|
1210
|
-
}
|
|
1211
|
-
function assertProducerConfirmation(payload) {
|
|
1212
|
-
if (typeof payload !== "object" || payload === null) return;
|
|
1213
|
-
const cnf = Reflect.get(payload, "cnf");
|
|
1214
|
-
if (typeof cnf !== "object" || cnf === null || !Object.hasOwn(cnf, "jwk")) return;
|
|
1215
|
-
if (!isPublicJWK(Reflect.get(cnf, "jwk"))) throw new JWTError("Invalid JWT \"cnf\" (Confirmation) Claim: \"jwk\" must be a public JWK (RFC 7800 §3.2).", "ERR_JWT_CLAIM_INVALID");
|
|
1216
|
-
}
|
|
1217
|
-
function assertOptionEntry(entry, options) {
|
|
1218
|
-
const { label, objects } = options;
|
|
1219
|
-
assertPlainObject(entry, label);
|
|
1220
|
-
for (const member of objects) if (entry[member] !== void 0) assertPlainObject(entry[member], `${label}.${member}`);
|
|
1221
|
-
}
|
|
1222
|
-
function assertProducerPartyInfo(name, value) {
|
|
1223
|
-
if (!(value instanceof Uint8Array)) throw new JWTError(`"${name === "apu" ? "partyUInfo" : "partyVInfo"}" (${name}) must be a Uint8Array.`, "ERR_INVALID_OPTION");
|
|
1224
|
-
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");
|
|
1225
|
-
}
|
|
1226
|
-
function assertProducerCEK(cek, enc) {
|
|
1227
|
-
if (cek === void 0) return;
|
|
1228
|
-
const expected = bitLengthCEK(enc) >> 3;
|
|
1229
|
-
if (!(cek instanceof Uint8Array) || cek.length !== expected) throw new JWTError(`"cek" must be ${expected} bytes for ${enc} (got ${cek instanceof Uint8Array ? cek.length : typeof cek}).`, "ERR_INVALID_OPTION");
|
|
1230
|
-
}
|
|
1231
|
-
function assertProducerIV(iv, enc) {
|
|
1232
|
-
if (iv === void 0) return;
|
|
1233
|
-
const expected = bitLengthIV(enc) >> 3;
|
|
1234
|
-
if (!(iv instanceof Uint8Array) || iv.length !== expected) throw new JWTError(`"contentEncryptionIV" must be ${expected} bytes for ${enc} (got ${iv instanceof Uint8Array ? iv.length : typeof iv}).`, "ERR_INVALID_OPTION");
|
|
1235
|
-
}
|
|
1236
|
-
function assertConsumerOptions(options) {
|
|
1237
|
-
assertOptionEntry(options, {
|
|
1238
|
-
label: "options",
|
|
1239
|
-
objects: ["limits"]
|
|
1240
|
-
});
|
|
1241
|
-
const typ = options.typ;
|
|
1242
|
-
if (typ !== void 0 && (typeof typ !== "string" || typ === "")) throw new JWTError("\"typ\" must be a non-empty string.", "ERR_INVALID_OPTION");
|
|
1243
|
-
assertStringList(options.algorithms, "algorithms");
|
|
1244
|
-
assertStringList(options.encryptionAlgorithms, "encryptionAlgorithms");
|
|
1245
|
-
assertStringList(options.recognizedHeaders, "recognizedHeaders");
|
|
1246
|
-
assertClaimValidationOptions(options);
|
|
1247
|
-
}
|
|
1248
|
-
const JWK_SET_KEY_OPS = /* @__PURE__ */ Object.freeze({
|
|
1249
|
-
sig: ["verify"],
|
|
1250
|
-
enc: [
|
|
1251
|
-
"unwrapKey",
|
|
1252
|
-
"decrypt",
|
|
1253
|
-
"deriveBits",
|
|
1254
|
-
"deriveKey"
|
|
1255
|
-
]
|
|
1256
|
-
});
|
|
1257
|
-
function buildJWKSetFilter(header, options) {
|
|
1258
|
-
const kid = header?.kid;
|
|
1259
|
-
const alg = header?.alg;
|
|
1260
|
-
const { op, allowSymmetric } = options;
|
|
1261
|
-
const use = op;
|
|
1262
|
-
const keyOps = JWK_SET_KEY_OPS[op];
|
|
1263
|
-
return (k) => {
|
|
1264
|
-
if (!isJWK(k)) return false;
|
|
1265
|
-
if (k.kty === "oct" && !allowSymmetric) return false;
|
|
1266
|
-
if (kid && k.kid !== kid) return false;
|
|
1267
|
-
if (k.alg && k.alg !== alg) return false;
|
|
1268
|
-
if (k.use !== void 0 && k.use !== use) return false;
|
|
1269
|
-
const ops = k.key_ops;
|
|
1270
|
-
if (ops !== void 0 && !(Array.isArray(ops) && ops.some((o) => keyOps.includes(o)))) return false;
|
|
1271
|
-
return true;
|
|
1272
|
-
};
|
|
1273
|
-
}
|
|
1274
|
-
function parseEphemeralKey(ephemeralKey) {
|
|
1275
|
-
if (isCryptoKeyPair(ephemeralKey)) return {
|
|
1276
|
-
epk: ephemeralKey.publicKey,
|
|
1277
|
-
epkPrivateKey: ephemeralKey.privateKey
|
|
1278
|
-
};
|
|
1279
|
-
if (typeof ephemeralKey === "object" && ephemeralKey !== null && "publicKey" in ephemeralKey && "privateKey" in ephemeralKey) {
|
|
1280
|
-
if (!ephemeralKey.publicKey || !ephemeralKey.privateKey) throw new JWTError("ECDH-ES custom ephemeral key must include both publicKey and privateKey.", "ERR_JWK_INVALID");
|
|
1281
|
-
return {
|
|
1282
|
-
epk: ephemeralKey.publicKey,
|
|
1283
|
-
epkPrivateKey: ephemeralKey.privateKey
|
|
1284
|
-
};
|
|
1285
|
-
}
|
|
1286
|
-
if (isCryptoKey(ephemeralKey)) {
|
|
1287
|
-
if (ephemeralKey.type !== "private") throw new JWTError("ECDH-ES custom ephemeral CryptoKey must include private key material.", "ERR_JWK_INVALID");
|
|
1288
|
-
return {
|
|
1289
|
-
epk: ephemeralKey,
|
|
1290
|
-
epkPrivateKey: ephemeralKey
|
|
1291
|
-
};
|
|
1292
|
-
}
|
|
1293
|
-
if (isJWK(ephemeralKey)) {
|
|
1294
|
-
if (!("d" in ephemeralKey) || typeof ephemeralKey.d !== "string") throw new JWTError("ECDH-ES custom ephemeral JWK must include private parameter \"d\".", "ERR_JWK_INVALID");
|
|
1295
|
-
return {
|
|
1296
|
-
epk: ephemeralKey,
|
|
1297
|
-
epkPrivateKey: ephemeralKey
|
|
1298
|
-
};
|
|
1299
|
-
}
|
|
1300
|
-
throw new JWTError("Unsupported ECDH-ES ephemeral key material provided.", "ERR_JWK_INVALID");
|
|
1301
|
-
}
|
|
1302
|
-
function buildECDHAgreementParams(ecdh) {
|
|
1303
|
-
const params = {};
|
|
1304
|
-
if (!ecdh) return params;
|
|
1305
|
-
const { partyUInfo, partyVInfo, ephemeralKey } = ecdh;
|
|
1306
|
-
if (partyUInfo !== void 0) {
|
|
1307
|
-
assertProducerPartyInfo("apu", partyUInfo);
|
|
1308
|
-
if (partyUInfo.length > 0) params.apu = partyUInfo;
|
|
1309
|
-
}
|
|
1310
|
-
if (partyVInfo !== void 0) {
|
|
1311
|
-
assertProducerPartyInfo("apv", partyVInfo);
|
|
1312
|
-
if (partyVInfo.length > 0) params.apv = partyVInfo;
|
|
1313
|
-
}
|
|
1314
|
-
if (ephemeralKey) {
|
|
1315
|
-
const { epk, epkPrivateKey } = parseEphemeralKey(ephemeralKey);
|
|
1316
|
-
params.epk = epk;
|
|
1317
|
-
params.epkPrivateKey = epkPrivateKey;
|
|
1318
|
-
}
|
|
1319
|
-
return params;
|
|
1320
|
-
}
|
|
1321
|
-
async function resolveSigningKey(alg, key, usage) {
|
|
1322
|
-
if (key instanceof Uint8Array) {
|
|
1323
|
-
if (alg !== "HS256" && alg !== "HS384" && alg !== "HS512") throw new JWTError(`Raw key bytes are HMAC secrets; ${alg} needs an asymmetric key. Pass a JWK or a CryptoKey.`, "ERR_JWK_INVALID");
|
|
1324
|
-
assertOctKeyLength(key, alg);
|
|
1325
|
-
return crypto.subtle.importKey("raw", key, {
|
|
1326
|
-
name: "HMAC",
|
|
1327
|
-
hash: jwsHashOf(alg)
|
|
1328
|
-
}, false, [usage]);
|
|
1329
|
-
}
|
|
1330
|
-
if (RSA_SIGNATURE_ALGORITHMS.has(alg)) {
|
|
1331
|
-
const modulusLength = modulusLengthOf(key.algorithm);
|
|
1332
|
-
if (typeof modulusLength !== "number" || modulusLength < 2048) throw new JWTError(`${alg} requires a key modulusLength of at least 2048 bits`, "ERR_JWK_INVALID");
|
|
1333
|
-
}
|
|
1334
|
-
return key;
|
|
1335
|
-
}
|
|
1336
|
-
function validateClaimsIfJson(payload, mode, options) {
|
|
1337
|
-
if (mode !== "json" || options.validateClaims === false) return;
|
|
1338
|
-
validateJwtClaims(payload, options);
|
|
1339
|
-
}
|
|
1340
|
-
const PAYLOAD_MODES = /* @__PURE__ */ new Set([
|
|
1341
|
-
"json",
|
|
1342
|
-
"string",
|
|
1343
|
-
"bytes"
|
|
1344
|
-
]);
|
|
1345
|
-
function resolvePayloadMode(options) {
|
|
1346
|
-
const { payload, forceUint8Array, validateClaims } = options;
|
|
1347
|
-
if (payload !== void 0 && !PAYLOAD_MODES.has(payload)) throw new JWTError("\"payload\" must be \"json\", \"string\" or \"bytes\".", "ERR_INVALID_OPTION");
|
|
1348
|
-
let mode = payload ?? "json";
|
|
1349
|
-
if (forceUint8Array !== void 0) {
|
|
1350
|
-
if (payload === void 0) mode = forceUint8Array ? "bytes" : "json";
|
|
1351
|
-
else if (forceUint8Array !== (payload === "bytes")) throw new JWTError("\"forceUint8Array\" contradicts \"payload\"; pass \"payload\" alone.", "ERR_INVALID_OPTION");
|
|
1352
|
-
}
|
|
1353
|
-
if (validateClaims === true && mode !== "json") throw new JWTError("\"validateClaims\" requires payload: \"json\".", "ERR_INVALID_OPTION");
|
|
1354
|
-
return mode;
|
|
1355
|
-
}
|
|
1356
|
-
function stringKeyError() {
|
|
1357
|
-
return new JWTError("String keys are not accepted: pass an oct JWK or a Uint8Array secret, or import PEM text with importPEM().", "ERR_JWK_INVALID");
|
|
1358
|
-
}
|
|
1359
|
-
function assertNotStringKey(key) {
|
|
1360
|
-
if (typeof key === "string") throw stringKeyError();
|
|
1361
|
-
}
|
|
1362
|
-
function resolvePasswordKey(alg, key) {
|
|
1363
|
-
if (typeof key !== "string") return key;
|
|
1364
|
-
if (!isPBES2Algorithm(alg)) throw stringKeyError();
|
|
1365
|
-
return textEncoder.encode(key);
|
|
1366
|
-
}
|
|
1367
|
-
const JWS_ALG_CTX = /* @__PURE__ */ Object.freeze({
|
|
1368
|
-
infer: inferJWSAllowedAlgorithms,
|
|
1369
|
-
octRoles: false,
|
|
1370
|
-
errorCode: "ERR_JWS_ALG_NOT_ALLOWED",
|
|
1371
|
-
label: "Algorithm",
|
|
1372
|
-
op: "sig"
|
|
1373
|
-
});
|
|
1374
|
-
const JWE_ALG_CTX = /* @__PURE__ */ Object.freeze({
|
|
1375
|
-
infer: inferJWEAllowedAlgorithms,
|
|
1376
|
-
octRoles: true,
|
|
1377
|
-
errorCode: "ERR_JWE_ALG_NOT_ALLOWED",
|
|
1378
|
-
label: "Key management algorithm",
|
|
1379
|
-
op: "enc"
|
|
1380
|
-
});
|
|
1381
|
-
function checkAlgAllowed(alg, key, options) {
|
|
1382
|
-
const { ctx, algorithms, allowSymmetric } = options;
|
|
1383
|
-
if (algorithms !== void 0) return algorithms.includes(alg) ? void 0 : notAllowed(alg, ctx);
|
|
1384
|
-
if (isJWK(key) && typeof key.alg === "string" && (!ctx.octRoles || key.kty !== "oct")) return key.alg === alg || isEd25519Signature(key.alg) && isEd25519Signature(alg) ? void 0 : notAllowed(alg, ctx);
|
|
1385
|
-
const inferred = ctx.infer(key, { allowSymmetric });
|
|
1386
|
-
if (!inferred) return new JWTError(`Cannot infer allowed ${ctx.label.toLowerCase()}s from this key; pass "options.algorithms" explicitly.`, ctx.errorCode);
|
|
1387
|
-
return inferred.includes(alg) ? void 0 : notAllowed(alg, ctx);
|
|
1388
|
-
}
|
|
1389
|
-
function notAllowed(alg, ctx) {
|
|
1390
|
-
return new JWTError(`${ctx.label} not allowed: ${alg}`, ctx.errorCode);
|
|
1391
|
-
}
|
|
1392
|
-
function keyMatchesHeader(header, keyMaterial, options) {
|
|
1393
|
-
const { ctx, allowSymmetric } = options;
|
|
1394
|
-
if (isJWKSet(keyMaterial)) {
|
|
1395
|
-
const candidates = keyMaterial.keys.filter(buildJWKSetFilter(header, {
|
|
1396
|
-
op: ctx.op,
|
|
1397
|
-
allowSymmetric
|
|
1398
|
-
}));
|
|
1399
|
-
return candidates.length === 0 || candidates.some((k) => keyServesHeaderAlg(header, k, ctx));
|
|
1400
|
-
}
|
|
1401
|
-
if (isJWK(keyMaterial)) return keyServesHeaderAlg(header, keyMaterial, ctx);
|
|
1402
|
-
if (isCryptoKey(keyMaterial)) return keyServesHeaderAlg(header, keyMaterial, ctx);
|
|
1403
|
-
return true;
|
|
1404
|
-
}
|
|
1405
|
-
function assertJWSAlgSupported(alg) {
|
|
1406
|
-
const error = checkJWSAlgSupported(alg);
|
|
1407
|
-
if (error) throw error;
|
|
1408
|
-
}
|
|
1409
|
-
function checkJWSAlgSupported(alg) {
|
|
1410
|
-
return checkAlgSupported(alg, {
|
|
1411
|
-
supported: isJWSAlgorithm,
|
|
1412
|
-
code: "ERR_JWS_ALG_NOT_ALLOWED",
|
|
1413
|
-
label: "Algorithm",
|
|
1414
|
-
role: "signing algorithm"
|
|
1415
|
-
});
|
|
1416
|
-
}
|
|
1417
|
-
function checkJWSB64(header) {
|
|
1418
|
-
if (Object.hasOwn(header, "b64") && typeof header.b64 !== "boolean") return new JWTError("Invalid JWS: \"b64\" must be a boolean (RFC 7797 §3).", "ERR_JWS_INVALID");
|
|
1419
|
-
if (header.b64 !== false) return void 0;
|
|
1420
|
-
if (Array.isArray(header.crit) && header.crit.includes("b64")) return void 0;
|
|
1421
|
-
return new JWTError("Invalid JWS: \"b64\" must be listed in \"crit\" (RFC 7797 §6).", "ERR_JWS_INVALID");
|
|
1422
|
-
}
|
|
1423
|
-
function assertB64Option(header) {
|
|
1424
|
-
if (!header || !Object.hasOwn(header, "b64") || typeof header.b64 === "boolean") return;
|
|
1425
|
-
throw new JWTError("\"b64\" must be a boolean (RFC 7797 §3).", "ERR_INVALID_OPTION");
|
|
1426
|
-
}
|
|
1427
|
-
function applyB64Header(header) {
|
|
1428
|
-
assertB64Option(header);
|
|
1429
|
-
if (header.b64 === true) {
|
|
1430
|
-
if (!(Array.isArray(header.crit) && header.crit.includes("b64"))) delete header.b64;
|
|
1431
|
-
return;
|
|
1432
|
-
}
|
|
1433
|
-
if (header.b64 !== false) return;
|
|
1434
|
-
const crit = header.crit;
|
|
1435
|
-
if (crit === void 0) header.crit = ["b64"];
|
|
1436
|
-
else if (Array.isArray(crit) && !crit.includes("b64")) header.crit = [...crit, "b64"];
|
|
1437
|
-
}
|
|
1438
|
-
function assertCompactToken(token, family) {
|
|
1439
|
-
if (typeof token !== "string") throw new JWTError(`Invalid ${family}: token must be a string.`, `ERR_${family}_INVALID`);
|
|
1440
|
-
}
|
|
1441
|
-
function assertSerializationObject(value, family) {
|
|
1442
|
-
if (!isPlainObject(value)) throw new JWTError(`Invalid ${family} JSON Serialization: expected an object.`, `ERR_${family}_INVALID_SERIALIZATION`);
|
|
1443
|
-
}
|
|
1444
|
-
function checkSerializationEntry(entry, options) {
|
|
1445
|
-
const { family, label, objects, strings } = options;
|
|
1446
|
-
const code = `ERR_${family}_INVALID`;
|
|
1447
|
-
if (!isPlainObject(entry)) return new JWTError(`Invalid ${family}: ${label} must be a JSON object.`, code);
|
|
1448
|
-
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);
|
|
1449
|
-
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);
|
|
1450
|
-
}
|
|
1451
|
-
function asKeyLookupError(error, what) {
|
|
1452
|
-
return asJWTError(error, {
|
|
1453
|
-
code: "ERR_JWK_KEY_NOT_FOUND",
|
|
1454
|
-
message: `Key lookup failed for ${what}.`
|
|
1455
|
-
});
|
|
1456
|
-
}
|
|
1457
|
-
function asJWTError(error, options) {
|
|
1458
|
-
if (error instanceof JWTError) return error;
|
|
1459
|
-
return new JWTError(options.message, options.code, error);
|
|
1460
|
-
}
|
|
1461
|
-
function asDecryptionError(error) {
|
|
1462
|
-
return asJWTError(error, {
|
|
1463
|
-
code: "ERR_JWE_DECRYPTION_FAILED",
|
|
1464
|
-
message: "JWE decryption failed."
|
|
1465
|
-
});
|
|
1466
|
-
}
|
|
1467
|
-
async function resolveKeyInput(key, options) {
|
|
1468
|
-
if (typeof key !== "function") return key;
|
|
1469
|
-
const { header, token, what } = options;
|
|
1470
|
-
let resolved;
|
|
1471
|
-
try {
|
|
1472
|
-
resolved = await key(header, token);
|
|
1473
|
-
} catch (error) {
|
|
1474
|
-
throw asKeyLookupError(error, what);
|
|
1475
|
-
}
|
|
1476
|
-
if (resolved === void 0 || resolved === null) throw new JWTError(`Key lookup returned no key for ${what}.`, "ERR_JWK_KEY_NOT_FOUND");
|
|
1477
|
-
return resolved;
|
|
1478
|
-
}
|
|
1479
|
-
function asKeyUsageError(error, alg) {
|
|
1480
|
-
return asJWTError(error, {
|
|
1481
|
-
code: "ERR_JWK_INVALID",
|
|
1482
|
-
message: `Key is not usable for ${alg}.`
|
|
1483
|
-
});
|
|
1484
|
-
}
|
|
1485
|
-
const JWE_ENC_CEK_FORMAT = /* @__PURE__ */ Object.freeze({
|
|
1486
|
-
"A128GCM": "cryptokey",
|
|
1487
|
-
"A192GCM": "cryptokey",
|
|
1488
|
-
"A256GCM": "cryptokey",
|
|
1489
|
-
"A128CBC-HS256": "raw",
|
|
1490
|
-
"A192CBC-HS384": "raw",
|
|
1491
|
-
"A256CBC-HS512": "raw"
|
|
1492
|
-
});
|
|
1493
|
-
function assertDirectKeyRole(key, enc) {
|
|
1494
|
-
if (!isJWK(key) || typeof key.alg !== "string" || key.alg === "dir") return;
|
|
1495
|
-
if (enc !== void 0 && key.alg === enc) return;
|
|
1496
|
-
throw new JWTError(`JWK "alg" is "${key.alg}" but "dir" requires a key declared as "dir" or as the token's "enc" (${enc ?? "unknown"}).`, "ERR_JWK_INVALID");
|
|
1497
|
-
}
|
|
1498
|
-
const JWE_DIRECT_ALGS = /* @__PURE__ */ new Set(["dir", "ECDH-ES"]);
|
|
1499
|
-
function checkJWEAlgSupported(alg) {
|
|
1500
|
-
return checkAlgSupported(alg, {
|
|
1501
|
-
supported: isKeyManagementAlgorithm,
|
|
1502
|
-
code: "ERR_JWE_ALG_NOT_ALLOWED",
|
|
1503
|
-
label: "Key management algorithm",
|
|
1504
|
-
role: "key management algorithm"
|
|
1505
|
-
});
|
|
1506
|
-
}
|
|
1507
|
-
function checkJWEEncSupported(enc) {
|
|
1508
|
-
if (typeof enc === "string" && isContentEncryptionAlgorithm(enc)) return void 0;
|
|
1509
|
-
return new JWTError(`Invalid JWE: unsupported "enc" ${quoteTokenValue(enc)}`, "ERR_JWE_INVALID");
|
|
1510
|
-
}
|
|
1511
|
-
function isJWEAlgSupported(alg) {
|
|
1512
|
-
return isKeyManagementAlgorithm(alg);
|
|
1513
|
-
}
|
|
1514
|
-
function assertJWEAlgSupported(alg) {
|
|
1515
|
-
const error = checkJWEAlgSupported(alg);
|
|
1516
|
-
if (error) throw error;
|
|
1517
|
-
}
|
|
1518
|
-
function assertJWEEncSupported(enc) {
|
|
1519
|
-
const error = checkJWEEncSupported(enc);
|
|
1520
|
-
if (error) throw error;
|
|
1521
|
-
}
|
|
1522
|
-
function checkJWEEncAllowed(enc, encryptionAlgorithms) {
|
|
1523
|
-
if (encryptionAlgorithms === void 0 || encryptionAlgorithms.includes(enc)) return void 0;
|
|
1524
|
-
return new JWTError(`Content encryption algorithm not allowed: ${enc}`, "ERR_JWE_ALG_NOT_ALLOWED");
|
|
1525
|
-
}
|
|
1526
|
-
async function exportReturnedCek(cek) {
|
|
1527
|
-
if (!isCryptoKey(cek)) return cek;
|
|
1528
|
-
if (!cek.extractable) throw new JWTError("\"returnCek\" requires an extractable content encryption key.", "ERR_INVALID_OPTION");
|
|
1529
|
-
return new Uint8Array(await crypto.subtle.exportKey("raw", cek));
|
|
1530
|
-
}
|
|
1531
|
-
function cekFormatForEnc(enc) {
|
|
1532
|
-
return JWE_ENC_CEK_FORMAT[enc];
|
|
1533
|
-
}
|
|
1534
|
-
function buildUnwrapKeyOptions(header, algorithms, options) {
|
|
1535
|
-
const { alg, enc } = algorithms;
|
|
1536
|
-
const opts = {
|
|
1537
|
-
alg,
|
|
1538
|
-
enc,
|
|
1539
|
-
format: cekFormatForEnc(enc),
|
|
1540
|
-
keyUsage: options?.keyUsage || ["decrypt"]
|
|
1541
|
-
};
|
|
1542
|
-
if (header.iv) opts.iv = header.iv;
|
|
1543
|
-
if (header.tag) opts.tag = header.tag;
|
|
1544
|
-
if (header.p2s) opts.p2s = header.p2s;
|
|
1545
|
-
if (header.p2c) opts.p2c = header.p2c;
|
|
1546
|
-
if (header.epk) opts.epk = header.epk;
|
|
1547
|
-
if (header.apu) opts.apu = header.apu;
|
|
1548
|
-
if (header.apv) opts.apv = header.apv;
|
|
1549
|
-
if (options?.unwrappedKeyAlgorithm) opts.unwrappedKeyAlgorithm = options.unwrappedKeyAlgorithm;
|
|
1550
|
-
if (options?.extractable !== void 0) opts.extractable = options.extractable;
|
|
1551
|
-
if (options?.minIterations !== void 0) opts.minIterations = options.minIterations;
|
|
1552
|
-
if (options?.maxIterations !== void 0) opts.maxIterations = options.maxIterations;
|
|
1553
|
-
return opts;
|
|
1554
|
-
}
|
|
1555
|
-
function checkJWEDirectEncryptedKey(alg, encryptedKey) {
|
|
1556
|
-
if (!JWE_DIRECT_ALGS.has(alg) || !encryptedKey) return void 0;
|
|
1557
|
-
return new JWTError(`Invalid JWE: Encrypted key must be empty for ${alg}.`, "ERR_JWE_INVALID");
|
|
1558
|
-
}
|
|
1559
|
-
function checkJWEContentSegments(enc, iv, tag) {
|
|
1560
|
-
const ivBytes = bitLengthIV(enc) >> 3;
|
|
1561
|
-
if (iv.length !== ivBytes) return new JWTError(`Invalid JWE: Initialization vector must be ${ivBytes} bytes for ${enc} (got ${iv.length}).`, "ERR_JWE_INVALID");
|
|
1562
|
-
const tagBytes = bitLengthTag(enc) >> 3;
|
|
1563
|
-
if (tag.length !== tagBytes) return new JWTError(`Invalid JWE: Authentication tag must be ${tagBytes} bytes for ${enc} (got ${tag.length}).`, "ERR_JWE_INVALID");
|
|
1564
|
-
}
|
|
1565
|
-
function checkJWEZip(header) {
|
|
1566
|
-
if (!header || !Object.hasOwn(header, "zip")) return void 0;
|
|
1567
|
-
return new JWTError("Invalid JWE: compression is not supported.", "ERR_JWE_INVALID");
|
|
1568
|
-
}
|
|
1569
|
-
function assertNoZipOption(header, tier) {
|
|
1570
|
-
if (!header || !Object.hasOwn(header, "zip")) return;
|
|
1571
|
-
throw new JWTError(`JWE compression ("zip") is not supported; remove it from the ${tier} header.`, "ERR_INVALID_OPTION");
|
|
1572
|
-
}
|
|
1573
|
-
function checkJWEUnprotectedCrit(header) {
|
|
1574
|
-
if (!header || !Object.hasOwn(header, "crit")) return void 0;
|
|
1575
|
-
return new JWTError("Invalid JWE: \"crit\" must be integrity protected (RFC 7516 §4.1.13).", "ERR_JWE_INVALID");
|
|
1576
|
-
}
|
|
1577
|
-
function typMatches(expected, actual) {
|
|
1578
|
-
if (typeof actual !== "string") return false;
|
|
1579
|
-
return normalizeTyp(expected) === normalizeTyp(actual);
|
|
1580
|
-
}
|
|
1581
|
-
function checkProtectedTyp(expected, protectedHeader, options) {
|
|
1582
|
-
if (expected === void 0) return void 0;
|
|
1583
|
-
const actual = protectedHeader.typ;
|
|
1584
|
-
if (typMatches(expected, actual)) return void 0;
|
|
1585
|
-
const { family, label } = options;
|
|
1586
|
-
const where = label ? ` on ${label}` : "";
|
|
1587
|
-
const message = actual === void 0 ? `Invalid ${family}: the protected header${where} carries no "typ"; expected "${expected}".` : `Invalid ${family}: "typ" (Type) Header Parameter mismatch${where}. Expected "${expected}", got ${JSON.stringify(actual)}.`;
|
|
1588
|
-
return new JWTError(message, `ERR_${family}_INVALID`);
|
|
1589
|
-
}
|
|
1590
|
-
function normalizeTyp(typ) {
|
|
1591
|
-
const lowered = typ.toLowerCase();
|
|
1592
|
-
return lowered.includes("/") ? lowered : `application/${lowered}`;
|
|
1593
|
-
}
|
|
1594
|
-
function decodeProtectedHeader(encoded, family) {
|
|
1595
|
-
let parsed = {};
|
|
1596
|
-
if (encoded) {
|
|
1597
|
-
try {
|
|
1598
|
-
parsed = safeJsonParse(base64UrlParse(encoded));
|
|
1599
|
-
} catch (error) {
|
|
1600
|
-
throw new JWTError(`Invalid ${family}: Protected header could not be decoded.`, `ERR_${family}_INVALID`, error);
|
|
1601
|
-
}
|
|
1602
|
-
if (!isPlainObject(parsed)) throw new JWTError(`Invalid ${family}: Protected header must be a JSON object.`, `ERR_${family}_INVALID`);
|
|
1603
|
-
}
|
|
1604
|
-
return parsed;
|
|
1605
|
-
}
|
|
1606
|
-
function decodeSegment(encoded, options) {
|
|
1607
|
-
try {
|
|
1608
|
-
return base64UrlParse(encoded, { returnAs: "uint8array" });
|
|
1609
|
-
} catch (error) {
|
|
1610
|
-
throw new JWTError(`Invalid ${options.family}: ${options.what} could not be decoded.`, `ERR_${options.family}_INVALID`, error);
|
|
1611
|
-
}
|
|
1612
|
-
}
|
|
1613
|
-
function decodeJWSPayload(payloadSegment, useB64, mode) {
|
|
1614
|
-
if (!useB64) return mode === "string" ? payloadSegment : parsePayloadBytes(textEncoder.encode(payloadSegment), {
|
|
1615
|
-
payload: mode,
|
|
1616
|
-
family: "JWS"
|
|
1617
|
-
});
|
|
1618
|
-
try {
|
|
1619
|
-
return parsePayloadSegment(payloadSegment, {
|
|
1620
|
-
payload: mode,
|
|
1621
|
-
family: "JWS"
|
|
1622
|
-
});
|
|
1623
|
-
} catch (error) {
|
|
1624
|
-
if (error instanceof JWTError) throw error;
|
|
1625
|
-
throw new JWTError("Invalid JWS: Payload could not be decoded.", "ERR_JWS_INVALID", error);
|
|
1626
|
-
}
|
|
1627
|
-
}
|
|
1628
|
-
function decodeJWEPayload(plaintext, mode) {
|
|
1629
|
-
return parsePayloadBytes(plaintext, {
|
|
1630
|
-
payload: mode,
|
|
1631
|
-
family: "JWE"
|
|
1632
|
-
});
|
|
1633
|
-
}
|
|
1634
|
-
function checkAlgSupported(alg, options) {
|
|
1635
|
-
const { supported, code, label, role } = options;
|
|
1636
|
-
if (alg === "none") return new JWTError(`"none" is not a valid ${role}`, code);
|
|
1637
|
-
if (!supported(alg)) return new JWTError(`${label} not supported: ${quoteTokenValue(alg)}`, code);
|
|
1638
|
-
}
|
|
1639
|
-
function keyServesHeaderAlg(header, key, ctx) {
|
|
1640
|
-
const { alg } = header;
|
|
1641
|
-
if (!alg) return false;
|
|
1642
|
-
const inferred = ctx.infer(key);
|
|
1643
|
-
if (inferred && !inferred.includes(alg)) return false;
|
|
1644
|
-
if (!isECDHESAlgorithm(alg)) return true;
|
|
1645
|
-
const epkCurve = header.epk?.crv;
|
|
1646
|
-
const keyCurve = isCryptoKey(key) ? cryptoKeyCurve(key) : "crv" in key ? key.crv : void 0;
|
|
1647
|
-
return !epkCurve || !keyCurve || epkCurve === keyCurve;
|
|
1648
|
-
}
|
|
1649
|
-
const RSA_SIGNATURE_ALGORITHMS = /* @__PURE__ */ new Set([
|
|
1650
|
-
"RS256",
|
|
1651
|
-
"RS384",
|
|
1652
|
-
"RS512",
|
|
1653
|
-
"PS256",
|
|
1654
|
-
"PS384",
|
|
1655
|
-
"PS512"
|
|
1656
|
-
]);
|
|
1657
|
-
async function generateKey(alg, options = {}) {
|
|
1658
|
-
const exportToJWK = options.toJWK !== void 0 && options.toJWK;
|
|
1659
|
-
const defaultExtractable = exportToJWK || options.extractable !== false;
|
|
1660
|
-
const kid = exportToJWK ? assertKidOption(options.kid) : void 0;
|
|
1661
|
-
if (alg === "A128CBC-HS256" || alg === "A192CBC-HS384" || alg === "A256CBC-HS512") {
|
|
1662
|
-
const keyLength = bitLengthCEK(alg);
|
|
1663
|
-
const keyBytes = secureRandomBytes(keyLength >> 3);
|
|
1664
|
-
if (exportToJWK) {
|
|
1665
|
-
const jwk = await keyToJWK(keyBytes);
|
|
1666
|
-
const keyOps = options.keyUsage === void 0 ? void 0 : resolveKeyUsage(options.keyUsage, {
|
|
1667
|
-
allowed: CONTENT_ENCRYPTION_USAGES,
|
|
1668
|
-
fallback: ["encrypt", "decrypt"],
|
|
1669
|
-
alg
|
|
1670
|
-
});
|
|
1671
|
-
return withGeneratedKid(kid, withKeyOps({
|
|
1672
|
-
...jwk,
|
|
1673
|
-
alg
|
|
1674
|
-
}, keyOps));
|
|
1675
|
-
}
|
|
1676
|
-
return keyBytes;
|
|
1677
|
-
}
|
|
1678
|
-
const { algorithm, keyUsages } = getGenerateKeyParams(alg, options);
|
|
1679
|
-
const key = await crypto.subtle.generateKey(algorithm, defaultExtractable, keyUsages);
|
|
1680
|
-
if (exportToJWK) {
|
|
1681
|
-
if (isCryptoKeyPair(key)) {
|
|
1682
|
-
const pairAlg = generatedPairAlgorithm(alg);
|
|
1683
|
-
const [pub, priv] = await Promise.all([exportJWK(key.publicKey), exportJWK(key.privateKey)]);
|
|
1684
|
-
const publicKey = await withGeneratedKid(kid, withKeyOps(withAlg(pub, pairAlg), requestedKeyOps(options, key.publicKey)));
|
|
1685
|
-
const named = publicKey.kid === void 0 ? void 0 : { kid: publicKey.kid };
|
|
1686
|
-
return {
|
|
1687
|
-
privateKey: {
|
|
1688
|
-
...withKeyOps(withAlg(priv, pairAlg), requestedKeyOps(options, key.privateKey)),
|
|
1689
|
-
...named
|
|
1690
|
-
},
|
|
1691
|
-
publicKey
|
|
1692
|
-
};
|
|
1693
|
-
}
|
|
1694
|
-
return withGeneratedKid(kid, withKeyOps(withAlg(await exportJWK(key), generatedSecretAlgorithm(alg)), requestedKeyOps(options, key)));
|
|
1695
|
-
}
|
|
1696
|
-
return key;
|
|
1697
|
-
}
|
|
1698
|
-
async function generateJWK(alg, options) {
|
|
1699
|
-
const { keyUsage, modulusLength, publicExponent, namedCurve, kid, extractable: _extractable, toJWK: _toJWK, ...jwkParams } = options ?? {};
|
|
1700
|
-
assertNoReservedJWKParams(jwkParams);
|
|
1701
|
-
const result = await generateKey(alg, {
|
|
1702
|
-
keyUsage,
|
|
1703
|
-
modulusLength,
|
|
1704
|
-
publicExponent,
|
|
1705
|
-
namedCurve,
|
|
1706
|
-
kid,
|
|
1707
|
-
toJWK: true
|
|
1708
|
-
});
|
|
1709
|
-
if (result && typeof result === "object" && "privateKey" in result && "publicKey" in result) return {
|
|
1710
|
-
privateKey: {
|
|
1711
|
-
...jwkParams,
|
|
1712
|
-
...result.privateKey
|
|
1713
|
-
},
|
|
1714
|
-
publicKey: {
|
|
1715
|
-
...jwkParams,
|
|
1716
|
-
...result.publicKey
|
|
1717
|
-
}
|
|
1718
|
-
};
|
|
1719
|
-
return {
|
|
1720
|
-
...jwkParams,
|
|
1721
|
-
...result
|
|
1722
|
-
};
|
|
1723
|
-
}
|
|
1724
|
-
async function deriveKeyFromPassword(password, options) {
|
|
1725
|
-
assertPlainObject(options, "options");
|
|
1726
|
-
if (typeof password !== "string" && !(password instanceof Uint8Array)) throw new JWTError("\"password\" must be a string or a Uint8Array.", "ERR_INVALID_OPTION");
|
|
1727
|
-
const { alg, salt, iterations, extractable, keyUsage } = options;
|
|
1728
|
-
assertPBES2Algorithm(alg);
|
|
1729
|
-
const exportToJWK = options.toJWK !== void 0 && options.toJWK;
|
|
1730
|
-
const saltBytes = typeof salt === "string" ? textEncoder.encode(salt) : salt;
|
|
1731
|
-
assertPBES2Salt(saltBytes, "\"salt\"");
|
|
1732
|
-
assertPBES2Count(iterations, "\"iterations\"");
|
|
1733
|
-
const derivedBytes = await deriveKey(saltBytes, alg, iterations, typeof password === "string" ? textEncoder.encode(password) : password);
|
|
1734
|
-
const wrappingAlg = innerKeyWrapAlg(alg);
|
|
1735
|
-
const finalUsages = keyUsage ?? ["wrapKey", "unwrapKey"];
|
|
1736
|
-
const finalExtractable = exportToJWK ? true : extractable === true;
|
|
1737
|
-
const derivedKey = await crypto.subtle.importKey("raw", derivedBytes, { name: "AES-KW" }, finalExtractable, finalUsages);
|
|
1738
|
-
if (exportToJWK) return withKeyOps({
|
|
1739
|
-
...await keyToJWK(derivedKey),
|
|
1740
|
-
alg: wrappingAlg,
|
|
1741
|
-
kty: "oct"
|
|
1742
|
-
}, requestedKeyOps(options, derivedKey));
|
|
1743
|
-
return derivedKey;
|
|
1744
|
-
}
|
|
1745
|
-
async function deriveJWKFromPassword(password, options) {
|
|
1746
|
-
assertPlainObject(options, "options");
|
|
1747
|
-
const { alg, salt, iterations, keyUsage, kid: kidOption, extractable: _extractable, toJWK: _toJWK, ...jwkParams } = options;
|
|
1748
|
-
assertNoReservedJWKParams(jwkParams);
|
|
1749
|
-
const kid = assertKidOption(kidOption);
|
|
1750
|
-
const result = await deriveKeyFromPassword(password, {
|
|
1751
|
-
alg,
|
|
1752
|
-
salt,
|
|
1753
|
-
iterations,
|
|
1754
|
-
keyUsage,
|
|
1755
|
-
toJWK: true
|
|
1756
|
-
});
|
|
1757
|
-
return {
|
|
1758
|
-
...jwkParams,
|
|
1759
|
-
...await withGeneratedKid(kid, result)
|
|
1760
|
-
};
|
|
1761
|
-
}
|
|
1762
|
-
var WeakMapJWKCache = class {
|
|
1763
|
-
_map = /* @__PURE__ */ new WeakMap();
|
|
1764
|
-
get(jwk, alg) {
|
|
1765
|
-
return this._map.get(jwk)?.[alg];
|
|
1766
|
-
}
|
|
1767
|
-
set(jwk, alg, key) {
|
|
1768
|
-
let entry = this._map.get(jwk);
|
|
1769
|
-
if (!entry) this._map.set(jwk, { [alg]: key });
|
|
1770
|
-
else entry[alg] = key;
|
|
1771
|
-
}
|
|
1772
|
-
};
|
|
1773
|
-
let _activeCache = new WeakMapJWKCache();
|
|
1774
|
-
function configureJWKCache(cache) {
|
|
1775
|
-
_activeCache = cache;
|
|
1776
|
-
}
|
|
1777
|
-
function clearJWKCache() {
|
|
1778
|
-
_activeCache = new WeakMapJWKCache();
|
|
1779
|
-
}
|
|
1780
|
-
async function importKey(key, algOrOptions) {
|
|
1781
|
-
const alg = typeof algOrOptions === "string" ? algOrOptions : algOrOptions?.alg ?? void 0;
|
|
1782
|
-
const expect = typeof algOrOptions === "object" ? algOrOptions.expect ?? void 0 : void 0;
|
|
1783
|
-
const extractable = typeof algOrOptions === "object" && !algOrOptions.asCryptoKey ? algOrOptions.extractable : void 0;
|
|
1784
|
-
if (typeof key === "string") throw stringKeyError();
|
|
1785
|
-
if (key instanceof Uint8Array) return key;
|
|
1786
|
-
if (isCryptoKey(key)) {
|
|
1787
|
-
assertIntentOnCryptoKey(key, expect);
|
|
1788
|
-
return key;
|
|
1789
|
-
}
|
|
1790
|
-
if (isJWK(key)) {
|
|
1791
|
-
assertJWKAlgAuthority(key, alg);
|
|
1792
|
-
if ("k" in key && typeof key.k === "string") {
|
|
1793
|
-
const rawBytes = decodeSegment(key.k, {
|
|
1794
|
-
family: "JWK",
|
|
1795
|
-
what: "\"k\""
|
|
1796
|
-
});
|
|
1797
|
-
assertOctKeyLength(rawBytes, alg ?? key.alg);
|
|
1798
|
-
if (typeof algOrOptions === "object" && algOrOptions.asCryptoKey) {
|
|
1799
|
-
const { algorithm, usage, extractable: rawExtractable = false } = algOrOptions;
|
|
1800
|
-
if (!algorithm || !usage) throw new JWTError("asCryptoKey option requires `algorithm` and `usage` to be provided.", "ERR_JWK_INVALID");
|
|
1801
|
-
if (typeof algorithm === "object" && algorithm.name === "HMAC") {
|
|
1802
|
-
const hash = "hash" in algorithm ? algorithm.hash : void 0;
|
|
1803
|
-
const hashName = typeof hash === "string" ? hash : typeof hash === "object" && hash !== null && "name" in hash && typeof hash.name === "string" ? hash.name : void 0;
|
|
1804
|
-
const minBytes = Number.parseInt(hashName?.slice(-3) ?? "", 10) / 8;
|
|
1805
|
-
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");
|
|
1806
|
-
}
|
|
1807
|
-
return crypto.subtle.importKey("raw", rawBytes, algorithm, rawExtractable, usage);
|
|
1808
|
-
}
|
|
1809
|
-
return rawBytes;
|
|
1810
|
-
} else {
|
|
1811
|
-
const effectiveAlg = key.alg || alg;
|
|
1812
|
-
if (!effectiveAlg) throw new JWTError("Algorithm must be provided when importing non-oct JWK", "ERR_JWK_INVALID");
|
|
1813
|
-
assertIntentOnJWK(key, expect);
|
|
1814
|
-
const cache = extractable === void 0 ? _activeCache : false;
|
|
1815
|
-
if (cache) {
|
|
1816
|
-
const cached = await cache.get(key, effectiveAlg);
|
|
1817
|
-
if (cached) return cached;
|
|
1818
|
-
}
|
|
1819
|
-
const cryptoKey = await jwkTokey(key, {
|
|
1820
|
-
extractable,
|
|
1821
|
-
alg
|
|
1822
|
-
});
|
|
1823
|
-
if (cache) await cache.set(key, effectiveAlg, cryptoKey);
|
|
1824
|
-
return cryptoKey;
|
|
1825
|
-
}
|
|
1826
|
-
}
|
|
1827
|
-
throw new JWTError("Invalid key type provided to importKey", "ERR_JWK_INVALID");
|
|
1828
|
-
}
|
|
1829
|
-
function assertJWKAlgAuthority(jwk, alg) {
|
|
1830
|
-
if (!alg || !jwk.alg || jwk.alg === alg) return;
|
|
1831
|
-
if (jwk.kty === "oct" && alg === "dir" && isContentEncryptionAlgorithm(jwk.alg)) return;
|
|
1832
|
-
if (isEd25519Signature(jwk.alg) && isEd25519Signature(alg)) return;
|
|
1833
|
-
throw new JWTError(`JWK "alg" is "${jwk.alg}" but the operation requires "${alg}".`, "ERR_JWK_INVALID");
|
|
1834
|
-
}
|
|
1835
|
-
function assertIntentOnJWK(jwk, expect) {
|
|
1836
|
-
if (expect === void 0 || jwk.kty === "oct") return;
|
|
1837
|
-
const hasPrivateScalar = "d" in jwk && typeof jwk.d === "string";
|
|
1838
|
-
if (expect === "public" && hasPrivateScalar) throw new JWTError("Private JWK passed where a public key is expected.", "ERR_JWK_INVALID");
|
|
1839
|
-
if (expect === "private" && !hasPrivateScalar) throw new JWTError("Public JWK passed where a private key is expected.", "ERR_JWK_INVALID");
|
|
1840
|
-
}
|
|
1841
|
-
function assertIntentOnCryptoKey(key, expect) {
|
|
1842
|
-
if (expect === void 0 || key.type === "secret") return;
|
|
1843
|
-
if (expect === "public" && key.type === "private") throw new JWTError("Private CryptoKey passed where a public key is expected.", "ERR_JWK_INVALID");
|
|
1844
|
-
if (expect === "private" && key.type === "public") throw new JWTError("Public CryptoKey passed where a private key is expected.", "ERR_JWK_INVALID");
|
|
1845
|
-
}
|
|
1846
|
-
async function exportJWK(key, jwkParams) {
|
|
1847
|
-
if (!isCryptoKey(key)) throw new JWTError(`exportJWK expects a CryptoKey, got ${key === null ? "null" : typeof key}.`, "ERR_INVALID_OPTION");
|
|
1848
|
-
if (!key.extractable) throw new JWTError("A non-extractable CryptoKey cannot be exported as a JWK.", "ERR_JWK_INVALID");
|
|
1849
|
-
const exportedJwk = await keyToJWK(key);
|
|
1850
|
-
if (jwkParams) {
|
|
1851
|
-
const extraParams = sanitizeObjectCopy(jwkParams);
|
|
1852
|
-
assertNoReservedJWKParams(extraParams);
|
|
1853
|
-
return {
|
|
1854
|
-
...exportedJwk,
|
|
1855
|
-
...extraParams
|
|
1856
|
-
};
|
|
1857
|
-
}
|
|
1858
|
-
return exportedJwk;
|
|
1859
|
-
}
|
|
1860
|
-
async function exportKey(key, jwkParams) {
|
|
1861
|
-
return exportJWK(key, jwkParams);
|
|
1862
|
-
}
|
|
1863
|
-
function toPublicJWK(jwk) {
|
|
1864
|
-
if (!isAsymmetricJWK(jwk)) throw new JWTError(isJWK(jwk) ? "An oct JWK has no public half." : "toPublicJWK expects an asymmetric JWK.", "ERR_JWK_INVALID");
|
|
1865
|
-
const { d: _d, p: _p, q: _q, dp: _dp, dq: _dq, qi: _qi, oth: _oth, key_ops, ...publicMembers } = sanitizeObjectCopy({ ...jwk });
|
|
1866
|
-
const publicKey = key_ops === void 0 ? publicMembers : {
|
|
1867
|
-
...publicMembers,
|
|
1868
|
-
key_ops: publicKeyOps(key_ops)
|
|
1869
|
-
};
|
|
1870
|
-
if (!isPublicJWK(publicKey)) throw new JWTError(`${jwk.kty} JWK lacks the public members of its key type.`, "ERR_JWK_INVALID");
|
|
1871
|
-
return publicKey;
|
|
1872
|
-
}
|
|
1873
|
-
async function wrapKey(keyToWrap, wrappingKey, options) {
|
|
1874
|
-
assertPlainObject(options, "options");
|
|
1875
|
-
assertKeyBytesArg(keyToWrap, "keyToWrap");
|
|
1876
|
-
const { alg } = options;
|
|
1877
|
-
const cekBytes = keyToWrap instanceof Uint8Array ? keyToWrap : new Uint8Array(await crypto.subtle.exportKey("raw", keyToWrap));
|
|
1878
|
-
if (alg.endsWith("KW")) assertWrappableCEK(cekBytes, alg);
|
|
1879
|
-
if (alg.startsWith("PBES2")) assertPBES2Params({
|
|
1880
|
-
p2s: options.p2s,
|
|
1881
|
-
p2c: options.p2c
|
|
1882
|
-
});
|
|
1883
|
-
const importedWrappingKey = await importKey(resolvePasswordKey(alg, wrappingKey), {
|
|
1884
|
-
alg,
|
|
1885
|
-
expect: "public"
|
|
1886
|
-
});
|
|
1887
|
-
switch (alg) {
|
|
1888
|
-
case "dir": return { encryptedKey: /* @__PURE__ */ new Uint8Array(0) };
|
|
1889
|
-
case "A128KW":
|
|
1890
|
-
case "A192KW":
|
|
1891
|
-
case "A256KW": return { encryptedKey: await aesKwWrap(alg, importedWrappingKey, cekBytes) };
|
|
1892
|
-
case "PBES2-HS256+A128KW":
|
|
1893
|
-
case "PBES2-HS384+A192KW":
|
|
1894
|
-
case "PBES2-HS512+A256KW": {
|
|
1895
|
-
const { p2s, p2c } = options;
|
|
1896
|
-
if (!p2s || typeof p2c !== "number") throw new JWTError("PBES2 requires 'p2s' (salt) and 'p2c' (count) options", "ERR_JWE_INVALID");
|
|
1897
|
-
return pbes2Wrap(alg, importedWrappingKey, cekBytes, p2c, p2s);
|
|
1898
|
-
}
|
|
1899
|
-
case "A128GCMKW":
|
|
1900
|
-
case "A192GCMKW":
|
|
1901
|
-
case "A256GCMKW": {
|
|
1902
|
-
const { encryptedKey, iv, tag } = await gcmkwEncrypt(alg, importedWrappingKey, cekBytes, options.iv);
|
|
1903
|
-
return {
|
|
1904
|
-
encryptedKey,
|
|
1905
|
-
iv,
|
|
1906
|
-
tag
|
|
1907
|
-
};
|
|
1908
|
-
}
|
|
1909
|
-
case "RSA-OAEP":
|
|
1910
|
-
case "RSA-OAEP-256":
|
|
1911
|
-
case "RSA-OAEP-384":
|
|
1912
|
-
case "RSA-OAEP-512":
|
|
1913
|
-
assertCryptoKeyFor(alg, importedWrappingKey, "wrapping");
|
|
1914
|
-
return { encryptedKey: await encryptRSAES(alg, importedWrappingKey, cekBytes) };
|
|
1915
|
-
case "ECDH-ES":
|
|
1916
|
-
case "ECDH-ES+A128KW":
|
|
1917
|
-
case "ECDH-ES+A192KW":
|
|
1918
|
-
case "ECDH-ES+A256KW": {
|
|
1919
|
-
assertCryptoKeyFor(alg, importedWrappingKey, "wrapping");
|
|
1920
|
-
const enc = options.ecdh?.enc;
|
|
1921
|
-
if (enc !== void 0) assertJWEEncSupported(enc);
|
|
1922
|
-
if (alg === "ECDH-ES" && !enc) throw new JWTError("ECDH-ES direct key agreement requires options.ecdh.enc (the target content encryption algorithm)", "ERR_JWK_INVALID");
|
|
1923
|
-
const { encryptedKey, parameters } = await ecdhesWrap(alg, {
|
|
1924
|
-
enc,
|
|
1925
|
-
recipientKey: importedWrappingKey,
|
|
1926
|
-
cek: cekBytes,
|
|
1927
|
-
parameters: buildECDHAgreementParams(options.ecdh)
|
|
1928
|
-
});
|
|
1929
|
-
const wrapResult = {
|
|
1930
|
-
encryptedKey: encryptedKey ?? /* @__PURE__ */ new Uint8Array(0),
|
|
1931
|
-
epk: parameters.epk
|
|
1932
|
-
};
|
|
1933
|
-
if (parameters.apu) wrapResult.apu = parameters.apu;
|
|
1934
|
-
if (parameters.apv) wrapResult.apv = parameters.apv;
|
|
1935
|
-
return wrapResult;
|
|
1936
|
-
}
|
|
1937
|
-
default: throw new JWTError(`Unsupported key wrapping algorithm: ${alg}`, "ERR_JWK_UNSUPPORTED");
|
|
1938
|
-
}
|
|
1939
|
-
}
|
|
1940
|
-
async function unwrapKey(wrappedKey, unwrappingKey, options) {
|
|
1941
|
-
assertPlainObject(options, "options");
|
|
1942
|
-
if (!(wrappedKey instanceof Uint8Array)) throw new JWTError("\"wrappedKey\" must be a Uint8Array.", "ERR_INVALID_OPTION");
|
|
1943
|
-
const { alg } = options;
|
|
1944
|
-
const returnRaw = options.format === "raw";
|
|
1945
|
-
const defaultExtractable = options.extractable !== false;
|
|
1946
|
-
const importedUnwrappingKey = await importKey(resolvePasswordKey(alg, unwrappingKey), {
|
|
1947
|
-
alg,
|
|
1948
|
-
expect: "private"
|
|
1949
|
-
});
|
|
1950
|
-
let unwrappedCekBytes;
|
|
1951
|
-
switch (alg) {
|
|
1952
|
-
case "dir":
|
|
1953
|
-
assertDirectKeyRole(unwrappingKey, options.enc);
|
|
1954
|
-
if (importedUnwrappingKey instanceof Uint8Array) {
|
|
1955
|
-
assertOctKeyLength(importedUnwrappingKey, options.enc);
|
|
1956
|
-
unwrappedCekBytes = importedUnwrappingKey;
|
|
1957
|
-
} else {
|
|
1958
|
-
if (!returnRaw) return importedUnwrappingKey;
|
|
1959
|
-
unwrappedCekBytes = new Uint8Array(await crypto.subtle.exportKey("raw", importedUnwrappingKey));
|
|
1960
|
-
}
|
|
1961
|
-
break;
|
|
1962
|
-
case "A128KW":
|
|
1963
|
-
case "A192KW":
|
|
1964
|
-
case "A256KW":
|
|
1965
|
-
unwrappedCekBytes = await unwrapping(aesKwUnwrap(alg, importedUnwrappingKey, wrappedKey));
|
|
1966
|
-
break;
|
|
1967
|
-
case "PBES2-HS256+A128KW":
|
|
1968
|
-
case "PBES2-HS384+A192KW":
|
|
1969
|
-
case "PBES2-HS512+A256KW": {
|
|
1970
|
-
const { p2s, p2c, minIterations, maxIterations } = options;
|
|
1971
|
-
if (!p2s || typeof p2c !== "number") throw new JWTError("PBES2 requires 'p2s' (salt) and 'p2c' (count) options", "ERR_JWE_INVALID");
|
|
1972
|
-
unwrappedCekBytes = await unwrapping(pbes2Unwrap(alg, importedUnwrappingKey, wrappedKey, p2c, segmentBytes(p2s, "p2s"), minIterations, maxIterations));
|
|
1973
|
-
break;
|
|
1974
|
-
}
|
|
1975
|
-
case "A128GCMKW":
|
|
1976
|
-
case "A192GCMKW":
|
|
1977
|
-
case "A256GCMKW": {
|
|
1978
|
-
if (!options.iv || !options.tag) throw new JWTError("AES-GCMKW requires 'iv' and 'tag' options for unwrapping", "ERR_JWE_INVALID");
|
|
1979
|
-
const ivBytes = segmentBytes(options.iv, "iv");
|
|
1980
|
-
const tagBytes = segmentBytes(options.tag, "tag");
|
|
1981
|
-
assertKeyWrapSegmentLength("iv", ivBytes, bitLengthIV(alg) >> 3, alg);
|
|
1982
|
-
assertKeyWrapSegmentLength("tag", tagBytes, bitLengthTag(alg) >> 3, alg);
|
|
1983
|
-
unwrappedCekBytes = await unwrapping(gcmkwDecrypt(alg, importedUnwrappingKey, wrappedKey, ivBytes, tagBytes));
|
|
1984
|
-
break;
|
|
1985
|
-
}
|
|
1986
|
-
case "RSA-OAEP":
|
|
1987
|
-
case "RSA-OAEP-256":
|
|
1988
|
-
case "RSA-OAEP-384":
|
|
1989
|
-
case "RSA-OAEP-512": {
|
|
1990
|
-
assertCryptoKeyFor(alg, importedUnwrappingKey, "unwrapping");
|
|
1991
|
-
unwrappedCekBytes = await unwrapping(decryptRSAES(alg, importedUnwrappingKey, wrappedKey));
|
|
1992
|
-
if (returnRaw) break;
|
|
1993
|
-
const inferredAlgorithm = options.unwrappedKeyAlgorithm || inferAesImportAlgorithm(options.enc, unwrappedCekBytes.length);
|
|
1994
|
-
if (!inferredAlgorithm) throw new JWTError("Unable to infer algorithm for RSA-OAEP unwrapped key. Provide \"unwrappedKeyAlgorithm\" in options.", "ERR_JWE_INVALID");
|
|
1995
|
-
const keyUsages = options.keyUsage || ["encrypt", "decrypt"];
|
|
1996
|
-
return await crypto.subtle.importKey("raw", unwrappedCekBytes, inferredAlgorithm, defaultExtractable, keyUsages);
|
|
1997
|
-
}
|
|
1998
|
-
case "ECDH-ES":
|
|
1999
|
-
case "ECDH-ES+A128KW":
|
|
2000
|
-
case "ECDH-ES+A192KW":
|
|
2001
|
-
case "ECDH-ES+A256KW": {
|
|
2002
|
-
if (!options.epk) throw new JWTError("ECDH-ES requires 'epk' (Ephemeral Public Key) option", "ERR_JWE_INVALID");
|
|
2003
|
-
assertCryptoKeyFor(alg, importedUnwrappingKey, "unwrapping");
|
|
2004
|
-
assertECDHKeyAllowed(importedUnwrappingKey);
|
|
2005
|
-
const epkCandidate = await normalizeEpk(options.epk, {
|
|
2006
|
-
alg,
|
|
2007
|
-
recipientKey: importedUnwrappingKey
|
|
2008
|
-
});
|
|
2009
|
-
const apuBytes = segmentBytes(options.apu ?? /* @__PURE__ */ new Uint8Array(0), "apu");
|
|
2010
|
-
const apvBytes = segmentBytes(options.apv ?? /* @__PURE__ */ new Uint8Array(0), "apv");
|
|
2011
|
-
assertPartyInfoLength("apu", apuBytes);
|
|
2012
|
-
assertPartyInfoLength("apv", apvBytes);
|
|
2013
|
-
if (options.enc !== void 0) assertJWEEncSupported(options.enc);
|
|
2014
|
-
const infoAlg = alg === "ECDH-ES" ? options.enc : alg;
|
|
2015
|
-
if (!infoAlg) throw new JWTError("ECDH-ES requires content encryption algorithm ('enc') to derive the shared secret", "ERR_JWE_INVALID");
|
|
2016
|
-
const sharedSecret = await unwrapping(deriveECDHESKey(epkCandidate, importedUnwrappingKey, infoAlg, ecdhDerivedBits(alg, options.enc), apuBytes, apvBytes));
|
|
2017
|
-
if (alg === "ECDH-ES") unwrappedCekBytes = sharedSecret;
|
|
2018
|
-
else {
|
|
2019
|
-
const kwAlg = innerKeyWrapAlg(alg);
|
|
2020
|
-
if (!(wrappedKey instanceof Uint8Array) || wrappedKey.length === 0) throw new JWTError("ECDH-ES key agreement with key wrapping requires an encrypted key", "ERR_JWE_INVALID");
|
|
2021
|
-
unwrappedCekBytes = await unwrapping(aesKwUnwrap(kwAlg, sharedSecret, wrappedKey));
|
|
2022
|
-
}
|
|
2023
|
-
break;
|
|
2024
|
-
}
|
|
2025
|
-
default: throw new JWTError(`Unsupported key unwrapping algorithm: ${alg}`, "ERR_JWK_UNSUPPORTED");
|
|
2026
|
-
}
|
|
2027
|
-
if (returnRaw) return unwrappedCekBytes;
|
|
2028
|
-
return await crypto.subtle.importKey("raw", unwrappedCekBytes, options.unwrappedKeyAlgorithm || { name: "AES-GCM" }, defaultExtractable, options.keyUsage || ["encrypt", "decrypt"]);
|
|
2029
|
-
}
|
|
2030
|
-
async function importPEM(pem, alg, options) {
|
|
2031
|
-
if (typeof pem !== "string") throw new JWTError(`"pem" must be a PEM-encoded string, got ${pem === null ? "null" : typeof pem}.`, "ERR_INVALID_OPTION");
|
|
2032
|
-
if (options !== void 0) assertPlainObject(options, "options");
|
|
2033
|
-
const extractable = true;
|
|
2034
|
-
const pemType = options?.pemType ?? inferPEMType(pem);
|
|
2035
|
-
if (!pemType) throw new JWTError("Cannot infer PEM type from the input; pass \"options.pemType\" explicitly.", "ERR_JWK_INVALID");
|
|
2036
|
-
let cryptoKey;
|
|
2037
|
-
switch (pemType) {
|
|
2038
|
-
case "pkcs8":
|
|
2039
|
-
cryptoKey = await fromPKCS8(pem, alg, { extractable });
|
|
2040
|
-
break;
|
|
2041
|
-
case "spki":
|
|
2042
|
-
cryptoKey = await fromSPKI(pem, alg, { extractable });
|
|
2043
|
-
break;
|
|
2044
|
-
case "x509":
|
|
2045
|
-
cryptoKey = await fromX509(pem, alg, { extractable });
|
|
2046
|
-
break;
|
|
2047
|
-
default: throw new JWTError(`Unsupported PEM type: ${String(pemType)}`, "ERR_JWK_UNSUPPORTED");
|
|
2048
|
-
}
|
|
2049
|
-
return {
|
|
2050
|
-
...await exportJWK(cryptoKey, options?.jwkParams),
|
|
2051
|
-
alg
|
|
2052
|
-
};
|
|
2053
|
-
}
|
|
2054
|
-
async function exportPEM(jwk, options) {
|
|
2055
|
-
assertPlainObject(jwk, "jwk");
|
|
2056
|
-
if (options !== void 0) assertPlainObject(options, "options");
|
|
2057
|
-
if (jwk.kty === "oct") throw new JWTError("Octet (symmetric) JWKs (kty: 'oct') cannot be exported to PKCS8 or SPKI PEM formats.", "ERR_JWK_UNSUPPORTED");
|
|
2058
|
-
const effectiveAlg = jwk.alg || options?.alg;
|
|
2059
|
-
if (!effectiveAlg && (jwk.kty === "RSA" || jwk.kty === "EC" || jwk.kty === "OKP")) throw new JWTError("Algorithm (alg) must be provided in the JWK or via options for converting this JWK type to a CryptoKey.", "ERR_JWK_INVALID");
|
|
2060
|
-
if (effectiveAlg !== void 0 && !PEM_ALGORITHMS.has(effectiveAlg)) throw new JWTError(`Unsupported PEM algorithm: ${effectiveAlg}`, "ERR_JWK_UNSUPPORTED");
|
|
2061
|
-
const pemFormat = options?.pemFormat ?? ("d" in jwk ? "pkcs8" : "spki");
|
|
2062
|
-
const cryptoKeyCandidate = await importKey({
|
|
2063
|
-
...jwk,
|
|
2064
|
-
ext: true
|
|
2065
|
-
}, {
|
|
2066
|
-
alg: effectiveAlg,
|
|
2067
|
-
expect: pemFormat === "pkcs8" ? "private" : "public",
|
|
2068
|
-
extractable: true
|
|
2069
|
-
});
|
|
2070
|
-
if (!isCryptoKey(cryptoKeyCandidate)) throw new JWTError("Failed to convert JWK to a CryptoKey instance suitable for PEM export.", "ERR_JWK_INVALID");
|
|
2071
|
-
const cryptoKey = cryptoKeyCandidate;
|
|
2072
|
-
switch (pemFormat) {
|
|
2073
|
-
case "pkcs8":
|
|
2074
|
-
if (cryptoKey.type !== "private") throw new JWTError(`Only 'private' type CryptoKeys can be exported to PKCS8 PEM format. Key type is '${cryptoKey.type}'.`, "ERR_JWK_INVALID");
|
|
2075
|
-
return toPKCS8(cryptoKey);
|
|
2076
|
-
case "spki":
|
|
2077
|
-
if (cryptoKey.type !== "public") throw new JWTError(`Only 'public' type CryptoKeys can be exported to SPKI PEM format. Key type is '${cryptoKey.type}'.`, "ERR_JWK_INVALID");
|
|
2078
|
-
return toSPKI(cryptoKey);
|
|
2079
|
-
default: throw new JWTError(`Unsupported PEM format: ${String(pemFormat)}`, "ERR_JWK_UNSUPPORTED");
|
|
2080
|
-
}
|
|
2081
|
-
}
|
|
2082
|
-
function inferPEMType(pem) {
|
|
2083
|
-
if (pem.includes("-----BEGIN PRIVATE KEY-----")) return "pkcs8";
|
|
2084
|
-
if (pem.includes("-----BEGIN PUBLIC KEY-----")) return "spki";
|
|
2085
|
-
if (pem.includes("-----BEGIN CERTIFICATE-----")) return "x509";
|
|
2086
|
-
}
|
|
2087
|
-
async function deriveSharedSecret(publicKey, privateKey, options) {
|
|
2088
|
-
assertPlainObject(options, "options");
|
|
2089
|
-
const { enc: alg, keyLength: requestedKeyLength, partyUInfo, partyVInfo } = options;
|
|
2090
|
-
if (!isECDHESAlgorithm(alg)) assertJWEEncSupported(alg);
|
|
2091
|
-
if (requestedKeyLength !== void 0) assertDerivedKeyLength(requestedKeyLength);
|
|
2092
|
-
const importAlg = isECDHESAlgorithm(alg) ? alg : "ECDH-ES";
|
|
2093
|
-
const pubCryptoKey = isCryptoKey(publicKey) ? publicKey : await importKey(publicKey, {
|
|
2094
|
-
alg: importAlg,
|
|
2095
|
-
expect: "public"
|
|
2096
|
-
});
|
|
2097
|
-
const privCryptoKey = isCryptoKey(privateKey) ? privateKey : await importKey(privateKey, {
|
|
2098
|
-
alg: importAlg,
|
|
2099
|
-
expect: "private"
|
|
2100
|
-
});
|
|
2101
|
-
let keyLength;
|
|
2102
|
-
if (requestedKeyLength !== void 0) keyLength = requestedKeyLength;
|
|
2103
|
-
else if (alg === "ECDH-ES") throw new JWTError("deriveSharedSecret with enc \"ECDH-ES\" requires an explicit keyLength in options", "ERR_JWK_INVALID");
|
|
2104
|
-
else if (isECDHESAlgorithm(alg)) keyLength = aesBitsOf(innerKeyWrapAlg(alg));
|
|
2105
|
-
else keyLength = bitLengthCEK(alg);
|
|
2106
|
-
return deriveECDHESKey(pubCryptoKey, privCryptoKey, alg, keyLength, partyUInfo ?? /* @__PURE__ */ new Uint8Array(0), partyVInfo ?? /* @__PURE__ */ new Uint8Array(0));
|
|
2107
|
-
}
|
|
2108
|
-
function getJWKsFromSet(jwkSet, filter) {
|
|
2109
|
-
if (!jwkSet || !isJWKSet(jwkSet)) throw new JWTError("Invalid JWK Set provided", "ERR_JWK_INVALID");
|
|
2110
|
-
const keys = jwkSet.keys.filter((key) => isJWK(key));
|
|
2111
|
-
return filter ? keys.filter(filter) : keys;
|
|
2112
|
-
}
|
|
1
|
+
import { secureRandomBytes, textEncoder } from "./libs/unsecure.mjs";
|
|
2
|
+
import { JWTError } from "../error.mjs";
|
|
3
|
+
import { assertPlainObject, isCryptoKey, isCryptoKeyPair } from "./jwt.mjs";
|
|
4
|
+
import { assertJWEEncSupported, assertPBES2Count, assertPBES2Salt, bitLengthCEK, deriveECDHESKey, deriveKey, fromPKCS8, fromSPKI, fromX509, importKey, keyToJWK, toPKCS8, toSPKI } from "./algorithms.mjs";
|
|
5
|
+
import { ECDSA_CURVES, PBES2_ALGORITHMS, aesBitsOf, innerKeyWrapAlg, isECDHESAlgorithm, jwsHashOf, rsaOaepHashOf } from "./digest.mjs";
|
|
6
|
+
import "./wrap.mjs";
|
|
7
|
+
import { assertKidOption, assertNoReservedJWKParams, exportJWK, requestedKeyOps, withGeneratedKid, withKeyOps } from "./export.mjs";
|
|
2113
8
|
function getGenerateKeyParams(alg, options) {
|
|
2114
9
|
let algorithm;
|
|
2115
10
|
let keyUsages;
|
|
@@ -2213,7 +108,7 @@ function getGenerateKeyParams(alg, options) {
|
|
|
2213
108
|
case "EdDSA": {
|
|
2214
109
|
keyUsages = pairUsage(SIGNATURE_USAGES, ["sign", "verify"]);
|
|
2215
110
|
const namedCurve = options?.namedCurve ?? "Ed25519";
|
|
2216
|
-
if (namedCurve !== "Ed25519") throw new JWTError("Unsupported namedCurve
|
|
111
|
+
if (namedCurve !== "Ed25519") throw new JWTError("Unsupported namedCurve, expected Ed25519", "ERR_JWK_UNSUPPORTED");
|
|
2217
112
|
algorithm = { name: namedCurve };
|
|
2218
113
|
break;
|
|
2219
114
|
}
|
|
@@ -2235,7 +130,7 @@ function getGenerateKeyParams(alg, options) {
|
|
|
2235
130
|
case "X25519":
|
|
2236
131
|
algorithm = { name: "X25519" };
|
|
2237
132
|
break;
|
|
2238
|
-
default: throw new JWTError("Unsupported namedCurve
|
|
133
|
+
default: throw new JWTError("Unsupported namedCurve, expected P-256, P-384, P-521 or X25519", "ERR_JWK_UNSUPPORTED");
|
|
2239
134
|
}
|
|
2240
135
|
break;
|
|
2241
136
|
}
|
|
@@ -2246,43 +141,6 @@ function getGenerateKeyParams(alg, options) {
|
|
|
2246
141
|
keyUsages
|
|
2247
142
|
};
|
|
2248
143
|
}
|
|
2249
|
-
function inferAesImportAlgorithm(enc, cekLengthBytes) {
|
|
2250
|
-
const bitLength = cekLengthBytes << 3;
|
|
2251
|
-
if (enc) {
|
|
2252
|
-
if (enc.includes("GCM")) return {
|
|
2253
|
-
name: "AES-GCM",
|
|
2254
|
-
length: aesBitsOf(enc)
|
|
2255
|
-
};
|
|
2256
|
-
if (enc.includes("CBC-HS")) return;
|
|
2257
|
-
}
|
|
2258
|
-
if (bitLength === 128 || bitLength === 192 || bitLength === 256) return {
|
|
2259
|
-
name: "AES-GCM",
|
|
2260
|
-
length: bitLength
|
|
2261
|
-
};
|
|
2262
|
-
}
|
|
2263
|
-
const RESERVED_JWK_PARAMS = /* @__PURE__ */ new Set([
|
|
2264
|
-
"kty",
|
|
2265
|
-
"alg",
|
|
2266
|
-
"key_ops",
|
|
2267
|
-
"ext",
|
|
2268
|
-
"k",
|
|
2269
|
-
"d",
|
|
2270
|
-
"n",
|
|
2271
|
-
"e",
|
|
2272
|
-
"p",
|
|
2273
|
-
"q",
|
|
2274
|
-
"dp",
|
|
2275
|
-
"dq",
|
|
2276
|
-
"qi",
|
|
2277
|
-
"oth",
|
|
2278
|
-
"x",
|
|
2279
|
-
"y",
|
|
2280
|
-
"crv"
|
|
2281
|
-
]);
|
|
2282
|
-
function assertNoReservedJWKParams(jwkParams) {
|
|
2283
|
-
const reserved = Object.keys(jwkParams).filter((name) => RESERVED_JWK_PARAMS.has(name));
|
|
2284
|
-
if (reserved.length > 0) throw new JWTError(`jwkParams must not set reserved JWK members: ${reserved.join(", ")}`, "ERR_INVALID_OPTION");
|
|
2285
|
-
}
|
|
2286
144
|
function assertNamedCurve(requested, expected, alg) {
|
|
2287
145
|
if (requested === void 0 || requested === expected) return;
|
|
2288
146
|
throw new JWTError(`"namedCurve" ${requested} contradicts ${alg}, which is defined over ${expected}.`, "ERR_INVALID_OPTION");
|
|
@@ -2311,7 +169,7 @@ function resolveKeyUsage(requested, options) {
|
|
|
2311
169
|
if (requested === void 0) return fallback;
|
|
2312
170
|
if (!Array.isArray(requested) || requested.length === 0) throw new JWTError("\"keyUsage\" must be a non-empty array.", "ERR_INVALID_OPTION");
|
|
2313
171
|
const rejected = requested.filter((entry) => !allowed.includes(entry));
|
|
2314
|
-
if (rejected.length > 0) throw new JWTError(`"keyUsage" ${rejected.join(", ")} cannot be served by ${alg}
|
|
172
|
+
if (rejected.length > 0) throw new JWTError(`"keyUsage" ${rejected.join(", ")} cannot be served by ${alg}, use ${allowed.join(", ")}.`, "ERR_INVALID_OPTION");
|
|
2315
173
|
return requested;
|
|
2316
174
|
}
|
|
2317
175
|
const PRIVATE_KEY_USAGES = [
|
|
@@ -2331,21 +189,197 @@ function assertPairUsages(usages, options) {
|
|
|
2331
189
|
for (const [half, side] of [[PRIVATE_KEY_USAGES, "private"], [PUBLIC_KEY_USAGES, "public"]]) {
|
|
2332
190
|
const wanted = fallback.filter((entry) => half.includes(entry));
|
|
2333
191
|
if (wanted.length === 0 || usages.some((entry) => half.includes(entry))) continue;
|
|
2334
|
-
throw new JWTError(`"keyUsage" leaves the ${side} half of a ${alg} key pair
|
|
192
|
+
throw new JWTError(`"keyUsage" leaves the ${side} half of a ${alg} key pair unusable, add ${wanted.join(" or ")}.`, "ERR_INVALID_OPTION");
|
|
2335
193
|
}
|
|
2336
194
|
return usages;
|
|
2337
195
|
}
|
|
2338
|
-
function
|
|
2339
|
-
|
|
2340
|
-
throw new JWTError("
|
|
196
|
+
function assertModulusLength(modulusLength) {
|
|
197
|
+
const length = modulusLength ?? 2048;
|
|
198
|
+
if (!Number.isInteger(length) || length < 2048) throw new JWTError("modulusLength must be at least 2048 bits", "ERR_INVALID_OPTION");
|
|
199
|
+
return length;
|
|
2341
200
|
}
|
|
2342
|
-
|
|
2343
|
-
if (kid === false) return jwk;
|
|
201
|
+
function withAlg(jwk, alg) {
|
|
2344
202
|
return {
|
|
2345
203
|
...jwk,
|
|
2346
|
-
|
|
204
|
+
alg
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
function generatedPairAlgorithm(alg) {
|
|
208
|
+
return alg;
|
|
209
|
+
}
|
|
210
|
+
function generatedSecretAlgorithm(alg) {
|
|
211
|
+
return alg;
|
|
212
|
+
}
|
|
213
|
+
async function generateKey(alg, options = {}) {
|
|
214
|
+
const exportToJWK = options.toJWK !== void 0 && options.toJWK;
|
|
215
|
+
const defaultExtractable = exportToJWK || options.extractable !== false;
|
|
216
|
+
const kid = exportToJWK ? assertKidOption(options.kid) : void 0;
|
|
217
|
+
if (alg === "A128CBC-HS256" || alg === "A192CBC-HS384" || alg === "A256CBC-HS512") {
|
|
218
|
+
const keyLength = bitLengthCEK(alg);
|
|
219
|
+
const keyBytes = secureRandomBytes(keyLength >> 3);
|
|
220
|
+
if (exportToJWK) {
|
|
221
|
+
const jwk = await keyToJWK(keyBytes);
|
|
222
|
+
const keyOps = options.keyUsage === void 0 ? void 0 : resolveKeyUsage(options.keyUsage, {
|
|
223
|
+
allowed: CONTENT_ENCRYPTION_USAGES,
|
|
224
|
+
fallback: ["encrypt", "decrypt"],
|
|
225
|
+
alg
|
|
226
|
+
});
|
|
227
|
+
return withGeneratedKid(kid, withKeyOps({
|
|
228
|
+
...jwk,
|
|
229
|
+
alg
|
|
230
|
+
}, keyOps));
|
|
231
|
+
}
|
|
232
|
+
return keyBytes;
|
|
233
|
+
}
|
|
234
|
+
const { algorithm, keyUsages } = getGenerateKeyParams(alg, options);
|
|
235
|
+
const key = await crypto.subtle.generateKey(algorithm, defaultExtractable, keyUsages);
|
|
236
|
+
if (exportToJWK) {
|
|
237
|
+
if (isCryptoKeyPair(key)) {
|
|
238
|
+
const pairAlg = generatedPairAlgorithm(alg);
|
|
239
|
+
const [pub, priv] = await Promise.all([exportJWK(key.publicKey), exportJWK(key.privateKey)]);
|
|
240
|
+
const publicKey = await withGeneratedKid(kid, withKeyOps(withAlg(pub, pairAlg), requestedKeyOps(options, key.publicKey)));
|
|
241
|
+
const named = publicKey.kid === void 0 ? void 0 : { kid: publicKey.kid };
|
|
242
|
+
return {
|
|
243
|
+
privateKey: {
|
|
244
|
+
...withKeyOps(withAlg(priv, pairAlg), requestedKeyOps(options, key.privateKey)),
|
|
245
|
+
...named
|
|
246
|
+
},
|
|
247
|
+
publicKey
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
return withGeneratedKid(kid, withKeyOps(withAlg(await exportJWK(key), generatedSecretAlgorithm(alg)), requestedKeyOps(options, key)));
|
|
251
|
+
}
|
|
252
|
+
return key;
|
|
253
|
+
}
|
|
254
|
+
async function generateJWK(alg, options) {
|
|
255
|
+
const { keyUsage, modulusLength, publicExponent, namedCurve, kid, extractable: _extractable, toJWK: _toJWK, ...jwkParams } = options ?? {};
|
|
256
|
+
assertNoReservedJWKParams(jwkParams);
|
|
257
|
+
const result = await generateKey(alg, {
|
|
258
|
+
keyUsage,
|
|
259
|
+
modulusLength,
|
|
260
|
+
publicExponent,
|
|
261
|
+
namedCurve,
|
|
262
|
+
kid,
|
|
263
|
+
toJWK: true
|
|
264
|
+
});
|
|
265
|
+
if (result && typeof result === "object" && "privateKey" in result && "publicKey" in result) return {
|
|
266
|
+
privateKey: {
|
|
267
|
+
...jwkParams,
|
|
268
|
+
...result.privateKey
|
|
269
|
+
},
|
|
270
|
+
publicKey: {
|
|
271
|
+
...jwkParams,
|
|
272
|
+
...result.publicKey
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
return {
|
|
276
|
+
...jwkParams,
|
|
277
|
+
...result
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
async function deriveKeyFromPassword(password, options) {
|
|
281
|
+
assertPlainObject(options, "options");
|
|
282
|
+
if (typeof password !== "string" && !(password instanceof Uint8Array)) throw new JWTError("\"password\" must be a string or a Uint8Array.", "ERR_INVALID_OPTION");
|
|
283
|
+
const { alg, salt, iterations, extractable, keyUsage } = options;
|
|
284
|
+
assertPBES2Algorithm(alg);
|
|
285
|
+
const exportToJWK = options.toJWK !== void 0 && options.toJWK;
|
|
286
|
+
const saltBytes = typeof salt === "string" ? textEncoder.encode(salt) : salt;
|
|
287
|
+
assertPBES2Salt(saltBytes, "\"salt\"");
|
|
288
|
+
assertPBES2Count(iterations, "\"iterations\"");
|
|
289
|
+
const passwordBytes = typeof password === "string" ? textEncoder.encode(password) : password;
|
|
290
|
+
const derivedBytes = await deriveKey(saltBytes, alg, iterations, passwordBytes);
|
|
291
|
+
const wrappingAlg = innerKeyWrapAlg(alg);
|
|
292
|
+
const finalUsages = keyUsage ?? ["wrapKey", "unwrapKey"];
|
|
293
|
+
const finalExtractable = exportToJWK ? true : extractable === true;
|
|
294
|
+
const derivedKey = await crypto.subtle.importKey("raw", derivedBytes, { name: "AES-KW" }, finalExtractable, finalUsages);
|
|
295
|
+
if (exportToJWK) {
|
|
296
|
+
const jwk = await keyToJWK(derivedKey);
|
|
297
|
+
return withKeyOps({
|
|
298
|
+
...jwk,
|
|
299
|
+
alg: wrappingAlg,
|
|
300
|
+
kty: "oct"
|
|
301
|
+
}, requestedKeyOps(options, derivedKey));
|
|
302
|
+
}
|
|
303
|
+
return derivedKey;
|
|
304
|
+
}
|
|
305
|
+
async function deriveJWKFromPassword(password, options) {
|
|
306
|
+
assertPlainObject(options, "options");
|
|
307
|
+
const { alg, salt, iterations, keyUsage, kid: kidOption, extractable: _extractable, toJWK: _toJWK, ...jwkParams } = options;
|
|
308
|
+
assertNoReservedJWKParams(jwkParams);
|
|
309
|
+
const kid = assertKidOption(kidOption);
|
|
310
|
+
const result = await deriveKeyFromPassword(password, {
|
|
311
|
+
alg,
|
|
312
|
+
salt,
|
|
313
|
+
iterations,
|
|
314
|
+
keyUsage,
|
|
315
|
+
toJWK: true
|
|
316
|
+
});
|
|
317
|
+
return {
|
|
318
|
+
...jwkParams,
|
|
319
|
+
...await withGeneratedKid(kid, result)
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
function assertPBES2Algorithm(alg) {
|
|
323
|
+
if (typeof alg === "string" && PBES2_ALGORITHMS.includes(alg)) return;
|
|
324
|
+
throw new JWTError(`Password derivation is defined for ${PBES2_ALGORITHMS.join(", ")}, not ${String(alg)}.`, "ERR_JWK_UNSUPPORTED");
|
|
325
|
+
}
|
|
326
|
+
async function importPEM(pem, alg, options) {
|
|
327
|
+
if (typeof pem !== "string") throw new JWTError("\"pem\" must be a PEM-encoded string.", "ERR_INVALID_OPTION");
|
|
328
|
+
if (options !== void 0) assertPlainObject(options, "options");
|
|
329
|
+
const extractable = true;
|
|
330
|
+
const pemType = options?.pemType ?? inferPEMType(pem);
|
|
331
|
+
if (!pemType) throw new JWTError("Cannot infer PEM type from the input, pass \"pemType\".", "ERR_JWK_INVALID");
|
|
332
|
+
let cryptoKey;
|
|
333
|
+
switch (pemType) {
|
|
334
|
+
case "pkcs8":
|
|
335
|
+
cryptoKey = await fromPKCS8(pem, alg, { extractable });
|
|
336
|
+
break;
|
|
337
|
+
case "spki":
|
|
338
|
+
cryptoKey = await fromSPKI(pem, alg, { extractable });
|
|
339
|
+
break;
|
|
340
|
+
case "x509":
|
|
341
|
+
cryptoKey = await fromX509(pem, alg, { extractable });
|
|
342
|
+
break;
|
|
343
|
+
default: throw new JWTError(`Unsupported PEM type: ${String(pemType)}`, "ERR_JWK_UNSUPPORTED");
|
|
344
|
+
}
|
|
345
|
+
return {
|
|
346
|
+
...await exportJWK(cryptoKey, options?.jwkParams),
|
|
347
|
+
alg
|
|
2347
348
|
};
|
|
2348
349
|
}
|
|
350
|
+
async function exportPEM(jwk, options) {
|
|
351
|
+
assertPlainObject(jwk, "jwk");
|
|
352
|
+
if (options !== void 0) assertPlainObject(options, "options");
|
|
353
|
+
if (jwk.kty === "oct") throw new JWTError("An oct JWK cannot be exported to PKCS8 or SPKI PEM.", "ERR_JWK_UNSUPPORTED");
|
|
354
|
+
const effectiveAlg = jwk.alg || options?.alg;
|
|
355
|
+
if (!effectiveAlg && (jwk.kty === "RSA" || jwk.kty === "EC" || jwk.kty === "OKP")) throw new JWTError("\"alg\" is required, set it on the JWK or pass it in options.", "ERR_JWK_INVALID");
|
|
356
|
+
if (effectiveAlg !== void 0 && !PEM_ALGORITHMS.has(effectiveAlg)) throw new JWTError(`Unsupported PEM algorithm: ${effectiveAlg}`, "ERR_JWK_UNSUPPORTED");
|
|
357
|
+
const pemFormat = options?.pemFormat ?? ("d" in jwk ? "pkcs8" : "spki");
|
|
358
|
+
const cryptoKeyCandidate = await importKey({
|
|
359
|
+
...jwk,
|
|
360
|
+
ext: true
|
|
361
|
+
}, {
|
|
362
|
+
alg: effectiveAlg,
|
|
363
|
+
expect: pemFormat === "pkcs8" ? "private" : "public",
|
|
364
|
+
extractable: true
|
|
365
|
+
});
|
|
366
|
+
if (!isCryptoKey(cryptoKeyCandidate)) throw new JWTError("Failed to convert JWK to a CryptoKey instance suitable for PEM export.", "ERR_JWK_INVALID");
|
|
367
|
+
const cryptoKey = cryptoKeyCandidate;
|
|
368
|
+
switch (pemFormat) {
|
|
369
|
+
case "pkcs8":
|
|
370
|
+
if (cryptoKey.type !== "private") throw new JWTError(`Only a private CryptoKey can be exported to PKCS8 PEM (got ${cryptoKey.type}).`, "ERR_JWK_INVALID");
|
|
371
|
+
return toPKCS8(cryptoKey);
|
|
372
|
+
case "spki":
|
|
373
|
+
if (cryptoKey.type !== "public") throw new JWTError(`Only a public CryptoKey can be exported to SPKI PEM (got ${cryptoKey.type}).`, "ERR_JWK_INVALID");
|
|
374
|
+
return toSPKI(cryptoKey);
|
|
375
|
+
default: throw new JWTError(`Unsupported PEM format: ${String(pemFormat)}`, "ERR_JWK_UNSUPPORTED");
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
function inferPEMType(pem) {
|
|
379
|
+
if (pem.includes("-----BEGIN PRIVATE KEY-----")) return "pkcs8";
|
|
380
|
+
if (pem.includes("-----BEGIN PUBLIC KEY-----")) return "spki";
|
|
381
|
+
if (pem.includes("-----BEGIN CERTIFICATE-----")) return "x509";
|
|
382
|
+
}
|
|
2349
383
|
const PEM_ALGORITHMS = /* @__PURE__ */ new Set(Object.keys({
|
|
2350
384
|
"PS256": true,
|
|
2351
385
|
"PS384": true,
|
|
@@ -2367,85 +401,28 @@ const PEM_ALGORITHMS = /* @__PURE__ */ new Set(Object.keys({
|
|
|
2367
401
|
"Ed25519": true,
|
|
2368
402
|
"EdDSA": true
|
|
2369
403
|
}));
|
|
2370
|
-
function
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
}
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
if (
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
const length = modulusLength ?? 2048;
|
|
2391
|
-
if (!Number.isInteger(length) || length < 2048) throw new JWTError("modulusLength must be at least 2048 bits (RFC 7518 §3.3)", "ERR_INVALID_OPTION");
|
|
2392
|
-
return length;
|
|
2393
|
-
}
|
|
2394
|
-
function assertPartyInfoLength(name, bytes) {
|
|
2395
|
-
if (bytes.length > 1024) throw new JWTError(`Invalid JWE: "${name}" must not exceed ${MAX_PARTY_INFO_BYTES} bytes (got ${bytes.length}).`, "ERR_JWE_INVALID");
|
|
2396
|
-
}
|
|
2397
|
-
function assertKeyWrapSegmentLength(name, bytes, expected, alg) {
|
|
2398
|
-
if (bytes.length !== expected) throw new JWTError(`Invalid JWE: "${name}" must be ${expected} bytes for ${alg} (got ${bytes.length}).`, "ERR_JWE_INVALID");
|
|
404
|
+
async function deriveSharedSecret(publicKey, privateKey, options) {
|
|
405
|
+
assertPlainObject(options, "options");
|
|
406
|
+
const { enc: alg, keyLength: requestedKeyLength, partyUInfo, partyVInfo } = options;
|
|
407
|
+
if (!isECDHESAlgorithm(alg)) assertJWEEncSupported(alg);
|
|
408
|
+
if (requestedKeyLength !== void 0) assertDerivedKeyLength(requestedKeyLength);
|
|
409
|
+
const importAlg = isECDHESAlgorithm(alg) ? alg : "ECDH-ES";
|
|
410
|
+
const pubCryptoKey = isCryptoKey(publicKey) ? publicKey : await importKey(publicKey, {
|
|
411
|
+
alg: importAlg,
|
|
412
|
+
expect: "public"
|
|
413
|
+
});
|
|
414
|
+
const privCryptoKey = isCryptoKey(privateKey) ? privateKey : await importKey(privateKey, {
|
|
415
|
+
alg: importAlg,
|
|
416
|
+
expect: "private"
|
|
417
|
+
});
|
|
418
|
+
let keyLength;
|
|
419
|
+
if (requestedKeyLength !== void 0) keyLength = requestedKeyLength;
|
|
420
|
+
else if (alg === "ECDH-ES") throw new JWTError("deriveSharedSecret with enc \"ECDH-ES\" requires an explicit keyLength in options", "ERR_JWK_INVALID");
|
|
421
|
+
else if (isECDHESAlgorithm(alg)) keyLength = aesBitsOf(innerKeyWrapAlg(alg));
|
|
422
|
+
else keyLength = bitLengthCEK(alg);
|
|
423
|
+
return deriveECDHESKey(pubCryptoKey, privCryptoKey, alg, keyLength, partyUInfo ?? /* @__PURE__ */ new Uint8Array(0), partyVInfo ?? /* @__PURE__ */ new Uint8Array(0));
|
|
2399
424
|
}
|
|
2400
425
|
function assertDerivedKeyLength(keyLength) {
|
|
2401
426
|
if (!Number.isInteger(keyLength) || keyLength <= 0 || keyLength % 8 !== 0 || keyLength > 8192) throw new JWTError(`keyLength must be a positive multiple of 8 no greater than 8192 bits, got ${keyLength}`, "ERR_INVALID_OPTION");
|
|
2402
427
|
}
|
|
2403
|
-
|
|
2404
|
-
if (isCryptoKey(key)) return;
|
|
2405
|
-
throw new JWTError(`${alg.startsWith("RSA") ? "RSA-OAEP" : "ECDH-ES"} requires the ${role} key to be a CryptoKey`, "ERR_JWK_INVALID");
|
|
2406
|
-
}
|
|
2407
|
-
function segmentBytes(value, name) {
|
|
2408
|
-
return typeof value === "string" ? decodeSegment(value, {
|
|
2409
|
-
family: "JWE",
|
|
2410
|
-
what: `"${name}"`
|
|
2411
|
-
}) : value;
|
|
2412
|
-
}
|
|
2413
|
-
function withAlg(jwk, alg) {
|
|
2414
|
-
return {
|
|
2415
|
-
...jwk,
|
|
2416
|
-
alg
|
|
2417
|
-
};
|
|
2418
|
-
}
|
|
2419
|
-
function generatedPairAlgorithm(alg) {
|
|
2420
|
-
return alg;
|
|
2421
|
-
}
|
|
2422
|
-
function generatedSecretAlgorithm(alg) {
|
|
2423
|
-
return alg;
|
|
2424
|
-
}
|
|
2425
|
-
const PUBLIC_KEY_OPS = /* @__PURE__ */ new Map([
|
|
2426
|
-
["sign", "verify"],
|
|
2427
|
-
["verify", "verify"],
|
|
2428
|
-
["decrypt", "encrypt"],
|
|
2429
|
-
["encrypt", "encrypt"],
|
|
2430
|
-
["unwrapKey", "wrapKey"],
|
|
2431
|
-
["wrapKey", "wrapKey"]
|
|
2432
|
-
]);
|
|
2433
|
-
function publicKeyOps(keyOps) {
|
|
2434
|
-
if (!Array.isArray(keyOps)) return [];
|
|
2435
|
-
const ops = [];
|
|
2436
|
-
for (const op of keyOps) {
|
|
2437
|
-
const publicOp = typeof op === "string" ? PUBLIC_KEY_OPS.get(op) : void 0;
|
|
2438
|
-
if (publicOp !== void 0 && !ops.includes(publicOp)) ops.push(publicOp);
|
|
2439
|
-
}
|
|
2440
|
-
return ops;
|
|
2441
|
-
}
|
|
2442
|
-
function requestedKeyOps(options, key) {
|
|
2443
|
-
return options.keyUsage === void 0 ? void 0 : [...key.usages];
|
|
2444
|
-
}
|
|
2445
|
-
function withKeyOps(jwk, keyOps) {
|
|
2446
|
-
return keyOps === void 0 ? jwk : {
|
|
2447
|
-
...jwk,
|
|
2448
|
-
key_ops: keyOps
|
|
2449
|
-
};
|
|
2450
|
-
}
|
|
2451
|
-
export { DEFAULT_JOSE_LIMITS, DEFAULT_PBES2_ITERATIONS, JWE_ALG_CTX, JWE_DIRECT_ALGS, JWS_ALG_CTX, WeakMapJWKCache, applyB64Header, asDecryptionError, asJWTError, asKeyLookupError, asKeyUsageError, assertB64Option, assertCompactToken, assertConsumerOptions, assertDirectKeyRole, assertEntryLimit, assertJWEAlgSupported, assertJWEEncSupported, assertJWKSetCandidates, assertJWSAlgSupported, assertNoZipOption, assertNotStringKey, assertOptionEntry, assertPBES2Params, assertProducerCEK, assertProducerConfirmation, assertProducerCrit, assertProducerIV, assertProducerKeyHeaders, assertProtectedOnlyOption, assertSerializationObject, buildECDHAgreementParams, buildJWKSetFilter, buildUnwrapKeyOptions, checkAlgAllowed, checkJWEAlgSupported, checkJWEContentSegments, checkJWEDirectEncryptedKey, checkJWEEncAllowed, checkJWEEncSupported, checkJWEUnprotectedCrit, checkJWEZip, checkJWSAlgSupported, checkJWSB64, checkProtectedTyp, checkSerializationEntry, clearJWKCache, configureJWKCache, createPBES2Budget, decodeJWEPayload, decodeJWSPayload, decodeProtectedHeader, decodeSegment, decrypt, deriveJWKFromPassword, deriveKeyFromPassword, deriveSharedSecret, encrypt, encryptKey, exportJWK, exportKey, exportPEM, exportReturnedCek, generateCEK, generateIV, generateJWK, generateKey, getJWKsFromSet, importKey, importPEM, isJWEAlgSupported, keyMatchesHeader, quoteTokenValue, resolveKeyInput, resolveLimit, resolvePasswordKey, resolvePayloadMode, resolveSigningKey, serializeProtectedHeader, sign, stringKeyError, thumbprint, toPublicJWK, unwrapKey, validateClaimsIfJson, verify, wrapKey };
|
|
428
|
+
export { deriveJWKFromPassword, deriveKeyFromPassword, deriveSharedSecret, exportPEM, generateJWK, generateKey, importPEM };
|