unjwt 0.5.39 → 0.6.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 +156 -13
- package/dist/_chunks/_crypto.mjs +1014 -0
- package/dist/_chunks/error.d.mts +67 -0
- package/dist/_chunks/index.d.mts +631 -219
- package/dist/_chunks/jwk.d.mts +220 -87
- package/dist/_chunks/jws.mjs +729 -682
- package/dist/_chunks/utils.mjs +74 -69
- package/dist/adapters/h3v1/index.d.mts +282 -0
- package/dist/{_chunks/h3v1.mjs → adapters/h3v1/index.mjs} +204 -91
- package/dist/adapters/h3v2/index.d.mts +213 -0
- package/dist/adapters/{h3v2.mjs → h3v2/index.mjs} +215 -113
- package/dist/core/_crypto/index.d.mts +71 -0
- package/dist/core/_crypto/index.mjs +2 -0
- package/dist/core/jwe.d.mts +40 -0
- package/dist/{_chunks → core}/jwe.mjs +61 -32
- package/dist/core/jwk.d.mts +231 -0
- package/dist/core/jwk.mjs +686 -0
- package/dist/core/jws.d.mts +31 -0
- package/dist/core/jws.mjs +104 -0
- package/dist/{_chunks/index2.d.mts → core/utils/index.d.mts} +13 -12
- package/dist/core/utils/index.mjs +2 -0
- package/dist/index.d.mts +7 -6
- package/dist/index.mjs +5 -5
- package/package.json +50 -41
- package/dist/_chunks/chunk.mjs +0 -13
- package/dist/_chunks/jwe.d.mts +0 -43
- package/dist/_chunks/jwk.mjs +0 -1592
- package/dist/_chunks/jws.d.mts +0 -46
- package/dist/adapters/h3.d.mts +0 -3
- package/dist/adapters/h3.mjs +0 -3
- package/dist/adapters/h3v1.d.mts +0 -3
- package/dist/adapters/h3v1.mjs +0 -3
- package/dist/adapters/h3v2.d.mts +0 -184
- package/dist/jwe.d.mts +0 -3
- package/dist/jwe.mjs +0 -4
- package/dist/jwk.d.mts +0 -3
- package/dist/jwk.mjs +0 -3
- package/dist/jws.d.mts +0 -3
- package/dist/jws.mjs +0 -4
- package/dist/utils.d.mts +0 -3
- package/dist/utils.mjs +0 -2
|
@@ -0,0 +1,1014 @@
|
|
|
1
|
+
import { a as base64UrlEncode, c as isCryptoKey, i as base64UrlDecode, n as base64Decode, o as concatUint8Arrays, r as base64Encode, s as isAsymmetricJWK, t as assertCryptoKey, u as isJWK, v as textEncoder, y as sanitizeObject } from "./utils.mjs";
|
|
2
|
+
//#region src/core/_crypto/_sign-verify.ts
|
|
3
|
+
async function sign(alg, key, data) {
|
|
4
|
+
checkSigCryptoKey(key, alg, "sign");
|
|
5
|
+
const signature = await crypto.subtle.sign(subtleAlgorithm(alg, key.algorithm), key, data);
|
|
6
|
+
return new Uint8Array(signature);
|
|
7
|
+
}
|
|
8
|
+
async function verify(alg, key, signature, data) {
|
|
9
|
+
checkSigCryptoKey(key, alg, "verify");
|
|
10
|
+
try {
|
|
11
|
+
return await crypto.subtle.verify(subtleAlgorithm(alg, key.algorithm), key, signature, data);
|
|
12
|
+
} catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function checkSigCryptoKey(key, alg, usage) {
|
|
17
|
+
switch (alg) {
|
|
18
|
+
case "HS256":
|
|
19
|
+
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
|
+
}
|
|
26
|
+
case "RS256":
|
|
27
|
+
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 "PS256":
|
|
35
|
+
case "PS384":
|
|
36
|
+
case "PS512": {
|
|
37
|
+
if (!isAlgorithm$3(key.algorithm, "RSA-PSS")) throw unusable$4("RSA-PSS");
|
|
38
|
+
const expected = Number.parseInt(alg.slice(2), 10);
|
|
39
|
+
if (getHashLength$1(key.algorithm.hash) !== expected) throw unusable$4(`SHA-${expected}`, "algorithm.hash");
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
case "Ed25519":
|
|
43
|
+
case "EdDSA":
|
|
44
|
+
if (!isAlgorithm$3(key.algorithm, "Ed25519")) throw unusable$4("Ed25519");
|
|
45
|
+
break;
|
|
46
|
+
case "ES256":
|
|
47
|
+
case "ES384":
|
|
48
|
+
case "ES512": {
|
|
49
|
+
if (!isAlgorithm$3(key.algorithm, "ECDSA")) throw unusable$4("ECDSA");
|
|
50
|
+
const expected = getNamedCurve$1(alg);
|
|
51
|
+
if (key.algorithm.namedCurve !== expected) throw unusable$4(expected, "algorithm.namedCurve");
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
default: throw new TypeError("CryptoKey does not support this operation");
|
|
55
|
+
}
|
|
56
|
+
checkUsage$4(key, usage);
|
|
57
|
+
}
|
|
58
|
+
function subtleAlgorithm(alg, algorithm) {
|
|
59
|
+
const hash = `SHA-${alg.slice(-3)}`;
|
|
60
|
+
switch (alg) {
|
|
61
|
+
case "HS256":
|
|
62
|
+
case "HS384":
|
|
63
|
+
case "HS512": return {
|
|
64
|
+
hash,
|
|
65
|
+
name: "HMAC"
|
|
66
|
+
};
|
|
67
|
+
case "PS256":
|
|
68
|
+
case "PS384":
|
|
69
|
+
case "PS512": return {
|
|
70
|
+
hash,
|
|
71
|
+
name: "RSA-PSS",
|
|
72
|
+
saltLength: Number.parseInt(alg.slice(-3), 10) >> 3
|
|
73
|
+
};
|
|
74
|
+
case "RS256":
|
|
75
|
+
case "RS384":
|
|
76
|
+
case "RS512": return {
|
|
77
|
+
hash,
|
|
78
|
+
name: "RSASSA-PKCS1-v1_5"
|
|
79
|
+
};
|
|
80
|
+
case "ES256":
|
|
81
|
+
case "ES384":
|
|
82
|
+
case "ES512": return {
|
|
83
|
+
hash,
|
|
84
|
+
name: "ECDSA",
|
|
85
|
+
namedCurve: algorithm.namedCurve
|
|
86
|
+
};
|
|
87
|
+
case "Ed25519":
|
|
88
|
+
case "EdDSA": return { name: "Ed25519" };
|
|
89
|
+
default: throw new Error(`alg ${alg} is not supported either by JOSE or your javascript runtime`);
|
|
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");
|
|
107
|
+
}
|
|
108
|
+
}
|
|
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
|
+
//#endregion
|
|
113
|
+
//#region src/core/_crypto/_aes.ts
|
|
114
|
+
const IV_BIT_LENGTHS = {
|
|
115
|
+
A128GCM: 96,
|
|
116
|
+
A128GCMKW: 96,
|
|
117
|
+
A192GCM: 96,
|
|
118
|
+
A192GCMKW: 96,
|
|
119
|
+
A256GCM: 96,
|
|
120
|
+
A256GCMKW: 96,
|
|
121
|
+
"A128CBC-HS256": 128,
|
|
122
|
+
"A192CBC-HS384": 128,
|
|
123
|
+
"A256CBC-HS512": 128
|
|
124
|
+
};
|
|
125
|
+
const CEK_BIT_LENGTHS = {
|
|
126
|
+
A128GCM: 128,
|
|
127
|
+
A192GCM: 192,
|
|
128
|
+
A256GCM: 256,
|
|
129
|
+
"A128CBC-HS256": 256,
|
|
130
|
+
"A192CBC-HS384": 384,
|
|
131
|
+
"A256CBC-HS512": 512
|
|
132
|
+
};
|
|
133
|
+
function bitLengthIV(alg) {
|
|
134
|
+
const length = IV_BIT_LENGTHS[alg];
|
|
135
|
+
if (length === void 0) throw new Error(`Unsupported JWE Algorithm: ${alg}`);
|
|
136
|
+
return length;
|
|
137
|
+
}
|
|
138
|
+
function generateIV(alg) {
|
|
139
|
+
return crypto.getRandomValues(new Uint8Array(bitLengthIV(alg) >> 3));
|
|
140
|
+
}
|
|
141
|
+
function checkIvLength(enc, iv) {
|
|
142
|
+
if (iv.length << 3 !== bitLengthIV(enc)) throw new Error("Invalid Initialization Vector length");
|
|
143
|
+
}
|
|
144
|
+
function bitLengthCEK(alg) {
|
|
145
|
+
const length = CEK_BIT_LENGTHS[alg];
|
|
146
|
+
if (length === void 0) throw new Error(`Unsupported JWE Algorithm: ${alg}`);
|
|
147
|
+
return length;
|
|
148
|
+
}
|
|
149
|
+
function generateCEK(alg) {
|
|
150
|
+
return crypto.getRandomValues(new Uint8Array(bitLengthCEK(alg) >> 3));
|
|
151
|
+
}
|
|
152
|
+
function checkCEKLength(cek, expected) {
|
|
153
|
+
const actual = cek.byteLength << 3;
|
|
154
|
+
if (actual !== expected) throw new Error(`Invalid Content Encryption Key length. Expected ${expected} bits, got ${actual} bits`);
|
|
155
|
+
}
|
|
156
|
+
function checkAESCryptoKey(key, alg, usage) {
|
|
157
|
+
switch (alg) {
|
|
158
|
+
case "A128GCM":
|
|
159
|
+
case "A192GCM":
|
|
160
|
+
case "A256GCM": {
|
|
161
|
+
if (!isAlgorithm$2(key.algorithm, "AES-GCM")) throw unusable$3("AES-GCM");
|
|
162
|
+
const expected = Number.parseInt(alg.slice(1, 4), 10);
|
|
163
|
+
if (key.algorithm.length !== expected) throw unusable$3(expected, "algorithm.length");
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
case "A128KW":
|
|
167
|
+
case "A192KW":
|
|
168
|
+
case "A256KW": {
|
|
169
|
+
if (!isAlgorithm$2(key.algorithm, "AES-KW")) throw unusable$3("AES-KW");
|
|
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
|
+
}
|
|
174
|
+
default: throw new TypeError("CryptoKey does not support this operation");
|
|
175
|
+
}
|
|
176
|
+
checkUsage$3(key, usage);
|
|
177
|
+
}
|
|
178
|
+
async function aesKwWrap(alg, key, cek) {
|
|
179
|
+
const cryptoKey = await getAESKWCryptoKey(key, alg, { usage: "wrapKey" });
|
|
180
|
+
checkAESKWKeySize(cryptoKey, alg);
|
|
181
|
+
const cryptoKeyCek = await crypto.subtle.importKey("raw", cek, {
|
|
182
|
+
hash: "SHA-256",
|
|
183
|
+
name: "HMAC"
|
|
184
|
+
}, true, ["sign"]);
|
|
185
|
+
return new Uint8Array(await crypto.subtle.wrapKey("raw", cryptoKeyCek, cryptoKey, "AES-KW"));
|
|
186
|
+
}
|
|
187
|
+
async function aesKwUnwrap(alg, key, encryptedKey) {
|
|
188
|
+
const cryptoKey = await getAESKWCryptoKey(key, alg, { usage: "unwrapKey" });
|
|
189
|
+
checkAESKWKeySize(cryptoKey, alg);
|
|
190
|
+
const cryptoKeyCek = await crypto.subtle.unwrapKey("raw", encryptedKey, cryptoKey, "AES-KW", {
|
|
191
|
+
hash: "SHA-256",
|
|
192
|
+
name: "HMAC"
|
|
193
|
+
}, true, ["sign"]);
|
|
194
|
+
return new Uint8Array(await crypto.subtle.exportKey("raw", cryptoKeyCek));
|
|
195
|
+
}
|
|
196
|
+
async function gcmkwEncrypt(alg, key, plaintext, iv) {
|
|
197
|
+
const wrapped = await encrypt(alg.slice(0, 7), plaintext, key, iv, new Uint8Array(0));
|
|
198
|
+
return {
|
|
199
|
+
encryptedKey: wrapped.ciphertext,
|
|
200
|
+
iv: base64UrlEncode(wrapped.iv),
|
|
201
|
+
tag: base64UrlEncode(wrapped.tag)
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
async function gcmkwDecrypt(alg, key, ciphertext, iv, tag) {
|
|
205
|
+
return decrypt(alg.slice(0, 7), key, ciphertext, iv, tag, new Uint8Array(0));
|
|
206
|
+
}
|
|
207
|
+
async function encrypt(enc, plaintext, cek, iv, aad) {
|
|
208
|
+
if (!isCryptoKey(cek) && !(cek instanceof Uint8Array)) throw new TypeError("Key must be one of type: CryptoKey or Uint8Array");
|
|
209
|
+
if (iv) checkIvLength(enc, iv);
|
|
210
|
+
else iv = generateIV(enc);
|
|
211
|
+
switch (enc) {
|
|
212
|
+
case "A128CBC-HS256":
|
|
213
|
+
case "A192CBC-HS384":
|
|
214
|
+
case "A256CBC-HS512":
|
|
215
|
+
if (cek instanceof Uint8Array) checkCEKLength(cek, Number.parseInt(enc.slice(-3), 10));
|
|
216
|
+
return cbcEncrypt(enc, plaintext, cek, iv, aad);
|
|
217
|
+
case "A128GCM":
|
|
218
|
+
case "A192GCM":
|
|
219
|
+
case "A256GCM":
|
|
220
|
+
if (cek instanceof Uint8Array) checkCEKLength(cek, Number.parseInt(enc.slice(1, 4), 10));
|
|
221
|
+
return gcmEncrypt(enc, plaintext, cek, iv, aad);
|
|
222
|
+
default: throw new Error("Unsupported JWE Content Encryption Algorithm");
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
async function decrypt(enc, cek, ciphertext, iv, tag, aad) {
|
|
226
|
+
if (!isCryptoKey(cek) && !(cek instanceof Uint8Array)) throw new TypeError("Key must be one of type: CryptoKey or Uint8Array");
|
|
227
|
+
if (!iv) throw new Error("JWE Initialization Vector missing");
|
|
228
|
+
if (!tag) throw new Error("JWE Authentication Tag missing");
|
|
229
|
+
checkIvLength(enc, iv);
|
|
230
|
+
switch (enc) {
|
|
231
|
+
case "A128CBC-HS256":
|
|
232
|
+
case "A192CBC-HS384":
|
|
233
|
+
case "A256CBC-HS512":
|
|
234
|
+
if (cek instanceof Uint8Array) checkCEKLength(cek, Number.parseInt(enc.slice(-3), 10));
|
|
235
|
+
return cbcDecrypt(enc, cek, ciphertext, iv, tag, aad);
|
|
236
|
+
case "A128GCM":
|
|
237
|
+
case "A192GCM":
|
|
238
|
+
case "A256GCM":
|
|
239
|
+
if (cek instanceof Uint8Array) checkCEKLength(cek, Number.parseInt(enc.slice(1, 4), 10));
|
|
240
|
+
return gcmDecrypt(enc, cek, ciphertext, iv, tag, aad);
|
|
241
|
+
default: throw new Error("Unsupported JWE Content Encryption Algorithm");
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
function unusable$3(name, prop = "algorithm.name") {
|
|
245
|
+
return /* @__PURE__ */ new TypeError(`CryptoKey does not support this operation, its ${prop} must be ${name}`);
|
|
246
|
+
}
|
|
247
|
+
function isAlgorithm$2(algorithm, name) {
|
|
248
|
+
return algorithm.name === name;
|
|
249
|
+
}
|
|
250
|
+
function checkUsage$3(key, usage) {
|
|
251
|
+
if (usage && !key.usages.includes(usage)) throw new TypeError(`CryptoKey does not support this operation, its usages must include ${usage}.`);
|
|
252
|
+
}
|
|
253
|
+
async function getAESKWCryptoKey(key, alg, options = {}) {
|
|
254
|
+
const { extractable = true, usage } = options;
|
|
255
|
+
if (key instanceof Uint8Array) return crypto.subtle.importKey("raw", key, "AES-KW", extractable, [usage ?? "wrapKey"]);
|
|
256
|
+
checkAESCryptoKey(key, alg, usage);
|
|
257
|
+
return key;
|
|
258
|
+
}
|
|
259
|
+
function checkAESKWKeySize(key, alg) {
|
|
260
|
+
if (key.algorithm.length !== Number.parseInt(alg.slice(1, 4), 10)) throw new TypeError(`Invalid key size for alg: ${alg}`);
|
|
261
|
+
}
|
|
262
|
+
async function importCBCKeys(enc, cek, usage) {
|
|
263
|
+
const keySize = Number.parseInt(enc.slice(1, 4), 10);
|
|
264
|
+
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), {
|
|
265
|
+
hash: `SHA-${keySize << 1}`,
|
|
266
|
+
name: "HMAC"
|
|
267
|
+
}, false, ["sign"])]);
|
|
268
|
+
return {
|
|
269
|
+
encKey,
|
|
270
|
+
macKey,
|
|
271
|
+
keySize
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
function cbcMacData(aad, iv, ciphertext) {
|
|
275
|
+
return concatUint8Arrays(aad, iv, ciphertext, uint64be(aad.length << 3));
|
|
276
|
+
}
|
|
277
|
+
async function cbcEncrypt(enc, plaintext, cek, iv, aad) {
|
|
278
|
+
if (!(cek instanceof Uint8Array)) throw new TypeError("CBC key must be of type Uint8Array");
|
|
279
|
+
const { encKey, macKey, keySize } = await importCBCKeys(enc, cek, "encrypt");
|
|
280
|
+
const ciphertext = new Uint8Array(await crypto.subtle.encrypt({
|
|
281
|
+
iv,
|
|
282
|
+
name: "AES-CBC"
|
|
283
|
+
}, encKey, plaintext));
|
|
284
|
+
const macData = cbcMacData(aad, iv, ciphertext);
|
|
285
|
+
return {
|
|
286
|
+
ciphertext,
|
|
287
|
+
tag: new Uint8Array((await crypto.subtle.sign("HMAC", macKey, macData)).slice(0, keySize >> 3)),
|
|
288
|
+
iv
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
async function gcmEncrypt(enc, plaintext, cek, iv, aad) {
|
|
292
|
+
let encKey;
|
|
293
|
+
if (cek instanceof Uint8Array) encKey = await crypto.subtle.importKey("raw", cek, "AES-GCM", false, ["encrypt"]);
|
|
294
|
+
else {
|
|
295
|
+
checkAESCryptoKey(cek, enc, "encrypt");
|
|
296
|
+
encKey = cek;
|
|
297
|
+
}
|
|
298
|
+
const encrypted = new Uint8Array(await crypto.subtle.encrypt({
|
|
299
|
+
additionalData: aad,
|
|
300
|
+
iv,
|
|
301
|
+
name: "AES-GCM",
|
|
302
|
+
tagLength: 128
|
|
303
|
+
}, encKey, plaintext));
|
|
304
|
+
const tag = encrypted.subarray(-16);
|
|
305
|
+
return {
|
|
306
|
+
ciphertext: encrypted.subarray(0, -16),
|
|
307
|
+
tag,
|
|
308
|
+
iv
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
async function cbcDecrypt(enc, cek, ciphertext, iv, tag, aad) {
|
|
312
|
+
if (!(cek instanceof Uint8Array)) throw new TypeError("CBC key must be of type Uint8Array");
|
|
313
|
+
const { encKey, macKey, keySize } = await importCBCKeys(enc, cek, "decrypt");
|
|
314
|
+
const macData = cbcMacData(aad, iv, ciphertext);
|
|
315
|
+
const expectedTag = new Uint8Array((await crypto.subtle.sign("HMAC", macKey, macData)).slice(0, keySize >> 3));
|
|
316
|
+
let macCheckPassed;
|
|
317
|
+
try {
|
|
318
|
+
macCheckPassed = await timingSafeEqual(tag, expectedTag);
|
|
319
|
+
} catch {}
|
|
320
|
+
if (!macCheckPassed) throw new Error("JWE Decryption Failed");
|
|
321
|
+
let plaintext;
|
|
322
|
+
try {
|
|
323
|
+
plaintext = new Uint8Array(await crypto.subtle.decrypt({
|
|
324
|
+
iv,
|
|
325
|
+
name: "AES-CBC"
|
|
326
|
+
}, encKey, ciphertext));
|
|
327
|
+
} catch {}
|
|
328
|
+
if (!plaintext) throw new Error("JWE Decryption Failed");
|
|
329
|
+
return plaintext;
|
|
330
|
+
}
|
|
331
|
+
async function gcmDecrypt(enc, cek, ciphertext, iv, tag, aad) {
|
|
332
|
+
let encKey;
|
|
333
|
+
if (cek instanceof Uint8Array) encKey = await crypto.subtle.importKey("raw", cek, "AES-GCM", false, ["decrypt"]);
|
|
334
|
+
else {
|
|
335
|
+
checkAESCryptoKey(cek, enc, "decrypt");
|
|
336
|
+
encKey = cek;
|
|
337
|
+
}
|
|
338
|
+
try {
|
|
339
|
+
return new Uint8Array(await crypto.subtle.decrypt({
|
|
340
|
+
additionalData: aad,
|
|
341
|
+
iv,
|
|
342
|
+
name: "AES-GCM",
|
|
343
|
+
tagLength: 128
|
|
344
|
+
}, encKey, concatUint8Arrays(ciphertext, tag)));
|
|
345
|
+
} catch {
|
|
346
|
+
throw new Error("JWE Decryption Failed");
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
let _timingSafeKey;
|
|
350
|
+
function getTimingSafeKey() {
|
|
351
|
+
if (!_timingSafeKey) _timingSafeKey = crypto.subtle.generateKey({
|
|
352
|
+
name: "HMAC",
|
|
353
|
+
hash: "SHA-256"
|
|
354
|
+
}, false, ["sign"]);
|
|
355
|
+
return _timingSafeKey;
|
|
356
|
+
}
|
|
357
|
+
async function timingSafeEqual(a, b) {
|
|
358
|
+
if (!(a instanceof Uint8Array)) throw new TypeError("First argument must be a buffer");
|
|
359
|
+
if (!(b instanceof Uint8Array)) throw new TypeError("Second argument must be a buffer");
|
|
360
|
+
const algorithm = {
|
|
361
|
+
name: "HMAC",
|
|
362
|
+
hash: "SHA-256"
|
|
363
|
+
};
|
|
364
|
+
const key = await getTimingSafeKey();
|
|
365
|
+
const aHmac = new Uint8Array(await crypto.subtle.sign(algorithm, key, a));
|
|
366
|
+
const bHmac = new Uint8Array(await crypto.subtle.sign(algorithm, key, b));
|
|
367
|
+
let out = 0;
|
|
368
|
+
let i = -1;
|
|
369
|
+
while (++i < 32) out |= aHmac[i] ^ bHmac[i];
|
|
370
|
+
return out === 0;
|
|
371
|
+
}
|
|
372
|
+
function writeUInt32BE$1(buf, value, offset) {
|
|
373
|
+
if (value < 0 || value >= 2 ** 32) throw new RangeError(`value must be >= 0 and <= ${2 ** 32 - 1}. Received ${value}`);
|
|
374
|
+
buf.set([
|
|
375
|
+
value >>> 24,
|
|
376
|
+
value >>> 16,
|
|
377
|
+
value >>> 8,
|
|
378
|
+
value & 255
|
|
379
|
+
], offset);
|
|
380
|
+
}
|
|
381
|
+
function uint64be(value) {
|
|
382
|
+
const high = Math.floor(value / 2 ** 32);
|
|
383
|
+
const low = value % 2 ** 32;
|
|
384
|
+
const buf = new Uint8Array(8);
|
|
385
|
+
writeUInt32BE$1(buf, high, 0);
|
|
386
|
+
writeUInt32BE$1(buf, low, 4);
|
|
387
|
+
return buf;
|
|
388
|
+
}
|
|
389
|
+
//#endregion
|
|
390
|
+
//#region src/core/_crypto/_key-codec.ts
|
|
391
|
+
async function jwkTokey(jwk) {
|
|
392
|
+
if (!jwk.alg) throw new TypeError("\"alg\" argument is required when \"jwk.alg\" is not present");
|
|
393
|
+
const { algorithm, keyUsages } = subtleMapping(jwk);
|
|
394
|
+
return crypto.subtle.importKey("jwk", jwk, algorithm, jwk.ext ?? (jwk.d ? false : true), jwk.key_ops ?? keyUsages);
|
|
395
|
+
}
|
|
396
|
+
async function keyToJWK(key) {
|
|
397
|
+
if (key instanceof Uint8Array) return {
|
|
398
|
+
kty: "oct",
|
|
399
|
+
k: base64UrlEncode(key)
|
|
400
|
+
};
|
|
401
|
+
if (!isCryptoKey(key)) throw new TypeError("Key must be one of type: CryptoKey or Uint8Array");
|
|
402
|
+
if (!key.extractable) throw new TypeError("non-extractable CryptoKey cannot be exported as a JWK");
|
|
403
|
+
return await crypto.subtle.exportKey("jwk", key);
|
|
404
|
+
}
|
|
405
|
+
function subtleMapping(jwk) {
|
|
406
|
+
let algorithm;
|
|
407
|
+
let keyUsages;
|
|
408
|
+
switch (jwk.kty) {
|
|
409
|
+
case "RSA":
|
|
410
|
+
switch (jwk.alg) {
|
|
411
|
+
case "PS256":
|
|
412
|
+
case "PS384":
|
|
413
|
+
case "PS512":
|
|
414
|
+
algorithm = {
|
|
415
|
+
name: "RSA-PSS",
|
|
416
|
+
hash: `SHA-${jwk.alg.slice(-3)}`
|
|
417
|
+
};
|
|
418
|
+
keyUsages = jwk.d ? ["sign"] : ["verify"];
|
|
419
|
+
break;
|
|
420
|
+
case "RS256":
|
|
421
|
+
case "RS384":
|
|
422
|
+
case "RS512":
|
|
423
|
+
algorithm = {
|
|
424
|
+
name: "RSASSA-PKCS1-v1_5",
|
|
425
|
+
hash: `SHA-${jwk.alg.slice(-3)}`
|
|
426
|
+
};
|
|
427
|
+
keyUsages = jwk.d ? ["sign"] : ["verify"];
|
|
428
|
+
break;
|
|
429
|
+
case "RSA-OAEP":
|
|
430
|
+
case "RSA-OAEP-256":
|
|
431
|
+
case "RSA-OAEP-384":
|
|
432
|
+
case "RSA-OAEP-512":
|
|
433
|
+
algorithm = {
|
|
434
|
+
name: "RSA-OAEP",
|
|
435
|
+
hash: `SHA-${Number.parseInt(jwk.alg.slice(-3), 10) || 1}`
|
|
436
|
+
};
|
|
437
|
+
keyUsages = jwk.d ? ["decrypt", "unwrapKey"] : ["encrypt", "wrapKey"];
|
|
438
|
+
break;
|
|
439
|
+
default: throw new Error("Invalid or unsupported JWK \"alg\" (Algorithm) Parameter value");
|
|
440
|
+
}
|
|
441
|
+
break;
|
|
442
|
+
case "EC":
|
|
443
|
+
switch (jwk.alg) {
|
|
444
|
+
case "ES256":
|
|
445
|
+
algorithm = {
|
|
446
|
+
name: "ECDSA",
|
|
447
|
+
namedCurve: "P-256"
|
|
448
|
+
};
|
|
449
|
+
keyUsages = jwk.d ? ["sign"] : ["verify"];
|
|
450
|
+
break;
|
|
451
|
+
case "ES384":
|
|
452
|
+
algorithm = {
|
|
453
|
+
name: "ECDSA",
|
|
454
|
+
namedCurve: "P-384"
|
|
455
|
+
};
|
|
456
|
+
keyUsages = jwk.d ? ["sign"] : ["verify"];
|
|
457
|
+
break;
|
|
458
|
+
case "ES512":
|
|
459
|
+
algorithm = {
|
|
460
|
+
name: "ECDSA",
|
|
461
|
+
namedCurve: "P-521"
|
|
462
|
+
};
|
|
463
|
+
keyUsages = jwk.d ? ["sign"] : ["verify"];
|
|
464
|
+
break;
|
|
465
|
+
case "ECDH-ES":
|
|
466
|
+
case "ECDH-ES+A128KW":
|
|
467
|
+
case "ECDH-ES+A192KW":
|
|
468
|
+
case "ECDH-ES+A256KW":
|
|
469
|
+
algorithm = {
|
|
470
|
+
name: "ECDH",
|
|
471
|
+
namedCurve: jwk.crv
|
|
472
|
+
};
|
|
473
|
+
keyUsages = jwk.d ? ["deriveBits"] : [];
|
|
474
|
+
break;
|
|
475
|
+
default: throw new Error("Invalid or unsupported JWK \"alg\" (Algorithm) Parameter value");
|
|
476
|
+
}
|
|
477
|
+
break;
|
|
478
|
+
case "OKP":
|
|
479
|
+
switch (jwk.alg) {
|
|
480
|
+
case "Ed25519":
|
|
481
|
+
case "EdDSA":
|
|
482
|
+
algorithm = { name: "Ed25519" };
|
|
483
|
+
keyUsages = jwk.d ? ["sign"] : ["verify"];
|
|
484
|
+
break;
|
|
485
|
+
case "ECDH-ES":
|
|
486
|
+
case "ECDH-ES+A128KW":
|
|
487
|
+
case "ECDH-ES+A192KW":
|
|
488
|
+
case "ECDH-ES+A256KW":
|
|
489
|
+
algorithm = { name: jwk.crv };
|
|
490
|
+
keyUsages = jwk.d ? ["deriveBits"] : [];
|
|
491
|
+
break;
|
|
492
|
+
default: throw new Error("Invalid or unsupported JWK \"alg\" (Algorithm) Parameter value");
|
|
493
|
+
}
|
|
494
|
+
break;
|
|
495
|
+
default: throw new Error("Invalid or unsupported JWK \"kty\" (Key Type) Parameter value");
|
|
496
|
+
}
|
|
497
|
+
return {
|
|
498
|
+
algorithm,
|
|
499
|
+
keyUsages
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
//#endregion
|
|
503
|
+
//#region src/core/_crypto/_rsa.ts
|
|
504
|
+
async function encryptRSAES(alg, key, cek) {
|
|
505
|
+
checkRSACryptoKey(key, alg, "encrypt");
|
|
506
|
+
checkRSAModulusLength(alg, key);
|
|
507
|
+
return new Uint8Array(await crypto.subtle.encrypt("RSA-OAEP", key, cek));
|
|
508
|
+
}
|
|
509
|
+
async function decryptRSAES(alg, key, encryptedKey) {
|
|
510
|
+
checkRSACryptoKey(key, alg, "decrypt");
|
|
511
|
+
checkRSAModulusLength(alg, key);
|
|
512
|
+
return new Uint8Array(await crypto.subtle.decrypt("RSA-OAEP", key, encryptedKey));
|
|
513
|
+
}
|
|
514
|
+
function checkRSACryptoKey(key, alg, usage) {
|
|
515
|
+
if (!isAlgorithm$1(key.algorithm, "RSA-OAEP")) throw unusable$2("RSA-OAEP");
|
|
516
|
+
const expected = Number.parseInt(alg.slice(9), 10) || 1;
|
|
517
|
+
if (getHashLength(key.algorithm.hash) !== expected) throw unusable$2(`SHA-${expected}`, "algorithm.hash");
|
|
518
|
+
checkUsage$2(key, usage);
|
|
519
|
+
}
|
|
520
|
+
function checkRSAModulusLength(alg, key) {
|
|
521
|
+
if (alg.startsWith("RS") || alg.startsWith("PS") || alg.startsWith("RSA")) {
|
|
522
|
+
const { modulusLength } = key.algorithm;
|
|
523
|
+
if (typeof modulusLength !== "number" || modulusLength < 2048) throw new TypeError(`${alg} requires key modulusLength to be 2048 bits or larger`);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
function unusable$2(name, prop = "algorithm.name") {
|
|
527
|
+
return /* @__PURE__ */ new TypeError(`CryptoKey does not support this operation, its ${prop} must be ${name}`);
|
|
528
|
+
}
|
|
529
|
+
function isAlgorithm$1(algorithm, name) {
|
|
530
|
+
return algorithm.name === name;
|
|
531
|
+
}
|
|
532
|
+
function getHashLength(hash) {
|
|
533
|
+
return Number.parseInt(hash.name.slice(4), 10);
|
|
534
|
+
}
|
|
535
|
+
function checkUsage$2(key, usage) {
|
|
536
|
+
if (usage && !key.usages.includes(usage)) throw new TypeError(`CryptoKey does not support this operation, its usages must include ${usage}.`);
|
|
537
|
+
}
|
|
538
|
+
//#endregion
|
|
539
|
+
//#region src/core/_crypto/_ecdh.ts
|
|
540
|
+
async function deriveECDHESKey(publicKey, privateKey, algorithm, keyLength, apu = new Uint8Array(0), apv = new Uint8Array(0)) {
|
|
541
|
+
checkECDHCryptoKey(publicKey, "ECDH");
|
|
542
|
+
checkECDHCryptoKey(privateKey, "ECDH", "deriveBits");
|
|
543
|
+
const value = concatUint8Arrays(lengthAndInput(textEncoder.encode(algorithm)), lengthAndInput(apu), lengthAndInput(apv), uint32be(keyLength));
|
|
544
|
+
const length = publicKey.algorithm.name === "X25519" ? 256 : Math.ceil(Number.parseInt(publicKey.algorithm.namedCurve.slice(-3), 10) / 8) << 3;
|
|
545
|
+
return concatKdf(new Uint8Array(await crypto.subtle.deriveBits({
|
|
546
|
+
name: publicKey.algorithm.name,
|
|
547
|
+
public: publicKey
|
|
548
|
+
}, privateKey, length)), keyLength, value);
|
|
549
|
+
}
|
|
550
|
+
function isECDHKeyAllowed(key) {
|
|
551
|
+
switch (key.algorithm.namedCurve) {
|
|
552
|
+
case "P-256":
|
|
553
|
+
case "P-384":
|
|
554
|
+
case "P-521": return true;
|
|
555
|
+
default: return key.algorithm.name === "X25519";
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
function checkECDHCryptoKey(key, alg, usage) {
|
|
559
|
+
switch (key.algorithm.name) {
|
|
560
|
+
case "ECDH":
|
|
561
|
+
case "X25519": break;
|
|
562
|
+
default: throw unusable$1("ECDH or X25519");
|
|
563
|
+
}
|
|
564
|
+
checkUsage$1(key, usage);
|
|
565
|
+
}
|
|
566
|
+
function unusable$1(name, prop = "algorithm.name") {
|
|
567
|
+
return /* @__PURE__ */ new TypeError(`CryptoKey does not support this operation, its ${prop} must be ${name}`);
|
|
568
|
+
}
|
|
569
|
+
function checkUsage$1(key, usage) {
|
|
570
|
+
if (usage && !key.usages.includes(usage)) throw new TypeError(`CryptoKey does not support this operation, its usages must include ${usage}.`);
|
|
571
|
+
}
|
|
572
|
+
async function concatKdf(secret, bits, value) {
|
|
573
|
+
const iterations = Math.ceil((bits >> 3) / 32);
|
|
574
|
+
const res = new Uint8Array(iterations * 32);
|
|
575
|
+
for (let iter = 0; iter < iterations; iter++) {
|
|
576
|
+
const buf = new Uint8Array(4 + secret.length + value.length);
|
|
577
|
+
buf.set(uint32be(iter + 1));
|
|
578
|
+
buf.set(secret, 4);
|
|
579
|
+
buf.set(value, 4 + secret.length);
|
|
580
|
+
res.set(await digest("sha256", buf), iter * 32);
|
|
581
|
+
}
|
|
582
|
+
return res.slice(0, bits >> 3);
|
|
583
|
+
}
|
|
584
|
+
function lengthAndInput(input) {
|
|
585
|
+
return concatUint8Arrays(uint32be(input.length), input);
|
|
586
|
+
}
|
|
587
|
+
async function digest(algorithm, data) {
|
|
588
|
+
return new Uint8Array(await crypto.subtle.digest(`SHA-${algorithm.slice(-3)}`, data));
|
|
589
|
+
}
|
|
590
|
+
function writeUInt32BE(buf, value, offset) {
|
|
591
|
+
if (value < 0 || value >= 2 ** 32) throw new RangeError(`value must be >= 0 and <= ${2 ** 32 - 1}. Received ${value}`);
|
|
592
|
+
buf.set([
|
|
593
|
+
value >>> 24,
|
|
594
|
+
value >>> 16,
|
|
595
|
+
value >>> 8,
|
|
596
|
+
value & 255
|
|
597
|
+
], offset);
|
|
598
|
+
}
|
|
599
|
+
function uint32be(value) {
|
|
600
|
+
const buf = new Uint8Array(4);
|
|
601
|
+
writeUInt32BE(buf, value, 0);
|
|
602
|
+
return buf;
|
|
603
|
+
}
|
|
604
|
+
//#endregion
|
|
605
|
+
//#region src/core/_crypto/_pbes2.ts
|
|
606
|
+
async function pbes2Wrap(alg, key, cek, p2c, p2s) {
|
|
607
|
+
const derived = await deriveKey(p2s, alg, p2c, key);
|
|
608
|
+
return {
|
|
609
|
+
encryptedKey: await aesKwWrap(alg.slice(-6), derived, cek),
|
|
610
|
+
p2c,
|
|
611
|
+
p2s: base64UrlEncode(p2s)
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
async function pbes2Unwrap(alg, key, encryptedKey, p2c, p2s) {
|
|
615
|
+
const derived = await deriveKey(p2s, alg, p2c, key);
|
|
616
|
+
return aesKwUnwrap(alg.slice(-6), derived, encryptedKey);
|
|
617
|
+
}
|
|
618
|
+
async function deriveKey(p2s, alg, p2c, key) {
|
|
619
|
+
if (!(p2s instanceof Uint8Array) || p2s.length < 8) throw new Error("PBES2 Salt Input must be 8 or more octets");
|
|
620
|
+
if (p2c === void 0 || p2c < 1) throw new Error("PBES2 Iteration Count Parameter must be a positive integer");
|
|
621
|
+
const salt = concatUint8Arrays(textEncoder.encode(alg), new Uint8Array([0]), p2s);
|
|
622
|
+
const keylen = Number.parseInt(alg.slice(13, 16), 10);
|
|
623
|
+
const subtleAlg = {
|
|
624
|
+
hash: `SHA-${alg.slice(8, 11)}`,
|
|
625
|
+
iterations: p2c,
|
|
626
|
+
name: "PBKDF2",
|
|
627
|
+
salt
|
|
628
|
+
};
|
|
629
|
+
const cryptoKey = await getPBKDF2CryptoKey(key, alg);
|
|
630
|
+
return new Uint8Array(await crypto.subtle.deriveBits(subtleAlg, cryptoKey, keylen));
|
|
631
|
+
}
|
|
632
|
+
function checkPBES2CryptoKey(key, alg, usage) {
|
|
633
|
+
if (!isAlgorithm(key.algorithm, "PBKDF2")) throw unusable("PBKDF2");
|
|
634
|
+
checkUsage(key, usage);
|
|
635
|
+
}
|
|
636
|
+
function unusable(name, prop = "algorithm.name") {
|
|
637
|
+
return /* @__PURE__ */ new TypeError(`CryptoKey does not support this operation, its ${prop} must be ${name}`);
|
|
638
|
+
}
|
|
639
|
+
function isAlgorithm(algorithm, name) {
|
|
640
|
+
return algorithm.name === name;
|
|
641
|
+
}
|
|
642
|
+
function checkUsage(key, usage) {
|
|
643
|
+
if (usage && !key.usages.includes(usage)) throw new TypeError(`CryptoKey does not support this operation, its usages must include ${usage}.`);
|
|
644
|
+
}
|
|
645
|
+
async function getPBKDF2CryptoKey(key, alg, options = {}) {
|
|
646
|
+
const { extractable = false, usage = "deriveBits" } = options;
|
|
647
|
+
if (key instanceof Uint8Array) return crypto.subtle.importKey("raw", key, "PBKDF2", extractable, [usage]);
|
|
648
|
+
checkPBES2CryptoKey(key, alg, usage);
|
|
649
|
+
return key;
|
|
650
|
+
}
|
|
651
|
+
//#endregion
|
|
652
|
+
//#region src/core/_crypto/_key-encryption.ts
|
|
653
|
+
const PUBLIC_EPK_FIELDS = new Set([
|
|
654
|
+
"kty",
|
|
655
|
+
"crv",
|
|
656
|
+
"x",
|
|
657
|
+
"y",
|
|
658
|
+
"kid",
|
|
659
|
+
"use",
|
|
660
|
+
"key_ops",
|
|
661
|
+
"alg",
|
|
662
|
+
"x5c",
|
|
663
|
+
"x5t",
|
|
664
|
+
"x5t#S256",
|
|
665
|
+
"x5u"
|
|
666
|
+
]);
|
|
667
|
+
async function encryptKey(alg, enc, key, providedCek, providedParameters = {}) {
|
|
668
|
+
let encryptedKey;
|
|
669
|
+
let parameters = {};
|
|
670
|
+
let cek;
|
|
671
|
+
switch (alg) {
|
|
672
|
+
case "dir":
|
|
673
|
+
cek = key;
|
|
674
|
+
break;
|
|
675
|
+
case "ECDH-ES":
|
|
676
|
+
case "ECDH-ES+A128KW":
|
|
677
|
+
case "ECDH-ES+A192KW":
|
|
678
|
+
case "ECDH-ES+A256KW": {
|
|
679
|
+
assertCryptoKey(key);
|
|
680
|
+
if (!isECDHKeyAllowed(key)) throw new Error("ECDH with the provided key is not allowed or not supported by your javascript runtime");
|
|
681
|
+
const { apu, apv, epk: providedEpk, epkPrivateKey } = providedParameters;
|
|
682
|
+
if (providedEpk && !epkPrivateKey) throw new TypeError("ECDH-ES custom ephemeral key material must include the matching private key.");
|
|
683
|
+
let ephemeralPrivateKey;
|
|
684
|
+
let epkHeader;
|
|
685
|
+
if (epkPrivateKey) {
|
|
686
|
+
ephemeralPrivateKey = await ensurePrivateKey(alg, epkPrivateKey);
|
|
687
|
+
epkHeader = await exportPublicJwkFrom(alg, providedEpk ?? epkPrivateKey);
|
|
688
|
+
} else {
|
|
689
|
+
if (providedEpk) throw new TypeError("ECDH-ES custom ephemeral public key requires a matching private key.");
|
|
690
|
+
const generated = await crypto.subtle.generateKey(key.algorithm, true, ["deriveBits"]);
|
|
691
|
+
ephemeralPrivateKey = generated.privateKey;
|
|
692
|
+
epkHeader = await exportPublicJwkFrom(alg, generated.publicKey);
|
|
693
|
+
}
|
|
694
|
+
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);
|
|
695
|
+
parameters = { epk: epkHeader };
|
|
696
|
+
if (apu) parameters.apu = base64UrlEncode(apu);
|
|
697
|
+
if (apv) parameters.apv = base64UrlEncode(apv);
|
|
698
|
+
if (alg === "ECDH-ES") {
|
|
699
|
+
cek = sharedSecret;
|
|
700
|
+
break;
|
|
701
|
+
}
|
|
702
|
+
cek = providedCek || generateCEK(enc);
|
|
703
|
+
encryptedKey = await aesKwWrap(alg.slice(-6), sharedSecret, cek);
|
|
704
|
+
break;
|
|
705
|
+
}
|
|
706
|
+
case "RSA-OAEP":
|
|
707
|
+
case "RSA-OAEP-256":
|
|
708
|
+
case "RSA-OAEP-384":
|
|
709
|
+
case "RSA-OAEP-512":
|
|
710
|
+
cek = providedCek || generateCEK(enc);
|
|
711
|
+
assertCryptoKey(key);
|
|
712
|
+
encryptedKey = await encryptRSAES(alg, key, cek);
|
|
713
|
+
break;
|
|
714
|
+
case "PBES2-HS256+A128KW":
|
|
715
|
+
case "PBES2-HS384+A192KW":
|
|
716
|
+
case "PBES2-HS512+A256KW": {
|
|
717
|
+
cek = providedCek || generateCEK(enc);
|
|
718
|
+
const { p2c, p2s } = providedParameters;
|
|
719
|
+
({encryptedKey, ...parameters} = await pbes2Wrap(alg, key, cek, p2c, p2s));
|
|
720
|
+
break;
|
|
721
|
+
}
|
|
722
|
+
case "A128KW":
|
|
723
|
+
case "A192KW":
|
|
724
|
+
case "A256KW":
|
|
725
|
+
cek = providedCek || generateCEK(enc);
|
|
726
|
+
encryptedKey = await aesKwWrap(alg, key, cek);
|
|
727
|
+
break;
|
|
728
|
+
case "A128GCMKW":
|
|
729
|
+
case "A192GCMKW":
|
|
730
|
+
case "A256GCMKW": {
|
|
731
|
+
cek = providedCek || generateCEK(enc);
|
|
732
|
+
const { iv } = providedParameters;
|
|
733
|
+
({encryptedKey, ...parameters} = await gcmkwEncrypt(alg, key, cek, iv));
|
|
734
|
+
break;
|
|
735
|
+
}
|
|
736
|
+
default: throw new Error("Invalid or unsupported \"alg\" (JWE Algorithm) header value");
|
|
737
|
+
}
|
|
738
|
+
return {
|
|
739
|
+
cek,
|
|
740
|
+
encryptedKey,
|
|
741
|
+
parameters
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
async function normalizeKey(key, alg) {
|
|
745
|
+
if (key instanceof Uint8Array) return key;
|
|
746
|
+
if (isCryptoKey(key)) return key;
|
|
747
|
+
if (isJWK(key)) {
|
|
748
|
+
if ("k" in key && key.k) return base64UrlDecode(key.k, false);
|
|
749
|
+
return jwkTokey(key.alg ? key : {
|
|
750
|
+
...key,
|
|
751
|
+
alg
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
throw new Error("unreachable");
|
|
755
|
+
}
|
|
756
|
+
function toPublicEpkJwk(jwk) {
|
|
757
|
+
const filtered = {};
|
|
758
|
+
for (const [key, value] of Object.entries(jwk)) if (value !== void 0 && PUBLIC_EPK_FIELDS.has(key)) filtered[key] = value;
|
|
759
|
+
const { kty, crv, x, y } = filtered;
|
|
760
|
+
if (typeof kty !== "string") throw new TypeError("ECDH-ES ephemeral key JWK must include the \"kty\" parameter.");
|
|
761
|
+
if (typeof crv !== "string") throw new TypeError("ECDH-ES ephemeral key JWK must include the \"crv\" parameter.");
|
|
762
|
+
if (typeof x !== "string") throw new TypeError("ECDH-ES ephemeral key JWK must include the \"x\" coordinate.");
|
|
763
|
+
if (kty === "EC" && typeof y !== "string") throw new TypeError("ECDH-ES EC ephemeral key JWK must include both \"x\" and \"y\" coordinates.");
|
|
764
|
+
const publicJwk = {
|
|
765
|
+
...filtered,
|
|
766
|
+
kty,
|
|
767
|
+
crv,
|
|
768
|
+
x
|
|
769
|
+
};
|
|
770
|
+
if (typeof y === "string") publicJwk.y = y;
|
|
771
|
+
else delete publicJwk.y;
|
|
772
|
+
return sanitizeObject(publicJwk);
|
|
773
|
+
}
|
|
774
|
+
async function ensurePrivateKey(alg, value) {
|
|
775
|
+
const normalized = await normalizeKey(value, alg);
|
|
776
|
+
if (!(normalized instanceof CryptoKey)) throw new TypeError("ECDH-ES ephemeral private key must be a CryptoKey or convertible JWK.");
|
|
777
|
+
if (normalized.type !== "private") throw new TypeError("ECDH-ES ephemeral private key must have type \"private\".");
|
|
778
|
+
return normalized;
|
|
779
|
+
}
|
|
780
|
+
async function exportPublicJwkFrom(alg, value) {
|
|
781
|
+
if (isCryptoKey(value)) try {
|
|
782
|
+
return toPublicEpkJwk(await keyToJWK(value));
|
|
783
|
+
} catch (error_) {
|
|
784
|
+
throw new TypeError("ECDH-ES ephemeral CryptoKey must be extractable to export as JWK.", { cause: error_ instanceof Error ? error_ : void 0 });
|
|
785
|
+
}
|
|
786
|
+
if (isJWK(value) && isAsymmetricJWK(value) && value.kty === "EC") return toPublicEpkJwk(value);
|
|
787
|
+
const normalized = await normalizeKey(value, alg);
|
|
788
|
+
if (!(normalized instanceof CryptoKey)) throw new TypeError("ECDH-ES ephemeral public key must be a CryptoKey or convertible JWK.");
|
|
789
|
+
return exportPublicJwkFrom(alg, normalized);
|
|
790
|
+
}
|
|
791
|
+
//#endregion
|
|
792
|
+
//#region src/core/_crypto/_pem.ts
|
|
793
|
+
const toSPKI = (key) => {
|
|
794
|
+
return genericExport("public", "spki", key);
|
|
795
|
+
};
|
|
796
|
+
const toPKCS8 = (key) => {
|
|
797
|
+
return genericExport("private", "pkcs8", key);
|
|
798
|
+
};
|
|
799
|
+
const fromPKCS8 = (pem, alg, options) => {
|
|
800
|
+
return genericImport(/(?:-----(?:BEGIN|END) PRIVATE KEY-----|\s)/g, "pkcs8", pem, alg, options);
|
|
801
|
+
};
|
|
802
|
+
const fromSPKI = (pem, alg, options) => {
|
|
803
|
+
return genericImport(/(?:-----(?:BEGIN|END) PUBLIC KEY-----|\s)/g, "spki", pem, alg, options);
|
|
804
|
+
};
|
|
805
|
+
const fromX509 = (pem, alg, options) => {
|
|
806
|
+
let spki;
|
|
807
|
+
try {
|
|
808
|
+
spki = getSPKI(pem);
|
|
809
|
+
} catch (error_) {
|
|
810
|
+
throw new TypeError("Failed to parse the X.509 certificate", { cause: error_ });
|
|
811
|
+
}
|
|
812
|
+
return fromSPKI(spki, alg, options);
|
|
813
|
+
};
|
|
814
|
+
const formatPEM = (b64, descriptor) => {
|
|
815
|
+
return `-----BEGIN ${descriptor}-----\n${(b64.match(/.{1,64}/g) || []).join("\n")}\n-----END ${descriptor}-----`;
|
|
816
|
+
};
|
|
817
|
+
const genericExport = async (keyType, keyFormat, key) => {
|
|
818
|
+
if (!isCryptoKey(key)) throw new TypeError(`Key must be ${key} of type CryptoKey`);
|
|
819
|
+
if (!key.extractable) throw new TypeError("CryptoKey is not extractable");
|
|
820
|
+
if (key.type !== keyType) throw new TypeError(`key is not a ${keyType} key`);
|
|
821
|
+
return formatPEM(base64Encode(new Uint8Array(await crypto.subtle.exportKey(keyFormat, key))), `${keyType.toUpperCase()} KEY`);
|
|
822
|
+
};
|
|
823
|
+
const genericImport = async (replace, keyFormat, pem, alg, options) => {
|
|
824
|
+
let algorithm;
|
|
825
|
+
let keyUsages;
|
|
826
|
+
const keyData = base64Decode(pem.replace(replace, ""), false);
|
|
827
|
+
const isPublic = keyFormat === "spki";
|
|
828
|
+
switch (alg) {
|
|
829
|
+
case "PS256":
|
|
830
|
+
case "PS384":
|
|
831
|
+
case "PS512":
|
|
832
|
+
algorithm = {
|
|
833
|
+
name: "RSA-PSS",
|
|
834
|
+
hash: `SHA-${alg.slice(-3)}`
|
|
835
|
+
};
|
|
836
|
+
keyUsages = isPublic ? ["verify"] : ["sign"];
|
|
837
|
+
break;
|
|
838
|
+
case "RS256":
|
|
839
|
+
case "RS384":
|
|
840
|
+
case "RS512":
|
|
841
|
+
algorithm = {
|
|
842
|
+
name: "RSASSA-PKCS1-v1_5",
|
|
843
|
+
hash: `SHA-${alg.slice(-3)}`
|
|
844
|
+
};
|
|
845
|
+
keyUsages = isPublic ? ["verify"] : ["sign"];
|
|
846
|
+
break;
|
|
847
|
+
case "RSA-OAEP":
|
|
848
|
+
case "RSA-OAEP-256":
|
|
849
|
+
case "RSA-OAEP-384":
|
|
850
|
+
case "RSA-OAEP-512":
|
|
851
|
+
algorithm = {
|
|
852
|
+
name: "RSA-OAEP",
|
|
853
|
+
hash: `SHA-${Number.parseInt(alg.slice(-3), 10) || 1}`
|
|
854
|
+
};
|
|
855
|
+
keyUsages = isPublic ? ["encrypt", "wrapKey"] : ["decrypt", "unwrapKey"];
|
|
856
|
+
break;
|
|
857
|
+
case "ES256":
|
|
858
|
+
algorithm = {
|
|
859
|
+
name: "ECDSA",
|
|
860
|
+
namedCurve: "P-256"
|
|
861
|
+
};
|
|
862
|
+
keyUsages = isPublic ? ["verify"] : ["sign"];
|
|
863
|
+
break;
|
|
864
|
+
case "ES384":
|
|
865
|
+
algorithm = {
|
|
866
|
+
name: "ECDSA",
|
|
867
|
+
namedCurve: "P-384"
|
|
868
|
+
};
|
|
869
|
+
keyUsages = isPublic ? ["verify"] : ["sign"];
|
|
870
|
+
break;
|
|
871
|
+
case "ES512":
|
|
872
|
+
algorithm = {
|
|
873
|
+
name: "ECDSA",
|
|
874
|
+
namedCurve: "P-521"
|
|
875
|
+
};
|
|
876
|
+
keyUsages = isPublic ? ["verify"] : ["sign"];
|
|
877
|
+
break;
|
|
878
|
+
case "ECDH-ES":
|
|
879
|
+
case "ECDH-ES+A128KW":
|
|
880
|
+
case "ECDH-ES+A192KW":
|
|
881
|
+
case "ECDH-ES+A256KW": {
|
|
882
|
+
const namedCurve = getNamedCurve(keyData);
|
|
883
|
+
algorithm = namedCurve?.startsWith("P-") ? {
|
|
884
|
+
name: "ECDH",
|
|
885
|
+
namedCurve
|
|
886
|
+
} : { name: "X25519" };
|
|
887
|
+
keyUsages = isPublic ? [] : ["deriveBits"];
|
|
888
|
+
break;
|
|
889
|
+
}
|
|
890
|
+
case "Ed25519":
|
|
891
|
+
case "EdDSA":
|
|
892
|
+
algorithm = { name: "Ed25519" };
|
|
893
|
+
keyUsages = isPublic ? ["verify"] : ["sign"];
|
|
894
|
+
break;
|
|
895
|
+
default: throw new Error("Invalid or unsupported \"alg\" (Algorithm) value");
|
|
896
|
+
}
|
|
897
|
+
return crypto.subtle.importKey(keyFormat, keyData, algorithm, options?.extractable ?? (isPublic ? true : false), keyUsages);
|
|
898
|
+
};
|
|
899
|
+
const findOid = (keyData, oid, from = 0) => {
|
|
900
|
+
if (from === 0) {
|
|
901
|
+
oid.unshift(oid.length);
|
|
902
|
+
oid.unshift(6);
|
|
903
|
+
}
|
|
904
|
+
const i = keyData.indexOf(oid[0], from);
|
|
905
|
+
if (i === -1) return false;
|
|
906
|
+
const sub = keyData.subarray(i, i + oid.length);
|
|
907
|
+
if (sub.length !== oid.length) return false;
|
|
908
|
+
return sub.every((value, index) => value === oid[index]) || findOid(keyData, oid, i + 1);
|
|
909
|
+
};
|
|
910
|
+
const getNamedCurve = (keyData) => {
|
|
911
|
+
switch (true) {
|
|
912
|
+
case findOid(keyData, [
|
|
913
|
+
42,
|
|
914
|
+
134,
|
|
915
|
+
72,
|
|
916
|
+
206,
|
|
917
|
+
61,
|
|
918
|
+
3,
|
|
919
|
+
1,
|
|
920
|
+
7
|
|
921
|
+
]): return "P-256";
|
|
922
|
+
case findOid(keyData, [
|
|
923
|
+
43,
|
|
924
|
+
129,
|
|
925
|
+
4,
|
|
926
|
+
0,
|
|
927
|
+
34
|
|
928
|
+
]): return "P-384";
|
|
929
|
+
case findOid(keyData, [
|
|
930
|
+
43,
|
|
931
|
+
129,
|
|
932
|
+
4,
|
|
933
|
+
0,
|
|
934
|
+
35
|
|
935
|
+
]): return "P-521";
|
|
936
|
+
default: return;
|
|
937
|
+
}
|
|
938
|
+
};
|
|
939
|
+
const getElement = (seq) => {
|
|
940
|
+
const result = [];
|
|
941
|
+
let next = 0;
|
|
942
|
+
while (next < seq.length) {
|
|
943
|
+
const nextPart = parseElement(seq.subarray(next));
|
|
944
|
+
result.push(nextPart);
|
|
945
|
+
next += nextPart.byteLength;
|
|
946
|
+
}
|
|
947
|
+
return result;
|
|
948
|
+
};
|
|
949
|
+
const parseElement = (bytes) => {
|
|
950
|
+
let position = 0;
|
|
951
|
+
let tag = bytes[0] & 31;
|
|
952
|
+
position++;
|
|
953
|
+
if (tag === 31) {
|
|
954
|
+
tag = 0;
|
|
955
|
+
while (bytes[position] >= 128) {
|
|
956
|
+
tag = tag * 128 + bytes[position] - 128;
|
|
957
|
+
position++;
|
|
958
|
+
}
|
|
959
|
+
tag = tag * 128 + bytes[position] - 128;
|
|
960
|
+
position++;
|
|
961
|
+
}
|
|
962
|
+
let length = 0;
|
|
963
|
+
if (bytes[position] < 128) {
|
|
964
|
+
length = bytes[position];
|
|
965
|
+
position++;
|
|
966
|
+
} else if (length === 128) {
|
|
967
|
+
length = 0;
|
|
968
|
+
while (bytes[position + length] !== 0 || bytes[position + length + 1] !== 0) {
|
|
969
|
+
if (length > bytes.byteLength) throw new TypeError("invalid indefinite form length");
|
|
970
|
+
length++;
|
|
971
|
+
}
|
|
972
|
+
const byteLength = position + length + 2;
|
|
973
|
+
return {
|
|
974
|
+
byteLength,
|
|
975
|
+
contents: bytes.subarray(position, position + length),
|
|
976
|
+
raw: bytes.subarray(0, byteLength)
|
|
977
|
+
};
|
|
978
|
+
} else {
|
|
979
|
+
const numberOfDigits = bytes[position] & 127;
|
|
980
|
+
position++;
|
|
981
|
+
length = 0;
|
|
982
|
+
for (let i = 0; i < numberOfDigits; i++) {
|
|
983
|
+
length = length * 256 + bytes[position];
|
|
984
|
+
position++;
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
const byteLength = position + length;
|
|
988
|
+
return {
|
|
989
|
+
byteLength,
|
|
990
|
+
contents: bytes.subarray(position, byteLength),
|
|
991
|
+
raw: bytes.subarray(0, byteLength)
|
|
992
|
+
};
|
|
993
|
+
};
|
|
994
|
+
const spkiFromX509 = (buf) => {
|
|
995
|
+
const tbsCertificate = getElement(getElement(parseElement(buf).contents)[0].contents);
|
|
996
|
+
return base64Encode(tbsCertificate[tbsCertificate[0].raw[0] === 160 ? 6 : 5].raw);
|
|
997
|
+
};
|
|
998
|
+
let createPublicKey;
|
|
999
|
+
const getSPKI = (x509) => {
|
|
1000
|
+
try {
|
|
1001
|
+
createPublicKey ??= globalThis.process?.getBuiltinModule?.("node:crypto")?.createPublicKey;
|
|
1002
|
+
} catch {
|
|
1003
|
+
createPublicKey = 0;
|
|
1004
|
+
}
|
|
1005
|
+
if (createPublicKey) try {
|
|
1006
|
+
return new createPublicKey(x509).export({
|
|
1007
|
+
format: "pem",
|
|
1008
|
+
type: "spki"
|
|
1009
|
+
});
|
|
1010
|
+
} catch {}
|
|
1011
|
+
return formatPEM(spkiFromX509(base64Decode(x509.replace(/(?:-----(?:BEGIN|END) CERTIFICATE-----|\s)/g, ""), false)), "PUBLIC KEY");
|
|
1012
|
+
};
|
|
1013
|
+
//#endregion
|
|
1014
|
+
export { gcmkwEncrypt as C, verify as D, sign as E, gcmkwDecrypt as S, generateIV as T, aesKwUnwrap as _, toSPKI as a, decrypt as b, deriveKey as c, deriveECDHESKey as d, isECDHKeyAllowed as f, keyToJWK as g, jwkTokey as h, toPKCS8 as i, pbes2Unwrap as l, encryptRSAES as m, fromSPKI as n, encryptKey as o, decryptRSAES as p, fromX509 as r, normalizeKey as s, fromPKCS8 as t, pbes2Wrap as u, aesKwWrap as v, generateCEK as w, encrypt as x, bitLengthCEK as y };
|