unjwt 0.5.38 → 0.5.39
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 +142 -666
- package/dist/_chunks/chunk.mjs +13 -0
- package/dist/_chunks/h3v1.mjs +598 -0
- package/dist/_chunks/index.d.mts +250 -0
- package/dist/_chunks/index2.d.mts +108 -0
- package/dist/_chunks/jwe.d.mts +43 -0
- package/dist/_chunks/jwe.mjs +132 -0
- package/dist/_chunks/jwk.d.mts +575 -0
- package/dist/_chunks/jwk.mjs +1592 -0
- package/dist/_chunks/jws.d.mts +46 -0
- package/dist/_chunks/jws.mjs +2069 -0
- package/dist/_chunks/utils.mjs +374 -0
- package/dist/adapters/h3.d.mts +3 -267
- package/dist/adapters/h3.mjs +3 -704
- package/dist/adapters/h3v1.d.mts +3 -8
- package/dist/adapters/h3v1.mjs +3 -7
- package/dist/adapters/h3v2.d.mts +145 -146
- package/dist/adapters/h3v2.mjs +505 -594
- package/dist/index.d.mts +6 -8
- package/dist/index.mjs +5 -4
- package/dist/jwe.d.mts +3 -4
- package/dist/jwe.mjs +4 -3
- package/dist/jwk.d.mts +3 -2
- package/dist/jwk.mjs +3 -2
- package/dist/jws.d.mts +3 -3
- package/dist/jws.mjs +4 -3
- package/dist/utils.d.mts +3 -3
- package/dist/utils.mjs +2 -1
- package/package.json +33 -31
- package/dist/shared/unjwt.4JQCoID_.d.mts +0 -161
- package/dist/shared/unjwt.B-v62plj.mjs +0 -249
- package/dist/shared/unjwt.BCi3FEWr.d.mts +0 -328
- package/dist/shared/unjwt.BYDTEWa-.d.mts +0 -269
- package/dist/shared/unjwt.Be8NxwFK.mjs +0 -438
- package/dist/shared/unjwt.BqdNUKDr.d.mts +0 -64
- package/dist/shared/unjwt.CMNtw8ek.d.mts +0 -155
- package/dist/shared/unjwt.Cy2MlkUe.d.mts +0 -174
- package/dist/shared/unjwt.D0_ccT7g.d.mts +0 -166
- package/dist/shared/unjwt.DdLN9vwI.mjs +0 -163
- package/dist/shared/unjwt.Dpl__ZAR.mjs +0 -2121
- package/dist/shared/unjwt.JdKqVANs.d.mts +0 -5
|
@@ -0,0 +1,1592 @@
|
|
|
1
|
+
import { t as __exportAll } from "./chunk.mjs";
|
|
2
|
+
import { a as base64UrlEncode, b as sanitizeObject, c as isCryptoKey, d as isJWKSet, g as randomBytes, i as base64UrlDecode, n as base64Decode, o as concatUint8Arrays, r as base64Encode, s as isAsymmetricJWK, t as assertCryptoKey, u as isJWK, v as textEncoder } from "./utils.mjs";
|
|
3
|
+
//#region src/core/jose/crypto_key.ts
|
|
4
|
+
/**
|
|
5
|
+
* This is a fork of Jose library's utility functions.
|
|
6
|
+
* @source https://github.com/panva/jose/tree/69b7960c67e05be55fa2ec31c74b987696c20c60/src/lib/crypto_key.ts
|
|
7
|
+
* @license MIT https://github.com/panva/jose/blob/69b7960c67e05be55fa2ec31c74b987696c20c60/LICENSE.md
|
|
8
|
+
*/
|
|
9
|
+
function unusable(name, prop = "algorithm.name") {
|
|
10
|
+
return /* @__PURE__ */ new TypeError(`CryptoKey does not support this operation, its ${prop} must be ${name}`);
|
|
11
|
+
}
|
|
12
|
+
function isAlgorithm(algorithm, name) {
|
|
13
|
+
return algorithm.name === name;
|
|
14
|
+
}
|
|
15
|
+
function getHashLength(hash) {
|
|
16
|
+
return Number.parseInt(hash.name.slice(4), 10);
|
|
17
|
+
}
|
|
18
|
+
function getNamedCurve$1(alg) {
|
|
19
|
+
switch (alg) {
|
|
20
|
+
case "ES256": return "P-256";
|
|
21
|
+
case "ES384": return "P-384";
|
|
22
|
+
case "ES512": return "P-521";
|
|
23
|
+
default: throw new Error("unreachable");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function checkUsage(key, usage) {
|
|
27
|
+
if (usage && !key.usages.includes(usage)) throw new TypeError(`CryptoKey does not support this operation, its usages must include ${usage}.`);
|
|
28
|
+
}
|
|
29
|
+
function checkSigCryptoKey(key, alg, usage) {
|
|
30
|
+
switch (alg) {
|
|
31
|
+
case "HS256":
|
|
32
|
+
case "HS384":
|
|
33
|
+
case "HS512": {
|
|
34
|
+
if (!isAlgorithm(key.algorithm, "HMAC")) throw unusable("HMAC");
|
|
35
|
+
const expected = Number.parseInt(alg.slice(2), 10);
|
|
36
|
+
if (getHashLength(key.algorithm.hash) !== expected) throw unusable(`SHA-${expected}`, "algorithm.hash");
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
case "RS256":
|
|
40
|
+
case "RS384":
|
|
41
|
+
case "RS512": {
|
|
42
|
+
if (!isAlgorithm(key.algorithm, "RSASSA-PKCS1-v1_5")) throw unusable("RSASSA-PKCS1-v1_5");
|
|
43
|
+
const expected = Number.parseInt(alg.slice(2), 10);
|
|
44
|
+
if (getHashLength(key.algorithm.hash) !== expected) throw unusable(`SHA-${expected}`, "algorithm.hash");
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
case "PS256":
|
|
48
|
+
case "PS384":
|
|
49
|
+
case "PS512": {
|
|
50
|
+
if (!isAlgorithm(key.algorithm, "RSA-PSS")) throw unusable("RSA-PSS");
|
|
51
|
+
const expected = Number.parseInt(alg.slice(2), 10);
|
|
52
|
+
if (getHashLength(key.algorithm.hash) !== expected) throw unusable(`SHA-${expected}`, "algorithm.hash");
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
case "Ed25519":
|
|
56
|
+
case "EdDSA":
|
|
57
|
+
if (!isAlgorithm(key.algorithm, "Ed25519")) throw unusable("Ed25519");
|
|
58
|
+
break;
|
|
59
|
+
case "ES256":
|
|
60
|
+
case "ES384":
|
|
61
|
+
case "ES512": {
|
|
62
|
+
if (!isAlgorithm(key.algorithm, "ECDSA")) throw unusable("ECDSA");
|
|
63
|
+
const expected = getNamedCurve$1(alg);
|
|
64
|
+
if (key.algorithm.namedCurve !== expected) throw unusable(expected, "algorithm.namedCurve");
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
default: throw new TypeError("CryptoKey does not support this operation");
|
|
68
|
+
}
|
|
69
|
+
checkUsage(key, usage);
|
|
70
|
+
}
|
|
71
|
+
function checkEncCryptoKey(key, alg, usage) {
|
|
72
|
+
switch (alg) {
|
|
73
|
+
case "A128GCM":
|
|
74
|
+
case "A192GCM":
|
|
75
|
+
case "A256GCM": {
|
|
76
|
+
if (!isAlgorithm(key.algorithm, "AES-GCM")) throw unusable("AES-GCM");
|
|
77
|
+
const expected = Number.parseInt(alg.slice(1, 4), 10);
|
|
78
|
+
if (key.algorithm.length !== expected) throw unusable(expected, "algorithm.length");
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
case "A128KW":
|
|
82
|
+
case "A192KW":
|
|
83
|
+
case "A256KW": {
|
|
84
|
+
if (!isAlgorithm(key.algorithm, "AES-KW")) throw unusable("AES-KW");
|
|
85
|
+
const expected = Number.parseInt(alg.slice(1, 4), 10);
|
|
86
|
+
if (key.algorithm.length !== expected) throw unusable(expected, "algorithm.length");
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
case "ECDH":
|
|
90
|
+
switch (key.algorithm.name) {
|
|
91
|
+
case "ECDH":
|
|
92
|
+
case "X25519": break;
|
|
93
|
+
default: throw unusable("ECDH or X25519");
|
|
94
|
+
}
|
|
95
|
+
break;
|
|
96
|
+
case "PBES2-HS256+A128KW":
|
|
97
|
+
case "PBES2-HS384+A192KW":
|
|
98
|
+
case "PBES2-HS512+A256KW":
|
|
99
|
+
if (!isAlgorithm(key.algorithm, "PBKDF2")) throw unusable("PBKDF2");
|
|
100
|
+
break;
|
|
101
|
+
case "RSA-OAEP":
|
|
102
|
+
case "RSA-OAEP-256":
|
|
103
|
+
case "RSA-OAEP-384":
|
|
104
|
+
case "RSA-OAEP-512": {
|
|
105
|
+
if (!isAlgorithm(key.algorithm, "RSA-OAEP")) throw unusable("RSA-OAEP");
|
|
106
|
+
const expected = Number.parseInt(alg.slice(9), 10) || 1;
|
|
107
|
+
if (getHashLength(key.algorithm.hash) !== expected) throw unusable(`SHA-${expected}`, "algorithm.hash");
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
default: throw new TypeError("CryptoKey does not support this operation");
|
|
111
|
+
}
|
|
112
|
+
checkUsage(key, usage);
|
|
113
|
+
}
|
|
114
|
+
//#endregion
|
|
115
|
+
//#region src/core/jose/sign-verify.ts
|
|
116
|
+
/**
|
|
117
|
+
* This is a fork of Jose library's utility functions.
|
|
118
|
+
* @source https://github.com/panva/jose/tree/69b7960c67e05be55fa2ec31c74b987696c20c60/src/lib/sign.ts
|
|
119
|
+
* @source https://github.com/panva/jose/tree/69b7960c67e05be55fa2ec31c74b987696c20c60/src/lib/verify.ts
|
|
120
|
+
* @license MIT https://github.com/panva/jose/blob/69b7960c67e05be55fa2ec31c74b987696c20c60/LICENSE.md
|
|
121
|
+
*/
|
|
122
|
+
async function sign(alg, key, data) {
|
|
123
|
+
const cryptoKey = await getSignVerifyKey(alg, key, "sign");
|
|
124
|
+
checkKeyLength(alg, cryptoKey);
|
|
125
|
+
const signature = await crypto.subtle.sign(subtleAlgorithm(alg, cryptoKey.algorithm), cryptoKey, data);
|
|
126
|
+
return new Uint8Array(signature);
|
|
127
|
+
}
|
|
128
|
+
async function verify(alg, key, signature, data) {
|
|
129
|
+
const cryptoKey = await getSignVerifyKey(alg, key, "verify");
|
|
130
|
+
checkKeyLength(alg, cryptoKey);
|
|
131
|
+
const algorithm = subtleAlgorithm(alg, cryptoKey.algorithm);
|
|
132
|
+
try {
|
|
133
|
+
return await crypto.subtle.verify(algorithm, cryptoKey, signature, data);
|
|
134
|
+
} catch {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function checkKeyLength(alg, key) {
|
|
139
|
+
if (alg.startsWith("RS") || alg.startsWith("PS")) {
|
|
140
|
+
const { modulusLength } = key.algorithm;
|
|
141
|
+
if (typeof modulusLength !== "number" || modulusLength < 2048) throw new TypeError(`${alg} requires key modulusLength to be 2048 bits or larger`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async function getSignVerifyKey(alg, key, usage) {
|
|
145
|
+
if (key instanceof Uint8Array) {
|
|
146
|
+
if (!alg.startsWith("HS")) throw new TypeError("Key must be of type CryptoKey or JSON Web Key for non-HMAC algorithms");
|
|
147
|
+
return crypto.subtle.importKey("raw", key, {
|
|
148
|
+
hash: `SHA-${alg.slice(-3)}`,
|
|
149
|
+
name: "HMAC"
|
|
150
|
+
}, false, [usage]);
|
|
151
|
+
}
|
|
152
|
+
checkSigCryptoKey(key, alg, usage);
|
|
153
|
+
return key;
|
|
154
|
+
}
|
|
155
|
+
function subtleAlgorithm(alg, algorithm) {
|
|
156
|
+
const hash = `SHA-${alg.slice(-3)}`;
|
|
157
|
+
switch (alg) {
|
|
158
|
+
case "HS256":
|
|
159
|
+
case "HS384":
|
|
160
|
+
case "HS512": return {
|
|
161
|
+
hash,
|
|
162
|
+
name: "HMAC"
|
|
163
|
+
};
|
|
164
|
+
case "PS256":
|
|
165
|
+
case "PS384":
|
|
166
|
+
case "PS512": return {
|
|
167
|
+
hash,
|
|
168
|
+
name: "RSA-PSS",
|
|
169
|
+
saltLength: Number.parseInt(alg.slice(-3), 10) >> 3
|
|
170
|
+
};
|
|
171
|
+
case "RS256":
|
|
172
|
+
case "RS384":
|
|
173
|
+
case "RS512": return {
|
|
174
|
+
hash,
|
|
175
|
+
name: "RSASSA-PKCS1-v1_5"
|
|
176
|
+
};
|
|
177
|
+
case "ES256":
|
|
178
|
+
case "ES384":
|
|
179
|
+
case "ES512": return {
|
|
180
|
+
hash,
|
|
181
|
+
name: "ECDSA",
|
|
182
|
+
namedCurve: algorithm.namedCurve
|
|
183
|
+
};
|
|
184
|
+
case "Ed25519":
|
|
185
|
+
case "EdDSA": return { name: "Ed25519" };
|
|
186
|
+
default: throw new Error(`alg ${alg} is not supported either by JOSE or your javascript runtime`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
//#endregion
|
|
190
|
+
//#region src/core/jose/buffer_utils.ts
|
|
191
|
+
/**
|
|
192
|
+
* This is a fork of Jose library's utility functions.
|
|
193
|
+
* @source https://github.com/panva/jose/tree/69b7960c67e05be55fa2ec31c74b987696c20c60/src/lib/buffer_utils.ts
|
|
194
|
+
* @license MIT https://github.com/panva/jose/blob/69b7960c67e05be55fa2ec31c74b987696c20c60/LICENSE.md
|
|
195
|
+
*/
|
|
196
|
+
const MAX_INT32 = 2 ** 32;
|
|
197
|
+
function writeUInt32BE(buf, value, offset) {
|
|
198
|
+
if (value < 0 || value >= MAX_INT32) throw new RangeError(`value must be >= 0 and <= ${MAX_INT32 - 1}. Received ${value}`);
|
|
199
|
+
buf.set([
|
|
200
|
+
value >>> 24,
|
|
201
|
+
value >>> 16,
|
|
202
|
+
value >>> 8,
|
|
203
|
+
value & 255
|
|
204
|
+
], offset);
|
|
205
|
+
}
|
|
206
|
+
function uint64be(value) {
|
|
207
|
+
const high = Math.floor(value / MAX_INT32);
|
|
208
|
+
const low = value % MAX_INT32;
|
|
209
|
+
const buf = new Uint8Array(8);
|
|
210
|
+
writeUInt32BE(buf, high, 0);
|
|
211
|
+
writeUInt32BE(buf, low, 4);
|
|
212
|
+
return buf;
|
|
213
|
+
}
|
|
214
|
+
function uint32be(value) {
|
|
215
|
+
const buf = new Uint8Array(4);
|
|
216
|
+
writeUInt32BE(buf, value);
|
|
217
|
+
return buf;
|
|
218
|
+
}
|
|
219
|
+
//#endregion
|
|
220
|
+
//#region src/core/jose/cek-iv.ts
|
|
221
|
+
/**
|
|
222
|
+
* This is a fork of Jose library's utility functions.
|
|
223
|
+
* @source https://github.com/panva/jose/tree/69b7960c67e05be55fa2ec31c74b987696c20c60/src/lib/cek.ts
|
|
224
|
+
* @source https://github.com/panva/jose/tree/69b7960c67e05be55fa2ec31c74b987696c20c60/src/lib/iv.ts
|
|
225
|
+
* @license MIT https://github.com/panva/jose/blob/69b7960c67e05be55fa2ec31c74b987696c20c60/LICENSE.md
|
|
226
|
+
*/
|
|
227
|
+
const IV_BIT_LENGTHS = {
|
|
228
|
+
A128GCM: 96,
|
|
229
|
+
A128GCMKW: 96,
|
|
230
|
+
A192GCM: 96,
|
|
231
|
+
A192GCMKW: 96,
|
|
232
|
+
A256GCM: 96,
|
|
233
|
+
A256GCMKW: 96,
|
|
234
|
+
"A128CBC-HS256": 128,
|
|
235
|
+
"A192CBC-HS384": 128,
|
|
236
|
+
"A256CBC-HS512": 128
|
|
237
|
+
};
|
|
238
|
+
const CEK_BIT_LENGTHS = {
|
|
239
|
+
A128GCM: 128,
|
|
240
|
+
A192GCM: 192,
|
|
241
|
+
A256GCM: 256,
|
|
242
|
+
"A128CBC-HS256": 256,
|
|
243
|
+
"A192CBC-HS384": 384,
|
|
244
|
+
"A256CBC-HS512": 512
|
|
245
|
+
};
|
|
246
|
+
function bitLengthIV(alg) {
|
|
247
|
+
const length = IV_BIT_LENGTHS[alg];
|
|
248
|
+
if (length === void 0) throw new Error(`Unsupported JWE Algorithm: ${alg}`);
|
|
249
|
+
return length;
|
|
250
|
+
}
|
|
251
|
+
function generateIV(alg) {
|
|
252
|
+
return crypto.getRandomValues(new Uint8Array(bitLengthIV(alg) >> 3));
|
|
253
|
+
}
|
|
254
|
+
function checkIvLength(enc, iv) {
|
|
255
|
+
if (iv.length << 3 !== bitLengthIV(enc)) throw new Error("Invalid Initialization Vector length");
|
|
256
|
+
}
|
|
257
|
+
function bitLengthCEK(alg) {
|
|
258
|
+
const length = CEK_BIT_LENGTHS[alg];
|
|
259
|
+
if (length === void 0) throw new Error(`Unsupported JWE Algorithm: ${alg}`);
|
|
260
|
+
return length;
|
|
261
|
+
}
|
|
262
|
+
function generateCEK(alg) {
|
|
263
|
+
return crypto.getRandomValues(new Uint8Array(bitLengthCEK(alg) >> 3));
|
|
264
|
+
}
|
|
265
|
+
function checkCEKLength(cek, expected) {
|
|
266
|
+
const actual = cek.byteLength << 3;
|
|
267
|
+
if (actual !== expected) throw new Error(`Invalid Content Encryption Key length. Expected ${expected} bits, got ${actual} bits`);
|
|
268
|
+
}
|
|
269
|
+
//#endregion
|
|
270
|
+
//#region src/core/jose/encrypt-decrypt.ts
|
|
271
|
+
/**
|
|
272
|
+
* This is a fork of Jose library's utility functions.
|
|
273
|
+
* @source https://github.com/panva/jose/tree/69b7960c67e05be55fa2ec31c74b987696c20c60/src/lib/encrypt.ts
|
|
274
|
+
* @source https://github.com/panva/jose/tree/69b7960c67e05be55fa2ec31c74b987696c20c60/src/lib/decrypt.ts
|
|
275
|
+
* @license MIT https://github.com/panva/jose/blob/69b7960c67e05be55fa2ec31c74b987696c20c60/LICENSE.md
|
|
276
|
+
*/
|
|
277
|
+
async function importCBCKeys(enc, cek, usage) {
|
|
278
|
+
const keySize = Number.parseInt(enc.slice(1, 4), 10);
|
|
279
|
+
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), {
|
|
280
|
+
hash: `SHA-${keySize << 1}`,
|
|
281
|
+
name: "HMAC"
|
|
282
|
+
}, false, ["sign"])]);
|
|
283
|
+
return {
|
|
284
|
+
encKey,
|
|
285
|
+
macKey,
|
|
286
|
+
keySize
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
function cbcMacData(aad, iv, ciphertext) {
|
|
290
|
+
return concatUint8Arrays(aad, iv, ciphertext, uint64be(aad.length << 3));
|
|
291
|
+
}
|
|
292
|
+
async function cbcEncrypt(enc, plaintext, cek, iv, aad) {
|
|
293
|
+
if (!(cek instanceof Uint8Array)) throw new TypeError("CBC key must be of type Uint8Array");
|
|
294
|
+
const { encKey, macKey, keySize } = await importCBCKeys(enc, cek, "encrypt");
|
|
295
|
+
const ciphertext = new Uint8Array(await crypto.subtle.encrypt({
|
|
296
|
+
iv,
|
|
297
|
+
name: "AES-CBC"
|
|
298
|
+
}, encKey, plaintext));
|
|
299
|
+
const macData = cbcMacData(aad, iv, ciphertext);
|
|
300
|
+
return {
|
|
301
|
+
ciphertext,
|
|
302
|
+
tag: new Uint8Array((await crypto.subtle.sign("HMAC", macKey, macData)).slice(0, keySize >> 3)),
|
|
303
|
+
iv
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
async function gcmEncrypt(enc, plaintext, cek, iv, aad) {
|
|
307
|
+
let encKey;
|
|
308
|
+
if (cek instanceof Uint8Array) encKey = await crypto.subtle.importKey("raw", cek, "AES-GCM", false, ["encrypt"]);
|
|
309
|
+
else {
|
|
310
|
+
checkEncCryptoKey(cek, enc, "encrypt");
|
|
311
|
+
encKey = cek;
|
|
312
|
+
}
|
|
313
|
+
const encrypted = new Uint8Array(await crypto.subtle.encrypt({
|
|
314
|
+
additionalData: aad,
|
|
315
|
+
iv,
|
|
316
|
+
name: "AES-GCM",
|
|
317
|
+
tagLength: 128
|
|
318
|
+
}, encKey, plaintext));
|
|
319
|
+
const tag = encrypted.subarray(-16);
|
|
320
|
+
return {
|
|
321
|
+
ciphertext: encrypted.subarray(0, -16),
|
|
322
|
+
tag,
|
|
323
|
+
iv
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
async function encrypt(enc, plaintext, cek, iv, aad) {
|
|
327
|
+
if (!isCryptoKey(cek) && !(cek instanceof Uint8Array)) throw new TypeError("Key must be one of type: CryptoKey or Uint8Array");
|
|
328
|
+
if (iv) checkIvLength(enc, iv);
|
|
329
|
+
else iv = generateIV(enc);
|
|
330
|
+
switch (enc) {
|
|
331
|
+
case "A128CBC-HS256":
|
|
332
|
+
case "A192CBC-HS384":
|
|
333
|
+
case "A256CBC-HS512":
|
|
334
|
+
if (cek instanceof Uint8Array) checkCEKLength(cek, Number.parseInt(enc.slice(-3), 10));
|
|
335
|
+
return cbcEncrypt(enc, plaintext, cek, iv, aad);
|
|
336
|
+
case "A128GCM":
|
|
337
|
+
case "A192GCM":
|
|
338
|
+
case "A256GCM":
|
|
339
|
+
if (cek instanceof Uint8Array) checkCEKLength(cek, Number.parseInt(enc.slice(1, 4), 10));
|
|
340
|
+
return gcmEncrypt(enc, plaintext, cek, iv, aad);
|
|
341
|
+
default: throw new Error("Unsupported JWE Content Encryption Algorithm");
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Decrypt fork
|
|
346
|
+
*/
|
|
347
|
+
let _timingSafeKey;
|
|
348
|
+
function getTimingSafeKey() {
|
|
349
|
+
if (!_timingSafeKey) _timingSafeKey = crypto.subtle.generateKey({
|
|
350
|
+
name: "HMAC",
|
|
351
|
+
hash: "SHA-256"
|
|
352
|
+
}, false, ["sign"]);
|
|
353
|
+
return _timingSafeKey;
|
|
354
|
+
}
|
|
355
|
+
async function timingSafeEqual(a, b) {
|
|
356
|
+
if (!(a instanceof Uint8Array)) throw new TypeError("First argument must be a buffer");
|
|
357
|
+
if (!(b instanceof Uint8Array)) throw new TypeError("Second argument must be a buffer");
|
|
358
|
+
const algorithm = {
|
|
359
|
+
name: "HMAC",
|
|
360
|
+
hash: "SHA-256"
|
|
361
|
+
};
|
|
362
|
+
const key = await getTimingSafeKey();
|
|
363
|
+
const aHmac = new Uint8Array(await crypto.subtle.sign(algorithm, key, a));
|
|
364
|
+
const bHmac = new Uint8Array(await crypto.subtle.sign(algorithm, key, b));
|
|
365
|
+
let out = 0;
|
|
366
|
+
let i = -1;
|
|
367
|
+
while (++i < 32) out |= aHmac[i] ^ bHmac[i];
|
|
368
|
+
return out === 0;
|
|
369
|
+
}
|
|
370
|
+
async function cbcDecrypt(enc, cek, ciphertext, iv, tag, aad) {
|
|
371
|
+
if (!(cek instanceof Uint8Array)) throw new TypeError("CBC key must be of type Uint8Array");
|
|
372
|
+
const { encKey, macKey, keySize } = await importCBCKeys(enc, cek, "decrypt");
|
|
373
|
+
const macData = cbcMacData(aad, iv, ciphertext);
|
|
374
|
+
const expectedTag = new Uint8Array((await crypto.subtle.sign("HMAC", macKey, macData)).slice(0, keySize >> 3));
|
|
375
|
+
let macCheckPassed;
|
|
376
|
+
try {
|
|
377
|
+
macCheckPassed = await timingSafeEqual(tag, expectedTag);
|
|
378
|
+
} catch {}
|
|
379
|
+
if (!macCheckPassed) throw new Error("JWE Decryption Failed");
|
|
380
|
+
let plaintext;
|
|
381
|
+
try {
|
|
382
|
+
plaintext = new Uint8Array(await crypto.subtle.decrypt({
|
|
383
|
+
iv,
|
|
384
|
+
name: "AES-CBC"
|
|
385
|
+
}, encKey, ciphertext));
|
|
386
|
+
} catch {}
|
|
387
|
+
if (!plaintext) throw new Error("JWE Decryption Failed");
|
|
388
|
+
return plaintext;
|
|
389
|
+
}
|
|
390
|
+
async function gcmDecrypt(enc, cek, ciphertext, iv, tag, aad) {
|
|
391
|
+
let encKey;
|
|
392
|
+
if (cek instanceof Uint8Array) encKey = await crypto.subtle.importKey("raw", cek, "AES-GCM", false, ["decrypt"]);
|
|
393
|
+
else {
|
|
394
|
+
checkEncCryptoKey(cek, enc, "decrypt");
|
|
395
|
+
encKey = cek;
|
|
396
|
+
}
|
|
397
|
+
try {
|
|
398
|
+
return new Uint8Array(await crypto.subtle.decrypt({
|
|
399
|
+
additionalData: aad,
|
|
400
|
+
iv,
|
|
401
|
+
name: "AES-GCM",
|
|
402
|
+
tagLength: 128
|
|
403
|
+
}, encKey, concatUint8Arrays(ciphertext, tag)));
|
|
404
|
+
} catch {
|
|
405
|
+
throw new Error("JWE Decryption Failed");
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
async function decrypt(enc, cek, ciphertext, iv, tag, aad) {
|
|
409
|
+
if (!isCryptoKey(cek) && !(cek instanceof Uint8Array)) throw new TypeError("Key must be one of type: CryptoKey or Uint8Array");
|
|
410
|
+
if (!iv) throw new Error("JWE Initialization Vector missing");
|
|
411
|
+
if (!tag) throw new Error("JWE Authentication Tag missing");
|
|
412
|
+
checkIvLength(enc, iv);
|
|
413
|
+
switch (enc) {
|
|
414
|
+
case "A128CBC-HS256":
|
|
415
|
+
case "A192CBC-HS384":
|
|
416
|
+
case "A256CBC-HS512":
|
|
417
|
+
if (cek instanceof Uint8Array) checkCEKLength(cek, Number.parseInt(enc.slice(-3), 10));
|
|
418
|
+
return cbcDecrypt(enc, cek, ciphertext, iv, tag, aad);
|
|
419
|
+
case "A128GCM":
|
|
420
|
+
case "A192GCM":
|
|
421
|
+
case "A256GCM":
|
|
422
|
+
if (cek instanceof Uint8Array) checkCEKLength(cek, Number.parseInt(enc.slice(1, 4), 10));
|
|
423
|
+
return gcmDecrypt(enc, cek, ciphertext, iv, tag, aad);
|
|
424
|
+
default: throw new Error("Unsupported JWE Content Encryption Algorithm");
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
//#endregion
|
|
428
|
+
//#region src/core/jose/jwk_to_key.ts
|
|
429
|
+
function subtleMapping(jwk) {
|
|
430
|
+
let algorithm;
|
|
431
|
+
let keyUsages;
|
|
432
|
+
switch (jwk.kty) {
|
|
433
|
+
case "RSA":
|
|
434
|
+
switch (jwk.alg) {
|
|
435
|
+
case "PS256":
|
|
436
|
+
case "PS384":
|
|
437
|
+
case "PS512":
|
|
438
|
+
algorithm = {
|
|
439
|
+
name: "RSA-PSS",
|
|
440
|
+
hash: `SHA-${jwk.alg.slice(-3)}`
|
|
441
|
+
};
|
|
442
|
+
keyUsages = jwk.d ? ["sign"] : ["verify"];
|
|
443
|
+
break;
|
|
444
|
+
case "RS256":
|
|
445
|
+
case "RS384":
|
|
446
|
+
case "RS512":
|
|
447
|
+
algorithm = {
|
|
448
|
+
name: "RSASSA-PKCS1-v1_5",
|
|
449
|
+
hash: `SHA-${jwk.alg.slice(-3)}`
|
|
450
|
+
};
|
|
451
|
+
keyUsages = jwk.d ? ["sign"] : ["verify"];
|
|
452
|
+
break;
|
|
453
|
+
case "RSA-OAEP":
|
|
454
|
+
case "RSA-OAEP-256":
|
|
455
|
+
case "RSA-OAEP-384":
|
|
456
|
+
case "RSA-OAEP-512":
|
|
457
|
+
algorithm = {
|
|
458
|
+
name: "RSA-OAEP",
|
|
459
|
+
hash: `SHA-${Number.parseInt(jwk.alg.slice(-3), 10) || 1}`
|
|
460
|
+
};
|
|
461
|
+
keyUsages = jwk.d ? ["decrypt", "unwrapKey"] : ["encrypt", "wrapKey"];
|
|
462
|
+
break;
|
|
463
|
+
default: throw new Error("Invalid or unsupported JWK \"alg\" (Algorithm) Parameter value");
|
|
464
|
+
}
|
|
465
|
+
break;
|
|
466
|
+
case "EC":
|
|
467
|
+
switch (jwk.alg) {
|
|
468
|
+
case "ES256":
|
|
469
|
+
algorithm = {
|
|
470
|
+
name: "ECDSA",
|
|
471
|
+
namedCurve: "P-256"
|
|
472
|
+
};
|
|
473
|
+
keyUsages = jwk.d ? ["sign"] : ["verify"];
|
|
474
|
+
break;
|
|
475
|
+
case "ES384":
|
|
476
|
+
algorithm = {
|
|
477
|
+
name: "ECDSA",
|
|
478
|
+
namedCurve: "P-384"
|
|
479
|
+
};
|
|
480
|
+
keyUsages = jwk.d ? ["sign"] : ["verify"];
|
|
481
|
+
break;
|
|
482
|
+
case "ES512":
|
|
483
|
+
algorithm = {
|
|
484
|
+
name: "ECDSA",
|
|
485
|
+
namedCurve: "P-521"
|
|
486
|
+
};
|
|
487
|
+
keyUsages = jwk.d ? ["sign"] : ["verify"];
|
|
488
|
+
break;
|
|
489
|
+
case "ECDH-ES":
|
|
490
|
+
case "ECDH-ES+A128KW":
|
|
491
|
+
case "ECDH-ES+A192KW":
|
|
492
|
+
case "ECDH-ES+A256KW":
|
|
493
|
+
algorithm = {
|
|
494
|
+
name: "ECDH",
|
|
495
|
+
namedCurve: jwk.crv
|
|
496
|
+
};
|
|
497
|
+
keyUsages = jwk.d ? ["deriveBits"] : [];
|
|
498
|
+
break;
|
|
499
|
+
default: throw new Error("Invalid or unsupported JWK \"alg\" (Algorithm) Parameter value");
|
|
500
|
+
}
|
|
501
|
+
break;
|
|
502
|
+
case "OKP":
|
|
503
|
+
switch (jwk.alg) {
|
|
504
|
+
case "Ed25519":
|
|
505
|
+
case "EdDSA":
|
|
506
|
+
algorithm = { name: "Ed25519" };
|
|
507
|
+
keyUsages = jwk.d ? ["sign"] : ["verify"];
|
|
508
|
+
break;
|
|
509
|
+
case "ECDH-ES":
|
|
510
|
+
case "ECDH-ES+A128KW":
|
|
511
|
+
case "ECDH-ES+A192KW":
|
|
512
|
+
case "ECDH-ES+A256KW":
|
|
513
|
+
algorithm = { name: jwk.crv };
|
|
514
|
+
keyUsages = jwk.d ? ["deriveBits"] : [];
|
|
515
|
+
break;
|
|
516
|
+
default: throw new Error("Invalid or unsupported JWK \"alg\" (Algorithm) Parameter value");
|
|
517
|
+
}
|
|
518
|
+
break;
|
|
519
|
+
default: throw new Error("Invalid or unsupported JWK \"kty\" (Key Type) Parameter value");
|
|
520
|
+
}
|
|
521
|
+
return {
|
|
522
|
+
algorithm,
|
|
523
|
+
keyUsages
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
async function jwkTokey(jwk) {
|
|
527
|
+
if (!jwk.alg) throw new TypeError("\"alg\" argument is required when \"jwk.alg\" is not present");
|
|
528
|
+
const { algorithm, keyUsages } = subtleMapping(jwk);
|
|
529
|
+
return crypto.subtle.importKey("jwk", jwk, algorithm, jwk.ext ?? (jwk.d ? false : true), jwk.key_ops ?? keyUsages);
|
|
530
|
+
}
|
|
531
|
+
//#endregion
|
|
532
|
+
//#region src/core/jose/key_to_jwk.ts
|
|
533
|
+
async function keyToJWK(key) {
|
|
534
|
+
if (key instanceof Uint8Array) return {
|
|
535
|
+
kty: "oct",
|
|
536
|
+
k: base64UrlEncode(key)
|
|
537
|
+
};
|
|
538
|
+
if (!isCryptoKey(key)) throw new TypeError("Key must be one of type: CryptoKey or Uint8Array");
|
|
539
|
+
if (!key.extractable) throw new TypeError("non-extractable CryptoKey cannot be exported as a JWK");
|
|
540
|
+
return await crypto.subtle.exportKey("jwk", key);
|
|
541
|
+
}
|
|
542
|
+
//#endregion
|
|
543
|
+
//#region src/core/jose/rsaes.ts
|
|
544
|
+
async function encryptRSAES(alg, key, cek) {
|
|
545
|
+
checkEncCryptoKey(key, alg, "encrypt");
|
|
546
|
+
checkKeyLength(alg, key);
|
|
547
|
+
return new Uint8Array(await crypto.subtle.encrypt("RSA-OAEP", key, cek));
|
|
548
|
+
}
|
|
549
|
+
async function decryptRSAES(alg, key, encryptedKey) {
|
|
550
|
+
checkEncCryptoKey(key, alg, "decrypt");
|
|
551
|
+
checkKeyLength(alg, key);
|
|
552
|
+
return new Uint8Array(await crypto.subtle.decrypt("RSA-OAEP", key, encryptedKey));
|
|
553
|
+
}
|
|
554
|
+
//#endregion
|
|
555
|
+
//#region src/core/jose/aesgcmkw.ts
|
|
556
|
+
/**
|
|
557
|
+
* This is a fork of Jose library's utility functions.
|
|
558
|
+
* @source https://github.com/panva/jose/tree/69b7960c67e05be55fa2ec31c74b987696c20c60/src/lib/aesgcmkw.ts
|
|
559
|
+
* @license MIT https://github.com/panva/jose/blob/69b7960c67e05be55fa2ec31c74b987696c20c60/LICENSE.md
|
|
560
|
+
*/
|
|
561
|
+
async function encryptIV(alg, key, plaintext, iv) {
|
|
562
|
+
const wrapped = await encrypt(alg.slice(0, 7), plaintext, key, iv, new Uint8Array(0));
|
|
563
|
+
return {
|
|
564
|
+
encryptedKey: wrapped.ciphertext,
|
|
565
|
+
iv: base64UrlEncode(wrapped.iv),
|
|
566
|
+
tag: base64UrlEncode(wrapped.tag)
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
async function decryptIV(alg, key, ciphertext, iv, tag) {
|
|
570
|
+
return decrypt(alg.slice(0, 7), key, ciphertext, iv, tag, new Uint8Array(0));
|
|
571
|
+
}
|
|
572
|
+
//#endregion
|
|
573
|
+
//#region src/core/jose/ecdhes.ts
|
|
574
|
+
function lengthAndInput(input) {
|
|
575
|
+
return concatUint8Arrays(uint32be(input.length), input);
|
|
576
|
+
}
|
|
577
|
+
async function concatKdf(secret, bits, value) {
|
|
578
|
+
const iterations = Math.ceil((bits >> 3) / 32);
|
|
579
|
+
const res = new Uint8Array(iterations * 32);
|
|
580
|
+
for (let iter = 0; iter < iterations; iter++) {
|
|
581
|
+
const buf = new Uint8Array(4 + secret.length + value.length);
|
|
582
|
+
buf.set(uint32be(iter + 1));
|
|
583
|
+
buf.set(secret, 4);
|
|
584
|
+
buf.set(value, 4 + secret.length);
|
|
585
|
+
res.set(await digest("sha256", buf), iter * 32);
|
|
586
|
+
}
|
|
587
|
+
return res.slice(0, bits >> 3);
|
|
588
|
+
}
|
|
589
|
+
async function deriveECDHESKey(publicKey, privateKey, algorithm, keyLength, apu = new Uint8Array(0), apv = new Uint8Array(0)) {
|
|
590
|
+
checkEncCryptoKey(publicKey, "ECDH");
|
|
591
|
+
checkEncCryptoKey(privateKey, "ECDH", "deriveBits");
|
|
592
|
+
const value = concatUint8Arrays(lengthAndInput(textEncoder.encode(algorithm)), lengthAndInput(apu), lengthAndInput(apv), uint32be(keyLength));
|
|
593
|
+
const length = publicKey.algorithm.name === "X25519" ? 256 : Math.ceil(Number.parseInt(publicKey.algorithm.namedCurve.slice(-3), 10) / 8) << 3;
|
|
594
|
+
return concatKdf(new Uint8Array(await crypto.subtle.deriveBits({
|
|
595
|
+
name: publicKey.algorithm.name,
|
|
596
|
+
public: publicKey
|
|
597
|
+
}, privateKey, length)), keyLength, value);
|
|
598
|
+
}
|
|
599
|
+
function allowed(key) {
|
|
600
|
+
switch (key.algorithm.namedCurve) {
|
|
601
|
+
case "P-256":
|
|
602
|
+
case "P-384":
|
|
603
|
+
case "P-521": return true;
|
|
604
|
+
default: return key.algorithm.name === "X25519";
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
async function digest(algorithm, data) {
|
|
608
|
+
const subtleDigest = `SHA-${algorithm.slice(-3)}`;
|
|
609
|
+
return new Uint8Array(await crypto.subtle.digest(subtleDigest, data));
|
|
610
|
+
}
|
|
611
|
+
//#endregion
|
|
612
|
+
//#region src/core/jose/pbes2kw.ts
|
|
613
|
+
/**
|
|
614
|
+
* This is a fork of Jose library's utility functions.
|
|
615
|
+
* @source https://github.com/panva/jose/tree/69b7960c67e05be55fa2ec31c74b987696c20c60/src/lib/pbes2kw.ts
|
|
616
|
+
* @license MIT https://github.com/panva/jose/blob/69b7960c67e05be55fa2ec31c74b987696c20c60/LICENSE.md
|
|
617
|
+
*/
|
|
618
|
+
async function wrap(alg, key, cek, p2c, p2s) {
|
|
619
|
+
if (p2c && p2s) {
|
|
620
|
+
const derived = await deriveKey(p2s, alg, p2c, key);
|
|
621
|
+
return {
|
|
622
|
+
encryptedKey: await _wrap(alg.slice(-6), derived, cek),
|
|
623
|
+
p2c,
|
|
624
|
+
p2s: base64UrlEncode(p2s)
|
|
625
|
+
};
|
|
626
|
+
} else return { encryptedKey: await _wrap(alg, key, cek) };
|
|
627
|
+
}
|
|
628
|
+
async function unwrap(alg, key, encryptedKey, p2c, p2s) {
|
|
629
|
+
if (p2c && p2s) {
|
|
630
|
+
const derived = await deriveKey(p2s, alg, p2c, key);
|
|
631
|
+
return _unwrap(alg.slice(-6), derived, encryptedKey);
|
|
632
|
+
} else return _unwrap(alg, key, encryptedKey);
|
|
633
|
+
}
|
|
634
|
+
function getCryptoKey(key, alg, options = {}) {
|
|
635
|
+
const { importAlg = "PBKDF2", extractable = false, usage = "deriveBits" } = options;
|
|
636
|
+
if (key instanceof Uint8Array) return crypto.subtle.importKey("raw", key, importAlg, extractable, [usage]);
|
|
637
|
+
checkEncCryptoKey(key, alg, usage);
|
|
638
|
+
return key;
|
|
639
|
+
}
|
|
640
|
+
async function deriveKey(p2s, alg, p2c, key) {
|
|
641
|
+
if (!(p2s instanceof Uint8Array) || p2s.length < 8) throw new Error("PBES2 Salt Input must be 8 or more octets");
|
|
642
|
+
if (p2c === void 0 || p2c < 1) throw new Error("PBES2 Iteration Count Parameter must be a positive integer");
|
|
643
|
+
const salt = concatUint8Arrays(textEncoder.encode(alg), new Uint8Array([0]), p2s);
|
|
644
|
+
const keylen = Number.parseInt(alg.slice(13, 16), 10);
|
|
645
|
+
const subtleAlg = {
|
|
646
|
+
hash: `SHA-${alg.slice(8, 11)}`,
|
|
647
|
+
iterations: p2c,
|
|
648
|
+
name: "PBKDF2",
|
|
649
|
+
salt
|
|
650
|
+
};
|
|
651
|
+
const cryptoKey = await getCryptoKey(key, alg);
|
|
652
|
+
return new Uint8Array(await crypto.subtle.deriveBits(subtleAlg, cryptoKey, keylen));
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Fork from: https://github.com/panva/jose/tree/69b7960c67e05be55fa2ec31c74b987696c20c60/src/lib/aeskw.ts
|
|
656
|
+
*/
|
|
657
|
+
function checkKeySize(key, alg) {
|
|
658
|
+
if (key.algorithm.length !== Number.parseInt(alg.slice(1, 4), 10)) throw new TypeError(`Invalid key size for alg: ${alg}`);
|
|
659
|
+
}
|
|
660
|
+
async function _wrap(alg, key, cek) {
|
|
661
|
+
const cryptoKey = await getCryptoKey(key, alg, {
|
|
662
|
+
importAlg: "AES-KW",
|
|
663
|
+
extractable: true,
|
|
664
|
+
usage: "wrapKey"
|
|
665
|
+
});
|
|
666
|
+
checkKeySize(cryptoKey, alg);
|
|
667
|
+
const cryptoKeyCek = await crypto.subtle.importKey("raw", cek, {
|
|
668
|
+
hash: "SHA-256",
|
|
669
|
+
name: "HMAC"
|
|
670
|
+
}, true, ["sign"]);
|
|
671
|
+
return new Uint8Array(await crypto.subtle.wrapKey("raw", cryptoKeyCek, cryptoKey, "AES-KW"));
|
|
672
|
+
}
|
|
673
|
+
async function _unwrap(alg, key, encryptedKey) {
|
|
674
|
+
const cryptoKey = await getCryptoKey(key, alg, {
|
|
675
|
+
importAlg: "AES-KW",
|
|
676
|
+
extractable: true,
|
|
677
|
+
usage: "unwrapKey"
|
|
678
|
+
});
|
|
679
|
+
checkKeySize(cryptoKey, alg);
|
|
680
|
+
const cryptoKeyCek = await crypto.subtle.unwrapKey("raw", encryptedKey, cryptoKey, "AES-KW", {
|
|
681
|
+
hash: "SHA-256",
|
|
682
|
+
name: "HMAC"
|
|
683
|
+
}, true, ["sign"]);
|
|
684
|
+
return new Uint8Array(await crypto.subtle.exportKey("raw", cryptoKeyCek));
|
|
685
|
+
}
|
|
686
|
+
//#endregion
|
|
687
|
+
//#region src/core/jose/encrypt_key.ts
|
|
688
|
+
const PUBLIC_EPK_FIELDS = new Set([
|
|
689
|
+
"kty",
|
|
690
|
+
"crv",
|
|
691
|
+
"x",
|
|
692
|
+
"y",
|
|
693
|
+
"kid",
|
|
694
|
+
"use",
|
|
695
|
+
"key_ops",
|
|
696
|
+
"alg",
|
|
697
|
+
"x5c",
|
|
698
|
+
"x5t",
|
|
699
|
+
"x5t#S256",
|
|
700
|
+
"x5u"
|
|
701
|
+
]);
|
|
702
|
+
function toPublicEpkJwk(jwk) {
|
|
703
|
+
const filtered = {};
|
|
704
|
+
for (const [key, value] of Object.entries(jwk)) if (value !== void 0 && PUBLIC_EPK_FIELDS.has(key)) filtered[key] = value;
|
|
705
|
+
const { kty, crv, x, y } = filtered;
|
|
706
|
+
if (typeof kty !== "string") throw new TypeError("ECDH-ES ephemeral key JWK must include the \"kty\" parameter.");
|
|
707
|
+
if (typeof crv !== "string") throw new TypeError("ECDH-ES ephemeral key JWK must include the \"crv\" parameter.");
|
|
708
|
+
if (typeof x !== "string") throw new TypeError("ECDH-ES ephemeral key JWK must include the \"x\" coordinate.");
|
|
709
|
+
if (kty === "EC" && typeof y !== "string") throw new TypeError("ECDH-ES EC ephemeral key JWK must include both \"x\" and \"y\" coordinates.");
|
|
710
|
+
const publicJwk = {
|
|
711
|
+
...filtered,
|
|
712
|
+
kty,
|
|
713
|
+
crv,
|
|
714
|
+
x
|
|
715
|
+
};
|
|
716
|
+
if (typeof y === "string") publicJwk.y = y;
|
|
717
|
+
else delete publicJwk.y;
|
|
718
|
+
return sanitizeObject(publicJwk);
|
|
719
|
+
}
|
|
720
|
+
async function ensurePrivateKey(alg, value) {
|
|
721
|
+
const normalized = await normalizeKey(value, alg);
|
|
722
|
+
if (!(normalized instanceof CryptoKey)) throw new TypeError("ECDH-ES ephemeral private key must be a CryptoKey or convertible JWK.");
|
|
723
|
+
if (normalized.type !== "private") throw new TypeError("ECDH-ES ephemeral private key must have type \"private\".");
|
|
724
|
+
return normalized;
|
|
725
|
+
}
|
|
726
|
+
async function exportPublicJwkFrom(alg, value) {
|
|
727
|
+
if (isCryptoKey(value)) try {
|
|
728
|
+
return toPublicEpkJwk(await keyToJWK(value));
|
|
729
|
+
} catch (error_) {
|
|
730
|
+
throw new TypeError("ECDH-ES ephemeral CryptoKey must be extractable to export as JWK.", { cause: error_ instanceof Error ? error_ : void 0 });
|
|
731
|
+
}
|
|
732
|
+
if (isJWK(value) && isAsymmetricJWK(value) && value.kty === "EC") return toPublicEpkJwk(value);
|
|
733
|
+
const normalized = await normalizeKey(value, alg);
|
|
734
|
+
if (!(normalized instanceof CryptoKey)) throw new TypeError("ECDH-ES ephemeral public key must be a CryptoKey or convertible JWK.");
|
|
735
|
+
return exportPublicJwkFrom(alg, normalized);
|
|
736
|
+
}
|
|
737
|
+
async function encryptKey(alg, enc, key, providedCek, providedParameters = {}) {
|
|
738
|
+
let encryptedKey;
|
|
739
|
+
let parameters = {};
|
|
740
|
+
let cek;
|
|
741
|
+
switch (alg) {
|
|
742
|
+
case "dir":
|
|
743
|
+
cek = key;
|
|
744
|
+
break;
|
|
745
|
+
case "ECDH-ES":
|
|
746
|
+
case "ECDH-ES+A128KW":
|
|
747
|
+
case "ECDH-ES+A192KW":
|
|
748
|
+
case "ECDH-ES+A256KW": {
|
|
749
|
+
assertCryptoKey(key);
|
|
750
|
+
if (!allowed(key)) throw new Error("ECDH with the provided key is not allowed or not supported by your javascript runtime");
|
|
751
|
+
const { apu, apv, epk: providedEpk, epkPrivateKey } = providedParameters;
|
|
752
|
+
if (providedEpk && !epkPrivateKey) throw new TypeError("ECDH-ES custom ephemeral key material must include the matching private key.");
|
|
753
|
+
let ephemeralPrivateKey;
|
|
754
|
+
let epkHeader;
|
|
755
|
+
if (epkPrivateKey) {
|
|
756
|
+
ephemeralPrivateKey = await ensurePrivateKey(alg, epkPrivateKey);
|
|
757
|
+
epkHeader = await exportPublicJwkFrom(alg, providedEpk ?? epkPrivateKey);
|
|
758
|
+
} else {
|
|
759
|
+
if (providedEpk) throw new TypeError("ECDH-ES custom ephemeral public key requires a matching private key.");
|
|
760
|
+
const generated = await crypto.subtle.generateKey(key.algorithm, true, ["deriveBits"]);
|
|
761
|
+
ephemeralPrivateKey = generated.privateKey;
|
|
762
|
+
epkHeader = await exportPublicJwkFrom(alg, generated.publicKey);
|
|
763
|
+
}
|
|
764
|
+
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);
|
|
765
|
+
parameters = { epk: epkHeader };
|
|
766
|
+
if (apu) parameters.apu = base64UrlEncode(apu);
|
|
767
|
+
if (apv) parameters.apv = base64UrlEncode(apv);
|
|
768
|
+
if (alg === "ECDH-ES") {
|
|
769
|
+
cek = sharedSecret;
|
|
770
|
+
break;
|
|
771
|
+
}
|
|
772
|
+
cek = providedCek || generateCEK(enc);
|
|
773
|
+
encryptedKey = (await wrap(alg.slice(-6), sharedSecret, cek)).encryptedKey;
|
|
774
|
+
break;
|
|
775
|
+
}
|
|
776
|
+
case "RSA-OAEP":
|
|
777
|
+
case "RSA-OAEP-256":
|
|
778
|
+
case "RSA-OAEP-384":
|
|
779
|
+
case "RSA-OAEP-512":
|
|
780
|
+
cek = providedCek || generateCEK(enc);
|
|
781
|
+
assertCryptoKey(key);
|
|
782
|
+
encryptedKey = await encryptRSAES(alg, key, cek);
|
|
783
|
+
break;
|
|
784
|
+
case "PBES2-HS256+A128KW":
|
|
785
|
+
case "PBES2-HS384+A192KW":
|
|
786
|
+
case "PBES2-HS512+A256KW": {
|
|
787
|
+
cek = providedCek || generateCEK(enc);
|
|
788
|
+
const { p2c, p2s } = providedParameters;
|
|
789
|
+
({encryptedKey, ...parameters} = await wrap(alg, key, cek, p2c, p2s));
|
|
790
|
+
break;
|
|
791
|
+
}
|
|
792
|
+
case "A128KW":
|
|
793
|
+
case "A192KW":
|
|
794
|
+
case "A256KW":
|
|
795
|
+
cek = providedCek || generateCEK(enc);
|
|
796
|
+
encryptedKey = (await wrap(alg, key, cek)).encryptedKey;
|
|
797
|
+
break;
|
|
798
|
+
case "A128GCMKW":
|
|
799
|
+
case "A192GCMKW":
|
|
800
|
+
case "A256GCMKW": {
|
|
801
|
+
cek = providedCek || generateCEK(enc);
|
|
802
|
+
const { iv } = providedParameters;
|
|
803
|
+
({encryptedKey, ...parameters} = await encryptIV(alg, key, cek, iv));
|
|
804
|
+
break;
|
|
805
|
+
}
|
|
806
|
+
default: throw new Error("Invalid or unsupported \"alg\" (JWE Algorithm) header value");
|
|
807
|
+
}
|
|
808
|
+
return {
|
|
809
|
+
cek,
|
|
810
|
+
encryptedKey,
|
|
811
|
+
parameters
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
async function normalizeKey(key, alg) {
|
|
815
|
+
if (key instanceof Uint8Array) return key;
|
|
816
|
+
if (isCryptoKey(key)) return key;
|
|
817
|
+
if (isJWK(key)) {
|
|
818
|
+
if ("k" in key && key.k) return base64UrlDecode(key.k, false);
|
|
819
|
+
return jwkTokey(key.alg ? key : {
|
|
820
|
+
...key,
|
|
821
|
+
alg
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
throw new Error("unreachable");
|
|
825
|
+
}
|
|
826
|
+
//#endregion
|
|
827
|
+
//#region src/core/jose/asn1.ts
|
|
828
|
+
/**
|
|
829
|
+
* This is a fork of Jose library's utility functions.
|
|
830
|
+
* @source https://github.com/panva/jose/tree/69b7960c67e05be55fa2ec31c74b987696c20c60/src/lib/asn1.ts
|
|
831
|
+
* @license MIT https://github.com/panva/jose/blob/69b7960c67e05be55fa2ec31c74b987696c20c60/LICENSE.md
|
|
832
|
+
*/
|
|
833
|
+
const formatPEM = (b64, descriptor) => {
|
|
834
|
+
return `-----BEGIN ${descriptor}-----\n${(b64.match(/.{1,64}/g) || []).join("\n")}\n-----END ${descriptor}-----`;
|
|
835
|
+
};
|
|
836
|
+
const genericExport = async (keyType, keyFormat, key) => {
|
|
837
|
+
if (!isCryptoKey(key)) throw new TypeError(`Key must be ${key} of type CryptoKey`);
|
|
838
|
+
if (!key.extractable) throw new TypeError("CryptoKey is not extractable");
|
|
839
|
+
if (key.type !== keyType) throw new TypeError(`key is not a ${keyType} key`);
|
|
840
|
+
return formatPEM(base64Encode(new Uint8Array(await crypto.subtle.exportKey(keyFormat, key))), `${keyType.toUpperCase()} KEY`);
|
|
841
|
+
};
|
|
842
|
+
const toSPKI = (key) => {
|
|
843
|
+
return genericExport("public", "spki", key);
|
|
844
|
+
};
|
|
845
|
+
const toPKCS8 = (key) => {
|
|
846
|
+
return genericExport("private", "pkcs8", key);
|
|
847
|
+
};
|
|
848
|
+
const findOid = (keyData, oid, from = 0) => {
|
|
849
|
+
if (from === 0) {
|
|
850
|
+
oid.unshift(oid.length);
|
|
851
|
+
oid.unshift(6);
|
|
852
|
+
}
|
|
853
|
+
const i = keyData.indexOf(oid[0], from);
|
|
854
|
+
if (i === -1) return false;
|
|
855
|
+
const sub = keyData.subarray(i, i + oid.length);
|
|
856
|
+
if (sub.length !== oid.length) return false;
|
|
857
|
+
return sub.every((value, index) => value === oid[index]) || findOid(keyData, oid, i + 1);
|
|
858
|
+
};
|
|
859
|
+
const getNamedCurve = (keyData) => {
|
|
860
|
+
switch (true) {
|
|
861
|
+
case findOid(keyData, [
|
|
862
|
+
42,
|
|
863
|
+
134,
|
|
864
|
+
72,
|
|
865
|
+
206,
|
|
866
|
+
61,
|
|
867
|
+
3,
|
|
868
|
+
1,
|
|
869
|
+
7
|
|
870
|
+
]): return "P-256";
|
|
871
|
+
case findOid(keyData, [
|
|
872
|
+
43,
|
|
873
|
+
129,
|
|
874
|
+
4,
|
|
875
|
+
0,
|
|
876
|
+
34
|
|
877
|
+
]): return "P-384";
|
|
878
|
+
case findOid(keyData, [
|
|
879
|
+
43,
|
|
880
|
+
129,
|
|
881
|
+
4,
|
|
882
|
+
0,
|
|
883
|
+
35
|
|
884
|
+
]): return "P-521";
|
|
885
|
+
default: return;
|
|
886
|
+
}
|
|
887
|
+
};
|
|
888
|
+
const genericImport = async (replace, keyFormat, pem, alg, options) => {
|
|
889
|
+
let algorithm;
|
|
890
|
+
let keyUsages;
|
|
891
|
+
const keyData = base64Decode(pem.replace(replace, ""), false);
|
|
892
|
+
const isPublic = keyFormat === "spki";
|
|
893
|
+
switch (alg) {
|
|
894
|
+
case "PS256":
|
|
895
|
+
case "PS384":
|
|
896
|
+
case "PS512":
|
|
897
|
+
algorithm = {
|
|
898
|
+
name: "RSA-PSS",
|
|
899
|
+
hash: `SHA-${alg.slice(-3)}`
|
|
900
|
+
};
|
|
901
|
+
keyUsages = isPublic ? ["verify"] : ["sign"];
|
|
902
|
+
break;
|
|
903
|
+
case "RS256":
|
|
904
|
+
case "RS384":
|
|
905
|
+
case "RS512":
|
|
906
|
+
algorithm = {
|
|
907
|
+
name: "RSASSA-PKCS1-v1_5",
|
|
908
|
+
hash: `SHA-${alg.slice(-3)}`
|
|
909
|
+
};
|
|
910
|
+
keyUsages = isPublic ? ["verify"] : ["sign"];
|
|
911
|
+
break;
|
|
912
|
+
case "RSA-OAEP":
|
|
913
|
+
case "RSA-OAEP-256":
|
|
914
|
+
case "RSA-OAEP-384":
|
|
915
|
+
case "RSA-OAEP-512":
|
|
916
|
+
algorithm = {
|
|
917
|
+
name: "RSA-OAEP",
|
|
918
|
+
hash: `SHA-${Number.parseInt(alg.slice(-3), 10) || 1}`
|
|
919
|
+
};
|
|
920
|
+
keyUsages = isPublic ? ["encrypt", "wrapKey"] : ["decrypt", "unwrapKey"];
|
|
921
|
+
break;
|
|
922
|
+
case "ES256":
|
|
923
|
+
algorithm = {
|
|
924
|
+
name: "ECDSA",
|
|
925
|
+
namedCurve: "P-256"
|
|
926
|
+
};
|
|
927
|
+
keyUsages = isPublic ? ["verify"] : ["sign"];
|
|
928
|
+
break;
|
|
929
|
+
case "ES384":
|
|
930
|
+
algorithm = {
|
|
931
|
+
name: "ECDSA",
|
|
932
|
+
namedCurve: "P-384"
|
|
933
|
+
};
|
|
934
|
+
keyUsages = isPublic ? ["verify"] : ["sign"];
|
|
935
|
+
break;
|
|
936
|
+
case "ES512":
|
|
937
|
+
algorithm = {
|
|
938
|
+
name: "ECDSA",
|
|
939
|
+
namedCurve: "P-521"
|
|
940
|
+
};
|
|
941
|
+
keyUsages = isPublic ? ["verify"] : ["sign"];
|
|
942
|
+
break;
|
|
943
|
+
case "ECDH-ES":
|
|
944
|
+
case "ECDH-ES+A128KW":
|
|
945
|
+
case "ECDH-ES+A192KW":
|
|
946
|
+
case "ECDH-ES+A256KW": {
|
|
947
|
+
const namedCurve = getNamedCurve(keyData);
|
|
948
|
+
algorithm = namedCurve?.startsWith("P-") ? {
|
|
949
|
+
name: "ECDH",
|
|
950
|
+
namedCurve
|
|
951
|
+
} : { name: "X25519" };
|
|
952
|
+
keyUsages = isPublic ? [] : ["deriveBits"];
|
|
953
|
+
break;
|
|
954
|
+
}
|
|
955
|
+
case "Ed25519":
|
|
956
|
+
case "EdDSA":
|
|
957
|
+
algorithm = { name: "Ed25519" };
|
|
958
|
+
keyUsages = isPublic ? ["verify"] : ["sign"];
|
|
959
|
+
break;
|
|
960
|
+
default: throw new Error("Invalid or unsupported \"alg\" (Algorithm) value");
|
|
961
|
+
}
|
|
962
|
+
return crypto.subtle.importKey(keyFormat, keyData, algorithm, options?.extractable ?? (isPublic ? true : false), keyUsages);
|
|
963
|
+
};
|
|
964
|
+
const fromPKCS8 = (pem, alg, options) => {
|
|
965
|
+
return genericImport(/(?:-----(?:BEGIN|END) PRIVATE KEY-----|\s)/g, "pkcs8", pem, alg, options);
|
|
966
|
+
};
|
|
967
|
+
const fromSPKI = (pem, alg, options) => {
|
|
968
|
+
return genericImport(/(?:-----(?:BEGIN|END) PUBLIC KEY-----|\s)/g, "spki", pem, alg, options);
|
|
969
|
+
};
|
|
970
|
+
function getElement(seq) {
|
|
971
|
+
const result = [];
|
|
972
|
+
let next = 0;
|
|
973
|
+
while (next < seq.length) {
|
|
974
|
+
const nextPart = parseElement(seq.subarray(next));
|
|
975
|
+
result.push(nextPart);
|
|
976
|
+
next += nextPart.byteLength;
|
|
977
|
+
}
|
|
978
|
+
return result;
|
|
979
|
+
}
|
|
980
|
+
function parseElement(bytes) {
|
|
981
|
+
let position = 0;
|
|
982
|
+
let tag = bytes[0] & 31;
|
|
983
|
+
position++;
|
|
984
|
+
if (tag === 31) {
|
|
985
|
+
tag = 0;
|
|
986
|
+
while (bytes[position] >= 128) {
|
|
987
|
+
tag = tag * 128 + bytes[position] - 128;
|
|
988
|
+
position++;
|
|
989
|
+
}
|
|
990
|
+
tag = tag * 128 + bytes[position] - 128;
|
|
991
|
+
position++;
|
|
992
|
+
}
|
|
993
|
+
let length = 0;
|
|
994
|
+
if (bytes[position] < 128) {
|
|
995
|
+
length = bytes[position];
|
|
996
|
+
position++;
|
|
997
|
+
} else if (length === 128) {
|
|
998
|
+
length = 0;
|
|
999
|
+
while (bytes[position + length] !== 0 || bytes[position + length + 1] !== 0) {
|
|
1000
|
+
if (length > bytes.byteLength) throw new TypeError("invalid indefinite form length");
|
|
1001
|
+
length++;
|
|
1002
|
+
}
|
|
1003
|
+
const byteLength = position + length + 2;
|
|
1004
|
+
return {
|
|
1005
|
+
byteLength,
|
|
1006
|
+
contents: bytes.subarray(position, position + length),
|
|
1007
|
+
raw: bytes.subarray(0, byteLength)
|
|
1008
|
+
};
|
|
1009
|
+
} else {
|
|
1010
|
+
const numberOfDigits = bytes[position] & 127;
|
|
1011
|
+
position++;
|
|
1012
|
+
length = 0;
|
|
1013
|
+
for (let i = 0; i < numberOfDigits; i++) {
|
|
1014
|
+
length = length * 256 + bytes[position];
|
|
1015
|
+
position++;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
const byteLength = position + length;
|
|
1019
|
+
return {
|
|
1020
|
+
byteLength,
|
|
1021
|
+
contents: bytes.subarray(position, byteLength),
|
|
1022
|
+
raw: bytes.subarray(0, byteLength)
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
function spkiFromX509(buf) {
|
|
1026
|
+
const tbsCertificate = getElement(getElement(parseElement(buf).contents)[0].contents);
|
|
1027
|
+
return base64Encode(tbsCertificate[tbsCertificate[0].raw[0] === 160 ? 6 : 5].raw);
|
|
1028
|
+
}
|
|
1029
|
+
let createPublicKey;
|
|
1030
|
+
function getSPKI(x509) {
|
|
1031
|
+
try {
|
|
1032
|
+
createPublicKey ??= globalThis.process?.getBuiltinModule?.("node:crypto")?.createPublicKey;
|
|
1033
|
+
} catch {
|
|
1034
|
+
createPublicKey = 0;
|
|
1035
|
+
}
|
|
1036
|
+
if (createPublicKey) try {
|
|
1037
|
+
return new createPublicKey(x509).export({
|
|
1038
|
+
format: "pem",
|
|
1039
|
+
type: "spki"
|
|
1040
|
+
});
|
|
1041
|
+
} catch {}
|
|
1042
|
+
return formatPEM(spkiFromX509(base64Decode(x509.replace(/(?:-----(?:BEGIN|END) CERTIFICATE-----|\s)/g, ""), false)), "PUBLIC KEY");
|
|
1043
|
+
}
|
|
1044
|
+
const fromX509 = (pem, alg, options) => {
|
|
1045
|
+
let spki;
|
|
1046
|
+
try {
|
|
1047
|
+
spki = getSPKI(pem);
|
|
1048
|
+
} catch (error_) {
|
|
1049
|
+
throw new TypeError("Failed to parse the X.509 certificate", { cause: error_ });
|
|
1050
|
+
}
|
|
1051
|
+
return fromSPKI(spki, alg, options);
|
|
1052
|
+
};
|
|
1053
|
+
//#endregion
|
|
1054
|
+
//#region src/core/jwk.ts
|
|
1055
|
+
var jwk_exports = /* @__PURE__ */ __exportAll({
|
|
1056
|
+
deriveJWKFromPassword: () => deriveJWKFromPassword,
|
|
1057
|
+
deriveKeyFromPassword: () => deriveKeyFromPassword,
|
|
1058
|
+
exportJWKToPEM: () => exportJWKToPEM,
|
|
1059
|
+
exportKey: () => exportKey,
|
|
1060
|
+
generateJWK: () => generateJWK,
|
|
1061
|
+
generateKey: () => generateKey,
|
|
1062
|
+
getJWKFromSet: () => getJWKFromSet,
|
|
1063
|
+
importJWKFromPEM: () => importJWKFromPEM,
|
|
1064
|
+
importKey: () => importKey,
|
|
1065
|
+
unwrapKey: () => unwrapKey,
|
|
1066
|
+
wrapKey: () => wrapKey
|
|
1067
|
+
});
|
|
1068
|
+
async function generateKey(alg, options = {}) {
|
|
1069
|
+
const exportToJWK = options.toJWK !== void 0 && options.toJWK !== false;
|
|
1070
|
+
const defaultExtractable = options.extractable !== false;
|
|
1071
|
+
if (alg === "A128CBC-HS256" || alg === "A192CBC-HS384" || alg === "A256CBC-HS512") {
|
|
1072
|
+
const keyBytes = randomBytes(bitLengthCEK(alg) >> 3);
|
|
1073
|
+
if (exportToJWK) return keyToJWK(keyBytes);
|
|
1074
|
+
return keyBytes;
|
|
1075
|
+
}
|
|
1076
|
+
const { algorithm, keyUsages } = getGenerateKeyParams(alg, options);
|
|
1077
|
+
const key = await crypto.subtle.generateKey(algorithm, defaultExtractable, keyUsages);
|
|
1078
|
+
if (exportToJWK) {
|
|
1079
|
+
const { alg: _a, kty: _k, key_ops: _ko, ext: _e, ...additionalKeyParams } = typeof options.toJWK === "object" ? options.toJWK : {};
|
|
1080
|
+
if (key instanceof CryptoKey) return exportKey(key, {
|
|
1081
|
+
...additionalKeyParams,
|
|
1082
|
+
alg
|
|
1083
|
+
});
|
|
1084
|
+
else {
|
|
1085
|
+
const [publicKey, privateKey] = await Promise.all([exportKey(key.publicKey, {
|
|
1086
|
+
...additionalKeyParams,
|
|
1087
|
+
alg
|
|
1088
|
+
}), exportKey(key.privateKey, {
|
|
1089
|
+
...additionalKeyParams,
|
|
1090
|
+
alg
|
|
1091
|
+
})]);
|
|
1092
|
+
return {
|
|
1093
|
+
privateKey,
|
|
1094
|
+
publicKey
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
return key;
|
|
1099
|
+
}
|
|
1100
|
+
/**
|
|
1101
|
+
* Generates a Json Web Key (JWK) for the specified algorithm.
|
|
1102
|
+
*
|
|
1103
|
+
* @param alg The JWA algorithm identifier (e.g., "HS256", "RS256", "A128GCM").
|
|
1104
|
+
* @param jwkParams Optional partial JWK to merge with the generated key, allowing overrides.
|
|
1105
|
+
* @param options Configuration options for key generation.
|
|
1106
|
+
* @returns A Promise resolving to the generated JWK or JWK pair representation.
|
|
1107
|
+
*/
|
|
1108
|
+
async function generateJWK(alg, jwkParams, options = {}) {
|
|
1109
|
+
const { toJWK: _, ...opts } = options;
|
|
1110
|
+
return generateKey(alg, {
|
|
1111
|
+
...opts,
|
|
1112
|
+
toJWK: {
|
|
1113
|
+
kid: typeof jwkParams?.kid === "string" ? jwkParams.kid : crypto.randomUUID(),
|
|
1114
|
+
...jwkParams
|
|
1115
|
+
}
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
async function deriveKeyFromPassword(password, alg, options) {
|
|
1119
|
+
const { salt, iterations, extractable, keyUsage } = options;
|
|
1120
|
+
const exportToJWK = options.toJWK !== void 0 && options.toJWK !== false;
|
|
1121
|
+
if (!(salt instanceof Uint8Array) || salt.length < 8) throw new Error("PBES2 Salt Input (salt) must be 8 or more octets");
|
|
1122
|
+
if (typeof iterations !== "number" || iterations < 1) throw new Error("PBES2 Iteration Count (iterations) must be a positive integer");
|
|
1123
|
+
const derivedBytes = await deriveKey(salt, alg, iterations, typeof password === "string" ? textEncoder.encode(password) : password);
|
|
1124
|
+
const wrappingAlg = alg.slice(-6);
|
|
1125
|
+
const finalUsages = keyUsage ?? ["wrapKey", "unwrapKey"];
|
|
1126
|
+
const finalExtractable = exportToJWK ? true : extractable === true;
|
|
1127
|
+
const derivedKey = await crypto.subtle.importKey("raw", derivedBytes, { name: "AES-KW" }, finalExtractable, finalUsages);
|
|
1128
|
+
if (exportToJWK) {
|
|
1129
|
+
const jwk = await keyToJWK(derivedKey);
|
|
1130
|
+
const { alg: _a, kty: _k, key_ops: _ko, ext: _e, ...additionalKeyParams } = typeof options.toJWK === "object" ? options.toJWK : {};
|
|
1131
|
+
return {
|
|
1132
|
+
...additionalKeyParams,
|
|
1133
|
+
...jwk,
|
|
1134
|
+
alg: wrappingAlg,
|
|
1135
|
+
kty: "oct"
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
return derivedKey;
|
|
1139
|
+
}
|
|
1140
|
+
/**
|
|
1141
|
+
* Derives a Json Web Key (JWK) from a password using PBKDF2 as specified by PBES2 algorithms.
|
|
1142
|
+
*
|
|
1143
|
+
* @param password The password to derive the key from (string or Uint8Array).
|
|
1144
|
+
* @param alg The PBES2 algorithm identifier (e.g., "PBES2-HS256+A128KW").
|
|
1145
|
+
* @param jwkParams Optional partial JWK to merge with the generated key, allowing overrides.
|
|
1146
|
+
* @param options Configuration options including salt and iterations.
|
|
1147
|
+
* @returns A Promise resolving to the derived JWK (JWK_oct).
|
|
1148
|
+
*/
|
|
1149
|
+
async function deriveJWKFromPassword(password, alg, options, jwkParams) {
|
|
1150
|
+
const { toJWK: _, ...opts } = options;
|
|
1151
|
+
return deriveKeyFromPassword(password, alg, {
|
|
1152
|
+
...opts,
|
|
1153
|
+
toJWK: jwkParams || true
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
/**
|
|
1157
|
+
* Cache for imported JWKs to avoid redundant CryptoKey creation.
|
|
1158
|
+
*/
|
|
1159
|
+
let _jwkCache;
|
|
1160
|
+
async function importKey(key, alg) {
|
|
1161
|
+
if (typeof key === "string") key = textEncoder.encode(key);
|
|
1162
|
+
if (key instanceof Uint8Array) return key;
|
|
1163
|
+
if (isCryptoKey(key)) return key;
|
|
1164
|
+
if (isJWK(key)) if ("k" in key && typeof key.k === "string") return base64UrlDecode(key.k, false);
|
|
1165
|
+
else {
|
|
1166
|
+
if (!key.alg && !alg) throw new TypeError("Algorithm must be provided when importing non-oct JWK");
|
|
1167
|
+
const effectiveAlg = key.alg || alg;
|
|
1168
|
+
_jwkCache ||= /* @__PURE__ */ new WeakMap();
|
|
1169
|
+
const cached = _jwkCache.get(key);
|
|
1170
|
+
if (cached?.[effectiveAlg]) return cached[effectiveAlg];
|
|
1171
|
+
const cryptoKey = await jwkTokey(key.alg ? key : {
|
|
1172
|
+
...key,
|
|
1173
|
+
alg
|
|
1174
|
+
});
|
|
1175
|
+
if (!cached) _jwkCache.set(key, { [effectiveAlg]: cryptoKey });
|
|
1176
|
+
else cached[effectiveAlg] = cryptoKey;
|
|
1177
|
+
return cryptoKey;
|
|
1178
|
+
}
|
|
1179
|
+
throw new Error("Invalid key type provided to importKey");
|
|
1180
|
+
}
|
|
1181
|
+
/**
|
|
1182
|
+
* Exports a CryptoKey to a JWK (JSON Web Key) format.
|
|
1183
|
+
*
|
|
1184
|
+
* @param key The CryptoKey to export.
|
|
1185
|
+
* @param jwk Optional partial JWK to merge with the exported key, allowing overrides.
|
|
1186
|
+
* @returns A Promise resolving to the exported JWK.
|
|
1187
|
+
*/
|
|
1188
|
+
async function exportKey(key, jwk) {
|
|
1189
|
+
const exportedJwk = await keyToJWK(key);
|
|
1190
|
+
if (jwk) return {
|
|
1191
|
+
...sanitizeObject(jwk),
|
|
1192
|
+
...exportedJwk
|
|
1193
|
+
};
|
|
1194
|
+
return exportedJwk;
|
|
1195
|
+
}
|
|
1196
|
+
async function resolveWrappingKey(alg, key) {
|
|
1197
|
+
if (alg.startsWith("PBES2") && (typeof key === "string" || key instanceof Uint8Array)) return typeof key === "string" ? textEncoder.encode(key) : key;
|
|
1198
|
+
if (typeof key === "string") throw new TypeError(`Key must be CryptoKey, JWK, or Uint8Array for ${alg}`);
|
|
1199
|
+
return importKey(key, alg);
|
|
1200
|
+
}
|
|
1201
|
+
/**
|
|
1202
|
+
* Wraps a Content Encryption Key (CEK) using the specified algorithm and wrapping key.
|
|
1203
|
+
*
|
|
1204
|
+
* @param alg The JWA key management algorithm (e.g., "A128KW", "RSA-OAEP", "PBES2-HS256+A128KW").
|
|
1205
|
+
* @param keyToWrap The key to be wrapped (CEK), typically a symmetric key as Uint8Array or CryptoKey.
|
|
1206
|
+
* @param wrappingKey The key used to wrap the CEK (CryptoKey, JWK, or password string/Uint8Array for PBES2).
|
|
1207
|
+
* @param options Additional options required by certain algorithms (e.g., p2s, p2c for PBES2).
|
|
1208
|
+
* @returns A Promise resolving to an object containing the wrapped key and any necessary parameters (iv, tag, epk, etc.).
|
|
1209
|
+
*/
|
|
1210
|
+
async function wrapKey(alg, keyToWrap, wrappingKey, options = {}) {
|
|
1211
|
+
const cekBytes = keyToWrap instanceof Uint8Array ? keyToWrap : new Uint8Array(await crypto.subtle.exportKey("raw", keyToWrap));
|
|
1212
|
+
const importedWrappingKey = await resolveWrappingKey(alg, wrappingKey);
|
|
1213
|
+
switch (alg) {
|
|
1214
|
+
case "A128KW":
|
|
1215
|
+
case "A192KW":
|
|
1216
|
+
case "A256KW":
|
|
1217
|
+
case "PBES2-HS256+A128KW":
|
|
1218
|
+
case "PBES2-HS384+A192KW":
|
|
1219
|
+
case "PBES2-HS512+A256KW": {
|
|
1220
|
+
const { p2s, p2c } = options;
|
|
1221
|
+
if (alg.startsWith("PBES2") && (!p2s || typeof p2c !== "number")) throw new Error("PBES2 requires 'p2s' (salt) and 'p2c' (count) options");
|
|
1222
|
+
return wrap(alg, importedWrappingKey, cekBytes, p2c, p2s);
|
|
1223
|
+
}
|
|
1224
|
+
case "A128GCMKW":
|
|
1225
|
+
case "A192GCMKW":
|
|
1226
|
+
case "A256GCMKW": {
|
|
1227
|
+
const { encryptedKey, iv, tag } = await encryptIV(alg, importedWrappingKey, cekBytes, options.iv);
|
|
1228
|
+
return {
|
|
1229
|
+
encryptedKey,
|
|
1230
|
+
iv,
|
|
1231
|
+
tag
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
case "RSA-OAEP":
|
|
1235
|
+
case "RSA-OAEP-256":
|
|
1236
|
+
case "RSA-OAEP-384":
|
|
1237
|
+
case "RSA-OAEP-512": {
|
|
1238
|
+
const keyToWrapImported = await crypto.subtle.importKey("raw", cekBytes, {
|
|
1239
|
+
name: "AES-GCM",
|
|
1240
|
+
length: cekBytes.length * 8
|
|
1241
|
+
}, true, ["encrypt", "decrypt"]);
|
|
1242
|
+
return { encryptedKey: new Uint8Array(await crypto.subtle.wrapKey("raw", keyToWrapImported, importedWrappingKey, { name: "RSA-OAEP" })) };
|
|
1243
|
+
}
|
|
1244
|
+
case "ECDH-ES":
|
|
1245
|
+
case "ECDH-ES+A128KW":
|
|
1246
|
+
case "ECDH-ES+A192KW":
|
|
1247
|
+
case "ECDH-ES+A256KW": throw new Error(`Algorithm ${alg} not yet implemented in wrapKey`);
|
|
1248
|
+
default: throw new Error(`Unsupported key wrapping algorithm: ${alg}`);
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
/**
|
|
1252
|
+
* Unwraps a Content Encryption Key (CEK) using the specified algorithm and unwrapping key.
|
|
1253
|
+
*
|
|
1254
|
+
* @param alg The JWA key management algorithm (e.g., "A128KW", "RSA-OAEP", "PBES2-HS256+A128KW").
|
|
1255
|
+
* @param wrappedKey The wrapped key (ciphertext) as Uint8Array.
|
|
1256
|
+
* @param unwrappingKey The key used to unwrap the CEK (CryptoKey, JWK, or password string/Uint8Array for PBES2).
|
|
1257
|
+
* @param options Additional options required by certain algorithms (e.g., iv, tag, p2s, p2c, epk).
|
|
1258
|
+
* @returns A Promise resolving to the unwrapped key (CEK) as a CryptoKey or Uint8Array.
|
|
1259
|
+
*/
|
|
1260
|
+
async function unwrapKey(alg, wrappedKey, unwrappingKey, options = {}) {
|
|
1261
|
+
const { returnAs = true } = options;
|
|
1262
|
+
const defaultExtractable = options.extractable !== false;
|
|
1263
|
+
const importedUnwrappingKey = isCryptoKey(unwrappingKey) ? unwrappingKey : await resolveWrappingKey(alg, unwrappingKey);
|
|
1264
|
+
let unwrappedCekBytes;
|
|
1265
|
+
switch (alg) {
|
|
1266
|
+
case "A128KW":
|
|
1267
|
+
case "A192KW":
|
|
1268
|
+
case "A256KW":
|
|
1269
|
+
case "PBES2-HS256+A128KW":
|
|
1270
|
+
case "PBES2-HS384+A192KW":
|
|
1271
|
+
case "PBES2-HS512+A256KW": {
|
|
1272
|
+
const { p2s, p2c } = options;
|
|
1273
|
+
let p2sBytes;
|
|
1274
|
+
if (alg.startsWith("PBES2")) {
|
|
1275
|
+
if (!p2s || typeof p2c !== "number") throw new Error("PBES2 requires 'p2s' (salt) and 'p2c' (count) options");
|
|
1276
|
+
p2sBytes = typeof p2s === "string" ? base64UrlDecode(p2s, false) : p2s;
|
|
1277
|
+
}
|
|
1278
|
+
unwrappedCekBytes = await unwrap(alg, importedUnwrappingKey, wrappedKey, p2c, p2sBytes);
|
|
1279
|
+
break;
|
|
1280
|
+
}
|
|
1281
|
+
case "A128GCMKW":
|
|
1282
|
+
case "A192GCMKW":
|
|
1283
|
+
case "A256GCMKW":
|
|
1284
|
+
if (!options.iv || !options.tag) throw new Error("AES-GCMKW requires 'iv' and 'tag' options for unwrapping");
|
|
1285
|
+
unwrappedCekBytes = await decryptIV(alg, importedUnwrappingKey, wrappedKey, typeof options.iv === "string" ? base64UrlDecode(options.iv, false) : options.iv, typeof options.tag === "string" ? base64UrlDecode(options.tag, false) : options.tag);
|
|
1286
|
+
break;
|
|
1287
|
+
case "RSA-OAEP":
|
|
1288
|
+
case "RSA-OAEP-256":
|
|
1289
|
+
case "RSA-OAEP-384":
|
|
1290
|
+
case "RSA-OAEP-512": {
|
|
1291
|
+
if (!(importedUnwrappingKey instanceof CryptoKey)) throw new TypeError("RSA-OAEP requires the unwrapping key to be provided as a CryptoKey");
|
|
1292
|
+
unwrappedCekBytes = await decryptRSAES(alg, importedUnwrappingKey, wrappedKey);
|
|
1293
|
+
if (!returnAs) break;
|
|
1294
|
+
const inferredAlgorithm = options.unwrappedKeyAlgorithm || inferAesImportAlgorithm(options.enc, unwrappedCekBytes.length);
|
|
1295
|
+
if (!inferredAlgorithm) throw new Error("Unable to infer algorithm for RSA-OAEP unwrapped key. Provide \"unwrappedKeyAlgorithm\" in options.");
|
|
1296
|
+
const keyUsages = options.keyUsage || ["encrypt", "decrypt"];
|
|
1297
|
+
return await crypto.subtle.importKey("raw", unwrappedCekBytes, inferredAlgorithm, defaultExtractable, keyUsages);
|
|
1298
|
+
}
|
|
1299
|
+
case "ECDH-ES":
|
|
1300
|
+
case "ECDH-ES+A128KW":
|
|
1301
|
+
case "ECDH-ES+A192KW":
|
|
1302
|
+
case "ECDH-ES+A256KW": {
|
|
1303
|
+
if (!options.epk) throw new Error("ECDH-ES requires 'epk' (Ephemeral Public Key) option");
|
|
1304
|
+
if (!(importedUnwrappingKey instanceof CryptoKey)) throw new TypeError("ECDH-ES requires the unwrapping key to be a CryptoKey");
|
|
1305
|
+
if (!allowed(importedUnwrappingKey)) throw new Error("ECDH with the provided key is not allowed or not supported");
|
|
1306
|
+
const epkCandidate = options.epk instanceof CryptoKey ? options.epk : await normalizeKey(options.epk, alg);
|
|
1307
|
+
if (!(epkCandidate instanceof CryptoKey)) throw new TypeError("Failed to normalize ECDH ephemeral public key");
|
|
1308
|
+
const apuBytes = typeof options.apu === "string" ? base64UrlDecode(options.apu, false) : options.apu ?? new Uint8Array(0);
|
|
1309
|
+
const apvBytes = typeof options.apv === "string" ? base64UrlDecode(options.apv, false) : options.apv ?? new Uint8Array(0);
|
|
1310
|
+
const infoAlg = alg === "ECDH-ES" ? options.enc : alg;
|
|
1311
|
+
if (!infoAlg) throw new Error("ECDH-ES requires content encryption algorithm ('enc') to derive the shared secret");
|
|
1312
|
+
const sharedSecret = await deriveECDHESKey(epkCandidate, importedUnwrappingKey, infoAlg, alg === "ECDH-ES" ? bitLengthCEK(infoAlg) : Number.parseInt(alg.slice(-5, -2), 10), apuBytes, apvBytes);
|
|
1313
|
+
if (alg === "ECDH-ES") unwrappedCekBytes = sharedSecret;
|
|
1314
|
+
else {
|
|
1315
|
+
const kwAlg = alg.slice(-6);
|
|
1316
|
+
if (!(wrappedKey instanceof Uint8Array) || wrappedKey.length === 0) throw new Error("ECDH-ES key agreement with key wrapping requires an encrypted key");
|
|
1317
|
+
unwrappedCekBytes = await unwrap(kwAlg, sharedSecret, wrappedKey);
|
|
1318
|
+
}
|
|
1319
|
+
break;
|
|
1320
|
+
}
|
|
1321
|
+
default: throw new Error(`Unsupported key unwrapping algorithm: ${alg}`);
|
|
1322
|
+
}
|
|
1323
|
+
if (!returnAs) return unwrappedCekBytes;
|
|
1324
|
+
return await crypto.subtle.importKey("raw", unwrappedCekBytes, options.unwrappedKeyAlgorithm || { name: "AES-GCM" }, defaultExtractable, options.keyUsage || ["encrypt", "decrypt"]);
|
|
1325
|
+
}
|
|
1326
|
+
/**
|
|
1327
|
+
* Imports a key from a PEM-encoded string and converts it to a JWK.
|
|
1328
|
+
*
|
|
1329
|
+
* @param pem The PEM-encoded string.
|
|
1330
|
+
* @param pemType The type of PEM encoding ('pkcs8' for private keys, 'spki' for public keys, 'x509' for certificates).
|
|
1331
|
+
* @param alg The JWA algorithm identifier. This is crucial for `crypto.subtle.importKey`
|
|
1332
|
+
* to understand the key's intended algorithm and for setting the 'alg' field in the resulting JWK.
|
|
1333
|
+
* @param importOptions Options for the PEM import process (e.g., extractable for the CryptoKey).
|
|
1334
|
+
* @param jwkExtras Additional properties to merge into the resulting JWK.
|
|
1335
|
+
* @returns A Promise resolving to the imported key as a JWK.
|
|
1336
|
+
*/
|
|
1337
|
+
async function importJWKFromPEM(pem, pemType, alg, importOptions, jwkExtras) {
|
|
1338
|
+
let cryptoKey;
|
|
1339
|
+
const defaultExtractable = importOptions?.extractable !== false;
|
|
1340
|
+
switch (pemType) {
|
|
1341
|
+
case "pkcs8":
|
|
1342
|
+
cryptoKey = await fromPKCS8(pem, alg, {
|
|
1343
|
+
...importOptions,
|
|
1344
|
+
extractable: defaultExtractable
|
|
1345
|
+
});
|
|
1346
|
+
break;
|
|
1347
|
+
case "spki":
|
|
1348
|
+
cryptoKey = await fromSPKI(pem, alg, {
|
|
1349
|
+
...importOptions,
|
|
1350
|
+
extractable: defaultExtractable
|
|
1351
|
+
});
|
|
1352
|
+
break;
|
|
1353
|
+
case "x509":
|
|
1354
|
+
cryptoKey = await fromX509(pem, alg, {
|
|
1355
|
+
...importOptions,
|
|
1356
|
+
extractable: defaultExtractable
|
|
1357
|
+
});
|
|
1358
|
+
break;
|
|
1359
|
+
default: throw new TypeError(`Unsupported PEM type: ${pemType}`);
|
|
1360
|
+
}
|
|
1361
|
+
const finalJWKExtras = {
|
|
1362
|
+
alg,
|
|
1363
|
+
...jwkExtras
|
|
1364
|
+
};
|
|
1365
|
+
return exportKey(cryptoKey, finalJWKExtras);
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* Exports a JWK to a PEM-encoded string.
|
|
1369
|
+
*
|
|
1370
|
+
* @param jwk The JWK to export.
|
|
1371
|
+
* @param pemFormat The desired PEM format ('pkcs8' for private keys, 'spki' for public keys).
|
|
1372
|
+
* @param algForCryptoKeyImport If the JWK does not have an 'alg' property, this algorithm hint is
|
|
1373
|
+
* required to correctly convert it to a CryptoKey first.
|
|
1374
|
+
* @returns A Promise resolving to the PEM-encoded key string.
|
|
1375
|
+
*/
|
|
1376
|
+
async function exportJWKToPEM(jwk, pemFormat, algForCryptoKeyImport) {
|
|
1377
|
+
if (jwk.kty === "oct") throw new TypeError("Octet (symmetric) JWKs (kty: 'oct') cannot be exported to PKCS8 or SPKI PEM formats.");
|
|
1378
|
+
const effectiveAlg = jwk.alg || algForCryptoKeyImport;
|
|
1379
|
+
if (!effectiveAlg && (jwk.kty === "RSA" || jwk.kty === "EC" || jwk.kty === "OKP")) throw new TypeError("Algorithm (alg) must be provided in the JWK or as a parameter for converting this JWK type to a CryptoKey.");
|
|
1380
|
+
const cryptoKeyCandidate = await importKey({
|
|
1381
|
+
...jwk,
|
|
1382
|
+
ext: true
|
|
1383
|
+
}, effectiveAlg);
|
|
1384
|
+
if (!isCryptoKey(cryptoKeyCandidate)) throw new Error("Failed to convert JWK to a CryptoKey instance suitable for PEM export.");
|
|
1385
|
+
const cryptoKey = cryptoKeyCandidate;
|
|
1386
|
+
switch (pemFormat) {
|
|
1387
|
+
case "pkcs8":
|
|
1388
|
+
if (cryptoKey.type !== "private") throw new TypeError(`Only 'private' type CryptoKeys can be exported to PKCS8 PEM format. Key type is '${cryptoKey.type}'.`);
|
|
1389
|
+
return toPKCS8(cryptoKey);
|
|
1390
|
+
case "spki":
|
|
1391
|
+
if (cryptoKey.type !== "public") throw new TypeError(`Only 'public' type CryptoKeys can be exported to SPKI PEM format. Key type is '${cryptoKey.type}'.`);
|
|
1392
|
+
return toSPKI(cryptoKey);
|
|
1393
|
+
default: throw new TypeError(`Unsupported PEM format: ${pemFormat}`);
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
/**
|
|
1397
|
+
* Retrieves a JWK (JSON Web Key) from a JWKSet based on a key ID (kid) or
|
|
1398
|
+
* properties from a JOSE Header.
|
|
1399
|
+
*
|
|
1400
|
+
* @param jwkSet The JWKSet to search within.
|
|
1401
|
+
* @param kidOrProtectedHeader Either a string representing the 'kid' (Key ID)
|
|
1402
|
+
* or an object representing the JOSE Protected Header. If an object is
|
|
1403
|
+
* provided, it must contain a 'kid' property. It can optionally contain
|
|
1404
|
+
* 'alg' (Algorithm) and 'kty' (Key Type) to further refine the search.
|
|
1405
|
+
* @returns The matching JWK from the set.
|
|
1406
|
+
* @throws TypeError if `jwkSet` is invalid or `kidOrProtectedHeader` is not a
|
|
1407
|
+
* string or a valid header object.
|
|
1408
|
+
* @throws Error if no key is found matching the provided criteria, or if the
|
|
1409
|
+
* header object is missing the 'kid' property.
|
|
1410
|
+
*/
|
|
1411
|
+
function getJWKFromSet(jwkSet, kidOrProtectedHeader) {
|
|
1412
|
+
if (!jwkSet || !isJWKSet(jwkSet)) throw new TypeError("Invalid JWK Set provided");
|
|
1413
|
+
if (typeof kidOrProtectedHeader === "string") {
|
|
1414
|
+
const kid = kidOrProtectedHeader;
|
|
1415
|
+
const selectedKey = jwkSet.keys.find((k) => k.kid === kid);
|
|
1416
|
+
if (!selectedKey) throw new Error(`No key found in JWK Set with kid "${kid}".`);
|
|
1417
|
+
return selectedKey;
|
|
1418
|
+
} else if (typeof kidOrProtectedHeader === "object") {
|
|
1419
|
+
const { kid, alg, kty } = kidOrProtectedHeader;
|
|
1420
|
+
if (!kid) throw new Error("JWS Protected Header is missing 'kid' (Key ID) and a JWK Set was provided. Cannot select key from JWK Set automatically.");
|
|
1421
|
+
const selectedKey = jwkSet.keys.find((k) => {
|
|
1422
|
+
return k.kid === kid && (!alg || k.alg === alg) && (!kty || k.kty === kty);
|
|
1423
|
+
});
|
|
1424
|
+
if (!selectedKey) {
|
|
1425
|
+
let errorMessage = `No key found in JWK Set with kid "${kid}"`;
|
|
1426
|
+
if (alg) errorMessage += ` and alg "${alg}"`;
|
|
1427
|
+
if (kty) errorMessage += ` and kty "${kty}"`;
|
|
1428
|
+
errorMessage += ".";
|
|
1429
|
+
throw new Error(errorMessage);
|
|
1430
|
+
}
|
|
1431
|
+
return selectedKey;
|
|
1432
|
+
}
|
|
1433
|
+
throw new TypeError("`kidOrProtectedHeader` must be a string (kid) or an object (JOSE Protected Header).");
|
|
1434
|
+
}
|
|
1435
|
+
function getGenerateKeyParams(alg, options) {
|
|
1436
|
+
let algorithm;
|
|
1437
|
+
let keyUsages;
|
|
1438
|
+
const defaultKeyUsage = options?.keyUsage;
|
|
1439
|
+
switch (alg) {
|
|
1440
|
+
case "HS256":
|
|
1441
|
+
case "HS384":
|
|
1442
|
+
case "HS512":
|
|
1443
|
+
algorithm = {
|
|
1444
|
+
name: "HMAC",
|
|
1445
|
+
hash: `SHA-${alg.slice(2)}`
|
|
1446
|
+
};
|
|
1447
|
+
keyUsages = defaultKeyUsage ?? ["sign", "verify"];
|
|
1448
|
+
break;
|
|
1449
|
+
case "RS256":
|
|
1450
|
+
case "RS384":
|
|
1451
|
+
case "RS512":
|
|
1452
|
+
algorithm = {
|
|
1453
|
+
name: "RSASSA-PKCS1-v1_5",
|
|
1454
|
+
modulusLength: options?.modulusLength ?? 2048,
|
|
1455
|
+
publicExponent: options?.publicExponent ?? new Uint8Array([
|
|
1456
|
+
1,
|
|
1457
|
+
0,
|
|
1458
|
+
1
|
|
1459
|
+
]),
|
|
1460
|
+
hash: `SHA-${alg.slice(2)}`
|
|
1461
|
+
};
|
|
1462
|
+
keyUsages = defaultKeyUsage ?? ["sign", "verify"];
|
|
1463
|
+
break;
|
|
1464
|
+
case "PS256":
|
|
1465
|
+
case "PS384":
|
|
1466
|
+
case "PS512":
|
|
1467
|
+
algorithm = {
|
|
1468
|
+
name: "RSA-PSS",
|
|
1469
|
+
modulusLength: options?.modulusLength ?? 2048,
|
|
1470
|
+
publicExponent: options?.publicExponent ?? new Uint8Array([
|
|
1471
|
+
1,
|
|
1472
|
+
0,
|
|
1473
|
+
1
|
|
1474
|
+
]),
|
|
1475
|
+
hash: `SHA-${alg.slice(2)}`
|
|
1476
|
+
};
|
|
1477
|
+
keyUsages = defaultKeyUsage ?? ["sign", "verify"];
|
|
1478
|
+
break;
|
|
1479
|
+
case "RSA-OAEP":
|
|
1480
|
+
case "RSA-OAEP-256":
|
|
1481
|
+
case "RSA-OAEP-384":
|
|
1482
|
+
case "RSA-OAEP-512":
|
|
1483
|
+
algorithm = {
|
|
1484
|
+
name: "RSA-OAEP",
|
|
1485
|
+
modulusLength: options?.modulusLength ?? 2048,
|
|
1486
|
+
publicExponent: options?.publicExponent ?? new Uint8Array([
|
|
1487
|
+
1,
|
|
1488
|
+
0,
|
|
1489
|
+
1
|
|
1490
|
+
]),
|
|
1491
|
+
hash: `SHA-${Number.parseInt(alg.slice(9), 10) || 1}`
|
|
1492
|
+
};
|
|
1493
|
+
keyUsages = defaultKeyUsage ?? [
|
|
1494
|
+
"encrypt",
|
|
1495
|
+
"decrypt",
|
|
1496
|
+
"wrapKey",
|
|
1497
|
+
"unwrapKey"
|
|
1498
|
+
];
|
|
1499
|
+
break;
|
|
1500
|
+
case "A128KW":
|
|
1501
|
+
case "A192KW":
|
|
1502
|
+
case "A256KW":
|
|
1503
|
+
algorithm = {
|
|
1504
|
+
name: "AES-KW",
|
|
1505
|
+
length: Number.parseInt(alg.slice(1, 4), 10)
|
|
1506
|
+
};
|
|
1507
|
+
keyUsages = defaultKeyUsage ?? ["wrapKey", "unwrapKey"];
|
|
1508
|
+
break;
|
|
1509
|
+
case "A128GCM":
|
|
1510
|
+
case "A192GCM":
|
|
1511
|
+
case "A256GCM":
|
|
1512
|
+
case "A128GCMKW":
|
|
1513
|
+
case "A192GCMKW":
|
|
1514
|
+
case "A256GCMKW": {
|
|
1515
|
+
const baseAlg = alg.replace("KW", "");
|
|
1516
|
+
algorithm = {
|
|
1517
|
+
name: "AES-GCM",
|
|
1518
|
+
length: Number.parseInt(baseAlg.slice(1, 4), 10)
|
|
1519
|
+
};
|
|
1520
|
+
keyUsages = defaultKeyUsage ?? (alg.endsWith("KW") ? ["wrapKey", "unwrapKey"] : ["encrypt", "decrypt"]);
|
|
1521
|
+
break;
|
|
1522
|
+
}
|
|
1523
|
+
case "ES256":
|
|
1524
|
+
case "ES384":
|
|
1525
|
+
case "ES512":
|
|
1526
|
+
algorithm = {
|
|
1527
|
+
name: "ECDSA",
|
|
1528
|
+
namedCurve: `P-${alg.slice(2)}`
|
|
1529
|
+
};
|
|
1530
|
+
keyUsages = defaultKeyUsage ?? ["sign", "verify"];
|
|
1531
|
+
break;
|
|
1532
|
+
case "Ed25519":
|
|
1533
|
+
keyUsages = defaultKeyUsage ?? ["sign", "verify"];
|
|
1534
|
+
algorithm = { name: "Ed25519" };
|
|
1535
|
+
break;
|
|
1536
|
+
case "EdDSA": {
|
|
1537
|
+
keyUsages = defaultKeyUsage ?? ["sign", "verify"];
|
|
1538
|
+
const namedCurve = options?.namedCurve ?? "Ed25519";
|
|
1539
|
+
switch (namedCurve) {
|
|
1540
|
+
case "Ed25519":
|
|
1541
|
+
case "Ed448":
|
|
1542
|
+
algorithm = { name: namedCurve };
|
|
1543
|
+
break;
|
|
1544
|
+
default: throw new Error("Unsupported namedCurve provided. Supported values are: Ed25519 and Ed448");
|
|
1545
|
+
}
|
|
1546
|
+
break;
|
|
1547
|
+
}
|
|
1548
|
+
case "ECDH-ES":
|
|
1549
|
+
case "ECDH-ES+A128KW":
|
|
1550
|
+
case "ECDH-ES+A192KW":
|
|
1551
|
+
case "ECDH-ES+A256KW": {
|
|
1552
|
+
keyUsages = ["deriveBits"];
|
|
1553
|
+
const namedCurve = options?.namedCurve ?? "P-256";
|
|
1554
|
+
switch (namedCurve) {
|
|
1555
|
+
case "P-256":
|
|
1556
|
+
case "P-384":
|
|
1557
|
+
case "P-521":
|
|
1558
|
+
algorithm = {
|
|
1559
|
+
name: "ECDH",
|
|
1560
|
+
namedCurve
|
|
1561
|
+
};
|
|
1562
|
+
break;
|
|
1563
|
+
case "X25519":
|
|
1564
|
+
algorithm = { name: "X25519" };
|
|
1565
|
+
break;
|
|
1566
|
+
default: throw new Error("Unsupported namedCurve provided. Supported values are: P-256, P-384, P-521 and X25519");
|
|
1567
|
+
}
|
|
1568
|
+
break;
|
|
1569
|
+
}
|
|
1570
|
+
default: throw new Error(`Unsupported or invalid algorithm for key generation: ${alg}`);
|
|
1571
|
+
}
|
|
1572
|
+
return {
|
|
1573
|
+
algorithm,
|
|
1574
|
+
keyUsages
|
|
1575
|
+
};
|
|
1576
|
+
}
|
|
1577
|
+
function inferAesImportAlgorithm(enc, cekLengthBytes) {
|
|
1578
|
+
const bitLength = cekLengthBytes << 3;
|
|
1579
|
+
if (enc) {
|
|
1580
|
+
if (enc.includes("GCM")) return {
|
|
1581
|
+
name: "AES-GCM",
|
|
1582
|
+
length: Number.parseInt(enc.slice(1, 4), 10)
|
|
1583
|
+
};
|
|
1584
|
+
if (enc.includes("CBC-HS")) return;
|
|
1585
|
+
}
|
|
1586
|
+
if (bitLength === 128 || bitLength === 192 || bitLength === 256) return {
|
|
1587
|
+
name: "AES-GCM",
|
|
1588
|
+
length: bitLength
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
//#endregion
|
|
1592
|
+
export { sign as _, generateJWK as a, importJWKFromPEM as c, unwrapKey as d, wrapKey as f, generateIV as g, encrypt as h, exportKey as i, importKey as l, decrypt as m, deriveKeyFromPassword as n, generateKey as o, encryptKey as p, exportJWKToPEM as r, getJWKFromSet as s, deriveJWKFromPassword as t, jwk_exports as u, verify as v };
|