unjwt 0.6.1 → 0.7.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 +1 -2
- package/dist/THIRD-PARTY-LICENSES.md +63 -0
- package/dist/_chunks/_internal.mjs +88 -0
- package/dist/_chunks/index.d.mts +401 -41
- package/dist/_chunks/jwe.d.mts +82 -0
- package/dist/_chunks/jwe.mjs +407 -0
- package/dist/_chunks/{_crypto.mjs → jwk.mjs} +637 -26
- package/dist/_chunks/jws.d.mts +111 -0
- package/dist/_chunks/jws.mjs +264 -2031
- package/dist/_chunks/libs/rou3.mjs +5 -0
- package/dist/_chunks/libs/unsecure.d.mts +94 -0
- package/dist/_chunks/libs/unsecure.mjs +144 -0
- package/dist/_chunks/utils.mjs +222 -211
- package/dist/adapters/h3v1/index.d.mts +9 -9
- package/dist/adapters/h3v1/index.mjs +13 -75
- package/dist/adapters/h3v2/index.d.mts +9 -9
- package/dist/adapters/h3v2/index.mjs +14 -38
- package/dist/core/jwe.d.mts +3 -40
- package/dist/core/jwe.mjs +3 -161
- package/dist/core/jwk.d.mts +202 -46
- package/dist/core/jwk.mjs +3 -686
- package/dist/core/jws.d.mts +3 -31
- package/dist/core/jws.mjs +3 -104
- package/dist/core/utils/index.d.mts +74 -60
- package/dist/core/utils/index.mjs +3 -2
- package/dist/index.d.mts +6 -7
- package/dist/index.mjs +5 -5
- package/package.json +24 -22
- package/dist/_chunks/error.d.mts +0 -67
- package/dist/_chunks/jwk.d.mts +0 -708
- package/dist/core/_crypto/index.d.mts +0 -71
- package/dist/core/_crypto/index.mjs +0 -2
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
import { D as JWTError, S as validateCriticalHeadersJWE, b as getPlaintextBytes, f as applyTypCtyDefaults, h as computeJwtTimeClaims, i as isCryptoKey, o as isJWK, s as isJWKSet, y as decodePayloadFromBytes } from "./utils.mjs";
|
|
2
|
+
import { a as base64UrlEncode, i as base64UrlDecode, s as textEncoder, t as secureRandomBytes, u as sanitizeObjectCopy } from "./libs/unsecure.mjs";
|
|
3
|
+
import { C as generateIV, S as generateCEK, _ as unwrapKey, b as decrypt$1, h as importKey, p as getJWKsFromSet, x as encrypt$1, y as encryptKey } from "./jwk.mjs";
|
|
4
|
+
import { i as checkAlgAllowed, l as validateJwtClaimsIfJsonPayload, o as decodeProtectedHeader, r as buildJWKSetFilter, s as parseEphemeralKey, t as JWE_ALG_CTX } from "./_internal.mjs";
|
|
5
|
+
const DIRECT_ALGS = /* @__PURE__ */ new Set(["dir", "ECDH-ES"]);
|
|
6
|
+
async function encryptMulti(payload, recipients, options = {}) {
|
|
7
|
+
if (!Array.isArray(recipients) || recipients.length === 0) throw new JWTError("encryptMulti requires at least one recipient.", "ERR_JWE_INVALID");
|
|
8
|
+
const resolvedAlgs = recipients.map((r, i) => _resolveRecipientAlg(r.key, i));
|
|
9
|
+
const enc = options.enc ?? "A256GCM";
|
|
10
|
+
const userProtected = sanitizeObjectCopy(options.protectedHeader);
|
|
11
|
+
const sharedUnprotected = sanitizeObjectCopy(options.sharedUnprotectedHeader);
|
|
12
|
+
const protectedHeader = {};
|
|
13
|
+
if (userProtected) Object.assign(protectedHeader, userProtected);
|
|
14
|
+
protectedHeader.enc = enc;
|
|
15
|
+
applyTypCtyDefaults(protectedHeader, payload);
|
|
16
|
+
const plaintextBytes = getPlaintextBytes(computeJwtTimeClaims(payload, options) || payload);
|
|
17
|
+
const cek = options.cek ?? generateCEK(enc);
|
|
18
|
+
const wireRecipients = [];
|
|
19
|
+
for (let i = 0; i < recipients.length; i++) {
|
|
20
|
+
const recipient = recipients[i];
|
|
21
|
+
const alg = resolvedAlgs[i];
|
|
22
|
+
const keyMgmtParams = _buildRecipientKeyMgmtParams(recipient, alg);
|
|
23
|
+
const { encryptedKey, parameters: keyMgmtOut } = await encryptKey(alg, enc, await importKey(recipient.key, {
|
|
24
|
+
alg,
|
|
25
|
+
expect: "public"
|
|
26
|
+
}), cek, keyMgmtParams);
|
|
27
|
+
const perRecipientHeader = _buildPerRecipientHeader(recipient, alg, keyMgmtOut);
|
|
28
|
+
_assertDisjointHeaders(protectedHeader, sharedUnprotected, perRecipientHeader);
|
|
29
|
+
const wireRecipient = {};
|
|
30
|
+
if (Object.keys(perRecipientHeader).length > 0) wireRecipient.header = perRecipientHeader;
|
|
31
|
+
if (encryptedKey) wireRecipient.encrypted_key = base64UrlEncode(encryptedKey);
|
|
32
|
+
wireRecipients.push(wireRecipient);
|
|
33
|
+
}
|
|
34
|
+
const protectedHeaderEncoded = base64UrlEncode(JSON.stringify(protectedHeader));
|
|
35
|
+
let aadEncoded;
|
|
36
|
+
let aadForCipher = protectedHeaderEncoded;
|
|
37
|
+
if (options.aad !== void 0) {
|
|
38
|
+
aadEncoded = base64UrlEncode(typeof options.aad === "string" ? textEncoder.encode(options.aad) : options.aad);
|
|
39
|
+
aadForCipher = `${protectedHeaderEncoded}.${aadEncoded}`;
|
|
40
|
+
}
|
|
41
|
+
const contentAadBytes = textEncoder.encode(aadForCipher);
|
|
42
|
+
const { ciphertext: contentCiphertext, tag: contentAuthTag, iv: actualContentIV } = await encrypt$1(enc, plaintextBytes, cek, options.contentEncryptionIV ?? generateIV(enc), contentAadBytes);
|
|
43
|
+
if (!actualContentIV || !contentAuthTag) throw new JWTError("Content encryption did not return an IV / auth tag.", "ERR_JWE_INVALID");
|
|
44
|
+
const output = {
|
|
45
|
+
protected: protectedHeaderEncoded,
|
|
46
|
+
recipients: wireRecipients,
|
|
47
|
+
iv: base64UrlEncode(actualContentIV),
|
|
48
|
+
ciphertext: base64UrlEncode(contentCiphertext),
|
|
49
|
+
tag: base64UrlEncode(contentAuthTag)
|
|
50
|
+
};
|
|
51
|
+
if (sharedUnprotected && Object.keys(sharedUnprotected).length > 0) output.unprotected = sharedUnprotected;
|
|
52
|
+
if (aadEncoded !== void 0) output.aad = aadEncoded;
|
|
53
|
+
return output;
|
|
54
|
+
}
|
|
55
|
+
async function decryptMulti(jwe, keyOrLookup, options = {}) {
|
|
56
|
+
const general = _normalizeSerialization(jwe);
|
|
57
|
+
if (typeof general.iv !== "string" || typeof general.ciphertext !== "string" || typeof general.tag !== "string" || !Array.isArray(general.recipients) || general.recipients.length === 0) throw new JWTError("Invalid JWE JSON Serialization: missing iv/ciphertext/tag/recipients.", "ERR_JWE_INVALID_SERIALIZATION");
|
|
58
|
+
const protectedHeaderEncoded = general.protected ?? "";
|
|
59
|
+
const protectedHeader = decodeProtectedHeader(protectedHeaderEncoded || void 0, "JWE");
|
|
60
|
+
const enc = protectedHeader.enc;
|
|
61
|
+
if (!enc) throw new JWTError("Invalid JWE: Protected header is missing \"enc\".", "ERR_JWE_INVALID");
|
|
62
|
+
if (options.encryptionAlgorithms && !options.encryptionAlgorithms.includes(enc)) throw new JWTError(`Content encryption algorithm not allowed: ${enc}`, "ERR_JWE_ALG_NOT_ALLOWED");
|
|
63
|
+
const sharedUnprotected = sanitizeObjectCopy(general.unprotected);
|
|
64
|
+
const ivBytes = base64UrlDecode(general.iv, { returnAs: "uint8array" });
|
|
65
|
+
const ciphertextBytes = base64UrlDecode(general.ciphertext, { returnAs: "uint8array" });
|
|
66
|
+
const tagBytes = base64UrlDecode(general.tag, { returnAs: "uint8array" });
|
|
67
|
+
const contentAadBytes = textEncoder.encode(general.aad ? `${protectedHeaderEncoded}.${general.aad}` : protectedHeaderEncoded);
|
|
68
|
+
let lastError;
|
|
69
|
+
let cryptoAttempted = false;
|
|
70
|
+
for (let i = 0; i < general.recipients.length; i++) {
|
|
71
|
+
const wireRecipient = general.recipients[i];
|
|
72
|
+
const recipientHeader = sanitizeObjectCopy(wireRecipient.header);
|
|
73
|
+
_assertDisjointHeaders(protectedHeader, sharedUnprotected, recipientHeader);
|
|
74
|
+
const effective = _mergeHeaders(protectedHeader, sharedUnprotected, recipientHeader);
|
|
75
|
+
const alg = effective.alg;
|
|
76
|
+
if (!alg) {
|
|
77
|
+
lastError = new JWTError(`Recipient[${i}] has no "alg" in the effective header.`, "ERR_JWE_INVALID");
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const rawKeyMaterial = typeof keyOrLookup === "function" ? await keyOrLookup(effective, protectedHeaderEncoded) : keyOrLookup;
|
|
81
|
+
const algError = checkAlgAllowed(alg, rawKeyMaterial, options.algorithms, JWE_ALG_CTX);
|
|
82
|
+
if (algError) {
|
|
83
|
+
lastError = algError;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (options.strictRecipientMatch && !_recipientKeyMatches(effective, rawKeyMaterial)) continue;
|
|
87
|
+
const encryptedKeyBytes = base64UrlDecode(wireRecipient.encrypted_key ?? "", { returnAs: "uint8array" });
|
|
88
|
+
const unwrapKeyOpts = _buildUnwrapKeyOptions(effective, enc, options);
|
|
89
|
+
cryptoAttempted = true;
|
|
90
|
+
try {
|
|
91
|
+
const { cek, plaintextBytes } = await _unwrapAndDecrypt(alg, enc, rawKeyMaterial, encryptedKeyBytes, unwrapKeyOpts, ciphertextBytes, ivBytes, tagBytes, contentAadBytes, effective.kid);
|
|
92
|
+
const payload = decodePayloadFromBytes(plaintextBytes, options.forceUint8Array);
|
|
93
|
+
if (protectedHeader.crit || options.recognizedHeaders?.length) validateCriticalHeadersJWE(protectedHeader, options.recognizedHeaders);
|
|
94
|
+
validateJwtClaimsIfJsonPayload(payload, options);
|
|
95
|
+
const result = {
|
|
96
|
+
payload,
|
|
97
|
+
protectedHeader: {
|
|
98
|
+
...protectedHeader,
|
|
99
|
+
alg,
|
|
100
|
+
enc
|
|
101
|
+
},
|
|
102
|
+
recipientIndex: i
|
|
103
|
+
};
|
|
104
|
+
if (sharedUnprotected) result.sharedUnprotectedHeader = sharedUnprotected;
|
|
105
|
+
if (recipientHeader) result.recipientHeader = recipientHeader;
|
|
106
|
+
if (options.returnCek) {
|
|
107
|
+
result.cek = isCryptoKey(cek) ? new Uint8Array(await crypto.subtle.exportKey("raw", cek)) : cek;
|
|
108
|
+
result.aad = contentAadBytes;
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
} catch (err) {
|
|
112
|
+
if (err instanceof JWTError && err.code !== "ERR_JWE_DECRYPTION_FAILED" && err.code !== "ERR_JWK_KEY_NOT_FOUND") throw err;
|
|
113
|
+
lastError = err instanceof JWTError ? err : void 0;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (options.strictRecipientMatch && !cryptoAttempted) throw new JWTError("No recipient matched the provided key under strictRecipientMatch.", "ERR_JWE_NO_MATCHING_RECIPIENT");
|
|
117
|
+
throw lastError ?? new JWTError("JWE decryption failed.", "ERR_JWE_DECRYPTION_FAILED");
|
|
118
|
+
}
|
|
119
|
+
function generalToFlattened(jwe) {
|
|
120
|
+
if (jwe.recipients.length !== 1) throw new JWTError(`Flattened JWE Serialization requires exactly one recipient, got ${jwe.recipients.length}.`, "ERR_JWE_INVALID_SERIALIZATION");
|
|
121
|
+
const { recipients, ...rest } = jwe;
|
|
122
|
+
const [recipient] = recipients;
|
|
123
|
+
const flattened = { ...rest };
|
|
124
|
+
if (recipient.header !== void 0) flattened.header = recipient.header;
|
|
125
|
+
if (recipient.encrypted_key !== void 0) flattened.encrypted_key = recipient.encrypted_key;
|
|
126
|
+
return flattened;
|
|
127
|
+
}
|
|
128
|
+
function _resolveRecipientAlg(key, index) {
|
|
129
|
+
if (!isJWK(key)) throw new JWTError(`Recipient[${index}] key is not a JWK.`, "ERR_JWE_RECIPIENT_ALG_INFERENCE");
|
|
130
|
+
const alg = key.alg;
|
|
131
|
+
if (!alg) throw new JWTError(`Cannot infer "alg" for recipient[${index}]: the JWK has no "alg" property.`, "ERR_JWE_RECIPIENT_ALG_INFERENCE");
|
|
132
|
+
if (DIRECT_ALGS.has(alg)) throw new JWTError(`Recipient[${index}] resolves to "${alg}", which requires exactly one recipient. Use encrypt() instead.`, "ERR_JWE_ALG_FORBIDDEN_IN_MULTI");
|
|
133
|
+
return alg;
|
|
134
|
+
}
|
|
135
|
+
function _buildRecipientKeyMgmtParams(recipient, alg) {
|
|
136
|
+
const params = {};
|
|
137
|
+
if (recipient.keyManagementIV) params.iv = recipient.keyManagementIV;
|
|
138
|
+
if (recipient.p2s) params.p2s = recipient.p2s;
|
|
139
|
+
else if (alg.startsWith("PBES2")) params.p2s = secureRandomBytes(16);
|
|
140
|
+
if (recipient.p2c) params.p2c = recipient.p2c;
|
|
141
|
+
else if (alg.startsWith("PBES2")) params.p2c = 6e5;
|
|
142
|
+
if (recipient.ecdh?.partyUInfo) params.apu = recipient.ecdh.partyUInfo;
|
|
143
|
+
if (recipient.ecdh?.partyVInfo) params.apv = recipient.ecdh.partyVInfo;
|
|
144
|
+
if (recipient.ecdh?.ephemeralKey) {
|
|
145
|
+
const { epk, epkPrivateKey } = parseEphemeralKey(recipient.ecdh.ephemeralKey);
|
|
146
|
+
params.epk = epk;
|
|
147
|
+
params.epkPrivateKey = epkPrivateKey;
|
|
148
|
+
}
|
|
149
|
+
return params;
|
|
150
|
+
}
|
|
151
|
+
function _buildPerRecipientHeader(recipient, alg, keyMgmtOut) {
|
|
152
|
+
const header = {};
|
|
153
|
+
if (recipient.key.kid) header.kid = recipient.key.kid;
|
|
154
|
+
const userHeader = sanitizeObjectCopy(recipient.header);
|
|
155
|
+
if (userHeader) Object.assign(header, userHeader);
|
|
156
|
+
if (keyMgmtOut) Object.assign(header, keyMgmtOut);
|
|
157
|
+
header.alg = alg;
|
|
158
|
+
return header;
|
|
159
|
+
}
|
|
160
|
+
function _assertDisjointHeaders(protectedHeader, sharedUnprotected, recipientHeader) {
|
|
161
|
+
const seen = /* @__PURE__ */ new Set();
|
|
162
|
+
const check = (obj) => {
|
|
163
|
+
if (!obj) return;
|
|
164
|
+
for (const k of Object.keys(obj)) {
|
|
165
|
+
if (seen.has(k)) throw new JWTError(`JWE header parameter "${k}" appears in multiple header tiers; protected, shared unprotected, and per-recipient headers must be disjoint (RFC 7516 §7.2.1).`, "ERR_JWE_HEADER_PARAMS_NOT_DISJOINT");
|
|
166
|
+
seen.add(k);
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
check(protectedHeader);
|
|
170
|
+
check(sharedUnprotected);
|
|
171
|
+
check(recipientHeader);
|
|
172
|
+
}
|
|
173
|
+
function _mergeHeaders(protectedHeader, sharedUnprotected, recipientHeader) {
|
|
174
|
+
const merged = { ...protectedHeader };
|
|
175
|
+
if (sharedUnprotected) Object.assign(merged, sharedUnprotected);
|
|
176
|
+
if (recipientHeader) Object.assign(merged, recipientHeader);
|
|
177
|
+
return merged;
|
|
178
|
+
}
|
|
179
|
+
function _normalizeSerialization(input) {
|
|
180
|
+
if (!input || typeof input !== "object") throw new JWTError("Invalid JWE JSON Serialization: expected an object.", "ERR_JWE_INVALID_SERIALIZATION");
|
|
181
|
+
const obj = input;
|
|
182
|
+
if (Array.isArray(obj.recipients)) return obj;
|
|
183
|
+
if ("encrypted_key" in obj || "header" in obj) {
|
|
184
|
+
const flattened = obj;
|
|
185
|
+
const general = {
|
|
186
|
+
recipients: [{
|
|
187
|
+
...flattened.header !== void 0 ? { header: flattened.header } : {},
|
|
188
|
+
...flattened.encrypted_key !== void 0 ? { encrypted_key: flattened.encrypted_key } : {}
|
|
189
|
+
}],
|
|
190
|
+
iv: flattened.iv,
|
|
191
|
+
ciphertext: flattened.ciphertext,
|
|
192
|
+
tag: flattened.tag
|
|
193
|
+
};
|
|
194
|
+
if (flattened.protected !== void 0) general.protected = flattened.protected;
|
|
195
|
+
if (flattened.unprotected !== void 0) general.unprotected = flattened.unprotected;
|
|
196
|
+
if (flattened.aad !== void 0) general.aad = flattened.aad;
|
|
197
|
+
return general;
|
|
198
|
+
}
|
|
199
|
+
throw new JWTError("Invalid JWE JSON Serialization: missing both recipients[] and flattened header/encrypted_key.", "ERR_JWE_INVALID_SERIALIZATION");
|
|
200
|
+
}
|
|
201
|
+
function _recipientKeyMatches(effectiveHeader, keyMaterial) {
|
|
202
|
+
const headerKid = effectiveHeader.kid;
|
|
203
|
+
if (isJWKSet(keyMaterial)) return keyMaterial.keys.some((k) => !headerKid || k.kid === headerKid);
|
|
204
|
+
if (isJWK(keyMaterial)) {
|
|
205
|
+
if (headerKid && keyMaterial.kid && headerKid !== keyMaterial.kid) return false;
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
return !headerKid;
|
|
209
|
+
}
|
|
210
|
+
function _buildUnwrapKeyOptions(effectiveHeader, enc, options) {
|
|
211
|
+
const opts = {
|
|
212
|
+
enc,
|
|
213
|
+
format: enc.includes("GCM") ? "cryptokey" : "raw",
|
|
214
|
+
keyUsage: options.keyUsage || ["decrypt"]
|
|
215
|
+
};
|
|
216
|
+
if (effectiveHeader.iv) opts.iv = effectiveHeader.iv;
|
|
217
|
+
if (effectiveHeader.tag) opts.tag = effectiveHeader.tag;
|
|
218
|
+
if (effectiveHeader.p2s) opts.p2s = effectiveHeader.p2s;
|
|
219
|
+
if (effectiveHeader.p2c) opts.p2c = effectiveHeader.p2c;
|
|
220
|
+
if (effectiveHeader.epk) opts.epk = effectiveHeader.epk;
|
|
221
|
+
if (effectiveHeader.apu) opts.apu = effectiveHeader.apu;
|
|
222
|
+
if (effectiveHeader.apv) opts.apv = effectiveHeader.apv;
|
|
223
|
+
if (options.unwrappedKeyAlgorithm) opts.unwrappedKeyAlgorithm = options.unwrappedKeyAlgorithm;
|
|
224
|
+
if (options.extractable) opts.extractable = options.extractable;
|
|
225
|
+
if (options.minIterations !== void 0) opts.minIterations = options.minIterations;
|
|
226
|
+
if (options.maxIterations !== void 0) opts.maxIterations = options.maxIterations;
|
|
227
|
+
return opts;
|
|
228
|
+
}
|
|
229
|
+
async function _unwrapAndDecrypt(alg, enc, rawKeyMaterial, encryptedKeyBytes, unwrapKeyOpts, ciphertextBytes, ivBytes, tagBytes, contentAadBytes, headerKid) {
|
|
230
|
+
if (isJWKSet(rawKeyMaterial)) {
|
|
231
|
+
const candidates = getJWKsFromSet(rawKeyMaterial, buildJWKSetFilter({
|
|
232
|
+
kid: headerKid,
|
|
233
|
+
alg
|
|
234
|
+
}));
|
|
235
|
+
if (candidates.length === 0) throw new JWTError("No key found in JWK Set.", "ERR_JWK_KEY_NOT_FOUND");
|
|
236
|
+
let lastErr;
|
|
237
|
+
for (const candidate of candidates) {
|
|
238
|
+
const unwrappingKey = await importKey(candidate, {
|
|
239
|
+
alg,
|
|
240
|
+
expect: "private"
|
|
241
|
+
});
|
|
242
|
+
try {
|
|
243
|
+
const cek = await unwrapKey(alg, encryptedKeyBytes, unwrappingKey, unwrapKeyOpts);
|
|
244
|
+
return {
|
|
245
|
+
cek,
|
|
246
|
+
plaintextBytes: await decrypt$1(enc, cek, ciphertextBytes, ivBytes, tagBytes, contentAadBytes)
|
|
247
|
+
};
|
|
248
|
+
} catch (err) {
|
|
249
|
+
if (err instanceof JWTError && err.code !== "ERR_JWE_DECRYPTION_FAILED") throw err;
|
|
250
|
+
lastErr = err;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
throw lastErr ?? new JWTError("JWE decryption failed.", "ERR_JWE_DECRYPTION_FAILED");
|
|
254
|
+
}
|
|
255
|
+
const unwrappingKey = await importKey(rawKeyMaterial, {
|
|
256
|
+
alg,
|
|
257
|
+
expect: "private"
|
|
258
|
+
});
|
|
259
|
+
try {
|
|
260
|
+
const cek = await unwrapKey(alg, encryptedKeyBytes, unwrappingKey, unwrapKeyOpts);
|
|
261
|
+
return {
|
|
262
|
+
cek,
|
|
263
|
+
plaintextBytes: await decrypt$1(enc, cek, ciphertextBytes, ivBytes, tagBytes, contentAadBytes)
|
|
264
|
+
};
|
|
265
|
+
} catch (err) {
|
|
266
|
+
if (err instanceof JWTError) throw err;
|
|
267
|
+
throw new JWTError("JWE decryption failed.", "ERR_JWE_DECRYPTION_FAILED", err);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
async function encrypt(payload, key, options = {}) {
|
|
271
|
+
const { protectedHeader: userHeader, cek: providedCek, contentEncryptionIV: providedContentIV, keyManagementIV, p2s, p2c, ecdh } = options;
|
|
272
|
+
let { alg, enc } = options;
|
|
273
|
+
if (!alg) {
|
|
274
|
+
if (typeof key === "string" || key instanceof Uint8Array) alg = "PBES2-HS256+A128KW";
|
|
275
|
+
else if (isJWK(key)) alg = key.alg;
|
|
276
|
+
}
|
|
277
|
+
if (!alg) throw new JWTError("JWE \"alg\" (Key Management Algorithm) must be provided in options or inferable from the key", "ERR_JWE_ALG_MISSING");
|
|
278
|
+
if (!enc) if (alg === "dir") {
|
|
279
|
+
enc = isJWK(key) && "enc" in key ? key.enc : void 0;
|
|
280
|
+
if (!enc) throw new JWTError("JWE \"enc\" must be provided when alg is \"dir\"", "ERR_JWE_ENC_MISSING");
|
|
281
|
+
} else enc = isJWK(key) && "enc" in key ? key.enc : "A128GCM";
|
|
282
|
+
const jweKeyManagementParams = {};
|
|
283
|
+
if (keyManagementIV) jweKeyManagementParams.iv = keyManagementIV;
|
|
284
|
+
if (p2s) jweKeyManagementParams.p2s = p2s;
|
|
285
|
+
else if (alg?.startsWith("PBES2")) jweKeyManagementParams.p2s = secureRandomBytes(16);
|
|
286
|
+
if (p2c) jweKeyManagementParams.p2c = p2c;
|
|
287
|
+
else if (alg?.startsWith("PBES2")) jweKeyManagementParams.p2c = 6e5;
|
|
288
|
+
if (ecdh?.partyUInfo) jweKeyManagementParams.apu = ecdh.partyUInfo;
|
|
289
|
+
if (ecdh?.partyVInfo) jweKeyManagementParams.apv = ecdh.partyVInfo;
|
|
290
|
+
if (ecdh?.ephemeralKey) {
|
|
291
|
+
const parsed = parseEphemeralKey(ecdh.ephemeralKey);
|
|
292
|
+
jweKeyManagementParams.epk = parsed.epk;
|
|
293
|
+
jweKeyManagementParams.epkPrivateKey = parsed.epkPrivateKey;
|
|
294
|
+
}
|
|
295
|
+
const wrappingKeyMaterial = await importKey(key, {
|
|
296
|
+
alg,
|
|
297
|
+
expect: "public"
|
|
298
|
+
});
|
|
299
|
+
const { cek: finalCek, encryptedKey: jweEncryptedKey, parameters: keyManagementHeaderParams } = await encryptKey(alg, enc, wrappingKeyMaterial, providedCek, jweKeyManagementParams);
|
|
300
|
+
const protectedHeader = _buildJWEHeader(alg, enc, key, userHeader, keyManagementHeaderParams, payload);
|
|
301
|
+
const plaintextBytes = getPlaintextBytes(computeJwtTimeClaims(payload, options) || payload);
|
|
302
|
+
const protectedHeaderEncoded = base64UrlEncode(JSON.stringify(protectedHeader));
|
|
303
|
+
const aadBytes = textEncoder.encode(protectedHeaderEncoded);
|
|
304
|
+
const contentIVBytes = providedContentIV ?? generateIV(enc);
|
|
305
|
+
const { ciphertext: contentCiphertext, tag: contentAuthTag, iv: actualContentIV } = await encrypt$1(enc, plaintextBytes, finalCek, contentIVBytes, aadBytes);
|
|
306
|
+
if (!actualContentIV) throw new JWTError("Content encryption IV was not generated or returned.", "ERR_JWE_INVALID");
|
|
307
|
+
if (!contentAuthTag) throw new JWTError("Content encryption auth tag was not generated or returned.", "ERR_JWE_INVALID");
|
|
308
|
+
return [
|
|
309
|
+
protectedHeaderEncoded,
|
|
310
|
+
jweEncryptedKey ? base64UrlEncode(jweEncryptedKey) : "",
|
|
311
|
+
base64UrlEncode(actualContentIV),
|
|
312
|
+
base64UrlEncode(contentCiphertext),
|
|
313
|
+
base64UrlEncode(contentAuthTag)
|
|
314
|
+
].join(".");
|
|
315
|
+
}
|
|
316
|
+
async function decrypt(jwe, key, options = {}) {
|
|
317
|
+
const parts = jwe.split(".");
|
|
318
|
+
if (parts.length !== 5) throw new JWTError("Invalid JWE: Must contain five sections (RFC7516, section-3).", "ERR_JWE_INVALID");
|
|
319
|
+
const [protectedHeaderEncoded, encryptedKeyEncoded, ivEncoded, ciphertextEncoded, authTagEncoded] = parts;
|
|
320
|
+
const protectedHeader = decodeProtectedHeader(protectedHeaderEncoded, "JWE");
|
|
321
|
+
if (!protectedHeader.alg || !protectedHeader.enc) throw new JWTError("Invalid JWE: Protected header must be an object with \"alg\" and \"enc\" properties.", "ERR_JWE_INVALID");
|
|
322
|
+
const alg = protectedHeader.alg;
|
|
323
|
+
const enc = protectedHeader.enc;
|
|
324
|
+
if (options?.encryptionAlgorithms && !options.encryptionAlgorithms.includes(enc)) throw new JWTError(`Content encryption algorithm not allowed: ${enc}`, "ERR_JWE_ALG_NOT_ALLOWED");
|
|
325
|
+
const rawKeyMaterial = typeof key === "function" ? await key(protectedHeader, jwe) : key;
|
|
326
|
+
const algError = checkAlgAllowed(alg, rawKeyMaterial, options?.algorithms, JWE_ALG_CTX);
|
|
327
|
+
if (algError) throw algError;
|
|
328
|
+
const encryptedKeyBytes = base64UrlDecode(encryptedKeyEncoded, { returnAs: "uint8array" });
|
|
329
|
+
const contentIVBytes = base64UrlDecode(ivEncoded, { returnAs: "uint8array" });
|
|
330
|
+
const contentAuthTagBytes = base64UrlDecode(authTagEncoded, { returnAs: "uint8array" });
|
|
331
|
+
const ciphertextBytes = base64UrlDecode(ciphertextEncoded, { returnAs: "uint8array" });
|
|
332
|
+
const unwrapKeyOpts = {
|
|
333
|
+
enc,
|
|
334
|
+
format: enc.includes("GCM") ? "cryptokey" : "raw",
|
|
335
|
+
keyUsage: options?.keyUsage || ["decrypt"]
|
|
336
|
+
};
|
|
337
|
+
if (protectedHeader.iv) unwrapKeyOpts.iv = protectedHeader.iv;
|
|
338
|
+
if (protectedHeader.tag) unwrapKeyOpts.tag = protectedHeader.tag;
|
|
339
|
+
if (protectedHeader.p2s) unwrapKeyOpts.p2s = protectedHeader.p2s;
|
|
340
|
+
if (protectedHeader.p2c) unwrapKeyOpts.p2c = protectedHeader.p2c;
|
|
341
|
+
if (protectedHeader.epk) unwrapKeyOpts.epk = protectedHeader.epk;
|
|
342
|
+
if (protectedHeader.apu) unwrapKeyOpts.apu = protectedHeader.apu;
|
|
343
|
+
if (protectedHeader.apv) unwrapKeyOpts.apv = protectedHeader.apv;
|
|
344
|
+
if (options?.unwrappedKeyAlgorithm) unwrapKeyOpts.unwrappedKeyAlgorithm = options.unwrappedKeyAlgorithm;
|
|
345
|
+
if (options?.extractable) unwrapKeyOpts.extractable = options.extractable;
|
|
346
|
+
if (options?.minIterations !== void 0) unwrapKeyOpts.minIterations = options.minIterations;
|
|
347
|
+
if (options?.maxIterations !== void 0) unwrapKeyOpts.maxIterations = options.maxIterations;
|
|
348
|
+
const aadBytes = textEncoder.encode(protectedHeaderEncoded);
|
|
349
|
+
let cek;
|
|
350
|
+
let plaintextBytes;
|
|
351
|
+
if (isJWKSet(rawKeyMaterial)) {
|
|
352
|
+
const candidates = getJWKsFromSet(rawKeyMaterial, buildJWKSetFilter(protectedHeader));
|
|
353
|
+
if (candidates.length === 0) throw new JWTError(`No key found in JWK Set${protectedHeader.kid ? ` with kid "${protectedHeader.kid}"` : ""}.`, "ERR_JWK_KEY_NOT_FOUND");
|
|
354
|
+
let decrypted = false;
|
|
355
|
+
for (const candidate of candidates) {
|
|
356
|
+
const unwrappingKey = await importKey(candidate, {
|
|
357
|
+
alg,
|
|
358
|
+
expect: "private"
|
|
359
|
+
});
|
|
360
|
+
try {
|
|
361
|
+
const candidateCek = await unwrapKey(alg, encryptedKeyBytes, unwrappingKey, unwrapKeyOpts);
|
|
362
|
+
plaintextBytes = await decrypt$1(enc, candidateCek, ciphertextBytes, contentIVBytes, contentAuthTagBytes, aadBytes);
|
|
363
|
+
cek = candidateCek;
|
|
364
|
+
decrypted = true;
|
|
365
|
+
break;
|
|
366
|
+
} catch (err) {
|
|
367
|
+
if (err instanceof JWTError && err.code !== "ERR_JWE_DECRYPTION_FAILED") throw err;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (!decrypted) throw new JWTError("JWE decryption failed.", "ERR_JWE_DECRYPTION_FAILED");
|
|
371
|
+
} else {
|
|
372
|
+
const unwrappingKey = await importKey(rawKeyMaterial, {
|
|
373
|
+
alg,
|
|
374
|
+
expect: "private"
|
|
375
|
+
});
|
|
376
|
+
try {
|
|
377
|
+
cek = await unwrapKey(alg, encryptedKeyBytes, unwrappingKey, unwrapKeyOpts);
|
|
378
|
+
plaintextBytes = await decrypt$1(enc, cek, ciphertextBytes, contentIVBytes, contentAuthTagBytes, aadBytes);
|
|
379
|
+
} catch (error_) {
|
|
380
|
+
if (error_ instanceof JWTError) throw error_;
|
|
381
|
+
throw new JWTError("JWE decryption failed.", "ERR_JWE_DECRYPTION_FAILED", error_);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
const payload = decodePayloadFromBytes(plaintextBytes, options?.forceUint8Array);
|
|
385
|
+
if (protectedHeader.crit || options?.recognizedHeaders?.length) validateCriticalHeadersJWE(protectedHeader, options?.recognizedHeaders);
|
|
386
|
+
validateJwtClaimsIfJsonPayload(payload, options);
|
|
387
|
+
const result = {
|
|
388
|
+
payload,
|
|
389
|
+
protectedHeader
|
|
390
|
+
};
|
|
391
|
+
if (options.returnCek) {
|
|
392
|
+
result.cek = isCryptoKey(cek) ? new Uint8Array(await crypto.subtle.exportKey("raw", cek)) : cek;
|
|
393
|
+
result.aad = aadBytes;
|
|
394
|
+
}
|
|
395
|
+
return result;
|
|
396
|
+
}
|
|
397
|
+
function _buildJWEHeader(alg, enc, key, userHeader, keyManagementParams, payload) {
|
|
398
|
+
const safeHeader = sanitizeObjectCopy(userHeader);
|
|
399
|
+
const header = {};
|
|
400
|
+
if (isJWK(key) && key.kid) header.kid = key.kid;
|
|
401
|
+
if (safeHeader) Object.assign(header, safeHeader);
|
|
402
|
+
if (keyManagementParams) Object.assign(header, keyManagementParams);
|
|
403
|
+
header.alg = alg;
|
|
404
|
+
header.enc = enc;
|
|
405
|
+
return applyTypCtyDefaults(header, payload);
|
|
406
|
+
}
|
|
407
|
+
export { generalToFlattened as a, encryptMulti as i, encrypt as n, decryptMulti as r, decrypt as t };
|