unjwt 0.8.2 → 0.8.4
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/dist/_chunks/_codec-jwe.d.mts +24 -28
- package/dist/_chunks/_codec-jws.mjs +33 -33
- package/dist/_chunks/{jwk.mjs → _import-key.mjs} +36 -741
- package/dist/_chunks/_jwt-internal.mjs +28 -25
- package/dist/_chunks/index.d.mts +5 -3
- package/dist/_chunks/jwe.d.mts +1 -0
- package/dist/_chunks/jwe.mjs +8 -7
- package/dist/_chunks/jwk.d.mts +205 -0
- package/dist/_chunks/jws.d.mts +4 -1
- package/dist/_chunks/jws.mjs +10 -10
- package/dist/_chunks/jwt.d.mts +2 -1
- package/dist/_chunks/libs/unsecure.mjs +143 -130
- package/dist/adapters/elysia/index.d.mts +1 -1
- package/dist/adapters/elysia/index.mjs +3 -2
- package/dist/adapters/h3/index.d.mts +1 -1
- package/dist/adapters/h3/index.mjs +2 -1
- package/dist/adapters/h3v1/index.d.mts +1 -1
- package/dist/adapters/h3v1/index.mjs +2 -1
- package/dist/core/jwe.mjs +1 -1
- package/dist/core/jwk.d.mts +2 -204
- package/dist/core/jwk.mjs +712 -3
- package/dist/core/jws.mjs +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +2 -1
- package/dist/session/index.d.mts +22 -22
- package/dist/session/index.mjs +22 -19
- package/package.json +3 -2
|
@@ -91,7 +91,7 @@ function concatUint8Arrays(...arrays) {
|
|
|
91
91
|
return result;
|
|
92
92
|
}
|
|
93
93
|
function uint32be(value) {
|
|
94
|
-
if (value < 0 || value >= 2 ** 32) throw new JWTError(`value must be >= 0 and <= ${2 ** 32 - 1}
|
|
94
|
+
if (value < 0 || value >= 2 ** 32) throw new JWTError(`value must be >= 0 and <= ${2 ** 32 - 1} (got ${value})`, "ERR_INVALID_OPTION");
|
|
95
95
|
return new Uint8Array([
|
|
96
96
|
value >>> 24,
|
|
97
97
|
value >>> 16,
|
|
@@ -142,7 +142,7 @@ function parseDurationSeconds(value) {
|
|
|
142
142
|
}
|
|
143
143
|
if (typeof value === "string") {
|
|
144
144
|
const match = value.match(DURATION_REGEX);
|
|
145
|
-
if (!match || !match[1]) throw new JWTError("
|
|
145
|
+
if (!match || !match[1]) throw new JWTError("Duration must be an integer or a string like '10m' or '7D'.", "ERR_INVALID_OPTION");
|
|
146
146
|
const n = Number.parseInt(match[1], 10);
|
|
147
147
|
const unit = match[2] || "s";
|
|
148
148
|
return n * TIME_CONSTANTS[unit];
|
|
@@ -155,7 +155,7 @@ function computeDurationInSeconds(duration) {
|
|
|
155
155
|
return seconds;
|
|
156
156
|
}
|
|
157
157
|
function expiredClaimMessage(exp) {
|
|
158
|
-
return `JWT "exp"
|
|
158
|
+
return `JWT "exp" is in the past (${(/* @__PURE__ */ new Date(exp * 1e3)).toISOString()}).`;
|
|
159
159
|
}
|
|
160
160
|
function validateJwtClaims(jwtClaims, options = {}) {
|
|
161
161
|
assertClaimValidationOptions(options);
|
|
@@ -171,29 +171,29 @@ function validateJwtClaims(jwtClaims, options = {}) {
|
|
|
171
171
|
for (const claimName of allRequiredClaims) if (!Object.hasOwn(jwtClaims, claimName)) missingClaims.push(claimName);
|
|
172
172
|
if (missingClaims.length > 0) throw new JWTError(`Missing required JWT Claims: ${missingClaims.join(", ")}`, "ERR_JWT_CLAIM_MISSING");
|
|
173
173
|
}
|
|
174
|
-
_assertNumericDate(jwtClaims.exp, "exp"
|
|
175
|
-
_assertNumericDate(jwtClaims.nbf, "nbf"
|
|
176
|
-
_assertNumericDate(jwtClaims.iat, "iat"
|
|
174
|
+
_assertNumericDate(jwtClaims.exp, "exp");
|
|
175
|
+
_assertNumericDate(jwtClaims.nbf, "nbf");
|
|
176
|
+
_assertNumericDate(jwtClaims.iat, "iat");
|
|
177
177
|
if (options.issuer !== void 0) {
|
|
178
178
|
const expectedIssuers = maybeArray(options.issuer);
|
|
179
|
-
if (!jwtClaims.iss || !expectedIssuers.includes(jwtClaims.iss)) throw new JWTError(`Invalid JWT "iss"
|
|
179
|
+
if (!jwtClaims.iss || !expectedIssuers.includes(jwtClaims.iss)) throw new JWTError(`Invalid JWT "iss": expected ${expectedIssuers.join(" or ")}, got ${jwtClaims.iss}`, "ERR_JWT_CLAIM_INVALID");
|
|
180
180
|
}
|
|
181
|
-
if (options.subject !== void 0 && jwtClaims.sub !== options.subject) throw new JWTError(`Invalid JWT "sub"
|
|
181
|
+
if (options.subject !== void 0 && jwtClaims.sub !== options.subject) throw new JWTError(`Invalid JWT "sub": expected ${options.subject}, got ${jwtClaims.sub}`, "ERR_JWT_CLAIM_INVALID");
|
|
182
182
|
if (options.audience !== void 0) {
|
|
183
183
|
const expectedAudiences = maybeArray(options.audience);
|
|
184
184
|
const claimAudience = maybeArray(jwtClaims.aud || []);
|
|
185
|
-
if (!claimAudience.some((aud) => expectedAudiences.includes(aud))) throw new JWTError(`Invalid JWT "aud"
|
|
185
|
+
if (!claimAudience.some((aud) => expectedAudiences.includes(aud))) throw new JWTError(`Invalid JWT "aud": expected ${expectedAudiences.join(" or ")}, got ${claimAudience.join(", ")}`, "ERR_JWT_CLAIM_INVALID");
|
|
186
186
|
}
|
|
187
|
-
if (typeof jwtClaims.nbf === "number" && jwtClaims.nbf > currentTime + clockTolerance) throw new JWTError(`JWT "nbf"
|
|
187
|
+
if (typeof jwtClaims.nbf === "number" && jwtClaims.nbf > currentTime + clockTolerance) throw new JWTError(`JWT "nbf" is in the future (${(/* @__PURE__ */ new Date(jwtClaims.nbf * 1e3)).toISOString()}).`, "ERR_JWT_NBF");
|
|
188
188
|
if (typeof jwtClaims.exp === "number" && jwtClaims.exp <= currentTime - clockTolerance) throw new JWTError(expiredClaimMessage(jwtClaims.exp), "ERR_JWT_EXPIRED", {
|
|
189
189
|
jti: jwtClaims.jti,
|
|
190
190
|
iat: jwtClaims.iat,
|
|
191
191
|
exp: jwtClaims.exp
|
|
192
192
|
});
|
|
193
193
|
if (options.maxTokenAge !== void 0) {
|
|
194
|
-
if (typeof jwtClaims.iat !== "number") throw new JWTError("JWT \"iat\"
|
|
195
|
-
if (jwtClaims.iat > currentTime + clockTolerance) throw new JWTError(`JWT "iat"
|
|
196
|
-
if (jwtClaims.iat < currentTime - computeDurationInSeconds(options.maxTokenAge) - clockTolerance) throw new JWTError(`JWT "iat"
|
|
194
|
+
if (typeof jwtClaims.iat !== "number") throw new JWTError("JWT \"iat\" must be a number when maxTokenAge is set.", "ERR_JWT_CLAIM_INVALID");
|
|
195
|
+
if (jwtClaims.iat > currentTime + clockTolerance) throw new JWTError(`JWT "iat" is in the future (${(/* @__PURE__ */ new Date(jwtClaims.iat * 1e3)).toISOString()}).`, "ERR_JWT_CLAIM_INVALID");
|
|
196
|
+
if (jwtClaims.iat < currentTime - computeDurationInSeconds(options.maxTokenAge) - clockTolerance) throw new JWTError(`JWT "iat" is older than maxTokenAge ${options.maxTokenAge}.`, "ERR_JWT_EXPIRED", {
|
|
197
197
|
jti: jwtClaims.jti,
|
|
198
198
|
iat: jwtClaims.iat,
|
|
199
199
|
exp: jwtClaims.exp
|
|
@@ -238,10 +238,10 @@ function _assertExpectedValues(value, name) {
|
|
|
238
238
|
function _isNonEmptyString(value) {
|
|
239
239
|
return typeof value === "string" && value !== "";
|
|
240
240
|
}
|
|
241
|
-
function _assertNumericDate(value, claim
|
|
241
|
+
function _assertNumericDate(value, claim) {
|
|
242
242
|
if (value === void 0) return;
|
|
243
|
-
if (typeof value !== "number" || !Number.isFinite(value)) throw new JWTError(`JWT "${claim}"
|
|
244
|
-
if (value < 0) throw new JWTError(`JWT "${claim}"
|
|
243
|
+
if (typeof value !== "number" || !Number.isFinite(value)) throw new JWTError(`JWT "${claim}" must be a number.`, "ERR_JWT_CLAIM_INVALID");
|
|
244
|
+
if (value < 0) throw new JWTError(`JWT "${claim}" must be a non-negative NumericDate.`, "ERR_JWT_CLAIM_INVALID");
|
|
245
245
|
}
|
|
246
246
|
function applyTypDefault(header, payload) {
|
|
247
247
|
const declared = header.typ;
|
|
@@ -283,29 +283,32 @@ function getPlaintextBytes(payload) {
|
|
|
283
283
|
throw new JWTError("Plaintext must be a string, Uint8Array, or a JSON-serializable object.", "ERR_INVALID_OPTION");
|
|
284
284
|
}
|
|
285
285
|
function computeJwtTimeClaims(payload, options) {
|
|
286
|
-
const { currentDate, expiresIn, expiresAt, notBeforeIn, notBeforeAt } = options ?? {};
|
|
287
|
-
if (
|
|
288
|
-
if (
|
|
286
|
+
const { currentDate, issuedAt, expiresIn, expiresAt, notBeforeIn, notBeforeAt } = options ?? {};
|
|
287
|
+
if (issuedAt !== void 0 && typeof issuedAt !== "boolean" && !(issuedAt instanceof Date)) throw new JWTError("\"issuedAt\" must be a boolean or a Date.", "ERR_INVALID_OPTION");
|
|
288
|
+
if (expiresIn !== void 0 && expiresAt !== void 0) throw new JWTError("\"expiresIn\" and \"expiresAt\" are mutually exclusive.", "ERR_JWT_CLAIM_INVALID");
|
|
289
|
+
if (notBeforeIn !== void 0 && notBeforeAt !== void 0) throw new JWTError("\"notBeforeIn\" and \"notBeforeAt\" are mutually exclusive.", "ERR_JWT_CLAIM_INVALID");
|
|
289
290
|
const hasExpHint = expiresIn !== void 0 || expiresAt !== void 0;
|
|
290
291
|
const hasNbfHint = notBeforeIn !== void 0 || notBeforeAt !== void 0;
|
|
291
|
-
|
|
292
|
+
const hasIatHint = issuedAt === true || issuedAt instanceof Date;
|
|
293
|
+
if (!hasExpHint && !hasNbfHint && !hasIatHint) return void 0;
|
|
292
294
|
if (!payload || typeof payload !== "object" || payload instanceof Uint8Array) return void 0;
|
|
293
295
|
const wantsExp = hasExpHint && (!("exp" in payload) || payload.exp === void 0);
|
|
294
296
|
const wantsNbf = hasNbfHint && (!("nbf" in payload) || payload.nbf === void 0);
|
|
295
|
-
|
|
297
|
+
const wantsIat = hasIatHint && (!("iat" in payload) || payload.iat === void 0);
|
|
298
|
+
if (!wantsExp && !wantsNbf && !wantsIat) return void 0;
|
|
296
299
|
const now = Math.round((currentDate ?? /* @__PURE__ */ new Date()).getTime() / 1e3);
|
|
297
300
|
const claims = { ...payload };
|
|
298
|
-
claims.iat ||= now;
|
|
301
|
+
claims.iat ||= issuedAt instanceof Date ? Math.floor(_instantOf(issuedAt, "issuedAt") / 1e3) : now;
|
|
299
302
|
if (wantsExp) {
|
|
300
303
|
if (expiresAt !== void 0) claims.exp = Math.floor(_instantOf(expiresAt, "expiresAt") / 1e3);
|
|
301
|
-
else if (expiresIn !== void 0) claims.exp =
|
|
304
|
+
else if (expiresIn !== void 0) claims.exp = now + computeDurationInSeconds(expiresIn);
|
|
302
305
|
}
|
|
303
306
|
if (wantsNbf) {
|
|
304
307
|
if (notBeforeAt !== void 0) claims.nbf = Math.floor(_instantOf(notBeforeAt, "notBeforeAt") / 1e3);
|
|
305
308
|
else if (notBeforeIn !== void 0) {
|
|
306
309
|
const offset = parseDurationSeconds(notBeforeIn);
|
|
307
310
|
if (offset < 0) throw new JWTError("\"notBeforeIn\" must be zero or a positive duration.", "ERR_JWT_CLAIM_INVALID");
|
|
308
|
-
claims.nbf =
|
|
311
|
+
claims.nbf = now + offset;
|
|
309
312
|
}
|
|
310
313
|
}
|
|
311
314
|
return claims;
|
|
@@ -381,7 +384,7 @@ function validateCriticalHeaders(protectedHeader, options) {
|
|
|
381
384
|
const { family, recognizedHeaders = [] } = options;
|
|
382
385
|
assertCritShape(crit, { family });
|
|
383
386
|
for (const param of crit) {
|
|
384
|
-
if (!Object.hasOwn(protectedHeader, param)) throw new JWTError(`Critical header parameter "${param}"
|
|
387
|
+
if (!Object.hasOwn(protectedHeader, param)) throw new JWTError(`Critical header parameter "${param}" is missing from the protected header.`, `ERR_${family}_INVALID`);
|
|
385
388
|
if (!recognizedHeaders.includes(param) && !BASE_PROCESSED[family].includes(param)) throw new JWTError(`Unprocessed critical header parameters: ${param}`, `ERR_${family}_INVALID`);
|
|
386
389
|
}
|
|
387
390
|
}
|
package/dist/_chunks/index.d.mts
CHANGED
|
@@ -210,13 +210,15 @@ export interface JWTClaimValidationOptions {
|
|
|
210
210
|
}
|
|
211
211
|
/** Time claims a producer computes; a claim already in the payload is kept as given. */
|
|
212
212
|
export interface JWTTimeClaimOptions {
|
|
213
|
-
/**
|
|
213
|
+
/** The clock: `iat` when written, and the base of `expiresIn` / `notBeforeIn`. @default new Date() */
|
|
214
214
|
currentDate?: Date;
|
|
215
|
-
/**
|
|
215
|
+
/** Writes `iat` on its own: `true` as `currentDate`, a `Date` as that instant; `false` is unset. `exp`/`nbf` options write it either way. */
|
|
216
|
+
issuedAt?: boolean | Date;
|
|
217
|
+
/** Duration from `currentDate` until `exp`; exclusive with `expiresAt`. */
|
|
216
218
|
expiresIn?: ExpiresIn;
|
|
217
219
|
/** Absolute `exp`; exclusive with `expiresIn`. */
|
|
218
220
|
expiresAt?: Date;
|
|
219
|
-
/** Duration from `
|
|
221
|
+
/** Duration from `currentDate` until `nbf` (`0` sets `nbf` to it); exclusive with `notBeforeAt`. */
|
|
220
222
|
notBeforeIn?: ExpiresIn;
|
|
221
223
|
/** Absolute `nbf` (RFC 7519 §4.1.5); exclusive with `notBeforeIn`. */
|
|
222
224
|
notBeforeAt?: Date;
|
package/dist/_chunks/jwe.d.mts
CHANGED
|
@@ -49,6 +49,7 @@ export declare function encrypt(payload: JOSEPayload, key: CryptoKey | JWEEncryp
|
|
|
49
49
|
/**
|
|
50
50
|
* Decrypt a JWE Compact Serialization string; `key` may be a lookup function.
|
|
51
51
|
* The payload type follows `options.payload` (claims `T` by default).
|
|
52
|
+
* A compact JWE has five sections (RFC 7516 §3).
|
|
52
53
|
*/
|
|
53
54
|
export declare function decrypt<T extends object = JWTClaims>(jwe: string, key: CryptoKey | JWKSet | JWEDecryptJWK | string | Uint8Array<ArrayBuffer> | JWEKeyLookup, options?: JWEDecryptOptions<"json"> & {
|
|
54
55
|
forceUint8Array?: false;
|
package/dist/_chunks/jwe.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { sanitizeObjectCopy, secureRandomBytes, textEncoder } from "./libs/unsecure.mjs";
|
|
2
2
|
import { JWTError, applyTypDefault, base64UrlStringify, computeJwtTimeClaims, getPlaintextBytes, isJWK, isJWKSet, validateCriticalHeadersJWE } from "./_jwt-internal.mjs";
|
|
3
3
|
import { isContentEncryptionAlgorithm, isPBES2Algorithm } from "./digest.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, assertProducerConfirmation, assertProducerCrit, assertProducerIV, assertProducerKeyHeaders, 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,
|
|
4
|
+
import { DEFAULT_PBES2_ITERATIONS, JWE_ALG_CTX, JWE_DIRECT_ALGS, asDecryptionError, asKeyLookupError, assertCompactToken, assertConsumerOptions, assertDirectKeyRole, assertEntryLimit, assertJWEAlgSupported, assertJWEEncSupported, assertJWKSetCandidates, assertNoZipOption, assertOptionEntry, assertPBES2Params, assertProducerCEK, assertProducerConfirmation, assertProducerCrit, assertProducerIV, assertProducerKeyHeaders, 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, validateClaimsIfJson } from "./_import-key.mjs";
|
|
5
|
+
import { unwrapKey } from "../core/jwk.mjs";
|
|
5
6
|
function buildKeyManagementParams(alg, source) {
|
|
6
7
|
const { keyManagementIV, p2s, p2c, ecdh } = source;
|
|
7
8
|
const isPBES2 = alg.startsWith("PBES2");
|
|
@@ -339,7 +340,7 @@ function _resolveRecipientAlg(key, index) {
|
|
|
339
340
|
const declared = key.alg;
|
|
340
341
|
if (!declared) throw new JWTError(`Cannot infer "alg" for recipient[${index}]: the JWK has no "alg" property.`, "ERR_JWE_RECIPIENT_ALG_INFERENCE");
|
|
341
342
|
const alg = key.kty === "oct" && isContentEncryptionAlgorithm(declared) ? "dir" : declared;
|
|
342
|
-
if (JWE_DIRECT_ALGS.has(alg)) throw new JWTError(`Recipient[${index}]
|
|
343
|
+
if (JWE_DIRECT_ALGS.has(alg)) throw new JWTError(`Recipient[${index}] "${alg}" allows a single recipient, use encrypt().`, "ERR_JWE_ALG_FORBIDDEN_IN_MULTI");
|
|
343
344
|
assertJWEAlgSupported(alg);
|
|
344
345
|
return alg;
|
|
345
346
|
}
|
|
@@ -357,7 +358,7 @@ function _assertDisjointHeaders(protectedHeader, sharedUnprotected, recipientHea
|
|
|
357
358
|
const check = (obj) => {
|
|
358
359
|
if (!obj) return;
|
|
359
360
|
for (const k of Object.keys(obj)) {
|
|
360
|
-
if (seen.has(k)) throw new JWTError(`JWE header parameter "${k}"
|
|
361
|
+
if (seen.has(k)) throw new JWTError(`Invalid JWE: header parameter "${k}" is set in more than one header.`, "ERR_JWE_HEADER_PARAMS_NOT_DISJOINT");
|
|
361
362
|
seen.add(k);
|
|
362
363
|
}
|
|
363
364
|
};
|
|
@@ -374,7 +375,7 @@ function _mergeHeaders(protectedHeader, sharedUnprotected, recipientHeader) {
|
|
|
374
375
|
function _normalizeSerialization(input) {
|
|
375
376
|
assertSerializationObject(input, "JWE");
|
|
376
377
|
if ("recipients" in input) {
|
|
377
|
-
if ("encrypted_key" in input || "header" in input) throw new JWTError("Invalid JWE JSON Serialization: \"recipients\"
|
|
378
|
+
if ("encrypted_key" in input || "header" in input) throw new JWTError("Invalid JWE JSON Serialization: \"recipients\" excludes top-level \"header\" and \"encrypted_key\".", "ERR_JWE_INVALID_SERIALIZATION");
|
|
378
379
|
return input;
|
|
379
380
|
}
|
|
380
381
|
const flattened = input;
|
|
@@ -416,7 +417,7 @@ async function encrypt(payload, key, options = {}) {
|
|
|
416
417
|
else if (cekRole) alg = "dir";
|
|
417
418
|
else if (isJWK(key)) alg = key.alg;
|
|
418
419
|
}
|
|
419
|
-
if (!alg) throw new JWTError("JWE \"alg\"
|
|
420
|
+
if (!alg) throw new JWTError("JWE \"alg\" must be provided in options or inferable from the key", "ERR_JWE_ALG_MISSING");
|
|
420
421
|
if (!enc) {
|
|
421
422
|
if (alg === "dir") {
|
|
422
423
|
enc = cekRole ?? (isJWK(key) ? key.enc : void 0);
|
|
@@ -466,7 +467,7 @@ async function decrypt(jwe, key, options = {}) {
|
|
|
466
467
|
const keyCandidateLimit = resolveLimit(options.limits, "keyCandidates");
|
|
467
468
|
const pbes2Budget = createPBES2Budget(resolveLimit(options.limits, "pbes2Iterations"));
|
|
468
469
|
const parts = jwe.split(".");
|
|
469
|
-
if (parts.length !== 5) throw new JWTError("Invalid JWE: Must contain five sections
|
|
470
|
+
if (parts.length !== 5) throw new JWTError("Invalid JWE: Must contain five sections.", "ERR_JWE_INVALID");
|
|
470
471
|
const [protectedHeaderEncoded, encryptedKeyEncoded, ivEncoded, ciphertextEncoded, authTagEncoded] = parts;
|
|
471
472
|
const protectedHeader = decodeProtectedHeader(protectedHeaderEncoded, "JWE");
|
|
472
473
|
assertHeaderAlgorithms(protectedHeader);
|
|
@@ -550,7 +551,7 @@ function _buildJWEHeader(alg, enc, key, userHeader, keyManagementParams, payload
|
|
|
550
551
|
return header;
|
|
551
552
|
}
|
|
552
553
|
function assertHeaderAlgorithms(header) {
|
|
553
|
-
if (!header.alg || !header.enc) throw new JWTError("Invalid JWE: Protected header must
|
|
554
|
+
if (!header.alg || !header.enc) throw new JWTError("Invalid JWE: Protected header must carry \"alg\" and \"enc\".", "ERR_JWE_INVALID");
|
|
554
555
|
assertJWEAlgSupported(header.alg);
|
|
555
556
|
assertJWEEncSupported(header.enc);
|
|
556
557
|
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { ContentEncryptionAlgorithm, DeriveJWKOptions, DeriveJWKReturn, DeriveKeyOptions, DeriveKeyReturn, GenerateJWKOptions, GenerateJWKReturn, GenerateKeyAlgorithm, GenerateKeyOptions, GenerateKeyReturn, ImportedKey, ImportedPEMJWK, JWK, JWKAlgorithm, JWKCacheAdapter, JWKMetadata, JWKPEMAlgorithm, JWKSet, JWK_Asymmetric, JWK_ECDH_ES, JWK_EC_Private, JWK_EC_Public, JWK_PBES2, JWK_oct, KeyManagementAlgorithm, PEMType, ToPublicJWKReturn, UnwrapKeyOptions, UnwrapKeyResult, UnwrappingKeyFor, WrapKeyOptions, WrapKeyResult, WrappingKeyFor } from "./index.mjs";
|
|
2
|
+
import "./index2.mjs";
|
|
3
|
+
/**
|
|
4
|
+
* Default JWK import cache, keyed by JWK object identity: a copied JWK misses it, and a JWK
|
|
5
|
+
* mutated in place keeps serving the old `CryptoKey`. Rotate by replacing the object.
|
|
6
|
+
*/
|
|
7
|
+
export declare class WeakMapJWKCache implements JWKCacheAdapter {
|
|
8
|
+
private readonly _map;
|
|
9
|
+
get(jwk: JWK, alg: JWKAlgorithm): CryptoKey | undefined;
|
|
10
|
+
set(jwk: JWK, alg: JWKAlgorithm, key: CryptoKey): void;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Replaces the JWK import cache used by `importKey`, or disables it with `false`.
|
|
14
|
+
*
|
|
15
|
+
* A cache shared across JWK objects keys on the RFC 7638 thumbprint, the pair half (`d` present)
|
|
16
|
+
* and `alg`: the thumbprint covers public members only, and a `kid` may name different keys.
|
|
17
|
+
* `get` and `set` may return promises.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* const cacheKey = async (jwk: JWK, alg: JWKAlgorithm) =>
|
|
22
|
+
* `${await thumbprint(jwk)}:${"d" in jwk ? "private" : "public"}:${alg}`;
|
|
23
|
+
* const map = new Map<string, CryptoKey>();
|
|
24
|
+
* configureJWKCache({
|
|
25
|
+
* get: async (jwk, alg) => map.get(await cacheKey(jwk, alg)),
|
|
26
|
+
* set: async (jwk, alg, key) => {
|
|
27
|
+
* map.set(await cacheKey(jwk, alg), key);
|
|
28
|
+
* },
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare function configureJWKCache(cache: JWKCacheAdapter | false): void;
|
|
33
|
+
/** Resets the JWK import cache to a fresh `WeakMapJWKCache`. */
|
|
34
|
+
export declare function clearJWKCache(): void;
|
|
35
|
+
/** Imports the secret of an `oct` JWK as a `CryptoKey` under `algorithm` and `usage`. */
|
|
36
|
+
export declare function importKey(key: JWK_oct, options: {
|
|
37
|
+
asCryptoKey: true;
|
|
38
|
+
/** Algorithm to import the key as (e.g. `{ name: "AES-GCM", length: 256 }`). */
|
|
39
|
+
algorithm: Parameters<typeof crypto.subtle.importKey>[2];
|
|
40
|
+
usage: KeyUsage[];
|
|
41
|
+
/** Mark the key as extractable. Defaults to `false`. */
|
|
42
|
+
extractable?: boolean;
|
|
43
|
+
}): Promise<CryptoKey>;
|
|
44
|
+
/**
|
|
45
|
+
* Imports key material: a `CryptoKey` or `Uint8Array` is returned as is, an `oct` JWK as its raw
|
|
46
|
+
* bytes, an asymmetric JWK as a `CryptoKey` under its own `alg` or the hint. A string throws
|
|
47
|
+
* `ERR_JWK_INVALID`.
|
|
48
|
+
*
|
|
49
|
+
* `expect` and `extractable` apply to asymmetric keys only; `expect` rejects the other pair half
|
|
50
|
+
* with `ERR_JWK_INVALID`. A JWK's `alg` is authoritative (RFC 7517 §4.4): a different requested
|
|
51
|
+
* `alg` throws `ERR_JWK_INVALID`, except an `oct` JWK declaring a content encryption `alg` under
|
|
52
|
+
* `"dir"`.
|
|
53
|
+
*/
|
|
54
|
+
export declare function importKey<K extends CryptoKey | JWK | Uint8Array<ArrayBuffer>>(key: K, options?: JWKAlgorithm | {
|
|
55
|
+
/** Algorithm hint. Required when `key.alg` is absent on an asymmetric JWK. */
|
|
56
|
+
alg?: JWKAlgorithm;
|
|
57
|
+
/** Reject on shape mismatch instead of silently importing. */
|
|
58
|
+
expect?: "public" | "private";
|
|
59
|
+
/** Defaults to `false` for private, `true` for public material; JWK `ext: false` wins. */
|
|
60
|
+
extractable?: boolean;
|
|
61
|
+
}): Promise<ImportedKey<K>>;
|
|
62
|
+
/**
|
|
63
|
+
* Returns the JWKs of a JWK Set, dropping non-JWK entries, optionally narrowed by `filter`.
|
|
64
|
+
* An invalid set throws `ERR_JWK_INVALID`.
|
|
65
|
+
*/
|
|
66
|
+
export declare function getJWKsFromSet(jwkSet: JWKSet, filter?: (jwk: JWK) => boolean): JWK[];
|
|
67
|
+
/**
|
|
68
|
+
* Generates a key for a JWA algorithm: a `CryptoKey` for symmetric algorithms, raw bytes for
|
|
69
|
+
* the `A*CBC-HS*` composites, a `CryptoKeyPair` for asymmetric ones; with `toJWK: true`, a JWK
|
|
70
|
+
* (or JWK pair) named by the `kid` option or its RFC 7638 thumbprint. PBES2, `"dir"` and `"none"` are refused.
|
|
71
|
+
*
|
|
72
|
+
* @param options
|
|
73
|
+
* - `extractable?` — defaults to `true`; ignored when `toJWK` is set.
|
|
74
|
+
* - `kid?` — with `toJWK`, a string set verbatim or `false` for no `kid`, either skipping the
|
|
75
|
+
* thumbprint digest; anything else throws `ERR_INVALID_OPTION`.
|
|
76
|
+
* - `keyUsage?` — narrows the usages derived from `alg`; leaving either half of a pair with
|
|
77
|
+
* no usage throws `ERR_INVALID_OPTION`.
|
|
78
|
+
* - `modulusLength?` — RSA only; defaults to `2048`.
|
|
79
|
+
* - `publicExponent?` — RSA only; big-endian bytes, defaults to `0x010001`.
|
|
80
|
+
* - `namedCurve?` — EC/OKP only; defaults to `"P-256"` / `"Ed25519"`. One that contradicts
|
|
81
|
+
* the curve `ES*` or `Ed25519` names throws `ERR_INVALID_OPTION`.
|
|
82
|
+
*/
|
|
83
|
+
export declare function generateKey<TAlg extends GenerateKeyAlgorithm, ToJWK extends boolean = false, Kid extends string | false = string>(alg: TAlg, options?: GenerateKeyOptions<ToJWK, Kid>): Promise<GenerateKeyReturn<TAlg, GenerateKeyOptions<ToJWK, Kid>>>;
|
|
84
|
+
/**
|
|
85
|
+
* Generates a JWK (or JWK pair) for a JWA algorithm, its `kid` the caller's, none with
|
|
86
|
+
* `kid: false`, or else its RFC 7638 thumbprint. PBES2, `"dir"` and `"none"` are refused.
|
|
87
|
+
*
|
|
88
|
+
* @param options
|
|
89
|
+
* - `keyUsage?`, `modulusLength?` (default `2048`), `publicExponent?` (default `0x010001`),
|
|
90
|
+
* `namedCurve?` (default `"P-256"` / `"Ed25519"`); a narrowing that empties a pair half or
|
|
91
|
+
* a curve that contradicts `alg` throws `ERR_INVALID_OPTION`.
|
|
92
|
+
* - `kid?` — a string set verbatim or `false` for no `kid`, either skipping the thumbprint
|
|
93
|
+
* digest; anything else throws `ERR_INVALID_OPTION`.
|
|
94
|
+
* - JWK metadata (`use`, `x5c`, `x5t`, `x5t#S256`, `x5u`) is merged onto the result;
|
|
95
|
+
* `alg`, `kty`, `key_ops`, `ext` and key-material members are reserved (`ERR_INVALID_OPTION`).
|
|
96
|
+
*/
|
|
97
|
+
export declare function generateJWK<TAlg extends GenerateKeyAlgorithm, Kid extends string | false = string>(alg: TAlg, options?: GenerateJWKOptions<Kid> & Omit<JWKMetadata, "kid">): Promise<GenerateJWKReturn<TAlg, Kid>>;
|
|
98
|
+
/**
|
|
99
|
+
* Derives an AES-KW key from a password with PBKDF2 per PBES2 (RFC 7518 §4.8).
|
|
100
|
+
*
|
|
101
|
+
* @param options
|
|
102
|
+
* - `alg` — a PBES2 algorithm; any other throws `ERR_JWK_UNSUPPORTED`.
|
|
103
|
+
* - `salt` — PBKDF2 salt (`p2s`), at least 8 octets (RFC 7518 §4.8.1.1).
|
|
104
|
+
* - `iterations` — PBKDF2 count (`p2c`), an integer of at least 1000 (RFC 7518 §4.8.1.2);
|
|
105
|
+
* anything else throws `ERR_INVALID_OPTION`.
|
|
106
|
+
* - `keyUsage?` — defaults to `["wrapKey", "unwrapKey"]`.
|
|
107
|
+
* - `extractable?` — defaults to `false`; ignored when `toJWK` is set.
|
|
108
|
+
* - `toJWK?` — return a `JWK_oct` carrying the inner AES-KW `alg` instead of a `CryptoKey`.
|
|
109
|
+
*/
|
|
110
|
+
export declare function deriveKeyFromPassword<TOptions extends DeriveKeyOptions>(password: string | Uint8Array<ArrayBuffer>, options: TOptions): Promise<DeriveKeyReturn<TOptions, TOptions["alg"]>>;
|
|
111
|
+
/**
|
|
112
|
+
* Derives a `JWK_oct` from a password per PBES2 (RFC 7518 §4.8); its `alg` is the inner AES-KW
|
|
113
|
+
* variant and its `kid` the caller's, none with `kid: false`, or else its RFC 7638 thumbprint.
|
|
114
|
+
*
|
|
115
|
+
* @param options
|
|
116
|
+
* - `alg` (PBES2 only, else `ERR_JWK_UNSUPPORTED`), `salt` (at least 8 octets), `iterations`
|
|
117
|
+
* (integer of at least 1000, else `ERR_INVALID_OPTION`), `keyUsage?` (default
|
|
118
|
+
* `["wrapKey", "unwrapKey"]`).
|
|
119
|
+
* - `kid?` — a string set verbatim or `false` for no `kid`, either skipping the thumbprint
|
|
120
|
+
* digest; anything else throws `ERR_INVALID_OPTION`.
|
|
121
|
+
* - JWK metadata (`use`, `x5c`, `x5t`, `x5t#S256`, `x5u`) is merged onto the result;
|
|
122
|
+
* `alg`, `kty`, `key_ops`, `ext` and key-material members are reserved (`ERR_INVALID_OPTION`).
|
|
123
|
+
*/
|
|
124
|
+
export declare function deriveJWKFromPassword<TAlg extends JWK_PBES2, Kid extends string | false = string>(password: string | Uint8Array<ArrayBuffer>, options: DeriveJWKOptions<TAlg, Kid> & Omit<JWKMetadata, "kid">): Promise<DeriveJWKReturn<TAlg, Kid>>;
|
|
125
|
+
/**
|
|
126
|
+
* Exports an extractable `CryptoKey` as a JWK, merging optional `jwkParams` metadata.
|
|
127
|
+
*
|
|
128
|
+
* The `alg` is WebCrypto's, which knows no JOSE roles: an AES-GCM key minted for `A128GCMKW`
|
|
129
|
+
* exports as `A128GCM`, so key-wrapping keys are minted with `generateJWK` instead.
|
|
130
|
+
*
|
|
131
|
+
* @throws `ERR_INVALID_OPTION` if `key` is not a `CryptoKey` or `jwkParams` sets a reserved member
|
|
132
|
+
* (`alg`, `kty`, `key_ops`, `ext`, key material); `ERR_JWK_INVALID` for a non-extractable key.
|
|
133
|
+
*/
|
|
134
|
+
export declare function exportJWK<T extends JWK = JWK>(key: CryptoKey, jwkParams?: JWKMetadata): Promise<T>;
|
|
135
|
+
/** @deprecated Use `exportJWK`; removed in 0.9.0. */
|
|
136
|
+
export declare function exportKey<T extends JWK = JWK>(key: CryptoKey, jwkParams?: JWKMetadata): Promise<T>;
|
|
137
|
+
/**
|
|
138
|
+
* Utility to derive the public half of an asymmetric JWK, or throw if the input is not asymmetric.
|
|
139
|
+
*
|
|
140
|
+
* @param jwk An asymmetric JWK (EC, OKP, RSA) with a private scalar (`d`).
|
|
141
|
+
* @throws `ERR_JWK_INVALID` if the input is not an asymmetric JWK.
|
|
142
|
+
* @returns A JWK with the same public members, without the private scalar.
|
|
143
|
+
*/
|
|
144
|
+
export declare function toPublicJWK<T extends JWK_Asymmetric>(jwk: T): ToPublicJWKReturn<T>;
|
|
145
|
+
/**
|
|
146
|
+
* Wraps a CEK under a JWA key management algorithm; the result shape follows `alg`.
|
|
147
|
+
*
|
|
148
|
+
* @param wrappingKey A `CryptoKey`, a JWK, or for PBES2 a password string or bytes.
|
|
149
|
+
* @param options `alg` plus its parameters (`p2s`/`p2c` for PBES2, `ecdh` for ECDH-ES, …).
|
|
150
|
+
*/
|
|
151
|
+
export declare function wrapKey<TAlg extends KeyManagementAlgorithm>(keyToWrap: CryptoKey | Uint8Array<ArrayBuffer>, wrappingKey: WrappingKeyFor<TAlg>, options: WrapKeyOptions<TAlg>): Promise<WrapKeyResult<TAlg>>;
|
|
152
|
+
/**
|
|
153
|
+
* Unwraps a CEK under a JWA key management algorithm.
|
|
154
|
+
*
|
|
155
|
+
* @param unwrappingKey A `CryptoKey`, a JWK, or for PBES2 a password string or bytes.
|
|
156
|
+
* @param options `alg`, `format?` (`"cryptokey"` by default, or `"raw"`), plus the algorithm's
|
|
157
|
+
* parameters (`iv`/`tag`, `p2s`/`p2c`, `epk`, …).
|
|
158
|
+
* @throws `ERR_JWE_DECRYPTION_FAILED` for any failed unwrap, its cause undisclosed (RFC 7516
|
|
159
|
+
* §11.5); `ERR_INVALID_OPTION` for a malformed `wrappedKey` or `options`; `ERR_JWE_INVALID` for
|
|
160
|
+
* missing or malformed algorithm parameters.
|
|
161
|
+
*/
|
|
162
|
+
export declare function unwrapKey<TOptions extends UnwrapKeyOptions>(wrappedKey: Uint8Array<ArrayBuffer>, unwrappingKey: UnwrappingKeyFor<TOptions["alg"]>, options: TOptions): Promise<UnwrapKeyResult<TOptions>>;
|
|
163
|
+
/**
|
|
164
|
+
* Imports a PEM key as a JWK carrying `alg`. The type follows the label — `PRIVATE KEY`
|
|
165
|
+
* (PKCS#8), `PUBLIC KEY` (SPKI), `CERTIFICATE` (X.509, only its SPKI kept) — unless
|
|
166
|
+
* `options.pemType` overrides it. The JWK holds the key material in plain JavaScript.
|
|
167
|
+
*
|
|
168
|
+
* @throws `ERR_JWK_INVALID` if the type cannot be inferred or does not match the label;
|
|
169
|
+
* `ERR_JWK_UNSUPPORTED` for an unknown `pemType`; `ERR_INVALID_OPTION` if `jwkParams` sets
|
|
170
|
+
* `alg`, `kty`, `key_ops`, `ext` or a key-material member.
|
|
171
|
+
*/
|
|
172
|
+
export declare function importPEM<A extends JWKPEMAlgorithm, Type extends PEMType | undefined = undefined>(pem: string, alg: A, options?: {
|
|
173
|
+
/** PEM type. Inferred from the PEM label when omitted, which leaves the result public or private. */
|
|
174
|
+
pemType?: Type;
|
|
175
|
+
/** Additional JWK properties merged into the exported key (e.g. `kid`). */
|
|
176
|
+
jwkParams?: JWKMetadata;
|
|
177
|
+
}): Promise<ImportedPEMJWK<A, Type>>;
|
|
178
|
+
/**
|
|
179
|
+
* Exports an asymmetric JWK as PEM: PKCS#8 when it carries `d`, SPKI otherwise.
|
|
180
|
+
*
|
|
181
|
+
* @throws `ERR_JWK_UNSUPPORTED` for `oct` JWKs or an unknown format or algorithm;
|
|
182
|
+
* `ERR_JWK_INVALID` for a missing `alg` or a `pemFormat` that contradicts the JWK's half.
|
|
183
|
+
*/
|
|
184
|
+
export declare function exportPEM(jwk: JWK, options?: {
|
|
185
|
+
/** PEM format. Inferred from the JWK shape when omitted. */
|
|
186
|
+
pemFormat?: "pkcs8" | "spki";
|
|
187
|
+
/** Algorithm hint used when `jwk.alg` is absent. */
|
|
188
|
+
alg?: JWKPEMAlgorithm;
|
|
189
|
+
}): Promise<string>;
|
|
190
|
+
/**
|
|
191
|
+
* Derives ECDH-ES key material (RFC 7518 §4.6): ECDH followed by the Concat KDF.
|
|
192
|
+
*
|
|
193
|
+
* @param publicKey The recipient's static public key (the ephemeral one when decrypting).
|
|
194
|
+
* @param privateKey The ephemeral private key (the recipient's static one when decrypting).
|
|
195
|
+
*/
|
|
196
|
+
export declare function deriveSharedSecret(publicKey: CryptoKey | JWK_EC_Public, privateKey: CryptoKey | JWK_EC_Private, options: {
|
|
197
|
+
/** Algorithm identifier fed to the concat KDF as `AlgorithmID`. */
|
|
198
|
+
enc: JWK_ECDH_ES | ContentEncryptionAlgorithm;
|
|
199
|
+
/** Derived length in bits. Defaults to `enc`'s length; required for `"ECDH-ES"`. */
|
|
200
|
+
keyLength?: number;
|
|
201
|
+
/** Agreement PartyUInfo (apu). */
|
|
202
|
+
partyUInfo?: Uint8Array<ArrayBuffer>;
|
|
203
|
+
/** Agreement PartyVInfo (apv). */
|
|
204
|
+
partyVInfo?: Uint8Array<ArrayBuffer>;
|
|
205
|
+
}): Promise<Uint8Array<ArrayBuffer>>;
|
package/dist/_chunks/jws.d.mts
CHANGED
|
@@ -37,7 +37,10 @@ export declare function verifyMultiAll(jws: JWSGeneralSerialization | JWSFlatten
|
|
|
37
37
|
export declare function verifyMultiAll<Mode extends JOSEPayloadMode = JOSEPayloadMode>(jws: JWSGeneralSerialization | JWSFlattenedSerialization, keyResolver: JWSKeyLookup, options?: JWSMultiVerifyAllOptions<Mode>): Promise<JWSMultiVerifyOutcome<PayloadOf<Mode>>[]>;
|
|
38
38
|
/** Converts a single-signature General JWS to Flattened form (RFC 7515 §7.2.2); otherwise `ERR_JWS_INVALID_SERIALIZATION`. */
|
|
39
39
|
export declare function generalToFlattenedJWS(jws: JWSGeneralSerialization): JWSFlattenedSerialization;
|
|
40
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* Creates a JWS Compact Serialization; an object payload is JSON-serialized, `alg` is required unless the JWK carries one.
|
|
42
|
+
* An unencoded (`b64: false`) compact payload cannot contain "." (RFC 7797 §5.2).
|
|
43
|
+
*/
|
|
41
44
|
export declare function sign(payload: JOSEPayload, key: JWSSignJWK, options?: JWSSignOptions): Promise<string>;
|
|
42
45
|
export declare function sign(payload: JOSEPayload, key: CryptoKey | Uint8Array<ArrayBuffer>, options: JWSSignOptions & {
|
|
43
46
|
alg: JWSAlgorithm;
|
package/dist/_chunks/jws.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { sanitizeObjectCopy, textEncoder } from "./libs/unsecure.mjs";
|
|
2
2
|
import { JWTError, applyTypDefault, base64UrlStringify, computeJwtTimeClaims, getPlaintextBytes, isJWK, isJWKSet, parsePayloadText, validateCriticalHeadersJWS } from "./_jwt-internal.mjs";
|
|
3
3
|
import { inferJWSAllowedAlgorithms } from "./digest.mjs";
|
|
4
|
-
import { JWS_ALG_CTX, applyB64Header, asJWTError, asKeyLookupError, asKeyUsageError, assertB64Option, assertCompactToken, assertConsumerOptions, assertEntryLimit, assertJWKSetCandidates, assertJWSAlgSupported, assertNotStringKey, assertOptionEntry, assertProducerConfirmation, assertProducerCrit, assertProducerKeyHeaders, assertProtectedOnlyOption, assertSerializationObject, buildJWKSetFilter, checkAlgAllowed, checkJWSAlgSupported, checkJWSB64, checkProtectedTyp, checkSerializationEntry, decodeJWSPayload, decodeProtectedHeader, decodeSegment, getJWKsFromSet, importKey, keyMatchesHeader, resolveKeyInput, resolveLimit, resolvePayloadMode, resolveSigningKey, serializeProtectedHeader, sign as sign$1, stringKeyError, validateClaimsIfJson, verify as verify$1 } from "./
|
|
4
|
+
import { JWS_ALG_CTX, applyB64Header, asJWTError, asKeyLookupError, asKeyUsageError, assertB64Option, assertCompactToken, assertConsumerOptions, assertEntryLimit, assertJWKSetCandidates, assertJWSAlgSupported, assertNotStringKey, assertOptionEntry, assertProducerConfirmation, assertProducerCrit, assertProducerKeyHeaders, assertProtectedOnlyOption, assertSerializationObject, buildJWKSetFilter, checkAlgAllowed, checkJWSAlgSupported, checkJWSB64, checkProtectedTyp, checkSerializationEntry, decodeJWSPayload, decodeProtectedHeader, decodeSegment, getJWKsFromSet, importKey, keyMatchesHeader, resolveKeyInput, resolveLimit, resolvePayloadMode, resolveSigningKey, serializeProtectedHeader, sign as sign$1, stringKeyError, validateClaimsIfJson, verify as verify$1 } from "./_import-key.mjs";
|
|
5
5
|
function buildJWSProtectedHeader(alg, key, userHeader, options) {
|
|
6
6
|
const safeHeader = sanitizeObjectCopy(userHeader);
|
|
7
7
|
const header = {};
|
|
@@ -256,8 +256,8 @@ const PER_SIGNATURE_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
|
256
256
|
]);
|
|
257
257
|
function _checkUnprotectedHeader(header) {
|
|
258
258
|
if (!header) return void 0;
|
|
259
|
-
if (Object.hasOwn(header, "crit")) return new JWTError("Invalid JWS: \"crit\" must be integrity protected
|
|
260
|
-
if (Object.hasOwn(header, "b64")) return new JWTError("Invalid JWS: \"b64\" must
|
|
259
|
+
if (Object.hasOwn(header, "crit")) return new JWTError("Invalid JWS: \"crit\" must be integrity protected.", "ERR_JWS_INVALID");
|
|
260
|
+
if (Object.hasOwn(header, "b64")) return new JWTError("Invalid JWS: \"b64\" must be in the protected header.", "ERR_JWS_INVALID");
|
|
261
261
|
}
|
|
262
262
|
function _readSignatureHeaders(entry, index) {
|
|
263
263
|
const shapeError = checkSerializationEntry(entry, {
|
|
@@ -290,7 +290,7 @@ function _readSignatureHeaders(entry, index) {
|
|
|
290
290
|
}
|
|
291
291
|
const effective = _mergeHeaders(protectedHeader, unprotectedHeader);
|
|
292
292
|
const alg = effective.alg;
|
|
293
|
-
if (!alg) return failed(new JWTError(`Invalid JWS: Signature[${index}] has no "alg" in its
|
|
293
|
+
if (!alg) return failed(new JWTError(`Invalid JWS: Signature[${index}] has no "alg" in its headers.`, "ERR_JWS_INVALID"));
|
|
294
294
|
const algError = checkJWSAlgSupported(alg) ?? checkJWSB64(protectedHeader);
|
|
295
295
|
if (algError) return failed(algError);
|
|
296
296
|
return {
|
|
@@ -323,13 +323,13 @@ function _resolveB64(signers) {
|
|
|
323
323
|
const raw = signers[i].protectedHeader?.b64;
|
|
324
324
|
if (raw === void 0) continue;
|
|
325
325
|
if (decided === void 0) decided = raw;
|
|
326
|
-
else if (decided !== raw) throw new JWTError(
|
|
326
|
+
else if (decided !== raw) throw new JWTError("Inconsistent \"b64\" across signers.", "ERR_JWS_B64_INCONSISTENT");
|
|
327
327
|
}
|
|
328
328
|
return decided ?? true;
|
|
329
329
|
}
|
|
330
330
|
function _assertDisjointHeaders(protectedHeader, unprotectedHeader) {
|
|
331
331
|
if (!protectedHeader || !unprotectedHeader) return;
|
|
332
|
-
for (const k of Object.keys(unprotectedHeader)) if (k in protectedHeader) throw new JWTError(`JWS header parameter "${k}"
|
|
332
|
+
for (const k of Object.keys(unprotectedHeader)) if (k in protectedHeader) throw new JWTError(`Invalid JWS: header parameter "${k}" is set in more than one header.`, "ERR_JWS_HEADER_PARAMS_NOT_DISJOINT");
|
|
333
333
|
}
|
|
334
334
|
function _mergeHeaders(protectedHeader, unprotectedHeader) {
|
|
335
335
|
if (!unprotectedHeader) return protectedHeader;
|
|
@@ -350,7 +350,7 @@ function _readGeneralSerialization(input, signatureLimit) {
|
|
|
350
350
|
function _normalizeSerialization(input) {
|
|
351
351
|
assertSerializationObject(input, "JWS");
|
|
352
352
|
if ("signatures" in input && Array.isArray(input.signatures)) {
|
|
353
|
-
if ("signature" in input || "protected" in input) throw new JWTError("Invalid JWS JSON Serialization: \"signatures\"
|
|
353
|
+
if ("signature" in input || "protected" in input) throw new JWTError("Invalid JWS JSON Serialization: \"signatures\" excludes top-level \"signature\" and \"protected\".", "ERR_JWS_INVALID_SERIALIZATION");
|
|
354
354
|
return input;
|
|
355
355
|
}
|
|
356
356
|
if ("signature" in input) {
|
|
@@ -363,7 +363,7 @@ function _normalizeSerialization(input) {
|
|
|
363
363
|
signatures: [signature]
|
|
364
364
|
};
|
|
365
365
|
}
|
|
366
|
-
throw new JWTError("Invalid JWS JSON Serialization: missing
|
|
366
|
+
throw new JWTError("Invalid JWS JSON Serialization: missing \"signatures\" or \"signature\".", "ERR_JWS_INVALID_SERIALIZATION");
|
|
367
367
|
}
|
|
368
368
|
async function _verifyOneSignature(payload, entry, index, keyResolver, context) {
|
|
369
369
|
const { options, payloadMode, keyCandidates } = context;
|
|
@@ -466,7 +466,7 @@ async function sign(payload, key, options = {}) {
|
|
|
466
466
|
let { alg } = options;
|
|
467
467
|
if (!alg) {
|
|
468
468
|
if (isJWK(key) && key.alg) alg = key.alg;
|
|
469
|
-
else throw new JWTError("JWS \"alg\"
|
|
469
|
+
else throw new JWTError("JWS \"alg\" must be provided in options", "ERR_JWS_ALG_MISSING");
|
|
470
470
|
}
|
|
471
471
|
assertJWSAlgSupported(alg);
|
|
472
472
|
let signingKey;
|
|
@@ -488,7 +488,7 @@ async function sign(payload, key, options = {}) {
|
|
|
488
488
|
if (useB64) payloadEncoded = base64UrlStringify(payloadBytes);
|
|
489
489
|
else {
|
|
490
490
|
payloadEncoded = parsePayloadText(payloadBytes);
|
|
491
|
-
if (payloadEncoded.includes(".")) throw new JWTError("Invalid JWS: unencoded compact payload must not contain \".\"
|
|
491
|
+
if (payloadEncoded.includes(".")) throw new JWTError("Invalid JWS: unencoded compact payload must not contain \".\".", "ERR_JWS_INVALID");
|
|
492
492
|
}
|
|
493
493
|
const signingInputString = `${protectedHeaderEncoded}.${payloadEncoded}`;
|
|
494
494
|
const signingInputBytes = textEncoder.encode(signingInputString);
|
package/dist/_chunks/jwt.d.mts
CHANGED
|
@@ -24,7 +24,8 @@ export declare function getPlaintextBytes(payload: JOSEPayload): Uint8Array<Arra
|
|
|
24
24
|
export type ComputeJwtTimeClaimsOptions = JWTTimeClaimOptions;
|
|
25
25
|
/**
|
|
26
26
|
* Return a copy of an object payload with `iat` and the requested `exp` / `nbf`, or `undefined` when none applies.
|
|
27
|
-
*
|
|
27
|
+
* `currentDate` is the clock: `iat` when written, and the base of `expiresIn` / `notBeforeIn`;
|
|
28
|
+
* `issuedAt` writes `iat` alone, a `Date` as that instant. Both members of `expiresIn`/`expiresAt` or `notBeforeIn`/`notBeforeAt` throw `ERR_JWT_CLAIM_INVALID`.
|
|
28
29
|
*/
|
|
29
30
|
export declare function computeJwtTimeClaims(payload: unknown, options?: ComputeJwtTimeClaimsOptions): JWTClaims | undefined;
|
|
30
31
|
/** Validate critical headers in JWS semantics. */
|