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