unjwt 0.5.40 → 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 +147 -6
- package/dist/_chunks/_crypto.mjs +1014 -0
- package/dist/_chunks/error.d.mts +67 -0
- package/dist/_chunks/index.d.mts +149 -45
- package/dist/_chunks/jwk.d.mts +154 -57
- package/dist/_chunks/jws.mjs +729 -683
- package/dist/_chunks/utils.mjs +74 -70
- package/dist/{_chunks/jws.d.mts → adapters/h3v1/index.d.mts} +98 -58
- package/dist/{_chunks/jws2.mjs → adapters/h3v1/index.mjs} +198 -85
- package/dist/adapters/h3v2/index.d.mts +213 -0
- package/dist/adapters/{h3v2.mjs → h3v2/index.mjs} +195 -89
- 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 +49 -40
- package/dist/_chunks/chunk.mjs +0 -13
- package/dist/_chunks/jwe.d.mts +0 -43
- package/dist/_chunks/jwk.mjs +0 -1592
- 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 -183
- package/dist/jwe.d.mts +0 -3
- package/dist/jwe.mjs +0 -2
- package/dist/jwk.d.mts +0 -3
- package/dist/jwk.mjs +0 -2
- package/dist/jws.d.mts +0 -3
- package/dist/jws.mjs +0 -2
- package/dist/utils.d.mts +0 -3
- package/dist/utils.mjs +0 -2
|
@@ -0,0 +1,686 @@
|
|
|
1
|
+
import { M as JWTError, N as isJWTError, a as base64UrlEncode, c as isCryptoKey, d as isJWKSet, g as randomBytes, i as base64UrlDecode, l as isCryptoKeyPair, u as isJWK, v as textEncoder, y as sanitizeObject } from "../_chunks/utils.mjs";
|
|
2
|
+
import { C as gcmkwEncrypt, S as gcmkwDecrypt, _ as aesKwUnwrap, a as toSPKI, c as deriveKey, d as deriveECDHESKey, f as isECDHKeyAllowed, g as keyToJWK, h as jwkTokey, i as toPKCS8, l as pbes2Unwrap, m as encryptRSAES, n as fromSPKI, p as decryptRSAES, r as fromX509, s as normalizeKey, t as fromPKCS8, u as pbes2Wrap, v as aesKwWrap, y as bitLengthCEK } from "../_chunks/_crypto.mjs";
|
|
3
|
+
//#region src/core/jwk.ts
|
|
4
|
+
async function generateKey(alg, options = {}) {
|
|
5
|
+
const exportToJWK = options.toJWK !== void 0 && options.toJWK !== false;
|
|
6
|
+
const defaultExtractable = options.extractable !== false;
|
|
7
|
+
if (alg === "A128CBC-HS256" || alg === "A192CBC-HS384" || alg === "A256CBC-HS512") {
|
|
8
|
+
const keyBytes = randomBytes(bitLengthCEK(alg) >> 3);
|
|
9
|
+
if (exportToJWK) return keyToJWK(keyBytes);
|
|
10
|
+
return keyBytes;
|
|
11
|
+
}
|
|
12
|
+
const { algorithm, keyUsages } = getGenerateKeyParams(alg, options);
|
|
13
|
+
const key = await crypto.subtle.generateKey(algorithm, defaultExtractable, keyUsages);
|
|
14
|
+
if (exportToJWK) if (key instanceof CryptoKey) return exportKey(key, { alg });
|
|
15
|
+
else {
|
|
16
|
+
const [publicKey, privateKey] = await Promise.all([exportKey(key.publicKey, { alg }), exportKey(key.privateKey, { alg })]);
|
|
17
|
+
return {
|
|
18
|
+
privateKey,
|
|
19
|
+
publicKey
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
return key;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Generates a Json Web Key (JWK) for the specified algorithm.
|
|
26
|
+
*
|
|
27
|
+
* @param alg The JWA algorithm identifier (e.g., "HS256", "RS256", "A128GCM").
|
|
28
|
+
* @param jwkParams Optional partial JWK to merge with the generated key, allowing overrides.
|
|
29
|
+
* @param options Configuration options for key generation.
|
|
30
|
+
* @returns A Promise resolving to the generated JWK or JWK pair representation.
|
|
31
|
+
*/
|
|
32
|
+
async function generateJWK(alg, jwkParams, options = {}) {
|
|
33
|
+
const extraParams = {
|
|
34
|
+
kid: typeof jwkParams?.kid === "string" ? jwkParams.kid : crypto.randomUUID(),
|
|
35
|
+
...jwkParams
|
|
36
|
+
};
|
|
37
|
+
const result = await generateKey(alg, {
|
|
38
|
+
...options,
|
|
39
|
+
toJWK: true
|
|
40
|
+
});
|
|
41
|
+
if (result && typeof result === "object" && "privateKey" in result && "publicKey" in result) {
|
|
42
|
+
const pair = result;
|
|
43
|
+
return {
|
|
44
|
+
privateKey: {
|
|
45
|
+
...extraParams,
|
|
46
|
+
...pair.privateKey
|
|
47
|
+
},
|
|
48
|
+
publicKey: {
|
|
49
|
+
...extraParams,
|
|
50
|
+
...pair.publicKey
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
...extraParams,
|
|
56
|
+
...result
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
async function deriveKeyFromPassword(password, alg, options) {
|
|
60
|
+
const { salt, iterations, extractable, keyUsage } = options;
|
|
61
|
+
const exportToJWK = options.toJWK !== void 0 && options.toJWK !== false;
|
|
62
|
+
if (!(salt instanceof Uint8Array) || salt.length < 8) throw new Error("PBES2 Salt Input (salt) must be 8 or more octets");
|
|
63
|
+
if (typeof iterations !== "number" || iterations < 1) throw new Error("PBES2 Iteration Count (iterations) must be a positive integer");
|
|
64
|
+
const derivedBytes = await deriveKey(salt, alg, iterations, typeof password === "string" ? textEncoder.encode(password) : password);
|
|
65
|
+
const wrappingAlg = alg.slice(-6);
|
|
66
|
+
const finalUsages = keyUsage ?? ["wrapKey", "unwrapKey"];
|
|
67
|
+
const finalExtractable = exportToJWK ? true : extractable === true;
|
|
68
|
+
const derivedKey = await crypto.subtle.importKey("raw", derivedBytes, { name: "AES-KW" }, finalExtractable, finalUsages);
|
|
69
|
+
if (exportToJWK) return {
|
|
70
|
+
...await keyToJWK(derivedKey),
|
|
71
|
+
alg: wrappingAlg,
|
|
72
|
+
kty: "oct"
|
|
73
|
+
};
|
|
74
|
+
return derivedKey;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Derives a Json Web Key (JWK) from a password using PBKDF2 as specified by PBES2 algorithms.
|
|
78
|
+
*
|
|
79
|
+
* @param password The password to derive the key from (string or Uint8Array).
|
|
80
|
+
* @param alg The PBES2 algorithm identifier (e.g., "PBES2-HS256+A128KW").
|
|
81
|
+
* @param jwkParams Optional partial JWK to merge with the generated key, allowing overrides.
|
|
82
|
+
* @param options Configuration options including salt and iterations.
|
|
83
|
+
* @returns A Promise resolving to the derived JWK (JWK_oct).
|
|
84
|
+
*/
|
|
85
|
+
async function deriveJWKFromPassword(password, alg, options, jwkParams) {
|
|
86
|
+
const result = await deriveKeyFromPassword(password, alg, {
|
|
87
|
+
...options,
|
|
88
|
+
toJWK: true
|
|
89
|
+
});
|
|
90
|
+
return jwkParams ? {
|
|
91
|
+
...jwkParams,
|
|
92
|
+
...result
|
|
93
|
+
} : result;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Default JWK import cache backed by a WeakMap.
|
|
97
|
+
*
|
|
98
|
+
* The outer WeakMap is keyed by the JWK object reference — a cache hit only
|
|
99
|
+
* occurs when the exact same object variable is passed to {@link importKey}.
|
|
100
|
+
* Reconstructing a structurally identical JWK (e.g. `{ ...jwk }`) will miss
|
|
101
|
+
* the cache.
|
|
102
|
+
*
|
|
103
|
+
* The inner `Record<string, CryptoKey>` uses a plain object rather than `Map`
|
|
104
|
+
* because typical entries have 1–2 algorithm strings per key. V8 applies
|
|
105
|
+
* hidden-class optimisation to small plain objects, making property access
|
|
106
|
+
* faster than `Map.get()` at this cardinality.
|
|
107
|
+
*/
|
|
108
|
+
var WeakMapJWKCache = class {
|
|
109
|
+
_map = /* @__PURE__ */ new WeakMap();
|
|
110
|
+
get(jwk, alg) {
|
|
111
|
+
return this._map.get(jwk)?.[alg];
|
|
112
|
+
}
|
|
113
|
+
set(jwk, alg, key) {
|
|
114
|
+
let entry = this._map.get(jwk);
|
|
115
|
+
if (!entry) this._map.set(jwk, { [alg]: key });
|
|
116
|
+
else entry[alg] = key;
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
let _activeCache = new WeakMapJWKCache();
|
|
120
|
+
/**
|
|
121
|
+
* Replace or disable the JWK import cache used by {@link importKey}.
|
|
122
|
+
*
|
|
123
|
+
* Pass a custom {@link JWKCacheAdapter} to use your own cache strategy
|
|
124
|
+
* (LRU, Redis-backed wrapper, test spy, etc.). Pass `false` to disable
|
|
125
|
+
* caching entirely.
|
|
126
|
+
*
|
|
127
|
+
* @example Disable caching:
|
|
128
|
+
* ```ts
|
|
129
|
+
* configureJWKCache(false);
|
|
130
|
+
* ```
|
|
131
|
+
* @example Use a custom kid-keyed cache:
|
|
132
|
+
* ```ts
|
|
133
|
+
* const map = new Map<string, CryptoKey>();
|
|
134
|
+
* configureJWKCache({
|
|
135
|
+
* get: (jwk, alg) => map.get(`${jwk.kid}:${alg}`),
|
|
136
|
+
* set: (jwk, alg, key) => map.set(`${jwk.kid}:${alg}`, key),
|
|
137
|
+
* });
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
function configureJWKCache(cache) {
|
|
141
|
+
_activeCache = cache;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Reset the JWK import cache to a fresh {@link WeakMapJWKCache}.
|
|
145
|
+
* Useful in test environments to clear all cached CryptoKey references
|
|
146
|
+
* between test runs without disabling the cache entirely.
|
|
147
|
+
*/
|
|
148
|
+
function clearJWKCache() {
|
|
149
|
+
_activeCache = new WeakMapJWKCache();
|
|
150
|
+
}
|
|
151
|
+
async function importKey(key, algOrOptions) {
|
|
152
|
+
const alg = typeof algOrOptions === "string" ? algOrOptions : void 0;
|
|
153
|
+
if (typeof key === "string") key = textEncoder.encode(key);
|
|
154
|
+
if (key instanceof Uint8Array) return key;
|
|
155
|
+
if (isCryptoKey(key)) return key;
|
|
156
|
+
if (isJWK(key)) if ("k" in key && typeof key.k === "string") {
|
|
157
|
+
const rawBytes = base64UrlDecode(key.k, false);
|
|
158
|
+
if (typeof algOrOptions === "object" && algOrOptions.asCryptoKey) {
|
|
159
|
+
const { algorithm, usage, extractable = false } = algOrOptions;
|
|
160
|
+
return crypto.subtle.importKey("raw", rawBytes, algorithm, extractable, usage);
|
|
161
|
+
}
|
|
162
|
+
return rawBytes;
|
|
163
|
+
} else {
|
|
164
|
+
if (!key.alg && !alg) throw new TypeError("Algorithm must be provided when importing non-oct JWK");
|
|
165
|
+
const effectiveAlg = key.alg || alg;
|
|
166
|
+
if (_activeCache) {
|
|
167
|
+
const cached = _activeCache.get(key, effectiveAlg);
|
|
168
|
+
if (cached) return cached;
|
|
169
|
+
}
|
|
170
|
+
const cryptoKey = await jwkTokey(key.alg ? key : {
|
|
171
|
+
...key,
|
|
172
|
+
alg
|
|
173
|
+
});
|
|
174
|
+
if (_activeCache) _activeCache.set(key, effectiveAlg, cryptoKey);
|
|
175
|
+
return cryptoKey;
|
|
176
|
+
}
|
|
177
|
+
throw new JWTError("Invalid key type provided to importKey", "ERR_JWK_INVALID");
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Exports a CryptoKey to a JWK (JSON Web Key) format.
|
|
181
|
+
*
|
|
182
|
+
* @param key The CryptoKey to export.
|
|
183
|
+
* @param jwk Optional partial JWK to merge with the exported key, allowing overrides.
|
|
184
|
+
* @returns A Promise resolving to the exported JWK.
|
|
185
|
+
*/
|
|
186
|
+
async function exportKey(key, jwk) {
|
|
187
|
+
const exportedJwk = await keyToJWK(key);
|
|
188
|
+
if (jwk) return {
|
|
189
|
+
...sanitizeObject(jwk),
|
|
190
|
+
...exportedJwk
|
|
191
|
+
};
|
|
192
|
+
return exportedJwk;
|
|
193
|
+
}
|
|
194
|
+
async function resolveWrappingKey(alg, key) {
|
|
195
|
+
if (alg.startsWith("PBES2") && (typeof key === "string" || key instanceof Uint8Array)) return typeof key === "string" ? textEncoder.encode(key) : key;
|
|
196
|
+
if (typeof key === "string") throw new TypeError(`Key must be CryptoKey, JWK, or Uint8Array for ${alg}`);
|
|
197
|
+
return importKey(key, alg);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Wraps a Content Encryption Key (CEK) using the specified algorithm and wrapping key.
|
|
201
|
+
*
|
|
202
|
+
* @param alg The JWA key management algorithm (e.g., "A128KW", "RSA-OAEP", "PBES2-HS256+A128KW").
|
|
203
|
+
* @param keyToWrap The key to be wrapped (CEK), typically a symmetric key as Uint8Array or CryptoKey.
|
|
204
|
+
* @param wrappingKey The key used to wrap the CEK (CryptoKey, JWK, or password string/Uint8Array for PBES2).
|
|
205
|
+
* @param options Additional options required by certain algorithms (e.g., p2s, p2c for PBES2).
|
|
206
|
+
* @returns A Promise resolving to an object containing the wrapped key and any necessary parameters (iv, tag, epk, etc.).
|
|
207
|
+
*/
|
|
208
|
+
async function wrapKey(alg, keyToWrap, wrappingKey, options = {}) {
|
|
209
|
+
const cekBytes = keyToWrap instanceof Uint8Array ? keyToWrap : new Uint8Array(await crypto.subtle.exportKey("raw", keyToWrap));
|
|
210
|
+
const importedWrappingKey = await resolveWrappingKey(alg, wrappingKey);
|
|
211
|
+
switch (alg) {
|
|
212
|
+
case "dir": return { encryptedKey: new Uint8Array(0) };
|
|
213
|
+
case "A128KW":
|
|
214
|
+
case "A192KW":
|
|
215
|
+
case "A256KW": return { encryptedKey: await aesKwWrap(alg, importedWrappingKey, cekBytes) };
|
|
216
|
+
case "PBES2-HS256+A128KW":
|
|
217
|
+
case "PBES2-HS384+A192KW":
|
|
218
|
+
case "PBES2-HS512+A256KW": {
|
|
219
|
+
const { p2s, p2c } = options;
|
|
220
|
+
if (!p2s || typeof p2c !== "number") throw new Error("PBES2 requires 'p2s' (salt) and 'p2c' (count) options");
|
|
221
|
+
return pbes2Wrap(alg, importedWrappingKey, cekBytes, p2c, p2s);
|
|
222
|
+
}
|
|
223
|
+
case "A128GCMKW":
|
|
224
|
+
case "A192GCMKW":
|
|
225
|
+
case "A256GCMKW": {
|
|
226
|
+
const { encryptedKey, iv, tag } = await gcmkwEncrypt(alg, importedWrappingKey, cekBytes, options.iv);
|
|
227
|
+
return {
|
|
228
|
+
encryptedKey,
|
|
229
|
+
iv,
|
|
230
|
+
tag
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
case "RSA-OAEP":
|
|
234
|
+
case "RSA-OAEP-256":
|
|
235
|
+
case "RSA-OAEP-384":
|
|
236
|
+
case "RSA-OAEP-512": return { encryptedKey: await encryptRSAES(alg, importedWrappingKey, cekBytes) };
|
|
237
|
+
case "ECDH-ES":
|
|
238
|
+
case "ECDH-ES+A128KW":
|
|
239
|
+
case "ECDH-ES+A192KW":
|
|
240
|
+
case "ECDH-ES+A256KW": {
|
|
241
|
+
if (!(importedWrappingKey instanceof CryptoKey)) throw new TypeError("ECDH-ES requires the wrapping key to be a CryptoKey (recipient's public key)");
|
|
242
|
+
if (!isECDHKeyAllowed(importedWrappingKey)) throw new JWTError("ECDH with the provided key is not allowed or not supported", "ERR_JWK_INVALID");
|
|
243
|
+
const apu = options.ecdh?.partyUInfo ?? new Uint8Array(0);
|
|
244
|
+
const apv = options.ecdh?.partyVInfo ?? new Uint8Array(0);
|
|
245
|
+
const { ephemeralPrivateKey, epkJwk } = await resolveECDHEphemeralPair(importedWrappingKey, options.ecdh?.ephemeralKey);
|
|
246
|
+
const infoAlg = alg === "ECDH-ES" ? (() => {
|
|
247
|
+
if (!options.ecdh?.enc) throw new JWTError("ECDH-ES direct key agreement requires options.ecdh.enc (the target content encryption algorithm)", "ERR_JWK_INVALID");
|
|
248
|
+
return options.ecdh.enc;
|
|
249
|
+
})() : alg;
|
|
250
|
+
const sharedSecret = await deriveECDHESKey(importedWrappingKey, ephemeralPrivateKey, infoAlg, alg === "ECDH-ES" ? bitLengthCEK(infoAlg) : Number.parseInt(alg.slice(-5, -2), 10), apu, apv);
|
|
251
|
+
const wrapResult = {
|
|
252
|
+
encryptedKey: new Uint8Array(0),
|
|
253
|
+
epk: epkJwk
|
|
254
|
+
};
|
|
255
|
+
if (apu.length > 0) wrapResult.apu = base64UrlEncode(apu);
|
|
256
|
+
if (apv.length > 0) wrapResult.apv = base64UrlEncode(apv);
|
|
257
|
+
if (alg === "ECDH-ES") return wrapResult;
|
|
258
|
+
wrapResult.encryptedKey = await aesKwWrap(alg.slice(-6), sharedSecret, cekBytes);
|
|
259
|
+
return wrapResult;
|
|
260
|
+
}
|
|
261
|
+
default: throw new Error(`Unsupported key wrapping algorithm: ${alg}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
async function unwrapKey(alg, wrappedKey, unwrappingKey, options = {}) {
|
|
265
|
+
const returnRaw = options.format === "raw";
|
|
266
|
+
const defaultExtractable = options.extractable !== false;
|
|
267
|
+
const importedUnwrappingKey = isCryptoKey(unwrappingKey) ? unwrappingKey : await resolveWrappingKey(alg, unwrappingKey);
|
|
268
|
+
let unwrappedCekBytes;
|
|
269
|
+
switch (alg) {
|
|
270
|
+
case "dir":
|
|
271
|
+
if (importedUnwrappingKey instanceof Uint8Array) unwrappedCekBytes = importedUnwrappingKey;
|
|
272
|
+
else {
|
|
273
|
+
if (!returnRaw) return importedUnwrappingKey;
|
|
274
|
+
unwrappedCekBytes = new Uint8Array(await crypto.subtle.exportKey("raw", importedUnwrappingKey));
|
|
275
|
+
}
|
|
276
|
+
break;
|
|
277
|
+
case "A128KW":
|
|
278
|
+
case "A192KW":
|
|
279
|
+
case "A256KW":
|
|
280
|
+
unwrappedCekBytes = await aesKwUnwrap(alg, importedUnwrappingKey, wrappedKey);
|
|
281
|
+
break;
|
|
282
|
+
case "PBES2-HS256+A128KW":
|
|
283
|
+
case "PBES2-HS384+A192KW":
|
|
284
|
+
case "PBES2-HS512+A256KW": {
|
|
285
|
+
const { p2s, p2c } = options;
|
|
286
|
+
if (!p2s || typeof p2c !== "number") throw new Error("PBES2 requires 'p2s' (salt) and 'p2c' (count) options");
|
|
287
|
+
unwrappedCekBytes = await pbes2Unwrap(alg, importedUnwrappingKey, wrappedKey, p2c, typeof p2s === "string" ? base64UrlDecode(p2s, false) : p2s);
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
case "A128GCMKW":
|
|
291
|
+
case "A192GCMKW":
|
|
292
|
+
case "A256GCMKW":
|
|
293
|
+
if (!options.iv || !options.tag) throw new Error("AES-GCMKW requires 'iv' and 'tag' options for unwrapping");
|
|
294
|
+
unwrappedCekBytes = await gcmkwDecrypt(alg, importedUnwrappingKey, wrappedKey, typeof options.iv === "string" ? base64UrlDecode(options.iv, false) : options.iv, typeof options.tag === "string" ? base64UrlDecode(options.tag, false) : options.tag);
|
|
295
|
+
break;
|
|
296
|
+
case "RSA-OAEP":
|
|
297
|
+
case "RSA-OAEP-256":
|
|
298
|
+
case "RSA-OAEP-384":
|
|
299
|
+
case "RSA-OAEP-512": {
|
|
300
|
+
if (!(importedUnwrappingKey instanceof CryptoKey)) throw new TypeError("RSA-OAEP requires the unwrapping key to be provided as a CryptoKey");
|
|
301
|
+
unwrappedCekBytes = await decryptRSAES(alg, importedUnwrappingKey, wrappedKey);
|
|
302
|
+
if (returnRaw) break;
|
|
303
|
+
const inferredAlgorithm = options.unwrappedKeyAlgorithm || inferAesImportAlgorithm(options.enc, unwrappedCekBytes.length);
|
|
304
|
+
if (!inferredAlgorithm) throw new Error("Unable to infer algorithm for RSA-OAEP unwrapped key. Provide \"unwrappedKeyAlgorithm\" in options.");
|
|
305
|
+
const keyUsages = options.keyUsage || ["encrypt", "decrypt"];
|
|
306
|
+
return await crypto.subtle.importKey("raw", unwrappedCekBytes, inferredAlgorithm, defaultExtractable, keyUsages);
|
|
307
|
+
}
|
|
308
|
+
case "ECDH-ES":
|
|
309
|
+
case "ECDH-ES+A128KW":
|
|
310
|
+
case "ECDH-ES+A192KW":
|
|
311
|
+
case "ECDH-ES+A256KW": {
|
|
312
|
+
if (!options.epk) throw new Error("ECDH-ES requires 'epk' (Ephemeral Public Key) option");
|
|
313
|
+
if (!(importedUnwrappingKey instanceof CryptoKey)) throw new TypeError("ECDH-ES requires the unwrapping key to be a CryptoKey");
|
|
314
|
+
if (!isECDHKeyAllowed(importedUnwrappingKey)) throw new Error("ECDH with the provided key is not allowed or not supported");
|
|
315
|
+
const epkCandidate = options.epk instanceof CryptoKey ? options.epk : await normalizeKey(options.epk, alg);
|
|
316
|
+
if (!(epkCandidate instanceof CryptoKey)) throw new TypeError("Failed to normalize ECDH ephemeral public key");
|
|
317
|
+
const apuBytes = typeof options.apu === "string" ? base64UrlDecode(options.apu, false) : options.apu ?? new Uint8Array(0);
|
|
318
|
+
const apvBytes = typeof options.apv === "string" ? base64UrlDecode(options.apv, false) : options.apv ?? new Uint8Array(0);
|
|
319
|
+
const infoAlg = alg === "ECDH-ES" ? options.enc : alg;
|
|
320
|
+
if (!infoAlg) throw new Error("ECDH-ES requires content encryption algorithm ('enc') to derive the shared secret");
|
|
321
|
+
const sharedSecret = await deriveECDHESKey(epkCandidate, importedUnwrappingKey, infoAlg, alg === "ECDH-ES" ? bitLengthCEK(infoAlg) : Number.parseInt(alg.slice(-5, -2), 10), apuBytes, apvBytes);
|
|
322
|
+
if (alg === "ECDH-ES") unwrappedCekBytes = sharedSecret;
|
|
323
|
+
else {
|
|
324
|
+
const kwAlg = alg.slice(-6);
|
|
325
|
+
if (!(wrappedKey instanceof Uint8Array) || wrappedKey.length === 0) throw new Error("ECDH-ES key agreement with key wrapping requires an encrypted key");
|
|
326
|
+
unwrappedCekBytes = await aesKwUnwrap(kwAlg, sharedSecret, wrappedKey);
|
|
327
|
+
}
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
default: throw new Error(`Unsupported key unwrapping algorithm: ${alg}`);
|
|
331
|
+
}
|
|
332
|
+
if (returnRaw) return unwrappedCekBytes;
|
|
333
|
+
return await crypto.subtle.importKey("raw", unwrappedCekBytes, options.unwrappedKeyAlgorithm || { name: "AES-GCM" }, defaultExtractable, options.keyUsage || ["encrypt", "decrypt"]);
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Imports a key from a PEM-encoded string and converts it to a JWK.
|
|
337
|
+
*
|
|
338
|
+
* @param pem The PEM-encoded string.
|
|
339
|
+
* @param pemType The type of PEM encoding ('pkcs8' for private keys, 'spki' for public keys, 'x509' for certificates).
|
|
340
|
+
* @param alg The JWA algorithm identifier. This is crucial for `crypto.subtle.importKey`
|
|
341
|
+
* to understand the key's intended algorithm and for setting the 'alg' field in the resulting JWK.
|
|
342
|
+
* @param importOptions Options for the PEM import process (e.g., extractable for the CryptoKey).
|
|
343
|
+
* @param jwkExtras Additional properties to merge into the resulting JWK.
|
|
344
|
+
* @returns A Promise resolving to the imported key as a JWK.
|
|
345
|
+
*/
|
|
346
|
+
async function importFromPEM(pem, pemType, alg, options) {
|
|
347
|
+
const extractable = options?.extractable !== false;
|
|
348
|
+
let cryptoKey;
|
|
349
|
+
switch (pemType) {
|
|
350
|
+
case "pkcs8":
|
|
351
|
+
cryptoKey = await fromPKCS8(pem, alg, { extractable });
|
|
352
|
+
break;
|
|
353
|
+
case "spki":
|
|
354
|
+
cryptoKey = await fromSPKI(pem, alg, { extractable });
|
|
355
|
+
break;
|
|
356
|
+
case "x509":
|
|
357
|
+
cryptoKey = await fromX509(pem, alg, { extractable });
|
|
358
|
+
break;
|
|
359
|
+
default: throw new TypeError(`Unsupported PEM type: ${pemType}`);
|
|
360
|
+
}
|
|
361
|
+
return exportKey(cryptoKey, {
|
|
362
|
+
alg,
|
|
363
|
+
...options?.jwkParams
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Exports a JWK to a PEM-encoded string.
|
|
368
|
+
*
|
|
369
|
+
* @param jwk The JWK to export.
|
|
370
|
+
* @param pemFormat The desired PEM format ('pkcs8' for private keys, 'spki' for public keys).
|
|
371
|
+
* @param algForCryptoKeyImport If the JWK does not have an 'alg' property, this algorithm hint is
|
|
372
|
+
* required to correctly convert it to a CryptoKey first.
|
|
373
|
+
* @returns A Promise resolving to the PEM-encoded key string.
|
|
374
|
+
*/
|
|
375
|
+
async function exportToPEM(jwk, pemFormat, algForCryptoKeyImport) {
|
|
376
|
+
if (jwk.kty === "oct") throw new TypeError("Octet (symmetric) JWKs (kty: 'oct') cannot be exported to PKCS8 or SPKI PEM formats.");
|
|
377
|
+
const effectiveAlg = jwk.alg || algForCryptoKeyImport;
|
|
378
|
+
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.");
|
|
379
|
+
const cryptoKeyCandidate = await importKey({
|
|
380
|
+
...jwk,
|
|
381
|
+
ext: true
|
|
382
|
+
}, effectiveAlg);
|
|
383
|
+
if (!isCryptoKey(cryptoKeyCandidate)) throw new Error("Failed to convert JWK to a CryptoKey instance suitable for PEM export.");
|
|
384
|
+
const cryptoKey = cryptoKeyCandidate;
|
|
385
|
+
switch (pemFormat) {
|
|
386
|
+
case "pkcs8":
|
|
387
|
+
if (cryptoKey.type !== "private") throw new TypeError(`Only 'private' type CryptoKeys can be exported to PKCS8 PEM format. Key type is '${cryptoKey.type}'.`);
|
|
388
|
+
return toPKCS8(cryptoKey);
|
|
389
|
+
case "spki":
|
|
390
|
+
if (cryptoKey.type !== "public") throw new TypeError(`Only 'public' type CryptoKeys can be exported to SPKI PEM format. Key type is '${cryptoKey.type}'.`);
|
|
391
|
+
return toSPKI(cryptoKey);
|
|
392
|
+
default: throw new TypeError(`Unsupported PEM format: ${pemFormat}`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Derives a shared secret using ECDH-ES key agreement (RFC 7518 §4.6).
|
|
397
|
+
*
|
|
398
|
+
* Performs an Elliptic Curve Diffie-Hellman Ephemeral-Static key derivation
|
|
399
|
+
* followed by a Concat KDF (NIST SP 800-56A) to produce key material of the
|
|
400
|
+
* requested length. The result can be used directly as a CEK (for `ECDH-ES`
|
|
401
|
+
* direct key agreement) or as a KEK to wrap a separately generated CEK (for
|
|
402
|
+
* `ECDH-ES+A*KW`).
|
|
403
|
+
*
|
|
404
|
+
* For multi-recipient JWE, call this once per recipient with their public key
|
|
405
|
+
* and a fresh ephemeral private key, then wrap the shared CEK with the derived
|
|
406
|
+
* material using {@link wrapKey}.
|
|
407
|
+
*
|
|
408
|
+
* @param publicKey The recipient's static public key (or the sender's
|
|
409
|
+
* ephemeral public key on the decryption side).
|
|
410
|
+
* @param privateKey The sender's ephemeral private key (or the recipient's
|
|
411
|
+
* static private key on the decryption side).
|
|
412
|
+
* @param alg The algorithm identifier used as the `AlgorithmID` in the
|
|
413
|
+
* concat KDF info structure. Pass a {@link JWK_ECDH_ES} variant
|
|
414
|
+
* (e.g. `"ECDH-ES+A128KW"`) or a {@link ContentEncryptionAlgorithm}
|
|
415
|
+
* (e.g. `"A256GCM"`) for direct key agreement.
|
|
416
|
+
* @param options Optional overrides for key length and party info.
|
|
417
|
+
*/
|
|
418
|
+
async function deriveSharedSecret(publicKey, privateKey, alg, options) {
|
|
419
|
+
const pubCryptoKey = isCryptoKey(publicKey) ? publicKey : await importKey(publicKey, "ECDH-ES");
|
|
420
|
+
const privCryptoKey = isCryptoKey(privateKey) ? privateKey : await importKey(privateKey, "ECDH-ES");
|
|
421
|
+
let keyLength;
|
|
422
|
+
if (options?.keyLength !== void 0) keyLength = options.keyLength;
|
|
423
|
+
else if (alg === "ECDH-ES") throw new JWTError("deriveSharedSecret with alg \"ECDH-ES\" requires an explicit keyLength in options", "ERR_JWK_INVALID");
|
|
424
|
+
else if (alg.startsWith("ECDH-ES+")) keyLength = Number.parseInt(alg.slice(-5, -2), 10);
|
|
425
|
+
else keyLength = bitLengthCEK(alg);
|
|
426
|
+
return deriveECDHESKey(pubCryptoKey, privCryptoKey, alg, keyLength, options?.partyUInfo ?? new Uint8Array(0), options?.partyVInfo ?? new Uint8Array(0));
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* @deprecated For set queries, use {@link getJWKsFromSet} which returns all
|
|
430
|
+
* matching keys as an array. This function remains available for internal
|
|
431
|
+
* single-key resolution by `kid` or JOSE header (used by `verify` and `decrypt`).
|
|
432
|
+
*
|
|
433
|
+
* Retrieves a JWK (JSON Web Key) from a JWKSet based on a key ID (kid) or
|
|
434
|
+
* properties from a JOSE Header.
|
|
435
|
+
*
|
|
436
|
+
* @param jwkSet The JWKSet to search within.
|
|
437
|
+
* @param kidOrProtectedHeader Either a string representing the 'kid' (Key ID)
|
|
438
|
+
* or an object representing the JOSE Protected Header. If an object is
|
|
439
|
+
* provided, it must contain a 'kid' property. It can optionally contain
|
|
440
|
+
* 'alg' (Algorithm) and 'kty' (Key Type) to further refine the search.
|
|
441
|
+
* @returns The matching JWK from the set.
|
|
442
|
+
* @throws TypeError if `jwkSet` is invalid or `kidOrProtectedHeader` is not a
|
|
443
|
+
* string or a valid header object.
|
|
444
|
+
* @throws Error if no key is found matching the provided criteria, or if the
|
|
445
|
+
* header object is missing the 'kid' property.
|
|
446
|
+
*/
|
|
447
|
+
function getJWKFromSet(jwkSet, kidOrProtectedHeader) {
|
|
448
|
+
if (!jwkSet || !isJWKSet(jwkSet)) throw new TypeError("Invalid JWK Set provided");
|
|
449
|
+
if (typeof kidOrProtectedHeader === "string") {
|
|
450
|
+
const kid = kidOrProtectedHeader;
|
|
451
|
+
const selectedKey = jwkSet.keys.find((k) => k.kid === kid);
|
|
452
|
+
if (!selectedKey) throw new JWTError(`No key found in JWK Set with kid "${kid}".`, "ERR_JWK_KEY_NOT_FOUND");
|
|
453
|
+
return selectedKey;
|
|
454
|
+
} else if (typeof kidOrProtectedHeader === "object") {
|
|
455
|
+
const { kid, alg, kty } = kidOrProtectedHeader;
|
|
456
|
+
if (!kid) {
|
|
457
|
+
if (jwkSet.keys.length === 1) return jwkSet.keys[0];
|
|
458
|
+
throw new JWTError("JWS Protected Header is missing 'kid' (Key ID) and the JWK Set contains multiple keys. Add a 'kid' parameter to the token header and to the matching JWK to enable automatic selection.", "ERR_JWK_KEY_NOT_FOUND");
|
|
459
|
+
}
|
|
460
|
+
const selectedKey = jwkSet.keys.find((k) => {
|
|
461
|
+
return k.kid === kid && (!alg || k.alg === alg) && (!kty || k.kty === kty);
|
|
462
|
+
});
|
|
463
|
+
if (!selectedKey) {
|
|
464
|
+
let errorMessage = `No key found in JWK Set with kid "${kid}"`;
|
|
465
|
+
if (alg) errorMessage += ` and alg "${alg}"`;
|
|
466
|
+
if (kty) errorMessage += ` and kty "${kty}"`;
|
|
467
|
+
errorMessage += ".";
|
|
468
|
+
throw new JWTError(errorMessage, "ERR_JWK_KEY_NOT_FOUND");
|
|
469
|
+
}
|
|
470
|
+
return selectedKey;
|
|
471
|
+
}
|
|
472
|
+
throw new TypeError("`kidOrProtectedHeader` must be a string (kid) or an object (JOSE Protected Header).");
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Returns all JWKs from a JWK Set, optionally narrowed by a predicate.
|
|
476
|
+
*
|
|
477
|
+
* Useful for multi-key verification retry, key rotation tooling, and
|
|
478
|
+
* constructing multi-recipient JWE JSON Serialization structures.
|
|
479
|
+
*
|
|
480
|
+
* @param jwkSet The JWK Set to search.
|
|
481
|
+
* @param filter Optional predicate `(jwk: JWK) => boolean`. Returns all keys when omitted.
|
|
482
|
+
* @returns An array of matching JWKs.
|
|
483
|
+
*/
|
|
484
|
+
function getJWKsFromSet(jwkSet, filter) {
|
|
485
|
+
if (!jwkSet || !isJWKSet(jwkSet)) throw new TypeError("Invalid JWK Set provided");
|
|
486
|
+
if (!filter) return [...jwkSet.keys];
|
|
487
|
+
return jwkSet.keys.filter(filter);
|
|
488
|
+
}
|
|
489
|
+
function getGenerateKeyParams(alg, options) {
|
|
490
|
+
let algorithm;
|
|
491
|
+
let keyUsages;
|
|
492
|
+
const defaultKeyUsage = options?.keyUsage;
|
|
493
|
+
switch (alg) {
|
|
494
|
+
case "HS256":
|
|
495
|
+
case "HS384":
|
|
496
|
+
case "HS512":
|
|
497
|
+
algorithm = {
|
|
498
|
+
name: "HMAC",
|
|
499
|
+
hash: `SHA-${alg.slice(2)}`
|
|
500
|
+
};
|
|
501
|
+
keyUsages = defaultKeyUsage ?? ["sign", "verify"];
|
|
502
|
+
break;
|
|
503
|
+
case "RS256":
|
|
504
|
+
case "RS384":
|
|
505
|
+
case "RS512":
|
|
506
|
+
algorithm = {
|
|
507
|
+
name: "RSASSA-PKCS1-v1_5",
|
|
508
|
+
modulusLength: options?.modulusLength ?? 2048,
|
|
509
|
+
publicExponent: options?.publicExponent ?? new Uint8Array([
|
|
510
|
+
1,
|
|
511
|
+
0,
|
|
512
|
+
1
|
|
513
|
+
]),
|
|
514
|
+
hash: `SHA-${alg.slice(2)}`
|
|
515
|
+
};
|
|
516
|
+
keyUsages = defaultKeyUsage ?? ["sign", "verify"];
|
|
517
|
+
break;
|
|
518
|
+
case "PS256":
|
|
519
|
+
case "PS384":
|
|
520
|
+
case "PS512":
|
|
521
|
+
algorithm = {
|
|
522
|
+
name: "RSA-PSS",
|
|
523
|
+
modulusLength: options?.modulusLength ?? 2048,
|
|
524
|
+
publicExponent: options?.publicExponent ?? new Uint8Array([
|
|
525
|
+
1,
|
|
526
|
+
0,
|
|
527
|
+
1
|
|
528
|
+
]),
|
|
529
|
+
hash: `SHA-${alg.slice(2)}`
|
|
530
|
+
};
|
|
531
|
+
keyUsages = defaultKeyUsage ?? ["sign", "verify"];
|
|
532
|
+
break;
|
|
533
|
+
case "RSA-OAEP":
|
|
534
|
+
case "RSA-OAEP-256":
|
|
535
|
+
case "RSA-OAEP-384":
|
|
536
|
+
case "RSA-OAEP-512":
|
|
537
|
+
algorithm = {
|
|
538
|
+
name: "RSA-OAEP",
|
|
539
|
+
modulusLength: options?.modulusLength ?? 2048,
|
|
540
|
+
publicExponent: options?.publicExponent ?? new Uint8Array([
|
|
541
|
+
1,
|
|
542
|
+
0,
|
|
543
|
+
1
|
|
544
|
+
]),
|
|
545
|
+
hash: `SHA-${Number.parseInt(alg.slice(9), 10) || 1}`
|
|
546
|
+
};
|
|
547
|
+
keyUsages = defaultKeyUsage ?? [
|
|
548
|
+
"encrypt",
|
|
549
|
+
"decrypt",
|
|
550
|
+
"wrapKey",
|
|
551
|
+
"unwrapKey"
|
|
552
|
+
];
|
|
553
|
+
break;
|
|
554
|
+
case "A128KW":
|
|
555
|
+
case "A192KW":
|
|
556
|
+
case "A256KW":
|
|
557
|
+
algorithm = {
|
|
558
|
+
name: "AES-KW",
|
|
559
|
+
length: Number.parseInt(alg.slice(1, 4), 10)
|
|
560
|
+
};
|
|
561
|
+
keyUsages = defaultKeyUsage ?? ["wrapKey", "unwrapKey"];
|
|
562
|
+
break;
|
|
563
|
+
case "A128GCM":
|
|
564
|
+
case "A192GCM":
|
|
565
|
+
case "A256GCM":
|
|
566
|
+
case "A128GCMKW":
|
|
567
|
+
case "A192GCMKW":
|
|
568
|
+
case "A256GCMKW": {
|
|
569
|
+
const baseAlg = alg.replace("KW", "");
|
|
570
|
+
algorithm = {
|
|
571
|
+
name: "AES-GCM",
|
|
572
|
+
length: Number.parseInt(baseAlg.slice(1, 4), 10)
|
|
573
|
+
};
|
|
574
|
+
keyUsages = defaultKeyUsage ?? (alg.endsWith("KW") ? ["wrapKey", "unwrapKey"] : ["encrypt", "decrypt"]);
|
|
575
|
+
break;
|
|
576
|
+
}
|
|
577
|
+
case "ES256":
|
|
578
|
+
case "ES384":
|
|
579
|
+
case "ES512":
|
|
580
|
+
algorithm = {
|
|
581
|
+
name: "ECDSA",
|
|
582
|
+
namedCurve: `P-${alg.slice(2)}`
|
|
583
|
+
};
|
|
584
|
+
keyUsages = defaultKeyUsage ?? ["sign", "verify"];
|
|
585
|
+
break;
|
|
586
|
+
case "Ed25519":
|
|
587
|
+
keyUsages = defaultKeyUsage ?? ["sign", "verify"];
|
|
588
|
+
algorithm = { name: "Ed25519" };
|
|
589
|
+
break;
|
|
590
|
+
case "EdDSA": {
|
|
591
|
+
keyUsages = defaultKeyUsage ?? ["sign", "verify"];
|
|
592
|
+
const namedCurve = options?.namedCurve ?? "Ed25519";
|
|
593
|
+
switch (namedCurve) {
|
|
594
|
+
case "Ed25519":
|
|
595
|
+
case "Ed448":
|
|
596
|
+
algorithm = { name: namedCurve };
|
|
597
|
+
break;
|
|
598
|
+
default: throw new Error("Unsupported namedCurve provided. Supported values are: Ed25519 and Ed448");
|
|
599
|
+
}
|
|
600
|
+
break;
|
|
601
|
+
}
|
|
602
|
+
case "ECDH-ES":
|
|
603
|
+
case "ECDH-ES+A128KW":
|
|
604
|
+
case "ECDH-ES+A192KW":
|
|
605
|
+
case "ECDH-ES+A256KW": {
|
|
606
|
+
keyUsages = ["deriveBits"];
|
|
607
|
+
const namedCurve = options?.namedCurve ?? "P-256";
|
|
608
|
+
switch (namedCurve) {
|
|
609
|
+
case "P-256":
|
|
610
|
+
case "P-384":
|
|
611
|
+
case "P-521":
|
|
612
|
+
algorithm = {
|
|
613
|
+
name: "ECDH",
|
|
614
|
+
namedCurve
|
|
615
|
+
};
|
|
616
|
+
break;
|
|
617
|
+
case "X25519":
|
|
618
|
+
algorithm = { name: "X25519" };
|
|
619
|
+
break;
|
|
620
|
+
default: throw new Error("Unsupported namedCurve provided. Supported values are: P-256, P-384, P-521 and X25519");
|
|
621
|
+
}
|
|
622
|
+
break;
|
|
623
|
+
}
|
|
624
|
+
default: throw new Error(`Unsupported or invalid algorithm for key generation: ${alg}`);
|
|
625
|
+
}
|
|
626
|
+
return {
|
|
627
|
+
algorithm,
|
|
628
|
+
keyUsages
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
async function resolveECDHEphemeralPair(recipientPublicKey, ephemeralKeyInput) {
|
|
632
|
+
if (!ephemeralKeyInput) {
|
|
633
|
+
const generated = await crypto.subtle.generateKey(recipientPublicKey.algorithm, true, ["deriveBits"]);
|
|
634
|
+
const rawJwk = await keyToJWK(generated.publicKey);
|
|
635
|
+
return {
|
|
636
|
+
ephemeralPrivateKey: generated.privateKey,
|
|
637
|
+
epkJwk: stripPrivateJwkFields(rawJwk)
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
if (isCryptoKeyPair(ephemeralKeyInput)) {
|
|
641
|
+
const rawJwk = await keyToJWK(ephemeralKeyInput.publicKey);
|
|
642
|
+
return {
|
|
643
|
+
ephemeralPrivateKey: ephemeralKeyInput.privateKey,
|
|
644
|
+
epkJwk: stripPrivateJwkFields(rawJwk)
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
if (typeof ephemeralKeyInput === "object" && "publicKey" in ephemeralKeyInput && "privateKey" in ephemeralKeyInput) {
|
|
648
|
+
const { publicKey, privateKey } = ephemeralKeyInput;
|
|
649
|
+
return {
|
|
650
|
+
ephemeralPrivateKey: isCryptoKey(privateKey) ? privateKey : await importKey(privateKey, "ECDH-ES"),
|
|
651
|
+
epkJwk: isCryptoKey(publicKey) ? stripPrivateJwkFields(await keyToJWK(publicKey)) : stripPrivateJwkFields(publicKey)
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
if (isCryptoKey(ephemeralKeyInput)) {
|
|
655
|
+
if (ephemeralKeyInput.type !== "private") throw new TypeError("ECDH-ES ephemeral CryptoKey must be a private key");
|
|
656
|
+
return {
|
|
657
|
+
ephemeralPrivateKey: ephemeralKeyInput,
|
|
658
|
+
epkJwk: stripPrivateJwkFields(await keyToJWK(ephemeralKeyInput))
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
if (isJWK(ephemeralKeyInput) && "d" in ephemeralKeyInput) return {
|
|
662
|
+
ephemeralPrivateKey: await importKey(ephemeralKeyInput, "ECDH-ES"),
|
|
663
|
+
epkJwk: stripPrivateJwkFields(ephemeralKeyInput)
|
|
664
|
+
};
|
|
665
|
+
throw new TypeError("Unsupported ECDH-ES ephemeral key material");
|
|
666
|
+
}
|
|
667
|
+
function stripPrivateJwkFields(jwk) {
|
|
668
|
+
const { d: _d, ...pub } = jwk;
|
|
669
|
+
return pub;
|
|
670
|
+
}
|
|
671
|
+
function inferAesImportAlgorithm(enc, cekLengthBytes) {
|
|
672
|
+
const bitLength = cekLengthBytes << 3;
|
|
673
|
+
if (enc) {
|
|
674
|
+
if (enc.includes("GCM")) return {
|
|
675
|
+
name: "AES-GCM",
|
|
676
|
+
length: Number.parseInt(enc.slice(1, 4), 10)
|
|
677
|
+
};
|
|
678
|
+
if (enc.includes("CBC-HS")) return;
|
|
679
|
+
}
|
|
680
|
+
if (bitLength === 128 || bitLength === 192 || bitLength === 256) return {
|
|
681
|
+
name: "AES-GCM",
|
|
682
|
+
length: bitLength
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
//#endregion
|
|
686
|
+
export { JWTError, WeakMapJWKCache, clearJWKCache, configureJWKCache, deriveJWKFromPassword, deriveKeyFromPassword, deriveSharedSecret, exportKey, exportToPEM, generateJWK, generateKey, getJWKFromSet, getJWKsFromSet, importFromPEM, importKey, isJWTError, unwrapKey, wrapKey };
|