unjwt 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/THIRD-PARTY-LICENSES.md.gz +0 -0
- package/dist/_chunks/_codec-jwe.d.mts +170 -0
- package/dist/_chunks/_codec-jws.mjs +878 -0
- package/dist/_chunks/_internal.d.mts +7 -0
- package/dist/_chunks/_jwt-internal.mjs +388 -0
- package/dist/_chunks/algorithms.mjs +186 -0
- package/dist/_chunks/index.d.mts +512 -818
- package/dist/_chunks/index2.d.mts +11 -0
- package/dist/_chunks/index3.d.mts +108 -0
- package/dist/_chunks/jwe.d.mts +43 -66
- package/dist/_chunks/jwe.mjs +384 -252
- package/dist/_chunks/jwk.mjs +1390 -622
- package/dist/_chunks/jws.d.mts +48 -103
- package/dist/_chunks/jws.mjs +372 -184
- package/dist/_chunks/jwt.d.mts +56 -0
- package/dist/_chunks/libs/unsecure.d.mts +164 -85
- package/dist/_chunks/libs/unsecure.mjs +390 -123
- package/dist/adapters/elysia/index.d.mts +33 -171
- package/dist/adapters/elysia/index.mjs +106 -575
- package/dist/adapters/h3v1/index.d.mts +59 -344
- package/dist/adapters/h3v1/index.mjs +114 -619
- package/dist/adapters/h3v2/index.d.mts +58 -326
- package/dist/adapters/h3v2/index.mjs +108 -611
- package/dist/core/internal.d.mts +2 -0
- package/dist/core/internal.mjs +1 -0
- package/dist/core/jwe.d.mts +4 -2
- package/dist/core/jwe.mjs +4 -2
- package/dist/core/jwk.d.mts +183 -371
- package/dist/core/jwk.mjs +4 -3
- package/dist/core/jws.d.mts +4 -2
- package/dist/core/jws.mjs +4 -2
- package/dist/core/utils/index.d.mts +5 -155
- package/dist/core/utils/index.mjs +12 -3
- package/dist/index.d.mts +8 -4
- package/dist/index.mjs +6 -3
- package/package.json +39 -36
- package/dist/_chunks/_internal.mjs +0 -88
- package/dist/_chunks/utils.mjs +0 -390
package/dist/_chunks/jwe.mjs
CHANGED
|
@@ -1,122 +1,318 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { JWE_ALG_CTX, buildJWKSetFilter, checkAlgAllowed, decodeProtectedHeader,
|
|
5
|
-
|
|
1
|
+
import { sanitizeObjectCopy, secureRandomBytes, textEncoder } from "./libs/unsecure.mjs";
|
|
2
|
+
import { JWTError, applyTypDefault, base64UrlStringify, computeJwtTimeClaims, getPlaintextBytes, isJWK, isJWKSet, validateCriticalHeadersJWE } from "./_jwt-internal.mjs";
|
|
3
|
+
import { isContentEncryptionAlgorithm } from "./algorithms.mjs";
|
|
4
|
+
import { DEFAULT_PBES2_ITERATIONS, JWE_ALG_CTX, JWE_DIRECT_ALGS, asDecryptionError, asKeyLookupError, assertCompactToken, assertConsumerOptions, assertDirectKeyRole, assertEntryLimit, assertJWEAlgSupported, assertJWEEncSupported, assertJWKSetCandidates, assertNoZipOption, assertOptionEntry, assertPBES2Params, assertProducerCEK, assertProducerCrit, assertProducerIV, assertProtectedOnlyOption, assertSerializationObject, buildECDHAgreementParams, buildJWKSetFilter, buildUnwrapKeyOptions, checkAlgAllowed, checkJWEAlgSupported, checkJWEContentSegments, checkJWEDirectEncryptedKey, checkJWEEncAllowed, checkJWEEncSupported, checkJWEUnprotectedCrit, checkJWEZip, checkProtectedTyp, checkSerializationEntry, createPBES2Budget, decodeJWEPayload, decodeProtectedHeader, decodeSegment, decrypt as decrypt$1, encrypt as encrypt$1, encryptKey, exportReturnedCek, generateCEK, generateIV, getJWKsFromSet, importKey, isJWEAlgSupported, keyMatchesHeader, resolveKeyInput, resolveLimit, resolvePasswordKey, resolvePayloadMode, serializeProtectedHeader, unwrapKey, validateClaimsIfJson } from "./jwk.mjs";
|
|
5
|
+
function buildKeyManagementParams(alg, source) {
|
|
6
|
+
const { keyManagementIV, p2s, p2c, ecdh } = source;
|
|
7
|
+
const isPBES2 = alg.startsWith("PBES2");
|
|
8
|
+
const params = buildECDHAgreementParams(ecdh);
|
|
9
|
+
if (keyManagementIV) params.iv = keyManagementIV;
|
|
10
|
+
if (p2s !== void 0) params.p2s = p2s;
|
|
11
|
+
else if (isPBES2) params.p2s = secureRandomBytes(16);
|
|
12
|
+
if (p2c !== void 0) params.p2c = p2c;
|
|
13
|
+
else if (isPBES2) params.p2c = DEFAULT_PBES2_ITERATIONS;
|
|
14
|
+
return params;
|
|
15
|
+
}
|
|
16
|
+
async function unwrapAndDecrypt(alg, rawKeyMaterial, options) {
|
|
17
|
+
const { enc, encryptedKey, unwrapKeyOptions, ciphertext, iv, tag, aad, kid, allowSymmetric, keyCandidates, pbes2Budget } = options;
|
|
18
|
+
if (isJWKSet(rawKeyMaterial)) {
|
|
19
|
+
const candidates = getJWKsFromSet(rawKeyMaterial, buildJWKSetFilter({
|
|
20
|
+
kid,
|
|
21
|
+
alg
|
|
22
|
+
}, {
|
|
23
|
+
op: "enc",
|
|
24
|
+
allowSymmetric
|
|
25
|
+
}));
|
|
26
|
+
assertJWKSetCandidates(candidates, {
|
|
27
|
+
limit: keyCandidates,
|
|
28
|
+
kid
|
|
29
|
+
});
|
|
30
|
+
const attempts = [];
|
|
31
|
+
for (const candidate of candidates) try {
|
|
32
|
+
if (alg === "dir") assertDirectKeyRole(candidate, enc);
|
|
33
|
+
pbes2Budget.spend(unwrapKeyOptions);
|
|
34
|
+
const unwrappingKey = await importKey(candidate, {
|
|
35
|
+
alg,
|
|
36
|
+
expect: "private"
|
|
37
|
+
});
|
|
38
|
+
const cek = await unwrapKey(encryptedKey, unwrappingKey, unwrapKeyOptions);
|
|
39
|
+
return {
|
|
40
|
+
cek,
|
|
41
|
+
plaintextBytes: await decrypt$1(enc, cek, ciphertext, iv, tag, aad)
|
|
42
|
+
};
|
|
43
|
+
} catch (err) {
|
|
44
|
+
attempts.push(asDecryptionError(err));
|
|
45
|
+
}
|
|
46
|
+
throw new JWTError("JWE decryption failed.", "ERR_JWE_DECRYPTION_FAILED", attempts);
|
|
47
|
+
}
|
|
48
|
+
if (alg === "dir") assertDirectKeyRole(rawKeyMaterial, enc);
|
|
49
|
+
pbes2Budget.spend(unwrapKeyOptions);
|
|
50
|
+
try {
|
|
51
|
+
const unwrappingKey = await importKey(resolvePasswordKey(alg, rawKeyMaterial), {
|
|
52
|
+
alg,
|
|
53
|
+
expect: "private"
|
|
54
|
+
});
|
|
55
|
+
const cek = await unwrapKey(encryptedKey, unwrappingKey, unwrapKeyOptions);
|
|
56
|
+
return {
|
|
57
|
+
cek,
|
|
58
|
+
plaintextBytes: await decrypt$1(enc, cek, ciphertext, iv, tag, aad)
|
|
59
|
+
};
|
|
60
|
+
} catch (err) {
|
|
61
|
+
throw asDecryptionError(err);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
6
64
|
async function encryptMulti(payload, recipients, options = {}) {
|
|
7
65
|
if (!Array.isArray(recipients) || recipients.length === 0) throw new JWTError("encryptMulti requires at least one recipient.", "ERR_JWE_INVALID");
|
|
66
|
+
assertOptionEntry(options, {
|
|
67
|
+
label: "options",
|
|
68
|
+
objects: ["protectedHeader", "sharedUnprotectedHeader"]
|
|
69
|
+
});
|
|
70
|
+
_assertAADOption(options.aad);
|
|
71
|
+
assertNoZipOption(options.protectedHeader, "protected");
|
|
72
|
+
assertNoZipOption(options.sharedUnprotectedHeader, "shared unprotected");
|
|
73
|
+
assertProtectedOnlyOption(options.sharedUnprotectedHeader, {
|
|
74
|
+
tier: "shared unprotected",
|
|
75
|
+
family: "JWE"
|
|
76
|
+
});
|
|
77
|
+
for (const [index, recipient] of recipients.entries()) {
|
|
78
|
+
assertOptionEntry(recipient, {
|
|
79
|
+
label: `recipients[${index}]`,
|
|
80
|
+
objects: ["header"]
|
|
81
|
+
});
|
|
82
|
+
assertNoZipOption(recipient.header, "per-recipient");
|
|
83
|
+
assertProtectedOnlyOption(recipient.header, {
|
|
84
|
+
tier: "per-recipient",
|
|
85
|
+
family: "JWE"
|
|
86
|
+
});
|
|
87
|
+
}
|
|
8
88
|
const resolvedAlgs = recipients.map((r, i) => _resolveRecipientAlg(r.key, i));
|
|
9
89
|
const enc = options.enc ?? "A256GCM";
|
|
90
|
+
const encError = checkJWEEncSupported(enc);
|
|
91
|
+
if (encError) throw encError;
|
|
92
|
+
assertProducerCEK(options.cek, enc);
|
|
93
|
+
assertProducerIV(options.contentEncryptionIV, enc);
|
|
94
|
+
for (const [index, recipient] of recipients.entries()) if (resolvedAlgs[index].startsWith("PBES2")) assertPBES2Params({
|
|
95
|
+
p2s: recipient.p2s,
|
|
96
|
+
p2c: recipient.p2c
|
|
97
|
+
});
|
|
10
98
|
const userProtected = sanitizeObjectCopy(options.protectedHeader);
|
|
11
99
|
const sharedUnprotected = sanitizeObjectCopy(options.sharedUnprotectedHeader);
|
|
12
100
|
const protectedHeader = {};
|
|
13
101
|
if (userProtected) Object.assign(protectedHeader, userProtected);
|
|
14
102
|
protectedHeader.enc = enc;
|
|
15
|
-
|
|
16
|
-
|
|
103
|
+
applyTypDefault(protectedHeader, payload);
|
|
104
|
+
assertProducerCrit(protectedHeader, "JWE");
|
|
105
|
+
const computedPayload = computeJwtTimeClaims(payload, options);
|
|
106
|
+
const plaintextBytes = getPlaintextBytes(computedPayload || payload);
|
|
17
107
|
const cek = options.cek ?? generateCEK(enc);
|
|
18
108
|
const wireRecipients = [];
|
|
19
109
|
for (let i = 0; i < recipients.length; i++) {
|
|
20
110
|
const recipient = recipients[i];
|
|
21
111
|
const alg = resolvedAlgs[i];
|
|
22
|
-
const keyMgmtParams =
|
|
23
|
-
const
|
|
112
|
+
const keyMgmtParams = buildKeyManagementParams(alg, recipient);
|
|
113
|
+
const wrappingKey = await importKey(recipient.key, {
|
|
24
114
|
alg,
|
|
25
115
|
expect: "public"
|
|
26
|
-
})
|
|
116
|
+
});
|
|
117
|
+
const { encryptedKey, parameters: keyMgmtOut } = await encryptKey(alg, enc, wrappingKey, cek, keyMgmtParams);
|
|
27
118
|
const perRecipientHeader = _buildPerRecipientHeader(recipient, alg, keyMgmtOut);
|
|
28
119
|
_assertDisjointHeaders(protectedHeader, sharedUnprotected, perRecipientHeader);
|
|
29
120
|
const wireRecipient = {};
|
|
30
121
|
if (Object.keys(perRecipientHeader).length > 0) wireRecipient.header = perRecipientHeader;
|
|
31
|
-
if (encryptedKey) wireRecipient.encrypted_key =
|
|
122
|
+
if (encryptedKey) wireRecipient.encrypted_key = base64UrlStringify(encryptedKey);
|
|
32
123
|
wireRecipients.push(wireRecipient);
|
|
33
124
|
}
|
|
34
|
-
const protectedHeaderEncoded =
|
|
125
|
+
const protectedHeaderEncoded = base64UrlStringify(serializeProtectedHeader(protectedHeader));
|
|
35
126
|
let aadEncoded;
|
|
36
127
|
let aadForCipher = protectedHeaderEncoded;
|
|
37
|
-
|
|
38
|
-
|
|
128
|
+
const aadBytes = typeof options.aad === "string" ? textEncoder.encode(options.aad) : options.aad;
|
|
129
|
+
if (aadBytes !== void 0 && aadBytes.length > 0) {
|
|
130
|
+
aadEncoded = base64UrlStringify(aadBytes);
|
|
39
131
|
aadForCipher = `${protectedHeaderEncoded}.${aadEncoded}`;
|
|
40
132
|
}
|
|
41
133
|
const contentAadBytes = textEncoder.encode(aadForCipher);
|
|
42
|
-
const
|
|
43
|
-
|
|
134
|
+
const contentIVBytes = options.contentEncryptionIV ?? generateIV(enc);
|
|
135
|
+
const { ciphertext: contentCiphertext, tag: contentAuthTag, iv: actualContentIV } = await encrypt$1(enc, plaintextBytes, cek, contentIVBytes, contentAadBytes);
|
|
44
136
|
const output = {
|
|
45
137
|
protected: protectedHeaderEncoded,
|
|
46
138
|
recipients: wireRecipients,
|
|
47
|
-
iv:
|
|
48
|
-
ciphertext:
|
|
49
|
-
tag:
|
|
139
|
+
iv: base64UrlStringify(actualContentIV),
|
|
140
|
+
ciphertext: base64UrlStringify(contentCiphertext),
|
|
141
|
+
tag: base64UrlStringify(contentAuthTag)
|
|
50
142
|
};
|
|
51
143
|
if (sharedUnprotected && Object.keys(sharedUnprotected).length > 0) output.unprotected = sharedUnprotected;
|
|
52
144
|
if (aadEncoded !== void 0) output.aad = aadEncoded;
|
|
53
145
|
return output;
|
|
54
146
|
}
|
|
55
147
|
async function decryptMulti(jwe, keyOrLookup, options = {}) {
|
|
148
|
+
assertConsumerOptions(options);
|
|
149
|
+
const payloadMode = resolvePayloadMode(options);
|
|
150
|
+
const recipientLimit = resolveLimit(options.limits, "recipients");
|
|
151
|
+
const keyCandidates = resolveLimit(options.limits, "keyCandidates");
|
|
152
|
+
const pbes2Budget = createPBES2Budget(resolveLimit(options.limits, "pbes2Iterations"));
|
|
56
153
|
const general = _normalizeSerialization(jwe);
|
|
57
154
|
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");
|
|
155
|
+
assertEntryLimit(general.recipients.length, recipientLimit, {
|
|
156
|
+
family: "JWE",
|
|
157
|
+
what: "recipients"
|
|
158
|
+
});
|
|
159
|
+
const envelopeError = checkSerializationEntry(general, {
|
|
160
|
+
family: "JWE",
|
|
161
|
+
label: "JSON Serialization",
|
|
162
|
+
objects: ["unprotected"],
|
|
163
|
+
strings: ["protected", "aad"]
|
|
164
|
+
});
|
|
165
|
+
if (envelopeError) throw envelopeError;
|
|
58
166
|
const protectedHeaderEncoded = general.protected ?? "";
|
|
59
167
|
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
168
|
const sharedUnprotected = sanitizeObjectCopy(general.unprotected);
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const
|
|
169
|
+
const sharedError = checkJWEZip(protectedHeader) ?? checkJWEUnprotectedCrit(sharedUnprotected) ?? checkJWEZip(sharedUnprotected);
|
|
170
|
+
if (sharedError) throw sharedError;
|
|
171
|
+
_assertDisjointHeaders(protectedHeader, sharedUnprotected, void 0);
|
|
172
|
+
const enc = protectedHeader.enc ?? sharedUnprotected?.enc;
|
|
173
|
+
if (!enc) throw new JWTError("Invalid JWE: \"enc\" is missing from the protected and shared unprotected headers.", "ERR_JWE_INVALID");
|
|
174
|
+
assertJWEEncSupported(enc);
|
|
175
|
+
const encError = checkJWEEncAllowed(enc, options.encryptionAlgorithms);
|
|
176
|
+
if (encError) throw encError;
|
|
177
|
+
validateCriticalHeadersJWE(protectedHeader, options.recognizedHeaders);
|
|
178
|
+
const typError = checkProtectedTyp(options.typ, protectedHeader, { family: "JWE" });
|
|
179
|
+
if (typError) throw typError;
|
|
180
|
+
const ivBytes = decodeSegment(general.iv, {
|
|
181
|
+
family: "JWE",
|
|
182
|
+
what: "Initialization vector"
|
|
183
|
+
});
|
|
184
|
+
const ciphertextBytes = decodeSegment(general.ciphertext, {
|
|
185
|
+
family: "JWE",
|
|
186
|
+
what: "Ciphertext"
|
|
187
|
+
});
|
|
188
|
+
const tagBytes = decodeSegment(general.tag, {
|
|
189
|
+
family: "JWE",
|
|
190
|
+
what: "Authentication tag"
|
|
191
|
+
});
|
|
192
|
+
const segmentError = checkJWEContentSegments(enc, ivBytes, tagBytes);
|
|
193
|
+
if (segmentError) throw segmentError;
|
|
194
|
+
const contentAadBytes = textEncoder.encode(general.aad === void 0 ? protectedHeaderEncoded : `${protectedHeaderEncoded}.${general.aad}`);
|
|
68
195
|
let lastError;
|
|
69
196
|
let cryptoAttempted = false;
|
|
70
197
|
for (let i = 0; i < general.recipients.length; i++) {
|
|
198
|
+
const shapeError = checkSerializationEntry(general.recipients[i], {
|
|
199
|
+
family: "JWE",
|
|
200
|
+
label: `Recipient[${i}]`,
|
|
201
|
+
objects: ["header"],
|
|
202
|
+
strings: ["encrypted_key"]
|
|
203
|
+
});
|
|
204
|
+
if (shapeError) {
|
|
205
|
+
lastError = shapeError;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
71
208
|
const wireRecipient = general.recipients[i];
|
|
72
209
|
const recipientHeader = sanitizeObjectCopy(wireRecipient.header);
|
|
73
|
-
|
|
210
|
+
const recipientError = checkJWEUnprotectedCrit(recipientHeader) ?? checkJWEZip(recipientHeader);
|
|
211
|
+
if (recipientError) {
|
|
212
|
+
lastError = recipientError;
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
_assertDisjointHeaders(protectedHeader, sharedUnprotected, recipientHeader);
|
|
217
|
+
} catch (err) {
|
|
218
|
+
lastError = asDecryptionError(err);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
74
221
|
const effective = _mergeHeaders(protectedHeader, sharedUnprotected, recipientHeader);
|
|
75
222
|
const alg = effective.alg;
|
|
76
223
|
if (!alg) {
|
|
77
224
|
lastError = new JWTError(`Recipient[${i}] has no "alg" in the effective header.`, "ERR_JWE_INVALID");
|
|
78
225
|
continue;
|
|
79
226
|
}
|
|
80
|
-
|
|
81
|
-
|
|
227
|
+
if (!isJWEAlgSupported(alg)) {
|
|
228
|
+
lastError = checkJWEAlgSupported(alg);
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
const directKeyError = checkJWEDirectEncryptedKey(alg, wireRecipient.encrypted_key);
|
|
232
|
+
if (directKeyError) {
|
|
233
|
+
lastError = directKeyError;
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
let rawKeyMaterial;
|
|
237
|
+
try {
|
|
238
|
+
rawKeyMaterial = await resolveKeyInput(keyOrLookup, {
|
|
239
|
+
header: effective,
|
|
240
|
+
token: protectedHeaderEncoded,
|
|
241
|
+
what: `recipient[${i}]`
|
|
242
|
+
});
|
|
243
|
+
} catch (err) {
|
|
244
|
+
lastError = asKeyLookupError(err, `recipient[${i}]`);
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
const algError = checkAlgAllowed(alg, rawKeyMaterial, {
|
|
248
|
+
ctx: JWE_ALG_CTX,
|
|
249
|
+
algorithms: options.algorithms,
|
|
250
|
+
allowSymmetric: options.allowSymmetricJWKSet
|
|
251
|
+
});
|
|
82
252
|
if (algError) {
|
|
83
253
|
lastError = algError;
|
|
84
254
|
continue;
|
|
85
255
|
}
|
|
86
|
-
if (options.strictRecipientMatch && !
|
|
87
|
-
|
|
88
|
-
|
|
256
|
+
if (options.strictRecipientMatch && !keyMatchesHeader(effective, rawKeyMaterial, {
|
|
257
|
+
ctx: JWE_ALG_CTX,
|
|
258
|
+
allowSymmetric: options.allowSymmetricJWKSet
|
|
259
|
+
})) continue;
|
|
260
|
+
let encryptedKeyBytes;
|
|
261
|
+
try {
|
|
262
|
+
encryptedKeyBytes = decodeSegment(wireRecipient.encrypted_key ?? "", {
|
|
263
|
+
family: "JWE",
|
|
264
|
+
what: "Encrypted key"
|
|
265
|
+
});
|
|
266
|
+
} catch (err) {
|
|
267
|
+
lastError = asDecryptionError(err);
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
const unwrapKeyOpts = buildUnwrapKeyOptions(effective, {
|
|
271
|
+
alg,
|
|
272
|
+
enc
|
|
273
|
+
}, options);
|
|
89
274
|
cryptoAttempted = true;
|
|
275
|
+
let cek;
|
|
276
|
+
let plaintextBytes;
|
|
90
277
|
try {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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;
|
|
278
|
+
({cek, plaintextBytes} = await unwrapAndDecrypt(alg, rawKeyMaterial, {
|
|
279
|
+
enc,
|
|
280
|
+
encryptedKey: encryptedKeyBytes,
|
|
281
|
+
unwrapKeyOptions: unwrapKeyOpts,
|
|
282
|
+
ciphertext: ciphertextBytes,
|
|
283
|
+
iv: ivBytes,
|
|
284
|
+
tag: tagBytes,
|
|
285
|
+
aad: contentAadBytes,
|
|
286
|
+
kid: effective.kid,
|
|
287
|
+
allowSymmetric: options.allowSymmetricJWKSet,
|
|
288
|
+
keyCandidates,
|
|
289
|
+
pbes2Budget
|
|
290
|
+
}));
|
|
111
291
|
} catch (err) {
|
|
112
|
-
|
|
113
|
-
|
|
292
|
+
lastError = asDecryptionError(err);
|
|
293
|
+
continue;
|
|
114
294
|
}
|
|
295
|
+
const payload = decodeJWEPayload(plaintextBytes, payloadMode);
|
|
296
|
+
validateClaimsIfJson(payload, payloadMode, options);
|
|
297
|
+
const result = {
|
|
298
|
+
payload,
|
|
299
|
+
protectedHeader,
|
|
300
|
+
recipientIndex: i
|
|
301
|
+
};
|
|
302
|
+
if (sharedUnprotected) result.sharedUnprotectedHeader = sharedUnprotected;
|
|
303
|
+
if (recipientHeader) result.recipientHeader = recipientHeader;
|
|
304
|
+
if (options.returnCek) {
|
|
305
|
+
result.cek = await exportReturnedCek(cek);
|
|
306
|
+
result.aad = contentAadBytes;
|
|
307
|
+
}
|
|
308
|
+
return result;
|
|
115
309
|
}
|
|
116
310
|
if (options.strictRecipientMatch && !cryptoAttempted) throw new JWTError("No recipient matched the provided key under strictRecipientMatch.", "ERR_JWE_NO_MATCHING_RECIPIENT");
|
|
117
311
|
throw lastError ?? new JWTError("JWE decryption failed.", "ERR_JWE_DECRYPTION_FAILED");
|
|
118
312
|
}
|
|
119
313
|
function generalToFlattened(jwe) {
|
|
314
|
+
assertSerializationObject(jwe, "JWE");
|
|
315
|
+
if (!Array.isArray(jwe.recipients)) throw new JWTError("Invalid JWE JSON Serialization: missing \"recipients\".", "ERR_JWE_INVALID_SERIALIZATION");
|
|
120
316
|
if (jwe.recipients.length !== 1) throw new JWTError(`Flattened JWE Serialization requires exactly one recipient, got ${jwe.recipients.length}.`, "ERR_JWE_INVALID_SERIALIZATION");
|
|
121
317
|
const { recipients, ...rest } = jwe;
|
|
122
318
|
const [recipient] = recipients;
|
|
@@ -127,27 +323,13 @@ function generalToFlattened(jwe) {
|
|
|
127
323
|
}
|
|
128
324
|
function _resolveRecipientAlg(key, index) {
|
|
129
325
|
if (!isJWK(key)) throw new JWTError(`Recipient[${index}] key is not a JWK.`, "ERR_JWE_RECIPIENT_ALG_INFERENCE");
|
|
130
|
-
const
|
|
131
|
-
if (!
|
|
132
|
-
|
|
326
|
+
const declared = key.alg;
|
|
327
|
+
if (!declared) throw new JWTError(`Cannot infer "alg" for recipient[${index}]: the JWK has no "alg" property.`, "ERR_JWE_RECIPIENT_ALG_INFERENCE");
|
|
328
|
+
const alg = key.kty === "oct" && isContentEncryptionAlgorithm(declared) ? "dir" : declared;
|
|
329
|
+
if (JWE_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");
|
|
330
|
+
assertJWEAlgSupported(alg);
|
|
133
331
|
return alg;
|
|
134
332
|
}
|
|
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
333
|
function _buildPerRecipientHeader(recipient, alg, keyMgmtOut) {
|
|
152
334
|
const header = {};
|
|
153
335
|
if (recipient.key.kid) header.kid = recipient.key.kid;
|
|
@@ -177,219 +359,162 @@ function _mergeHeaders(protectedHeader, sharedUnprotected, recipientHeader) {
|
|
|
177
359
|
return merged;
|
|
178
360
|
}
|
|
179
361
|
function _normalizeSerialization(input) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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;
|
|
362
|
+
assertSerializationObject(input, "JWE");
|
|
363
|
+
if ("recipients" in input) {
|
|
364
|
+
if ("encrypted_key" in input || "header" in input) throw new JWTError("Invalid JWE JSON Serialization: \"recipients\" must not be combined with a top-level \"header\" or \"encrypted_key\".", "ERR_JWE_INVALID_SERIALIZATION");
|
|
365
|
+
return input;
|
|
207
366
|
}
|
|
208
|
-
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
367
|
+
const flattened = input;
|
|
368
|
+
const recipient = {};
|
|
369
|
+
if (flattened.header !== void 0) recipient.header = flattened.header;
|
|
370
|
+
if (flattened.encrypted_key !== void 0) recipient.encrypted_key = flattened.encrypted_key;
|
|
371
|
+
const general = {
|
|
372
|
+
recipients: [recipient],
|
|
373
|
+
iv: flattened.iv,
|
|
374
|
+
ciphertext: flattened.ciphertext,
|
|
375
|
+
tag: flattened.tag
|
|
215
376
|
};
|
|
216
|
-
if (
|
|
217
|
-
if (
|
|
218
|
-
if (
|
|
219
|
-
|
|
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;
|
|
377
|
+
if (flattened.protected !== void 0) general.protected = flattened.protected;
|
|
378
|
+
if (flattened.unprotected !== void 0) general.unprotected = flattened.unprotected;
|
|
379
|
+
if (flattened.aad !== void 0) general.aad = flattened.aad;
|
|
380
|
+
return general;
|
|
228
381
|
}
|
|
229
|
-
|
|
230
|
-
if (
|
|
231
|
-
|
|
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
|
-
}
|
|
382
|
+
function _assertAADOption(aad) {
|
|
383
|
+
if (aad === void 0 || typeof aad === "string" || aad instanceof Uint8Array) return;
|
|
384
|
+
throw new JWTError("\"aad\" must be a string or a Uint8Array.", "ERR_INVALID_OPTION");
|
|
269
385
|
}
|
|
270
386
|
async function encrypt(payload, key, options = {}) {
|
|
387
|
+
assertOptionEntry(options, {
|
|
388
|
+
label: "options",
|
|
389
|
+
objects: ["protectedHeader"]
|
|
390
|
+
});
|
|
271
391
|
const { protectedHeader: userHeader, cek: providedCek, contentEncryptionIV: providedContentIV, keyManagementIV, p2s, p2c, ecdh } = options;
|
|
272
|
-
let
|
|
392
|
+
let alg = options.alg;
|
|
393
|
+
let enc = options.enc;
|
|
394
|
+
assertNoZipOption(userHeader, "protected");
|
|
395
|
+
const cekRole = isJWK(key) && key.kty === "oct" && typeof key.alg === "string" ? isContentEncryptionAlgorithm(key.alg) ? key.alg : void 0 : void 0;
|
|
273
396
|
if (!alg) {
|
|
274
|
-
if (typeof key === "string"
|
|
397
|
+
if (typeof key === "string") alg = "PBES2-HS256+A128KW";
|
|
398
|
+
else if (cekRole) alg = "dir";
|
|
275
399
|
else if (isJWK(key)) alg = key.alg;
|
|
276
400
|
}
|
|
277
401
|
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)
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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;
|
|
402
|
+
if (!enc) {
|
|
403
|
+
if (alg === "dir") {
|
|
404
|
+
enc = cekRole ?? (isJWK(key) ? key.enc : void 0);
|
|
405
|
+
if (!enc) throw new JWTError("JWE \"enc\" must be provided when alg is \"dir\"", "ERR_JWE_ENC_MISSING");
|
|
406
|
+
} else enc = isJWK(key) && key.enc !== void 0 ? key.enc : "A256GCM";
|
|
294
407
|
}
|
|
295
|
-
|
|
408
|
+
assertJWEAlgSupported(alg);
|
|
409
|
+
assertJWEEncSupported(enc);
|
|
410
|
+
assertProducerCEK(providedCek, enc);
|
|
411
|
+
assertProducerIV(providedContentIV, enc);
|
|
412
|
+
if (alg.startsWith("PBES2")) assertPBES2Params({
|
|
413
|
+
p2s,
|
|
414
|
+
p2c
|
|
415
|
+
});
|
|
416
|
+
const jweKeyManagementParams = buildKeyManagementParams(alg, {
|
|
417
|
+
keyManagementIV,
|
|
418
|
+
p2s,
|
|
419
|
+
p2c,
|
|
420
|
+
ecdh
|
|
421
|
+
});
|
|
422
|
+
const wrappingKeyMaterial = await importKey(resolvePasswordKey(alg, key), {
|
|
296
423
|
alg,
|
|
297
424
|
expect: "public"
|
|
298
425
|
});
|
|
426
|
+
if (alg === "dir") assertDirectKeyRole(key, enc);
|
|
299
427
|
const { cek: finalCek, encryptedKey: jweEncryptedKey, parameters: keyManagementHeaderParams } = await encryptKey(alg, enc, wrappingKeyMaterial, providedCek, jweKeyManagementParams);
|
|
300
428
|
const protectedHeader = _buildJWEHeader(alg, enc, key, userHeader, keyManagementHeaderParams, payload);
|
|
301
|
-
const
|
|
302
|
-
const
|
|
429
|
+
const computedPayload = computeJwtTimeClaims(payload, options);
|
|
430
|
+
const plaintextBytes = getPlaintextBytes(computedPayload || payload);
|
|
431
|
+
const protectedHeaderSerialized = serializeProtectedHeader(protectedHeader);
|
|
432
|
+
const protectedHeaderEncoded = base64UrlStringify(protectedHeaderSerialized);
|
|
303
433
|
const aadBytes = textEncoder.encode(protectedHeaderEncoded);
|
|
304
434
|
const contentIVBytes = providedContentIV ?? generateIV(enc);
|
|
305
435
|
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
436
|
return [
|
|
309
437
|
protectedHeaderEncoded,
|
|
310
|
-
jweEncryptedKey ?
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
438
|
+
jweEncryptedKey ? base64UrlStringify(jweEncryptedKey) : "",
|
|
439
|
+
base64UrlStringify(actualContentIV),
|
|
440
|
+
base64UrlStringify(contentCiphertext),
|
|
441
|
+
base64UrlStringify(contentAuthTag)
|
|
314
442
|
].join(".");
|
|
315
443
|
}
|
|
316
444
|
async function decrypt(jwe, key, options = {}) {
|
|
445
|
+
assertCompactToken(jwe, "JWE");
|
|
446
|
+
assertConsumerOptions(options);
|
|
447
|
+
const payloadMode = resolvePayloadMode(options);
|
|
448
|
+
const keyCandidateLimit = resolveLimit(options.limits, "keyCandidates");
|
|
449
|
+
const pbes2Budget = createPBES2Budget(resolveLimit(options.limits, "pbes2Iterations"));
|
|
317
450
|
const parts = jwe.split(".");
|
|
318
451
|
if (parts.length !== 5) throw new JWTError("Invalid JWE: Must contain five sections (RFC7516, section-3).", "ERR_JWE_INVALID");
|
|
319
452
|
const [protectedHeaderEncoded, encryptedKeyEncoded, ivEncoded, ciphertextEncoded, authTagEncoded] = parts;
|
|
320
453
|
const protectedHeader = decodeProtectedHeader(protectedHeaderEncoded, "JWE");
|
|
321
|
-
|
|
322
|
-
const alg = protectedHeader
|
|
323
|
-
const
|
|
324
|
-
if (
|
|
325
|
-
|
|
326
|
-
const
|
|
454
|
+
assertHeaderAlgorithms(protectedHeader);
|
|
455
|
+
const { alg, enc } = protectedHeader;
|
|
456
|
+
const headerError = checkJWEZip(protectedHeader) ?? checkJWEEncAllowed(enc, options.encryptionAlgorithms);
|
|
457
|
+
if (headerError) throw headerError;
|
|
458
|
+
validateCriticalHeadersJWE(protectedHeader, options.recognizedHeaders);
|
|
459
|
+
const typError = checkProtectedTyp(options.typ, protectedHeader, { family: "JWE" });
|
|
460
|
+
if (typError) throw typError;
|
|
461
|
+
const directKeyError = checkJWEDirectEncryptedKey(alg, encryptedKeyEncoded);
|
|
462
|
+
if (directKeyError) throw directKeyError;
|
|
463
|
+
const encryptedKeyBytes = decodeSegment(encryptedKeyEncoded, {
|
|
464
|
+
family: "JWE",
|
|
465
|
+
what: "Encrypted key"
|
|
466
|
+
});
|
|
467
|
+
const contentIVBytes = decodeSegment(ivEncoded, {
|
|
468
|
+
family: "JWE",
|
|
469
|
+
what: "Initialization vector"
|
|
470
|
+
});
|
|
471
|
+
const contentAuthTagBytes = decodeSegment(authTagEncoded, {
|
|
472
|
+
family: "JWE",
|
|
473
|
+
what: "Authentication tag"
|
|
474
|
+
});
|
|
475
|
+
const ciphertextBytes = decodeSegment(ciphertextEncoded, {
|
|
476
|
+
family: "JWE",
|
|
477
|
+
what: "Ciphertext"
|
|
478
|
+
});
|
|
479
|
+
const segmentError = checkJWEContentSegments(enc, contentIVBytes, contentAuthTagBytes);
|
|
480
|
+
if (segmentError) throw segmentError;
|
|
481
|
+
const rawKeyMaterial = await resolveKeyInput(key, {
|
|
482
|
+
header: protectedHeader,
|
|
483
|
+
token: jwe,
|
|
484
|
+
what: "the JWE"
|
|
485
|
+
});
|
|
486
|
+
const algError = checkAlgAllowed(alg, rawKeyMaterial, {
|
|
487
|
+
ctx: JWE_ALG_CTX,
|
|
488
|
+
algorithms: options.algorithms,
|
|
489
|
+
allowSymmetric: options.allowSymmetricJWKSet
|
|
490
|
+
});
|
|
327
491
|
if (algError) throw algError;
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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;
|
|
492
|
+
const unwrapKeyOpts = buildUnwrapKeyOptions(protectedHeader, {
|
|
493
|
+
alg,
|
|
494
|
+
enc
|
|
495
|
+
}, options);
|
|
348
496
|
const aadBytes = textEncoder.encode(protectedHeaderEncoded);
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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);
|
|
497
|
+
const { cek, plaintextBytes } = await unwrapAndDecrypt(alg, rawKeyMaterial, {
|
|
498
|
+
enc,
|
|
499
|
+
encryptedKey: encryptedKeyBytes,
|
|
500
|
+
unwrapKeyOptions: unwrapKeyOpts,
|
|
501
|
+
ciphertext: ciphertextBytes,
|
|
502
|
+
iv: contentIVBytes,
|
|
503
|
+
tag: contentAuthTagBytes,
|
|
504
|
+
aad: aadBytes,
|
|
505
|
+
kid: protectedHeader.kid,
|
|
506
|
+
allowSymmetric: options.allowSymmetricJWKSet,
|
|
507
|
+
keyCandidates: keyCandidateLimit,
|
|
508
|
+
pbes2Budget
|
|
509
|
+
});
|
|
510
|
+
const payload = decodeJWEPayload(plaintextBytes, payloadMode);
|
|
511
|
+
validateClaimsIfJson(payload, payloadMode, options);
|
|
387
512
|
const result = {
|
|
388
513
|
payload,
|
|
389
514
|
protectedHeader
|
|
390
515
|
};
|
|
391
516
|
if (options.returnCek) {
|
|
392
|
-
result.cek =
|
|
517
|
+
result.cek = await exportReturnedCek(cek);
|
|
393
518
|
result.aad = aadBytes;
|
|
394
519
|
}
|
|
395
520
|
return result;
|
|
@@ -402,6 +527,13 @@ function _buildJWEHeader(alg, enc, key, userHeader, keyManagementParams, payload
|
|
|
402
527
|
if (keyManagementParams) Object.assign(header, keyManagementParams);
|
|
403
528
|
header.alg = alg;
|
|
404
529
|
header.enc = enc;
|
|
405
|
-
|
|
530
|
+
applyTypDefault(header, payload);
|
|
531
|
+
assertProducerCrit(header, "JWE");
|
|
532
|
+
return header;
|
|
533
|
+
}
|
|
534
|
+
function assertHeaderAlgorithms(header) {
|
|
535
|
+
if (!header.alg || !header.enc) throw new JWTError("Invalid JWE: Protected header must be an object with \"alg\" and \"enc\" properties.", "ERR_JWE_INVALID");
|
|
536
|
+
assertJWEAlgSupported(header.alg);
|
|
537
|
+
assertJWEEncSupported(header.enc);
|
|
406
538
|
}
|
|
407
539
|
export { decrypt, decryptMulti, encrypt, encryptMulti, generalToFlattened };
|