unjwt 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +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 +512 -818
- 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 +1390 -622
- 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 -155
- package/dist/core/utils/index.mjs +12 -3
- package/dist/index.d.mts +8 -4
- package/dist/index.mjs +6 -3
- package/package.json +39 -36
- package/dist/_chunks/_internal.mjs +0 -88
- package/dist/_chunks/utils.mjs +0 -390
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,66 +84,65 @@ 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
|
-
A128GCM: 96,
|
|
114
|
-
A128GCMKW: 96,
|
|
115
|
-
A192GCM: 96,
|
|
116
|
-
A192GCMKW: 96,
|
|
117
|
-
A256GCM: 96,
|
|
118
|
-
A256GCMKW: 96,
|
|
95
|
+
"A128GCM": 96,
|
|
96
|
+
"A128GCMKW": 96,
|
|
97
|
+
"A192GCM": 96,
|
|
98
|
+
"A192GCMKW": 96,
|
|
99
|
+
"A256GCM": 96,
|
|
100
|
+
"A256GCMKW": 96,
|
|
119
101
|
"A128CBC-HS256": 128,
|
|
120
102
|
"A192CBC-HS384": 128,
|
|
121
103
|
"A256CBC-HS512": 128
|
|
122
104
|
};
|
|
123
105
|
const CEK_BIT_LENGTHS = {
|
|
124
|
-
A128GCM: 128,
|
|
125
|
-
A192GCM: 192,
|
|
126
|
-
A256GCM: 256,
|
|
106
|
+
"A128GCM": 128,
|
|
107
|
+
"A192GCM": 192,
|
|
108
|
+
"A256GCM": 256,
|
|
127
109
|
"A128CBC-HS256": 256,
|
|
128
110
|
"A192CBC-HS384": 384,
|
|
129
111
|
"A256CBC-HS512": 512
|
|
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" });
|
|
@@ -202,18 +199,18 @@ async function aesKwUnwrap(alg, key, encryptedKey) {
|
|
|
202
199
|
return new Uint8Array(await crypto.subtle.exportKey("raw", cryptoKeyCek));
|
|
203
200
|
}
|
|
204
201
|
async function gcmkwEncrypt(alg, key, plaintext, iv) {
|
|
205
|
-
const wrapped = await encrypt(alg.slice(0, 7), plaintext, key, iv, new Uint8Array(0));
|
|
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
|
-
return decrypt(alg.slice(0, 7), key, ciphertext, iv, tag, new Uint8Array(0));
|
|
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
|
|
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
|
-
async function deriveECDHESKey(publicKey, privateKey, algorithm, keyLength, apu = new Uint8Array(0), apv = new Uint8Array(0)) {
|
|
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 = 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 = 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);
|
|
711
|
+
}
|
|
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;
|
|
755
734
|
}
|
|
756
|
-
throw
|
|
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");
|
|
930
|
+
}
|
|
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);
|
|
905
935
|
}
|
|
906
|
-
return crypto.subtle.importKey(keyFormat, keyData, algorithm, options?.extractable ?? (isPublic ? true : false), keyUsages);
|
|
907
936
|
};
|
|
908
|
-
const findOid = (keyData, oid
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
oid.
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
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;
|
|
946
|
+
};
|
|
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,82 +1069,680 @@ 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
|
+
}
|
|
1085
|
+
return formatPEM(spkiFromX509(raw), "PUBLIC KEY");
|
|
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"]
|
|
1021
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)) {
|
|
1382
|
+
if (header.kid && keyMaterial.kid && header.kid !== keyMaterial.kid) return false;
|
|
1383
|
+
return keyServesHeaderAlg(header, keyMaterial, ctx);
|
|
1384
|
+
}
|
|
1385
|
+
if (isCryptoKey(keyMaterial)) return keyServesHeaderAlg(header, keyMaterial, ctx);
|
|
1386
|
+
return true;
|
|
1387
|
+
}
|
|
1388
|
+
const JWS_ALGORITHM_SET = /* @__PURE__ */ new Set(Object.keys({
|
|
1389
|
+
HS256: true,
|
|
1390
|
+
HS384: true,
|
|
1391
|
+
HS512: true,
|
|
1392
|
+
RS256: true,
|
|
1393
|
+
RS384: true,
|
|
1394
|
+
RS512: true,
|
|
1395
|
+
PS256: true,
|
|
1396
|
+
PS384: true,
|
|
1397
|
+
PS512: true,
|
|
1398
|
+
ES256: true,
|
|
1399
|
+
ES384: true,
|
|
1400
|
+
ES512: true,
|
|
1401
|
+
Ed25519: true,
|
|
1402
|
+
EdDSA: true
|
|
1403
|
+
}));
|
|
1404
|
+
function checkJWSAlgSupported(alg) {
|
|
1405
|
+
return checkAlgSupported(alg, {
|
|
1406
|
+
supported: JWS_ALGORITHM_SET,
|
|
1407
|
+
code: "ERR_JWS_ALG_NOT_ALLOWED",
|
|
1408
|
+
label: "Algorithm",
|
|
1409
|
+
role: "signing algorithm"
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
function checkJWSB64(header) {
|
|
1413
|
+
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");
|
|
1414
|
+
if (header.b64 !== false) return void 0;
|
|
1415
|
+
if (Array.isArray(header.crit) && header.crit.includes("b64")) return void 0;
|
|
1416
|
+
return new JWTError("Invalid JWS: \"b64\" must be listed in \"crit\" (RFC 7797 §6).", "ERR_JWS_INVALID");
|
|
1417
|
+
}
|
|
1418
|
+
function assertB64Option(header) {
|
|
1419
|
+
if (!header || !Object.hasOwn(header, "b64") || typeof header.b64 === "boolean") return;
|
|
1420
|
+
throw new JWTError("\"b64\" must be a boolean (RFC 7797 §3).", "ERR_INVALID_OPTION");
|
|
1421
|
+
}
|
|
1422
|
+
function applyB64Header(header) {
|
|
1423
|
+
assertB64Option(header);
|
|
1424
|
+
if (header.b64 === true) {
|
|
1425
|
+
if (!(Array.isArray(header.crit) && header.crit.includes("b64"))) delete header.b64;
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1428
|
+
if (header.b64 !== false) return;
|
|
1429
|
+
const crit = header.crit;
|
|
1430
|
+
if (crit === void 0) header.crit = ["b64"];
|
|
1431
|
+
else if (Array.isArray(crit) && !crit.includes("b64")) header.crit = [...crit, "b64"];
|
|
1432
|
+
}
|
|
1433
|
+
function assertCompactToken(token, family) {
|
|
1434
|
+
if (typeof token !== "string") throw new JWTError(`Invalid ${family}: token must be a string.`, `ERR_${family}_INVALID`);
|
|
1435
|
+
}
|
|
1436
|
+
function assertSerializationObject(value, family) {
|
|
1437
|
+
if (!isPlainObject(value)) throw new JWTError(`Invalid ${family} JSON Serialization: expected an object.`, `ERR_${family}_INVALID_SERIALIZATION`);
|
|
1438
|
+
}
|
|
1439
|
+
function checkSerializationEntry(entry, options) {
|
|
1440
|
+
const { family, label, objects, strings } = options;
|
|
1441
|
+
const code = `ERR_${family}_INVALID`;
|
|
1442
|
+
if (!isPlainObject(entry)) return new JWTError(`Invalid ${family}: ${label} must be a JSON object.`, code);
|
|
1443
|
+
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);
|
|
1444
|
+
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);
|
|
1445
|
+
}
|
|
1446
|
+
function asKeyLookupError(error, what) {
|
|
1447
|
+
return asJWTError(error, {
|
|
1448
|
+
code: "ERR_JWK_KEY_NOT_FOUND",
|
|
1449
|
+
message: `Key lookup failed for ${what}.`
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1452
|
+
function asJWTError(error, options) {
|
|
1453
|
+
if (error instanceof JWTError) return error;
|
|
1454
|
+
return new JWTError(options.message, options.code, error);
|
|
1455
|
+
}
|
|
1456
|
+
function asDecryptionError(error) {
|
|
1457
|
+
return asJWTError(error, {
|
|
1458
|
+
code: "ERR_JWE_DECRYPTION_FAILED",
|
|
1459
|
+
message: "JWE decryption failed."
|
|
1460
|
+
});
|
|
1461
|
+
}
|
|
1462
|
+
async function resolveKeyInput(key, options) {
|
|
1463
|
+
if (typeof key !== "function") return key;
|
|
1464
|
+
const { header, token, what } = options;
|
|
1465
|
+
let resolved;
|
|
1466
|
+
try {
|
|
1467
|
+
resolved = await key(header, token);
|
|
1468
|
+
} catch (error) {
|
|
1469
|
+
throw asKeyLookupError(error, what);
|
|
1470
|
+
}
|
|
1471
|
+
if (resolved === void 0 || resolved === null) throw new JWTError(`Key lookup returned no key for ${what}.`, "ERR_JWK_KEY_NOT_FOUND");
|
|
1472
|
+
return resolved;
|
|
1473
|
+
}
|
|
1474
|
+
function asKeyUsageError(error, alg) {
|
|
1475
|
+
return asJWTError(error, {
|
|
1476
|
+
code: "ERR_JWK_INVALID",
|
|
1477
|
+
message: `Key is not usable for ${alg}.`
|
|
1478
|
+
});
|
|
1479
|
+
}
|
|
1480
|
+
const JWE_ALGORITHM_SET = /* @__PURE__ */ new Set(Object.keys({
|
|
1481
|
+
"dir": true,
|
|
1482
|
+
"A128KW": true,
|
|
1483
|
+
"A192KW": true,
|
|
1484
|
+
"A256KW": true,
|
|
1485
|
+
"A128GCMKW": true,
|
|
1486
|
+
"A192GCMKW": true,
|
|
1487
|
+
"A256GCMKW": true,
|
|
1488
|
+
"PBES2-HS256+A128KW": true,
|
|
1489
|
+
"PBES2-HS384+A192KW": true,
|
|
1490
|
+
"PBES2-HS512+A256KW": true,
|
|
1491
|
+
"RSA-OAEP": true,
|
|
1492
|
+
"RSA-OAEP-256": true,
|
|
1493
|
+
"RSA-OAEP-384": true,
|
|
1494
|
+
"RSA-OAEP-512": true,
|
|
1495
|
+
"ECDH-ES": true,
|
|
1496
|
+
"ECDH-ES+A128KW": true,
|
|
1497
|
+
"ECDH-ES+A192KW": true,
|
|
1498
|
+
"ECDH-ES+A256KW": true
|
|
1499
|
+
}));
|
|
1500
|
+
const JWE_ENC_CEK_FORMAT = /* @__PURE__ */ Object.freeze({
|
|
1501
|
+
"A128GCM": "cryptokey",
|
|
1502
|
+
"A192GCM": "cryptokey",
|
|
1503
|
+
"A256GCM": "cryptokey",
|
|
1504
|
+
"A128CBC-HS256": "raw",
|
|
1505
|
+
"A192CBC-HS384": "raw",
|
|
1506
|
+
"A256CBC-HS512": "raw"
|
|
1507
|
+
});
|
|
1508
|
+
function assertDirectKeyRole(key, enc) {
|
|
1509
|
+
if (!isJWK(key) || typeof key.alg !== "string" || key.alg === "dir") return;
|
|
1510
|
+
if (enc !== void 0 && key.alg === enc) return;
|
|
1511
|
+
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");
|
|
1512
|
+
}
|
|
1513
|
+
const JWE_DIRECT_ALGS = /* @__PURE__ */ new Set(["dir", "ECDH-ES"]);
|
|
1514
|
+
function checkJWEAlgSupported(alg) {
|
|
1515
|
+
return checkAlgSupported(alg, {
|
|
1516
|
+
supported: JWE_ALGORITHM_SET,
|
|
1517
|
+
code: "ERR_JWE_ALG_NOT_ALLOWED",
|
|
1518
|
+
label: "Key management algorithm",
|
|
1519
|
+
role: "key management algorithm"
|
|
1520
|
+
});
|
|
1521
|
+
}
|
|
1522
|
+
function checkJWEEncSupported(enc) {
|
|
1523
|
+
if (typeof enc === "string" && isContentEncryptionAlgorithm(enc)) return void 0;
|
|
1524
|
+
return new JWTError(`Invalid JWE: unsupported "enc" ${quoteTokenValue(enc)}`, "ERR_JWE_INVALID");
|
|
1525
|
+
}
|
|
1526
|
+
function isJWEAlgSupported(alg) {
|
|
1527
|
+
return JWE_ALGORITHM_SET.has(alg);
|
|
1528
|
+
}
|
|
1529
|
+
function assertJWEAlgSupported(alg) {
|
|
1530
|
+
const error = checkJWEAlgSupported(alg);
|
|
1531
|
+
if (error) throw error;
|
|
1532
|
+
}
|
|
1533
|
+
function assertJWEEncSupported(enc) {
|
|
1534
|
+
const error = checkJWEEncSupported(enc);
|
|
1535
|
+
if (error) throw error;
|
|
1536
|
+
}
|
|
1537
|
+
function checkJWEEncAllowed(enc, encryptionAlgorithms) {
|
|
1538
|
+
if (encryptionAlgorithms === void 0 || encryptionAlgorithms.includes(enc)) return void 0;
|
|
1539
|
+
return new JWTError(`Content encryption algorithm not allowed: ${enc}`, "ERR_JWE_ALG_NOT_ALLOWED");
|
|
1540
|
+
}
|
|
1541
|
+
async function exportReturnedCek(cek) {
|
|
1542
|
+
if (!isCryptoKey(cek)) return cek;
|
|
1543
|
+
if (!cek.extractable) throw new JWTError("\"returnCek\" requires an extractable content encryption key.", "ERR_INVALID_OPTION");
|
|
1544
|
+
return new Uint8Array(await crypto.subtle.exportKey("raw", cek));
|
|
1545
|
+
}
|
|
1546
|
+
function cekFormatForEnc(enc) {
|
|
1547
|
+
return JWE_ENC_CEK_FORMAT[enc];
|
|
1548
|
+
}
|
|
1549
|
+
function buildUnwrapKeyOptions(header, algorithms, options) {
|
|
1550
|
+
const { alg, enc } = algorithms;
|
|
1551
|
+
const opts = {
|
|
1552
|
+
alg,
|
|
1553
|
+
enc,
|
|
1554
|
+
format: cekFormatForEnc(enc),
|
|
1555
|
+
keyUsage: options?.keyUsage || ["decrypt"]
|
|
1556
|
+
};
|
|
1557
|
+
if (header.iv) opts.iv = header.iv;
|
|
1558
|
+
if (header.tag) opts.tag = header.tag;
|
|
1559
|
+
if (header.p2s) opts.p2s = header.p2s;
|
|
1560
|
+
if (header.p2c) opts.p2c = header.p2c;
|
|
1561
|
+
if (header.epk) opts.epk = header.epk;
|
|
1562
|
+
if (header.apu) opts.apu = header.apu;
|
|
1563
|
+
if (header.apv) opts.apv = header.apv;
|
|
1564
|
+
if (options?.unwrappedKeyAlgorithm) opts.unwrappedKeyAlgorithm = options.unwrappedKeyAlgorithm;
|
|
1565
|
+
if (options?.extractable !== void 0) opts.extractable = options.extractable;
|
|
1566
|
+
if (options?.minIterations !== void 0) opts.minIterations = options.minIterations;
|
|
1567
|
+
if (options?.maxIterations !== void 0) opts.maxIterations = options.maxIterations;
|
|
1568
|
+
return opts;
|
|
1569
|
+
}
|
|
1570
|
+
function checkJWEDirectEncryptedKey(alg, encryptedKey) {
|
|
1571
|
+
if (!JWE_DIRECT_ALGS.has(alg) || !encryptedKey) return void 0;
|
|
1572
|
+
return new JWTError(`Invalid JWE: Encrypted key must be empty for ${alg}.`, "ERR_JWE_INVALID");
|
|
1573
|
+
}
|
|
1574
|
+
function checkJWEContentSegments(enc, iv, tag) {
|
|
1575
|
+
const ivBytes = bitLengthIV(enc) >> 3;
|
|
1576
|
+
if (iv.length !== ivBytes) return new JWTError(`Invalid JWE: Initialization vector must be ${ivBytes} bytes for ${enc} (got ${iv.length}).`, "ERR_JWE_INVALID");
|
|
1577
|
+
const tagBytes = bitLengthTag(enc) >> 3;
|
|
1578
|
+
if (tag.length !== tagBytes) return new JWTError(`Invalid JWE: Authentication tag must be ${tagBytes} bytes for ${enc} (got ${tag.length}).`, "ERR_JWE_INVALID");
|
|
1579
|
+
}
|
|
1580
|
+
function checkJWEZip(header) {
|
|
1581
|
+
if (!header || !Object.hasOwn(header, "zip")) return void 0;
|
|
1582
|
+
return new JWTError("Invalid JWE: compression is not supported.", "ERR_JWE_INVALID");
|
|
1583
|
+
}
|
|
1584
|
+
function assertNoZipOption(header, tier) {
|
|
1585
|
+
if (!header || !Object.hasOwn(header, "zip")) return;
|
|
1586
|
+
throw new JWTError(`JWE compression ("zip") is not supported; remove it from the ${tier} header.`, "ERR_INVALID_OPTION");
|
|
1587
|
+
}
|
|
1588
|
+
function checkJWEUnprotectedCrit(header) {
|
|
1589
|
+
if (!header || !Object.hasOwn(header, "crit")) return void 0;
|
|
1590
|
+
return new JWTError("Invalid JWE: \"crit\" must be integrity protected (RFC 7516 §4.1.13).", "ERR_JWE_INVALID");
|
|
1591
|
+
}
|
|
1592
|
+
function typMatches(expected, actual) {
|
|
1593
|
+
if (typeof actual !== "string") return false;
|
|
1594
|
+
return normalizeTyp(expected) === normalizeTyp(actual);
|
|
1595
|
+
}
|
|
1596
|
+
function checkProtectedTyp(expected, protectedHeader, options) {
|
|
1597
|
+
if (expected === void 0) return void 0;
|
|
1598
|
+
const actual = protectedHeader.typ;
|
|
1599
|
+
if (typMatches(expected, actual)) return void 0;
|
|
1600
|
+
const { family, label } = options;
|
|
1601
|
+
const where = label ? ` on ${label}` : "";
|
|
1602
|
+
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)}.`;
|
|
1603
|
+
return new JWTError(message, `ERR_${family}_INVALID`);
|
|
1604
|
+
}
|
|
1605
|
+
function normalizeTyp(typ) {
|
|
1606
|
+
const lowered = typ.toLowerCase();
|
|
1607
|
+
return lowered.includes("/") ? lowered : `application/${lowered}`;
|
|
1608
|
+
}
|
|
1609
|
+
function decodeProtectedHeader(encoded, family) {
|
|
1610
|
+
let parsed = {};
|
|
1611
|
+
if (encoded) {
|
|
1612
|
+
try {
|
|
1613
|
+
parsed = safeJsonParse(base64UrlParse(encoded));
|
|
1614
|
+
} catch (error) {
|
|
1615
|
+
throw new JWTError(`Invalid ${family}: Protected header could not be decoded.`, `ERR_${family}_INVALID`, error);
|
|
1616
|
+
}
|
|
1617
|
+
if (!isPlainObject(parsed)) throw new JWTError(`Invalid ${family}: Protected header must be a JSON object.`, `ERR_${family}_INVALID`);
|
|
1618
|
+
}
|
|
1619
|
+
return parsed;
|
|
1620
|
+
}
|
|
1621
|
+
function decodeSegment(encoded, options) {
|
|
1622
|
+
try {
|
|
1623
|
+
return base64UrlParse(encoded, { returnAs: "uint8array" });
|
|
1624
|
+
} catch (error) {
|
|
1625
|
+
throw new JWTError(`Invalid ${options.family}: ${options.what} could not be decoded.`, `ERR_${options.family}_INVALID`, error);
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
function decodeJWSPayload(payloadSegment, useB64, mode) {
|
|
1629
|
+
if (!useB64) return mode === "string" ? payloadSegment : parsePayloadBytes(textEncoder.encode(payloadSegment), {
|
|
1630
|
+
payload: mode,
|
|
1631
|
+
family: "JWS"
|
|
1632
|
+
});
|
|
1633
|
+
try {
|
|
1634
|
+
return parsePayloadSegment(payloadSegment, {
|
|
1635
|
+
payload: mode,
|
|
1636
|
+
family: "JWS"
|
|
1637
|
+
});
|
|
1638
|
+
} catch (error) {
|
|
1639
|
+
if (error instanceof JWTError) throw error;
|
|
1640
|
+
throw new JWTError("Invalid JWS: Payload could not be decoded.", "ERR_JWS_INVALID", error);
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
function decodeJWEPayload(plaintext, mode) {
|
|
1644
|
+
return parsePayloadBytes(plaintext, {
|
|
1645
|
+
payload: mode,
|
|
1646
|
+
family: "JWE"
|
|
1647
|
+
});
|
|
1648
|
+
}
|
|
1649
|
+
function checkAlgSupported(alg, options) {
|
|
1650
|
+
const { supported, code, label, role } = options;
|
|
1651
|
+
if (alg === "none") return new JWTError(`"none" is not a valid ${role}`, code);
|
|
1652
|
+
if (!supported.has(alg)) return new JWTError(`${label} not supported: ${quoteTokenValue(alg)}`, code);
|
|
1653
|
+
}
|
|
1654
|
+
function keyServesHeaderAlg(header, key, ctx) {
|
|
1655
|
+
const { alg } = header;
|
|
1656
|
+
if (!alg) return false;
|
|
1657
|
+
const inferred = ctx.infer(key);
|
|
1658
|
+
if (inferred && !inferred.includes(alg)) return false;
|
|
1659
|
+
if (!alg.startsWith("ECDH-ES")) return true;
|
|
1660
|
+
const epkCurve = header.epk?.crv;
|
|
1661
|
+
const keyCurve = isCryptoKey(key) ? cryptoKeyCurve(key) : "crv" in key ? key.crv : void 0;
|
|
1662
|
+
return !epkCurve || !keyCurve || epkCurve === keyCurve;
|
|
1663
|
+
}
|
|
1022
1664
|
async function generateKey(alg, options = {}) {
|
|
1023
|
-
const exportToJWK = options.toJWK !== void 0 && options.toJWK
|
|
1665
|
+
const exportToJWK = options.toJWK !== void 0 && options.toJWK;
|
|
1024
1666
|
const defaultExtractable = exportToJWK || options.extractable !== false;
|
|
1667
|
+
const kid = exportToJWK ? assertKidOption(options.kid) : void 0;
|
|
1025
1668
|
if (alg === "A128CBC-HS256" || alg === "A192CBC-HS384" || alg === "A256CBC-HS512") {
|
|
1026
|
-
const
|
|
1027
|
-
|
|
1669
|
+
const keyLength = bitLengthCEK(alg);
|
|
1670
|
+
const keyBytes = secureRandomBytes(keyLength >> 3);
|
|
1671
|
+
if (exportToJWK) return withGeneratedKid(kid, {
|
|
1672
|
+
...await keyToJWK(keyBytes),
|
|
1673
|
+
alg,
|
|
1674
|
+
ext: true,
|
|
1675
|
+
key_ops: resolveKeyUsage(options.keyUsage, {
|
|
1676
|
+
allowed: CONTENT_ENCRYPTION_USAGES,
|
|
1677
|
+
fallback: ["encrypt", "decrypt"],
|
|
1678
|
+
alg
|
|
1679
|
+
})
|
|
1680
|
+
});
|
|
1028
1681
|
return keyBytes;
|
|
1029
1682
|
}
|
|
1030
1683
|
const { algorithm, keyUsages } = getGenerateKeyParams(alg, options);
|
|
1031
1684
|
const key = await crypto.subtle.generateKey(algorithm, defaultExtractable, keyUsages);
|
|
1032
|
-
if (exportToJWK)
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
...priv,
|
|
1041
|
-
alg
|
|
1042
|
-
},
|
|
1043
|
-
publicKey: {
|
|
1685
|
+
if (exportToJWK) {
|
|
1686
|
+
if (key instanceof CryptoKey) return withGeneratedKid(kid, {
|
|
1687
|
+
...await exportKey(key),
|
|
1688
|
+
alg
|
|
1689
|
+
});
|
|
1690
|
+
else {
|
|
1691
|
+
const [pub, priv] = await Promise.all([exportKey(key.publicKey), exportKey(key.privateKey)]);
|
|
1692
|
+
const publicKey = await withGeneratedKid(kid, {
|
|
1044
1693
|
...pub,
|
|
1045
1694
|
alg
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1695
|
+
});
|
|
1696
|
+
const named = publicKey.kid === void 0 ? void 0 : { kid: publicKey.kid };
|
|
1697
|
+
return {
|
|
1698
|
+
privateKey: {
|
|
1699
|
+
...priv,
|
|
1700
|
+
alg,
|
|
1701
|
+
...named
|
|
1702
|
+
},
|
|
1703
|
+
publicKey
|
|
1704
|
+
};
|
|
1705
|
+
}
|
|
1048
1706
|
}
|
|
1049
1707
|
return key;
|
|
1050
1708
|
}
|
|
1051
1709
|
async function generateJWK(alg, options) {
|
|
1052
|
-
const { keyUsage, modulusLength, publicExponent, namedCurve, ...jwkParams } = options ?? {};
|
|
1053
|
-
|
|
1054
|
-
const extraParams = {
|
|
1055
|
-
...jwkParams,
|
|
1056
|
-
kid
|
|
1057
|
-
};
|
|
1710
|
+
const { keyUsage, modulusLength, publicExponent, namedCurve, kid, extractable: _extractable, toJWK: _toJWK, ...jwkParams } = options ?? {};
|
|
1711
|
+
assertNoReservedJWKParams(jwkParams);
|
|
1058
1712
|
const result = await generateKey(alg, {
|
|
1059
1713
|
keyUsage,
|
|
1060
1714
|
modulusLength,
|
|
1061
1715
|
publicExponent,
|
|
1062
1716
|
namedCurve,
|
|
1717
|
+
kid,
|
|
1063
1718
|
toJWK: true
|
|
1064
1719
|
});
|
|
1065
|
-
if (result && typeof result === "object" && "privateKey" in result && "publicKey" in result) {
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
privateKey
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
publicKey
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
}
|
|
1076
|
-
};
|
|
1077
|
-
}
|
|
1720
|
+
if (result && typeof result === "object" && "privateKey" in result && "publicKey" in result) return {
|
|
1721
|
+
privateKey: {
|
|
1722
|
+
...jwkParams,
|
|
1723
|
+
...result.privateKey
|
|
1724
|
+
},
|
|
1725
|
+
publicKey: {
|
|
1726
|
+
...jwkParams,
|
|
1727
|
+
...result.publicKey
|
|
1728
|
+
}
|
|
1729
|
+
};
|
|
1078
1730
|
return {
|
|
1079
|
-
...
|
|
1731
|
+
...jwkParams,
|
|
1080
1732
|
...result
|
|
1081
1733
|
};
|
|
1082
1734
|
}
|
|
1083
|
-
async function deriveKeyFromPassword(password,
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
const
|
|
1089
|
-
const
|
|
1735
|
+
async function deriveKeyFromPassword(password, options) {
|
|
1736
|
+
assertPlainObject(options, "options");
|
|
1737
|
+
if (typeof password !== "string" && !(password instanceof Uint8Array)) throw new JWTError("\"password\" must be a string or a Uint8Array.", "ERR_INVALID_OPTION");
|
|
1738
|
+
const { alg, salt, iterations, extractable, keyUsage } = options;
|
|
1739
|
+
assertPBES2Algorithm(alg);
|
|
1740
|
+
const exportToJWK = options.toJWK !== void 0 && options.toJWK;
|
|
1741
|
+
const saltBytes = typeof salt === "string" ? textEncoder.encode(salt) : salt;
|
|
1742
|
+
assertPBES2Salt(saltBytes, "\"salt\"");
|
|
1743
|
+
assertPBES2Count(iterations, "\"iterations\"");
|
|
1744
|
+
const derivedBytes = await deriveKey(saltBytes, alg, iterations, typeof password === "string" ? textEncoder.encode(password) : password);
|
|
1745
|
+
const wrappingAlg = innerKeyWrapAlg(alg);
|
|
1090
1746
|
const finalUsages = keyUsage ?? ["wrapKey", "unwrapKey"];
|
|
1091
1747
|
const finalExtractable = exportToJWK ? true : extractable === true;
|
|
1092
1748
|
const derivedKey = await crypto.subtle.importKey("raw", derivedBytes, { name: "AES-KW" }, finalExtractable, finalUsages);
|
|
@@ -1097,18 +1753,22 @@ async function deriveKeyFromPassword(password, alg, options) {
|
|
|
1097
1753
|
};
|
|
1098
1754
|
return derivedKey;
|
|
1099
1755
|
}
|
|
1100
|
-
async function deriveJWKFromPassword(password,
|
|
1101
|
-
|
|
1102
|
-
const
|
|
1756
|
+
async function deriveJWKFromPassword(password, options) {
|
|
1757
|
+
assertPlainObject(options, "options");
|
|
1758
|
+
const { alg, salt, iterations, keyUsage, kid: kidOption, extractable: _extractable, toJWK: _toJWK, ...jwkParams } = options;
|
|
1759
|
+
assertNoReservedJWKParams(jwkParams);
|
|
1760
|
+
const kid = assertKidOption(kidOption);
|
|
1761
|
+
const result = await deriveKeyFromPassword(password, {
|
|
1762
|
+
alg,
|
|
1103
1763
|
salt,
|
|
1104
1764
|
iterations,
|
|
1105
1765
|
keyUsage,
|
|
1106
1766
|
toJWK: true
|
|
1107
1767
|
});
|
|
1108
|
-
return
|
|
1768
|
+
return {
|
|
1109
1769
|
...jwkParams,
|
|
1110
|
-
...result
|
|
1111
|
-
}
|
|
1770
|
+
...await withGeneratedKid(kid, result)
|
|
1771
|
+
};
|
|
1112
1772
|
}
|
|
1113
1773
|
var WeakMapJWKCache = class {
|
|
1114
1774
|
_map = /* @__PURE__ */ new WeakMap();
|
|
@@ -1131,44 +1791,58 @@ function clearJWKCache() {
|
|
|
1131
1791
|
async function importKey(key, algOrOptions) {
|
|
1132
1792
|
const alg = typeof algOrOptions === "string" ? algOrOptions : algOrOptions?.alg ?? void 0;
|
|
1133
1793
|
const expect = typeof algOrOptions === "object" ? algOrOptions.expect ?? void 0 : void 0;
|
|
1134
|
-
|
|
1794
|
+
const extractable = typeof algOrOptions === "object" && !algOrOptions.asCryptoKey ? algOrOptions.extractable : void 0;
|
|
1795
|
+
if (typeof key === "string") throw stringKeyError();
|
|
1135
1796
|
if (key instanceof Uint8Array) return key;
|
|
1136
1797
|
if (isCryptoKey(key)) {
|
|
1137
1798
|
assertIntentOnCryptoKey(key, expect);
|
|
1138
1799
|
return key;
|
|
1139
1800
|
}
|
|
1140
|
-
if (isJWK(key))
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
const
|
|
1150
|
-
if (
|
|
1801
|
+
if (isJWK(key)) {
|
|
1802
|
+
assertJWKAlgAuthority(key, alg);
|
|
1803
|
+
if ("k" in key && typeof key.k === "string") {
|
|
1804
|
+
const rawBytes = decodeSegment(key.k, {
|
|
1805
|
+
family: "JWK",
|
|
1806
|
+
what: "\"k\""
|
|
1807
|
+
});
|
|
1808
|
+
assertOctKeyLength(rawBytes, alg ?? key.alg);
|
|
1809
|
+
if (typeof algOrOptions === "object" && algOrOptions.asCryptoKey) {
|
|
1810
|
+
const { algorithm, usage, extractable: rawExtractable = false } = algOrOptions;
|
|
1811
|
+
if (!algorithm || !usage) throw new JWTError("asCryptoKey option requires `algorithm` and `usage` to be provided.", "ERR_JWK_INVALID");
|
|
1812
|
+
if (typeof algorithm === "object" && algorithm.name === "HMAC") {
|
|
1813
|
+
const hash = "hash" in algorithm ? algorithm.hash : void 0;
|
|
1814
|
+
const hashName = typeof hash === "string" ? hash : typeof hash === "object" && hash !== null && "name" in hash && typeof hash.name === "string" ? hash.name : void 0;
|
|
1815
|
+
const minBytes = Number.parseInt(hashName?.slice(-3) ?? "", 10) / 8;
|
|
1816
|
+
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");
|
|
1817
|
+
}
|
|
1818
|
+
return crypto.subtle.importKey("raw", rawBytes, algorithm, rawExtractable, usage);
|
|
1151
1819
|
}
|
|
1152
|
-
return
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1820
|
+
return rawBytes;
|
|
1821
|
+
} else {
|
|
1822
|
+
const effectiveAlg = key.alg || alg;
|
|
1823
|
+
if (!effectiveAlg) throw new JWTError("Algorithm must be provided when importing non-oct JWK", "ERR_JWK_INVALID");
|
|
1824
|
+
assertIntentOnJWK(key, expect);
|
|
1825
|
+
const cache = extractable === void 0 ? _activeCache : false;
|
|
1826
|
+
if (cache) {
|
|
1827
|
+
const cached = await cache.get(key, effectiveAlg);
|
|
1828
|
+
if (cached) return cached;
|
|
1829
|
+
}
|
|
1830
|
+
const cryptoKey = await jwkTokey(key.alg ? key : {
|
|
1831
|
+
...key,
|
|
1832
|
+
alg
|
|
1833
|
+
}, { extractable });
|
|
1834
|
+
if (cache) await cache.set(key, effectiveAlg, cryptoKey);
|
|
1835
|
+
return cryptoKey;
|
|
1162
1836
|
}
|
|
1163
|
-
const cryptoKey = await jwkTokey(key.alg ? key : {
|
|
1164
|
-
...key,
|
|
1165
|
-
alg
|
|
1166
|
-
});
|
|
1167
|
-
if (_activeCache) _activeCache.set(key, effectiveAlg, cryptoKey);
|
|
1168
|
-
return cryptoKey;
|
|
1169
1837
|
}
|
|
1170
1838
|
throw new JWTError("Invalid key type provided to importKey", "ERR_JWK_INVALID");
|
|
1171
1839
|
}
|
|
1840
|
+
function assertJWKAlgAuthority(jwk, alg) {
|
|
1841
|
+
if (!alg || !jwk.alg || jwk.alg === alg) return;
|
|
1842
|
+
if (jwk.kty === "oct" && alg === "dir" && isContentEncryptionAlgorithm(jwk.alg)) return;
|
|
1843
|
+
if (isEd25519Signature(jwk.alg) && isEd25519Signature(alg)) return;
|
|
1844
|
+
throw new JWTError(`JWK "alg" is "${jwk.alg}" but the operation requires "${alg}".`, "ERR_JWK_INVALID");
|
|
1845
|
+
}
|
|
1172
1846
|
function assertIntentOnJWK(jwk, expect) {
|
|
1173
1847
|
if (expect === void 0 || jwk.kty === "oct") return;
|
|
1174
1848
|
const hasPrivateScalar = "d" in jwk && typeof jwk.d === "string";
|
|
@@ -1181,27 +1855,35 @@ function assertIntentOnCryptoKey(key, expect) {
|
|
|
1181
1855
|
if (expect === "private" && key.type === "public") throw new JWTError("Public CryptoKey passed where a private key is expected.", "ERR_JWK_INVALID");
|
|
1182
1856
|
}
|
|
1183
1857
|
async function exportKey(key, jwkParams) {
|
|
1858
|
+
if (!isCryptoKey(key)) throw new JWTError(`exportKey expects a CryptoKey, got ${key === null ? "null" : typeof key}.`, "ERR_INVALID_OPTION");
|
|
1859
|
+
if (!key.extractable) throw new JWTError("A non-extractable CryptoKey cannot be exported as a JWK.", "ERR_JWK_INVALID");
|
|
1184
1860
|
const exportedJwk = await keyToJWK(key);
|
|
1185
|
-
if (jwkParams)
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1861
|
+
if (jwkParams) {
|
|
1862
|
+
const extraParams = sanitizeObjectCopy(jwkParams);
|
|
1863
|
+
assertNoReservedJWKParams(extraParams);
|
|
1864
|
+
return {
|
|
1865
|
+
...exportedJwk,
|
|
1866
|
+
...extraParams
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1189
1869
|
return exportedJwk;
|
|
1190
1870
|
}
|
|
1191
|
-
async function
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1871
|
+
async function wrapKey(keyToWrap, wrappingKey, options) {
|
|
1872
|
+
assertPlainObject(options, "options");
|
|
1873
|
+
assertKeyBytesArg(keyToWrap, "keyToWrap");
|
|
1874
|
+
const { alg } = options;
|
|
1875
|
+
const cekBytes = keyToWrap instanceof Uint8Array ? keyToWrap : new Uint8Array(await crypto.subtle.exportKey("raw", keyToWrap));
|
|
1876
|
+
if (alg.endsWith("KW")) assertWrappableCEK(cekBytes, alg);
|
|
1877
|
+
if (alg.startsWith("PBES2")) assertPBES2Params({
|
|
1878
|
+
p2s: options.p2s,
|
|
1879
|
+
p2c: options.p2c
|
|
1880
|
+
});
|
|
1881
|
+
const importedWrappingKey = await importKey(resolvePasswordKey(alg, wrappingKey), {
|
|
1195
1882
|
alg,
|
|
1196
|
-
expect
|
|
1883
|
+
expect: "public"
|
|
1197
1884
|
});
|
|
1198
|
-
return importKey(key, alg);
|
|
1199
|
-
}
|
|
1200
|
-
async function wrapKey(alg, keyToWrap, wrappingKey, options = {}) {
|
|
1201
|
-
const cekBytes = keyToWrap instanceof Uint8Array ? keyToWrap : new Uint8Array(await crypto.subtle.exportKey("raw", keyToWrap));
|
|
1202
|
-
const importedWrappingKey = await resolveWrappingKey(alg, wrappingKey, "public");
|
|
1203
1885
|
switch (alg) {
|
|
1204
|
-
case "dir": return { encryptedKey: new Uint8Array(0) };
|
|
1886
|
+
case "dir": return { encryptedKey: /* @__PURE__ */ new Uint8Array(0) };
|
|
1205
1887
|
case "A128KW":
|
|
1206
1888
|
case "A192KW":
|
|
1207
1889
|
case "A256KW": return { encryptedKey: await aesKwWrap(alg, importedWrappingKey, cekBytes) };
|
|
@@ -1225,43 +1907,51 @@ async function wrapKey(alg, keyToWrap, wrappingKey, options = {}) {
|
|
|
1225
1907
|
case "RSA-OAEP":
|
|
1226
1908
|
case "RSA-OAEP-256":
|
|
1227
1909
|
case "RSA-OAEP-384":
|
|
1228
|
-
case "RSA-OAEP-512":
|
|
1910
|
+
case "RSA-OAEP-512":
|
|
1911
|
+
assertCryptoKeyFor(alg, importedWrappingKey, "wrapping");
|
|
1912
|
+
return { encryptedKey: await encryptRSAES(alg, importedWrappingKey, cekBytes) };
|
|
1229
1913
|
case "ECDH-ES":
|
|
1230
1914
|
case "ECDH-ES+A128KW":
|
|
1231
1915
|
case "ECDH-ES+A192KW":
|
|
1232
1916
|
case "ECDH-ES+A256KW": {
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
const
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
})
|
|
1242
|
-
const sharedSecret = await deriveECDHESKey(importedWrappingKey, ephemeralPrivateKey, infoAlg, alg === "ECDH-ES" ? bitLengthCEK(infoAlg) : Number.parseInt(alg.slice(-5, -2), 10), apu, apv);
|
|
1917
|
+
assertCryptoKeyFor(alg, importedWrappingKey, "wrapping");
|
|
1918
|
+
const enc = options.ecdh?.enc;
|
|
1919
|
+
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");
|
|
1920
|
+
const { encryptedKey, parameters } = await ecdhesWrap(alg, {
|
|
1921
|
+
enc,
|
|
1922
|
+
recipientKey: importedWrappingKey,
|
|
1923
|
+
cek: cekBytes,
|
|
1924
|
+
parameters: buildECDHAgreementParams(options.ecdh)
|
|
1925
|
+
});
|
|
1243
1926
|
const wrapResult = {
|
|
1244
|
-
encryptedKey: new Uint8Array(0),
|
|
1245
|
-
epk:
|
|
1927
|
+
encryptedKey: encryptedKey ?? /* @__PURE__ */ new Uint8Array(0),
|
|
1928
|
+
epk: parameters.epk
|
|
1246
1929
|
};
|
|
1247
|
-
if (apu
|
|
1248
|
-
if (apv
|
|
1249
|
-
if (alg === "ECDH-ES") return wrapResult;
|
|
1250
|
-
wrapResult.encryptedKey = await aesKwWrap(alg.slice(-6), sharedSecret, cekBytes);
|
|
1930
|
+
if (parameters.apu) wrapResult.apu = parameters.apu;
|
|
1931
|
+
if (parameters.apv) wrapResult.apv = parameters.apv;
|
|
1251
1932
|
return wrapResult;
|
|
1252
1933
|
}
|
|
1253
1934
|
default: throw new JWTError(`Unsupported key wrapping algorithm: ${alg}`, "ERR_JWK_UNSUPPORTED");
|
|
1254
1935
|
}
|
|
1255
1936
|
}
|
|
1256
|
-
async function unwrapKey(
|
|
1937
|
+
async function unwrapKey(wrappedKey, unwrappingKey, options) {
|
|
1938
|
+
assertPlainObject(options, "options");
|
|
1939
|
+
if (!(wrappedKey instanceof Uint8Array)) throw new JWTError("\"wrappedKey\" must be a Uint8Array.", "ERR_INVALID_OPTION");
|
|
1940
|
+
const { alg } = options;
|
|
1257
1941
|
const returnRaw = options.format === "raw";
|
|
1258
1942
|
const defaultExtractable = options.extractable !== false;
|
|
1259
|
-
const importedUnwrappingKey = await
|
|
1943
|
+
const importedUnwrappingKey = await importKey(resolvePasswordKey(alg, unwrappingKey), {
|
|
1944
|
+
alg,
|
|
1945
|
+
expect: "private"
|
|
1946
|
+
});
|
|
1260
1947
|
let unwrappedCekBytes;
|
|
1261
1948
|
switch (alg) {
|
|
1262
1949
|
case "dir":
|
|
1263
|
-
|
|
1264
|
-
|
|
1950
|
+
assertDirectKeyRole(unwrappingKey, options.enc);
|
|
1951
|
+
if (importedUnwrappingKey instanceof Uint8Array) {
|
|
1952
|
+
assertOctKeyLength(importedUnwrappingKey, options.enc);
|
|
1953
|
+
unwrappedCekBytes = importedUnwrappingKey;
|
|
1954
|
+
} else {
|
|
1265
1955
|
if (!returnRaw) return importedUnwrappingKey;
|
|
1266
1956
|
unwrappedCekBytes = new Uint8Array(await crypto.subtle.exportKey("raw", importedUnwrappingKey));
|
|
1267
1957
|
}
|
|
@@ -1269,28 +1959,33 @@ async function unwrapKey(alg, wrappedKey, unwrappingKey, options = {}) {
|
|
|
1269
1959
|
case "A128KW":
|
|
1270
1960
|
case "A192KW":
|
|
1271
1961
|
case "A256KW":
|
|
1272
|
-
unwrappedCekBytes = await aesKwUnwrap(alg, importedUnwrappingKey, wrappedKey);
|
|
1962
|
+
unwrappedCekBytes = await unwrapping(aesKwUnwrap(alg, importedUnwrappingKey, wrappedKey));
|
|
1273
1963
|
break;
|
|
1274
1964
|
case "PBES2-HS256+A128KW":
|
|
1275
1965
|
case "PBES2-HS384+A192KW":
|
|
1276
1966
|
case "PBES2-HS512+A256KW": {
|
|
1277
1967
|
const { p2s, p2c, minIterations, maxIterations } = options;
|
|
1278
1968
|
if (!p2s || typeof p2c !== "number") throw new JWTError("PBES2 requires 'p2s' (salt) and 'p2c' (count) options", "ERR_JWE_INVALID");
|
|
1279
|
-
unwrappedCekBytes = await pbes2Unwrap(alg, importedUnwrappingKey, wrappedKey, p2c,
|
|
1969
|
+
unwrappedCekBytes = await unwrapping(pbes2Unwrap(alg, importedUnwrappingKey, wrappedKey, p2c, segmentBytes(p2s, "p2s"), minIterations, maxIterations));
|
|
1280
1970
|
break;
|
|
1281
1971
|
}
|
|
1282
1972
|
case "A128GCMKW":
|
|
1283
1973
|
case "A192GCMKW":
|
|
1284
|
-
case "A256GCMKW":
|
|
1974
|
+
case "A256GCMKW": {
|
|
1285
1975
|
if (!options.iv || !options.tag) throw new JWTError("AES-GCMKW requires 'iv' and 'tag' options for unwrapping", "ERR_JWE_INVALID");
|
|
1286
|
-
|
|
1976
|
+
const ivBytes = segmentBytes(options.iv, "iv");
|
|
1977
|
+
const tagBytes = segmentBytes(options.tag, "tag");
|
|
1978
|
+
assertKeyWrapSegmentLength("iv", ivBytes, bitLengthIV(alg) >> 3, alg);
|
|
1979
|
+
assertKeyWrapSegmentLength("tag", tagBytes, bitLengthTag(alg) >> 3, alg);
|
|
1980
|
+
unwrappedCekBytes = await unwrapping(gcmkwDecrypt(alg, importedUnwrappingKey, wrappedKey, ivBytes, tagBytes));
|
|
1287
1981
|
break;
|
|
1982
|
+
}
|
|
1288
1983
|
case "RSA-OAEP":
|
|
1289
1984
|
case "RSA-OAEP-256":
|
|
1290
1985
|
case "RSA-OAEP-384":
|
|
1291
1986
|
case "RSA-OAEP-512": {
|
|
1292
|
-
|
|
1293
|
-
unwrappedCekBytes = await decryptRSAES(alg, importedUnwrappingKey, wrappedKey);
|
|
1987
|
+
assertCryptoKeyFor(alg, importedUnwrappingKey, "unwrapping");
|
|
1988
|
+
unwrappedCekBytes = await unwrapping(decryptRSAES(alg, importedUnwrappingKey, wrappedKey));
|
|
1294
1989
|
if (returnRaw) break;
|
|
1295
1990
|
const inferredAlgorithm = options.unwrappedKeyAlgorithm || inferAesImportAlgorithm(options.enc, unwrappedCekBytes.length);
|
|
1296
1991
|
if (!inferredAlgorithm) throw new JWTError("Unable to infer algorithm for RSA-OAEP unwrapped key. Provide \"unwrappedKeyAlgorithm\" in options.", "ERR_JWE_INVALID");
|
|
@@ -1302,20 +1997,24 @@ async function unwrapKey(alg, wrappedKey, unwrappingKey, options = {}) {
|
|
|
1302
1997
|
case "ECDH-ES+A192KW":
|
|
1303
1998
|
case "ECDH-ES+A256KW": {
|
|
1304
1999
|
if (!options.epk) throw new JWTError("ECDH-ES requires 'epk' (Ephemeral Public Key) option", "ERR_JWE_INVALID");
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
const epkCandidate =
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
2000
|
+
assertCryptoKeyFor(alg, importedUnwrappingKey, "unwrapping");
|
|
2001
|
+
assertECDHKeyAllowed(importedUnwrappingKey);
|
|
2002
|
+
const epkCandidate = await normalizeEpk(options.epk, {
|
|
2003
|
+
alg,
|
|
2004
|
+
recipientKey: importedUnwrappingKey
|
|
2005
|
+
});
|
|
2006
|
+
const apuBytes = segmentBytes(options.apu ?? /* @__PURE__ */ new Uint8Array(0), "apu");
|
|
2007
|
+
const apvBytes = segmentBytes(options.apv ?? /* @__PURE__ */ new Uint8Array(0), "apv");
|
|
2008
|
+
assertPartyInfoLength("apu", apuBytes);
|
|
2009
|
+
assertPartyInfoLength("apv", apvBytes);
|
|
1311
2010
|
const infoAlg = alg === "ECDH-ES" ? options.enc : alg;
|
|
1312
2011
|
if (!infoAlg) throw new JWTError("ECDH-ES requires content encryption algorithm ('enc') to derive the shared secret", "ERR_JWE_INVALID");
|
|
1313
|
-
const sharedSecret = await deriveECDHESKey(epkCandidate, importedUnwrappingKey, infoAlg,
|
|
2012
|
+
const sharedSecret = await unwrapping(deriveECDHESKey(epkCandidate, importedUnwrappingKey, infoAlg, ecdhDerivedBits(alg, infoAlg), apuBytes, apvBytes));
|
|
1314
2013
|
if (alg === "ECDH-ES") unwrappedCekBytes = sharedSecret;
|
|
1315
2014
|
else {
|
|
1316
|
-
const kwAlg = alg
|
|
2015
|
+
const kwAlg = innerKeyWrapAlg(alg);
|
|
1317
2016
|
if (!(wrappedKey instanceof Uint8Array) || wrappedKey.length === 0) throw new JWTError("ECDH-ES key agreement with key wrapping requires an encrypted key", "ERR_JWE_INVALID");
|
|
1318
|
-
unwrappedCekBytes = await aesKwUnwrap(kwAlg, sharedSecret, wrappedKey);
|
|
2017
|
+
unwrappedCekBytes = await unwrapping(aesKwUnwrap(kwAlg, sharedSecret, wrappedKey));
|
|
1319
2018
|
}
|
|
1320
2019
|
break;
|
|
1321
2020
|
}
|
|
@@ -1325,6 +2024,8 @@ async function unwrapKey(alg, wrappedKey, unwrappingKey, options = {}) {
|
|
|
1325
2024
|
return await crypto.subtle.importKey("raw", unwrappedCekBytes, options.unwrappedKeyAlgorithm || { name: "AES-GCM" }, defaultExtractable, options.keyUsage || ["encrypt", "decrypt"]);
|
|
1326
2025
|
}
|
|
1327
2026
|
async function importPEM(pem, alg, options) {
|
|
2027
|
+
if (typeof pem !== "string") throw new JWTError(`"pem" must be a PEM-encoded string, got ${pem === null ? "null" : typeof pem}.`, "ERR_INVALID_OPTION");
|
|
2028
|
+
if (options !== void 0) assertPlainObject(options, "options");
|
|
1328
2029
|
const extractable = true;
|
|
1329
2030
|
const pemType = options?.pemType ?? inferPEMType(pem);
|
|
1330
2031
|
if (!pemType) throw new JWTError("Cannot infer PEM type from the input; pass \"options.pemType\" explicitly.", "ERR_JWK_INVALID");
|
|
@@ -1339,30 +2040,28 @@ async function importPEM(pem, alg, options) {
|
|
|
1339
2040
|
case "x509":
|
|
1340
2041
|
cryptoKey = await fromX509(pem, alg, { extractable });
|
|
1341
2042
|
break;
|
|
1342
|
-
default: throw new JWTError(`Unsupported PEM type: ${pemType}`, "ERR_JWK_UNSUPPORTED");
|
|
2043
|
+
default: throw new JWTError(`Unsupported PEM type: ${String(pemType)}`, "ERR_JWK_UNSUPPORTED");
|
|
1343
2044
|
}
|
|
1344
2045
|
return {
|
|
1345
2046
|
...await exportKey(cryptoKey, options?.jwkParams),
|
|
1346
2047
|
alg
|
|
1347
2048
|
};
|
|
1348
2049
|
}
|
|
1349
|
-
async function importFromPEM(pem, pemType, alg, options) {
|
|
1350
|
-
return importPEM(pem, alg, {
|
|
1351
|
-
pemType,
|
|
1352
|
-
...options
|
|
1353
|
-
});
|
|
1354
|
-
}
|
|
1355
2050
|
async function exportPEM(jwk, options) {
|
|
2051
|
+
assertPlainObject(jwk, "jwk");
|
|
2052
|
+
if (options !== void 0) assertPlainObject(options, "options");
|
|
1356
2053
|
if (jwk.kty === "oct") throw new JWTError("Octet (symmetric) JWKs (kty: 'oct') cannot be exported to PKCS8 or SPKI PEM formats.", "ERR_JWK_UNSUPPORTED");
|
|
1357
2054
|
const effectiveAlg = jwk.alg || options?.alg;
|
|
1358
2055
|
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");
|
|
2056
|
+
if (effectiveAlg !== void 0 && !PEM_ALGORITHMS.has(effectiveAlg)) throw new JWTError(`Unsupported PEM algorithm: ${effectiveAlg}`, "ERR_JWK_UNSUPPORTED");
|
|
1359
2057
|
const pemFormat = options?.pemFormat ?? ("d" in jwk ? "pkcs8" : "spki");
|
|
1360
2058
|
const cryptoKeyCandidate = await importKey({
|
|
1361
2059
|
...jwk,
|
|
1362
2060
|
ext: true
|
|
1363
2061
|
}, {
|
|
1364
2062
|
alg: effectiveAlg,
|
|
1365
|
-
expect: pemFormat === "pkcs8" ? "private" : "public"
|
|
2063
|
+
expect: pemFormat === "pkcs8" ? "private" : "public",
|
|
2064
|
+
extractable: true
|
|
1366
2065
|
});
|
|
1367
2066
|
if (!isCryptoKey(cryptoKeyCandidate)) throw new JWTError("Failed to convert JWK to a CryptoKey instance suitable for PEM export.", "ERR_JWK_INVALID");
|
|
1368
2067
|
const cryptoKey = cryptoKeyCandidate;
|
|
@@ -1373,111 +2072,83 @@ async function exportPEM(jwk, options) {
|
|
|
1373
2072
|
case "spki":
|
|
1374
2073
|
if (cryptoKey.type !== "public") throw new JWTError(`Only 'public' type CryptoKeys can be exported to SPKI PEM format. Key type is '${cryptoKey.type}'.`, "ERR_JWK_INVALID");
|
|
1375
2074
|
return toSPKI(cryptoKey);
|
|
1376
|
-
default: throw new JWTError(`Unsupported PEM format: ${pemFormat}`, "ERR_JWK_UNSUPPORTED");
|
|
2075
|
+
default: throw new JWTError(`Unsupported PEM format: ${String(pemFormat)}`, "ERR_JWK_UNSUPPORTED");
|
|
1377
2076
|
}
|
|
1378
2077
|
}
|
|
1379
|
-
async function exportToPEM(jwk, pemFormat, algForCryptoKeyImport) {
|
|
1380
|
-
return exportPEM(jwk, {
|
|
1381
|
-
pemFormat,
|
|
1382
|
-
alg: algForCryptoKeyImport
|
|
1383
|
-
});
|
|
1384
|
-
}
|
|
1385
2078
|
function inferPEMType(pem) {
|
|
1386
2079
|
if (pem.includes("-----BEGIN PRIVATE KEY-----")) return "pkcs8";
|
|
1387
2080
|
if (pem.includes("-----BEGIN PUBLIC KEY-----")) return "spki";
|
|
1388
2081
|
if (pem.includes("-----BEGIN CERTIFICATE-----")) return "x509";
|
|
1389
2082
|
}
|
|
1390
|
-
async function deriveSharedSecret(publicKey, privateKey,
|
|
2083
|
+
async function deriveSharedSecret(publicKey, privateKey, options) {
|
|
2084
|
+
assertPlainObject(options, "options");
|
|
2085
|
+
const { enc: alg, keyLength: requestedKeyLength, partyUInfo, partyVInfo } = options;
|
|
2086
|
+
if (requestedKeyLength !== void 0) assertDerivedKeyLength(requestedKeyLength);
|
|
2087
|
+
const importAlg = alg.startsWith("ECDH-ES") ? alg : "ECDH-ES";
|
|
1391
2088
|
const pubCryptoKey = isCryptoKey(publicKey) ? publicKey : await importKey(publicKey, {
|
|
1392
|
-
alg:
|
|
2089
|
+
alg: importAlg,
|
|
1393
2090
|
expect: "public"
|
|
1394
2091
|
});
|
|
1395
2092
|
const privCryptoKey = isCryptoKey(privateKey) ? privateKey : await importKey(privateKey, {
|
|
1396
|
-
alg:
|
|
2093
|
+
alg: importAlg,
|
|
1397
2094
|
expect: "private"
|
|
1398
2095
|
});
|
|
1399
2096
|
let keyLength;
|
|
1400
|
-
if (
|
|
1401
|
-
else if (alg === "ECDH-ES") throw new JWTError("deriveSharedSecret with
|
|
2097
|
+
if (requestedKeyLength !== void 0) keyLength = requestedKeyLength;
|
|
2098
|
+
else if (alg === "ECDH-ES") throw new JWTError("deriveSharedSecret with enc \"ECDH-ES\" requires an explicit keyLength in options", "ERR_JWK_INVALID");
|
|
1402
2099
|
else if (alg.startsWith("ECDH-ES+")) keyLength = Number.parseInt(alg.slice(-5, -2), 10);
|
|
1403
2100
|
else keyLength = bitLengthCEK(alg);
|
|
1404
|
-
return deriveECDHESKey(pubCryptoKey, privCryptoKey, alg, keyLength,
|
|
1405
|
-
}
|
|
1406
|
-
function getJWKFromSet(jwkSet, kidOrProtectedHeader) {
|
|
1407
|
-
if (!jwkSet || !isJWKSet(jwkSet)) throw new JWTError("Invalid JWK Set provided", "ERR_JWK_INVALID");
|
|
1408
|
-
if (typeof kidOrProtectedHeader === "string") {
|
|
1409
|
-
const kid = kidOrProtectedHeader;
|
|
1410
|
-
const selectedKey = jwkSet.keys.find((k) => k.kid === kid);
|
|
1411
|
-
if (!selectedKey) throw new JWTError(`No key found in JWK Set with kid "${kid}".`, "ERR_JWK_KEY_NOT_FOUND");
|
|
1412
|
-
return selectedKey;
|
|
1413
|
-
} else if (typeof kidOrProtectedHeader === "object") {
|
|
1414
|
-
const { kid, alg, kty } = kidOrProtectedHeader;
|
|
1415
|
-
if (!kid) {
|
|
1416
|
-
if (jwkSet.keys.length === 1) return jwkSet.keys[0];
|
|
1417
|
-
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");
|
|
1418
|
-
}
|
|
1419
|
-
const selectedKey = jwkSet.keys.find((k) => {
|
|
1420
|
-
return k.kid === kid && (!alg || k.alg === alg) && (!kty || k.kty === kty);
|
|
1421
|
-
});
|
|
1422
|
-
if (!selectedKey) {
|
|
1423
|
-
let errorMessage = `No key found in JWK Set with kid "${kid}"`;
|
|
1424
|
-
if (alg) errorMessage += ` and alg "${alg}"`;
|
|
1425
|
-
if (kty) errorMessage += ` and kty "${kty}"`;
|
|
1426
|
-
errorMessage += ".";
|
|
1427
|
-
throw new JWTError(errorMessage, "ERR_JWK_KEY_NOT_FOUND");
|
|
1428
|
-
}
|
|
1429
|
-
return selectedKey;
|
|
1430
|
-
}
|
|
1431
|
-
throw new TypeError("`kidOrProtectedHeader` must be a string (kid) or an object (JOSE Protected Header).");
|
|
2101
|
+
return deriveECDHESKey(pubCryptoKey, privCryptoKey, alg, keyLength, partyUInfo ?? /* @__PURE__ */ new Uint8Array(0), partyVInfo ?? /* @__PURE__ */ new Uint8Array(0));
|
|
1432
2102
|
}
|
|
1433
2103
|
function getJWKsFromSet(jwkSet, filter) {
|
|
1434
2104
|
if (!jwkSet || !isJWKSet(jwkSet)) throw new JWTError("Invalid JWK Set provided", "ERR_JWK_INVALID");
|
|
1435
|
-
|
|
1436
|
-
return
|
|
2105
|
+
const keys = jwkSet.keys.filter((key) => isJWK(key));
|
|
2106
|
+
return filter ? keys.filter(filter) : keys;
|
|
1437
2107
|
}
|
|
1438
2108
|
function getGenerateKeyParams(alg, options) {
|
|
1439
2109
|
let algorithm;
|
|
1440
2110
|
let keyUsages;
|
|
1441
|
-
const
|
|
2111
|
+
const requested = options?.keyUsage;
|
|
2112
|
+
const usage = (allowed, fallback) => resolveKeyUsage(requested, {
|
|
2113
|
+
allowed,
|
|
2114
|
+
fallback,
|
|
2115
|
+
alg
|
|
2116
|
+
});
|
|
2117
|
+
const pairUsage = (allowed, fallback) => assertPairUsages(usage(allowed, fallback), {
|
|
2118
|
+
fallback,
|
|
2119
|
+
alg
|
|
2120
|
+
});
|
|
1442
2121
|
switch (alg) {
|
|
1443
2122
|
case "HS256":
|
|
1444
2123
|
case "HS384":
|
|
1445
2124
|
case "HS512":
|
|
1446
2125
|
algorithm = {
|
|
1447
2126
|
name: "HMAC",
|
|
1448
|
-
hash:
|
|
2127
|
+
hash: jwsHashOf(alg)
|
|
1449
2128
|
};
|
|
1450
|
-
keyUsages =
|
|
2129
|
+
keyUsages = usage(SIGNATURE_USAGES, ["sign", "verify"]);
|
|
1451
2130
|
break;
|
|
1452
2131
|
case "RS256":
|
|
1453
2132
|
case "RS384":
|
|
1454
2133
|
case "RS512":
|
|
1455
2134
|
algorithm = {
|
|
1456
2135
|
name: "RSASSA-PKCS1-v1_5",
|
|
1457
|
-
modulusLength: options?.modulusLength
|
|
1458
|
-
publicExponent: options?.publicExponent
|
|
1459
|
-
|
|
1460
|
-
0,
|
|
1461
|
-
1
|
|
1462
|
-
]),
|
|
1463
|
-
hash: `SHA-${alg.slice(2)}`
|
|
2136
|
+
modulusLength: assertModulusLength(options?.modulusLength),
|
|
2137
|
+
publicExponent: assertPublicExponent(options?.publicExponent),
|
|
2138
|
+
hash: jwsHashOf(alg)
|
|
1464
2139
|
};
|
|
1465
|
-
keyUsages =
|
|
2140
|
+
keyUsages = pairUsage(SIGNATURE_USAGES, ["sign", "verify"]);
|
|
1466
2141
|
break;
|
|
1467
2142
|
case "PS256":
|
|
1468
2143
|
case "PS384":
|
|
1469
2144
|
case "PS512":
|
|
1470
2145
|
algorithm = {
|
|
1471
2146
|
name: "RSA-PSS",
|
|
1472
|
-
modulusLength: options?.modulusLength
|
|
1473
|
-
publicExponent: options?.publicExponent
|
|
1474
|
-
|
|
1475
|
-
0,
|
|
1476
|
-
1
|
|
1477
|
-
]),
|
|
1478
|
-
hash: `SHA-${alg.slice(2)}`
|
|
2147
|
+
modulusLength: assertModulusLength(options?.modulusLength),
|
|
2148
|
+
publicExponent: assertPublicExponent(options?.publicExponent),
|
|
2149
|
+
hash: jwsHashOf(alg)
|
|
1479
2150
|
};
|
|
1480
|
-
keyUsages =
|
|
2151
|
+
keyUsages = pairUsage(SIGNATURE_USAGES, ["sign", "verify"]);
|
|
1481
2152
|
break;
|
|
1482
2153
|
case "RSA-OAEP":
|
|
1483
2154
|
case "RSA-OAEP-256":
|
|
@@ -1485,74 +2156,67 @@ function getGenerateKeyParams(alg, options) {
|
|
|
1485
2156
|
case "RSA-OAEP-512":
|
|
1486
2157
|
algorithm = {
|
|
1487
2158
|
name: "RSA-OAEP",
|
|
1488
|
-
modulusLength: options?.modulusLength
|
|
1489
|
-
publicExponent: options?.publicExponent
|
|
1490
|
-
|
|
1491
|
-
0,
|
|
1492
|
-
1
|
|
1493
|
-
]),
|
|
1494
|
-
hash: `SHA-${Number.parseInt(alg.slice(9), 10) || 1}`
|
|
2159
|
+
modulusLength: assertModulusLength(options?.modulusLength),
|
|
2160
|
+
publicExponent: assertPublicExponent(options?.publicExponent),
|
|
2161
|
+
hash: rsaOaepHashOf(alg)
|
|
1495
2162
|
};
|
|
1496
|
-
keyUsages =
|
|
2163
|
+
keyUsages = pairUsage(KEY_ENCRYPTION_USAGES, [
|
|
1497
2164
|
"encrypt",
|
|
1498
2165
|
"decrypt",
|
|
1499
2166
|
"wrapKey",
|
|
1500
2167
|
"unwrapKey"
|
|
1501
|
-
];
|
|
2168
|
+
]);
|
|
1502
2169
|
break;
|
|
1503
2170
|
case "A128KW":
|
|
1504
2171
|
case "A192KW":
|
|
1505
2172
|
case "A256KW":
|
|
1506
2173
|
algorithm = {
|
|
1507
2174
|
name: "AES-KW",
|
|
1508
|
-
length:
|
|
2175
|
+
length: aesBitsOf(alg)
|
|
1509
2176
|
};
|
|
1510
|
-
keyUsages =
|
|
2177
|
+
keyUsages = usage(KEY_WRAPPING_USAGES, ["wrapKey", "unwrapKey"]);
|
|
1511
2178
|
break;
|
|
1512
2179
|
case "A128GCM":
|
|
1513
2180
|
case "A192GCM":
|
|
1514
2181
|
case "A256GCM":
|
|
1515
2182
|
case "A128GCMKW":
|
|
1516
2183
|
case "A192GCMKW":
|
|
1517
|
-
case "A256GCMKW":
|
|
1518
|
-
const baseAlg = alg.replace("KW", "");
|
|
2184
|
+
case "A256GCMKW":
|
|
1519
2185
|
algorithm = {
|
|
1520
2186
|
name: "AES-GCM",
|
|
1521
|
-
length:
|
|
2187
|
+
length: aesBitsOf(alg)
|
|
1522
2188
|
};
|
|
1523
|
-
keyUsages =
|
|
2189
|
+
keyUsages = usage(KEY_ENCRYPTION_USAGES, ["encrypt", "decrypt"]);
|
|
1524
2190
|
break;
|
|
1525
|
-
}
|
|
1526
2191
|
case "ES256":
|
|
1527
2192
|
case "ES384":
|
|
1528
|
-
case "ES512":
|
|
2193
|
+
case "ES512": {
|
|
2194
|
+
const namedCurve = ECDSA_CURVES[alg];
|
|
2195
|
+
assertNamedCurve(options?.namedCurve, namedCurve, alg);
|
|
1529
2196
|
algorithm = {
|
|
1530
2197
|
name: "ECDSA",
|
|
1531
|
-
namedCurve
|
|
2198
|
+
namedCurve
|
|
1532
2199
|
};
|
|
1533
|
-
keyUsages =
|
|
2200
|
+
keyUsages = pairUsage(SIGNATURE_USAGES, ["sign", "verify"]);
|
|
1534
2201
|
break;
|
|
2202
|
+
}
|
|
1535
2203
|
case "Ed25519":
|
|
1536
|
-
|
|
2204
|
+
assertNamedCurve(options?.namedCurve, "Ed25519", alg);
|
|
2205
|
+
keyUsages = pairUsage(SIGNATURE_USAGES, ["sign", "verify"]);
|
|
1537
2206
|
algorithm = { name: "Ed25519" };
|
|
1538
2207
|
break;
|
|
1539
2208
|
case "EdDSA": {
|
|
1540
|
-
keyUsages =
|
|
2209
|
+
keyUsages = pairUsage(SIGNATURE_USAGES, ["sign", "verify"]);
|
|
1541
2210
|
const namedCurve = options?.namedCurve ?? "Ed25519";
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
case "Ed448":
|
|
1545
|
-
algorithm = { name: namedCurve };
|
|
1546
|
-
break;
|
|
1547
|
-
default: throw new JWTError("Unsupported namedCurve provided. Supported values are: Ed25519 and Ed448", "ERR_JWK_UNSUPPORTED");
|
|
1548
|
-
}
|
|
2211
|
+
if (namedCurve !== "Ed25519") throw new JWTError("Unsupported namedCurve provided. Supported values are: Ed25519", "ERR_JWK_UNSUPPORTED");
|
|
2212
|
+
algorithm = { name: namedCurve };
|
|
1549
2213
|
break;
|
|
1550
2214
|
}
|
|
1551
2215
|
case "ECDH-ES":
|
|
1552
2216
|
case "ECDH-ES+A128KW":
|
|
1553
2217
|
case "ECDH-ES+A192KW":
|
|
1554
2218
|
case "ECDH-ES+A256KW": {
|
|
1555
|
-
keyUsages = ["deriveBits"];
|
|
2219
|
+
keyUsages = pairUsage(DERIVATION_USAGES, ["deriveBits"]);
|
|
1556
2220
|
const namedCurve = options?.namedCurve ?? "P-256";
|
|
1557
2221
|
switch (namedCurve) {
|
|
1558
2222
|
case "P-256":
|
|
@@ -1577,58 +2241,12 @@ function getGenerateKeyParams(alg, options) {
|
|
|
1577
2241
|
keyUsages
|
|
1578
2242
|
};
|
|
1579
2243
|
}
|
|
1580
|
-
async function resolveECDHEphemeralPair(recipientPublicKey, ephemeralKeyInput) {
|
|
1581
|
-
if (!ephemeralKeyInput) {
|
|
1582
|
-
const generated = await crypto.subtle.generateKey(recipientPublicKey.algorithm, true, ["deriveBits"]);
|
|
1583
|
-
const rawJwk = await keyToJWK(generated.publicKey);
|
|
1584
|
-
return {
|
|
1585
|
-
ephemeralPrivateKey: generated.privateKey,
|
|
1586
|
-
epkJwk: stripPrivateJwkFields(rawJwk)
|
|
1587
|
-
};
|
|
1588
|
-
}
|
|
1589
|
-
if (isCryptoKeyPair(ephemeralKeyInput)) {
|
|
1590
|
-
const rawJwk = await keyToJWK(ephemeralKeyInput.publicKey);
|
|
1591
|
-
return {
|
|
1592
|
-
ephemeralPrivateKey: ephemeralKeyInput.privateKey,
|
|
1593
|
-
epkJwk: stripPrivateJwkFields(rawJwk)
|
|
1594
|
-
};
|
|
1595
|
-
}
|
|
1596
|
-
if (typeof ephemeralKeyInput === "object" && "publicKey" in ephemeralKeyInput && "privateKey" in ephemeralKeyInput) {
|
|
1597
|
-
const { publicKey, privateKey } = ephemeralKeyInput;
|
|
1598
|
-
return {
|
|
1599
|
-
ephemeralPrivateKey: isCryptoKey(privateKey) ? privateKey : await importKey(privateKey, {
|
|
1600
|
-
alg: "ECDH-ES",
|
|
1601
|
-
expect: "private"
|
|
1602
|
-
}),
|
|
1603
|
-
epkJwk: isCryptoKey(publicKey) ? stripPrivateJwkFields(await keyToJWK(publicKey)) : stripPrivateJwkFields(publicKey)
|
|
1604
|
-
};
|
|
1605
|
-
}
|
|
1606
|
-
if (isCryptoKey(ephemeralKeyInput)) {
|
|
1607
|
-
if (ephemeralKeyInput.type !== "private") throw new JWTError("ECDH-ES ephemeral CryptoKey must be a private key", "ERR_JWK_INVALID");
|
|
1608
|
-
return {
|
|
1609
|
-
ephemeralPrivateKey: ephemeralKeyInput,
|
|
1610
|
-
epkJwk: stripPrivateJwkFields(await keyToJWK(ephemeralKeyInput))
|
|
1611
|
-
};
|
|
1612
|
-
}
|
|
1613
|
-
if (isJWK(ephemeralKeyInput) && "d" in ephemeralKeyInput) return {
|
|
1614
|
-
ephemeralPrivateKey: await importKey(ephemeralKeyInput, {
|
|
1615
|
-
alg: "ECDH-ES",
|
|
1616
|
-
expect: "private"
|
|
1617
|
-
}),
|
|
1618
|
-
epkJwk: stripPrivateJwkFields(ephemeralKeyInput)
|
|
1619
|
-
};
|
|
1620
|
-
throw new JWTError("Unsupported ECDH-ES ephemeral key material", "ERR_JWK_INVALID");
|
|
1621
|
-
}
|
|
1622
|
-
function stripPrivateJwkFields(jwk) {
|
|
1623
|
-
const { d: _d, ...pub } = jwk;
|
|
1624
|
-
return pub;
|
|
1625
|
-
}
|
|
1626
2244
|
function inferAesImportAlgorithm(enc, cekLengthBytes) {
|
|
1627
2245
|
const bitLength = cekLengthBytes << 3;
|
|
1628
2246
|
if (enc) {
|
|
1629
2247
|
if (enc.includes("GCM")) return {
|
|
1630
2248
|
name: "AES-GCM",
|
|
1631
|
-
length:
|
|
2249
|
+
length: aesBitsOf(enc)
|
|
1632
2250
|
};
|
|
1633
2251
|
if (enc.includes("CBC-HS")) return;
|
|
1634
2252
|
}
|
|
@@ -1637,4 +2255,154 @@ function inferAesImportAlgorithm(enc, cekLengthBytes) {
|
|
|
1637
2255
|
length: bitLength
|
|
1638
2256
|
};
|
|
1639
2257
|
}
|
|
1640
|
-
|
|
2258
|
+
const RESERVED_JWK_PARAMS = /* @__PURE__ */ new Set([
|
|
2259
|
+
"kty",
|
|
2260
|
+
"alg",
|
|
2261
|
+
"key_ops",
|
|
2262
|
+
"ext",
|
|
2263
|
+
"k",
|
|
2264
|
+
"d",
|
|
2265
|
+
"n",
|
|
2266
|
+
"e",
|
|
2267
|
+
"p",
|
|
2268
|
+
"q",
|
|
2269
|
+
"dp",
|
|
2270
|
+
"dq",
|
|
2271
|
+
"qi",
|
|
2272
|
+
"oth",
|
|
2273
|
+
"x",
|
|
2274
|
+
"y",
|
|
2275
|
+
"crv"
|
|
2276
|
+
]);
|
|
2277
|
+
function assertNoReservedJWKParams(jwkParams) {
|
|
2278
|
+
const reserved = Object.keys(jwkParams).filter((name) => RESERVED_JWK_PARAMS.has(name));
|
|
2279
|
+
if (reserved.length > 0) throw new JWTError(`jwkParams must not set reserved JWK members: ${reserved.join(", ")}`, "ERR_INVALID_OPTION");
|
|
2280
|
+
}
|
|
2281
|
+
function assertNamedCurve(requested, expected, alg) {
|
|
2282
|
+
if (requested === void 0 || requested === expected) return;
|
|
2283
|
+
throw new JWTError(`"namedCurve" ${requested} contradicts ${alg}, which is defined over ${expected}.`, "ERR_INVALID_OPTION");
|
|
2284
|
+
}
|
|
2285
|
+
function assertPublicExponent(publicExponent) {
|
|
2286
|
+
if (publicExponent === void 0) return new Uint8Array([
|
|
2287
|
+
1,
|
|
2288
|
+
0,
|
|
2289
|
+
1
|
|
2290
|
+
]);
|
|
2291
|
+
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");
|
|
2292
|
+
return publicExponent;
|
|
2293
|
+
}
|
|
2294
|
+
const SIGNATURE_USAGES = ["sign", "verify"];
|
|
2295
|
+
const KEY_WRAPPING_USAGES = ["wrapKey", "unwrapKey"];
|
|
2296
|
+
const KEY_ENCRYPTION_USAGES = [
|
|
2297
|
+
"encrypt",
|
|
2298
|
+
"decrypt",
|
|
2299
|
+
"wrapKey",
|
|
2300
|
+
"unwrapKey"
|
|
2301
|
+
];
|
|
2302
|
+
const DERIVATION_USAGES = ["deriveBits", "deriveKey"];
|
|
2303
|
+
const CONTENT_ENCRYPTION_USAGES = ["encrypt", "decrypt"];
|
|
2304
|
+
function resolveKeyUsage(requested, options) {
|
|
2305
|
+
const { allowed, fallback, alg } = options;
|
|
2306
|
+
if (requested === void 0) return fallback;
|
|
2307
|
+
if (!Array.isArray(requested) || requested.length === 0) throw new JWTError("\"keyUsage\" must be a non-empty array.", "ERR_INVALID_OPTION");
|
|
2308
|
+
const rejected = requested.filter((entry) => !allowed.includes(entry));
|
|
2309
|
+
if (rejected.length > 0) throw new JWTError(`"keyUsage" ${rejected.join(", ")} cannot be served by ${alg}; it admits ${allowed.join(", ")}.`, "ERR_INVALID_OPTION");
|
|
2310
|
+
return requested;
|
|
2311
|
+
}
|
|
2312
|
+
const PRIVATE_KEY_USAGES = [
|
|
2313
|
+
"sign",
|
|
2314
|
+
"decrypt",
|
|
2315
|
+
"unwrapKey",
|
|
2316
|
+
"deriveBits",
|
|
2317
|
+
"deriveKey"
|
|
2318
|
+
];
|
|
2319
|
+
const PUBLIC_KEY_USAGES = [
|
|
2320
|
+
"verify",
|
|
2321
|
+
"encrypt",
|
|
2322
|
+
"wrapKey"
|
|
2323
|
+
];
|
|
2324
|
+
function assertPairUsages(usages, options) {
|
|
2325
|
+
const { fallback, alg } = options;
|
|
2326
|
+
for (const [half, side] of [[PRIVATE_KEY_USAGES, "private"], [PUBLIC_KEY_USAGES, "public"]]) {
|
|
2327
|
+
const wanted = fallback.filter((entry) => half.includes(entry));
|
|
2328
|
+
if (wanted.length === 0 || usages.some((entry) => half.includes(entry))) continue;
|
|
2329
|
+
throw new JWTError(`"keyUsage" leaves the ${side} half of a ${alg} key pair with no usage; include one of ${wanted.join(", ")}.`, "ERR_INVALID_OPTION");
|
|
2330
|
+
}
|
|
2331
|
+
return usages;
|
|
2332
|
+
}
|
|
2333
|
+
function assertKidOption(kid) {
|
|
2334
|
+
if (kid === void 0 || kid === false || typeof kid === "string") return kid;
|
|
2335
|
+
throw new JWTError("\"kid\" must be a string or false.", "ERR_INVALID_OPTION");
|
|
2336
|
+
}
|
|
2337
|
+
async function withGeneratedKid(kid, jwk) {
|
|
2338
|
+
if (kid === false) return jwk;
|
|
2339
|
+
return {
|
|
2340
|
+
...jwk,
|
|
2341
|
+
kid: kid ?? await thumbprint(jwk)
|
|
2342
|
+
};
|
|
2343
|
+
}
|
|
2344
|
+
const PEM_ALGORITHMS = /* @__PURE__ */ new Set(Object.keys({
|
|
2345
|
+
"PS256": true,
|
|
2346
|
+
"PS384": true,
|
|
2347
|
+
"PS512": true,
|
|
2348
|
+
"RS256": true,
|
|
2349
|
+
"RS384": true,
|
|
2350
|
+
"RS512": true,
|
|
2351
|
+
"RSA-OAEP": true,
|
|
2352
|
+
"RSA-OAEP-256": true,
|
|
2353
|
+
"RSA-OAEP-384": true,
|
|
2354
|
+
"RSA-OAEP-512": true,
|
|
2355
|
+
"ES256": true,
|
|
2356
|
+
"ES384": true,
|
|
2357
|
+
"ES512": true,
|
|
2358
|
+
"ECDH-ES": true,
|
|
2359
|
+
"ECDH-ES+A128KW": true,
|
|
2360
|
+
"ECDH-ES+A192KW": true,
|
|
2361
|
+
"ECDH-ES+A256KW": true,
|
|
2362
|
+
"Ed25519": true,
|
|
2363
|
+
"EdDSA": true
|
|
2364
|
+
}));
|
|
2365
|
+
function assertWrappableCEK(cek, alg) {
|
|
2366
|
+
if (cek.length >= 16 && cek.length % 8 === 0) return;
|
|
2367
|
+
throw new JWTError(`${alg} wraps a key of 16 or more bytes in whole 8-byte blocks (got ${cek.length}).`, "ERR_INVALID_OPTION");
|
|
2368
|
+
}
|
|
2369
|
+
async function unwrapping(operation) {
|
|
2370
|
+
try {
|
|
2371
|
+
return await operation;
|
|
2372
|
+
} catch (error) {
|
|
2373
|
+
throw asDecryptionError(error);
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
function assertPBES2Algorithm(alg) {
|
|
2377
|
+
if (typeof alg === "string" && PBES2_ALGORITHMS.includes(alg)) return;
|
|
2378
|
+
throw new JWTError(`Password derivation is defined for ${PBES2_ALGORITHMS.join(", ")}, not ${String(alg)}.`, "ERR_JWK_UNSUPPORTED");
|
|
2379
|
+
}
|
|
2380
|
+
function assertKeyBytesArg(value, name) {
|
|
2381
|
+
if (value instanceof Uint8Array || isCryptoKey(value)) return;
|
|
2382
|
+
throw new JWTError(`"${name}" must be a Uint8Array or a CryptoKey, got ${value === null ? "null" : typeof value}.`, "ERR_INVALID_OPTION");
|
|
2383
|
+
}
|
|
2384
|
+
function assertModulusLength(modulusLength) {
|
|
2385
|
+
const length = modulusLength ?? 2048;
|
|
2386
|
+
if (!Number.isInteger(length) || length < 2048) throw new JWTError("modulusLength must be at least 2048 bits (RFC 7518 §3.3)", "ERR_INVALID_OPTION");
|
|
2387
|
+
return length;
|
|
2388
|
+
}
|
|
2389
|
+
function assertPartyInfoLength(name, bytes) {
|
|
2390
|
+
if (bytes.length > 1024) throw new JWTError(`Invalid JWE: "${name}" must not exceed ${MAX_PARTY_INFO_BYTES} bytes (got ${bytes.length}).`, "ERR_JWE_INVALID");
|
|
2391
|
+
}
|
|
2392
|
+
function assertKeyWrapSegmentLength(name, bytes, expected, alg) {
|
|
2393
|
+
if (bytes.length !== expected) throw new JWTError(`Invalid JWE: "${name}" must be ${expected} bytes for ${alg} (got ${bytes.length}).`, "ERR_JWE_INVALID");
|
|
2394
|
+
}
|
|
2395
|
+
function assertDerivedKeyLength(keyLength) {
|
|
2396
|
+
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");
|
|
2397
|
+
}
|
|
2398
|
+
function assertCryptoKeyFor(alg, key, role) {
|
|
2399
|
+
if (isCryptoKey(key)) return;
|
|
2400
|
+
throw new JWTError(`${alg.startsWith("RSA") ? "RSA-OAEP" : "ECDH-ES"} requires the ${role} key to be a CryptoKey`, "ERR_JWK_INVALID");
|
|
2401
|
+
}
|
|
2402
|
+
function segmentBytes(value, name) {
|
|
2403
|
+
return typeof value === "string" ? decodeSegment(value, {
|
|
2404
|
+
family: "JWE",
|
|
2405
|
+
what: `"${name}"`
|
|
2406
|
+
}) : value;
|
|
2407
|
+
}
|
|
2408
|
+
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 };
|