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.
Files changed (39) hide show
  1. package/README.md +4 -4
  2. package/dist/THIRD-PARTY-LICENSES.md.gz +0 -0
  3. package/dist/_chunks/_codec-jwe.d.mts +170 -0
  4. package/dist/_chunks/_codec-jws.mjs +878 -0
  5. package/dist/_chunks/_internal.d.mts +7 -0
  6. package/dist/_chunks/_jwt-internal.mjs +388 -0
  7. package/dist/_chunks/algorithms.mjs +186 -0
  8. package/dist/_chunks/index.d.mts +512 -818
  9. package/dist/_chunks/index2.d.mts +11 -0
  10. package/dist/_chunks/index3.d.mts +108 -0
  11. package/dist/_chunks/jwe.d.mts +43 -66
  12. package/dist/_chunks/jwe.mjs +384 -252
  13. package/dist/_chunks/jwk.mjs +1390 -622
  14. package/dist/_chunks/jws.d.mts +48 -103
  15. package/dist/_chunks/jws.mjs +372 -184
  16. package/dist/_chunks/jwt.d.mts +56 -0
  17. package/dist/_chunks/libs/unsecure.d.mts +164 -85
  18. package/dist/_chunks/libs/unsecure.mjs +390 -123
  19. package/dist/adapters/elysia/index.d.mts +33 -171
  20. package/dist/adapters/elysia/index.mjs +106 -575
  21. package/dist/adapters/h3v1/index.d.mts +59 -344
  22. package/dist/adapters/h3v1/index.mjs +114 -619
  23. package/dist/adapters/h3v2/index.d.mts +58 -326
  24. package/dist/adapters/h3v2/index.mjs +108 -611
  25. package/dist/core/internal.d.mts +2 -0
  26. package/dist/core/internal.mjs +1 -0
  27. package/dist/core/jwe.d.mts +4 -2
  28. package/dist/core/jwe.mjs +4 -2
  29. package/dist/core/jwk.d.mts +183 -371
  30. package/dist/core/jwk.mjs +4 -3
  31. package/dist/core/jws.d.mts +4 -2
  32. package/dist/core/jws.mjs +4 -2
  33. package/dist/core/utils/index.d.mts +5 -155
  34. package/dist/core/utils/index.mjs +12 -3
  35. package/dist/index.d.mts +8 -4
  36. package/dist/index.mjs +6 -3
  37. package/package.json +39 -36
  38. package/dist/_chunks/_internal.mjs +0 -88
  39. package/dist/_chunks/utils.mjs +0 -390
@@ -1,28 +1,103 @@
1
- import { JWTError, applyTypCtyDefaults, computeJwtTimeClaims, getPlaintextBytes, isJWK, isJWKSet, validateCriticalHeadersJWS } from "./utils.mjs";
2
- import { base64UrlDecode, base64UrlEncode, sanitizeObjectCopy, textDecoder, textEncoder } from "./libs/unsecure.mjs";
3
- import { getJWKsFromSet, importKey, sign as sign$1, verify as verify$1 } from "./jwk.mjs";
4
- import { JWS_ALG_CTX, buildJWKSetFilter, checkAlgAllowed, decodeJWSPayload, decodeProtectedHeader, resolveSigningKey, validateJwtClaimsIfJsonPayload } from "./_internal.mjs";
1
+ import { sanitizeObjectCopy, textEncoder } from "./libs/unsecure.mjs";
2
+ import { JWTError, applyTypDefault, base64UrlStringify, computeJwtTimeClaims, getPlaintextBytes, isJWK, isJWKSet, parsePayloadText, validateCriticalHeadersJWS } from "./_jwt-internal.mjs";
3
+ import { inferJWSAllowedAlgorithms } from "./algorithms.mjs";
4
+ import { JWS_ALG_CTX, applyB64Header, asJWTError, asKeyLookupError, asKeyUsageError, assertB64Option, assertCompactToken, assertConsumerOptions, assertEntryLimit, assertJWKSetCandidates, assertNotStringKey, assertOptionEntry, assertProducerCrit, 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 "./jwk.mjs";
5
+ function buildJWSProtectedHeader(alg, key, userHeader, options) {
6
+ const safeHeader = sanitizeObjectCopy(userHeader);
7
+ const header = {};
8
+ if (isJWK(key) && key.kid) header.kid = key.kid;
9
+ if (safeHeader) Object.assign(header, safeHeader);
10
+ header.alg = alg;
11
+ applyTypDefault(header, options.payload);
12
+ if (options.b64 === false) header.b64 = false;
13
+ applyB64Header(header);
14
+ assertProducerCrit(header, "JWS");
15
+ return header;
16
+ }
17
+ async function verifyWithKey(alg, rawKeyMaterial, options) {
18
+ try {
19
+ return await verifyWithKeyMaterial(alg, rawKeyMaterial, options);
20
+ } catch (err) {
21
+ throw asKeyUsageError(err, alg);
22
+ }
23
+ }
24
+ async function verifyWithKeyMaterial(alg, rawKeyMaterial, options) {
25
+ const { signature, signingInput, kid, allowSymmetric, keyCandidates } = options;
26
+ if (isJWKSet(rawKeyMaterial)) {
27
+ const candidates = getJWKsFromSet(rawKeyMaterial, buildJWKSetFilter({
28
+ kid,
29
+ alg
30
+ }, {
31
+ op: "sig",
32
+ allowSymmetric
33
+ }));
34
+ assertJWKSetCandidates(candidates, {
35
+ limit: keyCandidates,
36
+ kid
37
+ });
38
+ const attempts = [];
39
+ for (const candidate of candidates) try {
40
+ const verificationKey = await resolveSigningKey(alg, await importKey(candidate, {
41
+ alg,
42
+ expect: "public"
43
+ }), "verify");
44
+ if (await verify$1(alg, verificationKey, signature, signingInput)) return true;
45
+ attempts.push(new JWTError("JWS signature verification failed.", "ERR_JWS_SIGNATURE_INVALID"));
46
+ } catch (error) {
47
+ attempts.push(asKeyUsageError(error, alg));
48
+ }
49
+ throw new JWTError("JWS signature verification failed.", "ERR_JWS_SIGNATURE_INVALID", attempts);
50
+ }
51
+ const verificationKey = await resolveSigningKey(alg, await importKey(rawKeyMaterial, {
52
+ alg,
53
+ expect: "public"
54
+ }), "verify");
55
+ return verify$1(alg, verificationKey, signature, signingInput);
56
+ }
5
57
  async function signMulti(payload, signers, options = {}) {
6
58
  if (!Array.isArray(signers) || signers.length === 0) throw new JWTError("signMulti requires at least one signer.", "ERR_JWS_INVALID");
7
- const resolvedAlgs = signers.map((s, i) => _resolveSignerAlg(s.key, i));
59
+ for (const [index, signer] of signers.entries()) {
60
+ assertOptionEntry(signer, {
61
+ label: `signers[${index}]`,
62
+ objects: ["protectedHeader", "unprotectedHeader"]
63
+ });
64
+ assertNotStringKey(signer.key);
65
+ assertProtectedOnlyOption(signer.unprotectedHeader, {
66
+ tier: "unprotected",
67
+ family: "JWS"
68
+ });
69
+ assertB64Option(signer.protectedHeader);
70
+ }
71
+ const resolvedAlgs = signers.map((s, i) => _resolveSignerAlg(s, i));
8
72
  const b64 = _resolveB64(signers);
9
- const payloadBytes = getPlaintextBytes(computeJwtTimeClaims(payload, options) || payload);
10
- const payloadEncoded = b64 ? base64UrlEncode(payloadBytes) : textDecoder.decode(payloadBytes);
73
+ const computedPayload = computeJwtTimeClaims(payload, options);
74
+ const payloadBytes = getPlaintextBytes(computedPayload || payload);
75
+ const payloadEncoded = b64 ? base64UrlStringify(payloadBytes) : parsePayloadText(payloadBytes);
11
76
  const wireSignatures = [];
12
77
  for (let i = 0; i < signers.length; i++) {
13
78
  const signer = signers[i];
14
79
  const alg = resolvedAlgs[i];
15
- const protectedHeader = _buildSignerProtectedHeader(signer, alg, payload, b64);
80
+ const protectedHeader = buildJWSProtectedHeader(alg, signer.key, signer.protectedHeader, {
81
+ payload,
82
+ b64
83
+ });
16
84
  const unprotectedHeader = sanitizeObjectCopy(signer.unprotectedHeader);
17
85
  _assertDisjointHeaders(protectedHeader, unprotectedHeader);
18
- const protectedHeaderEncoded = base64UrlEncode(JSON.stringify(protectedHeader));
86
+ const protectedHeaderEncoded = base64UrlStringify(serializeProtectedHeader(protectedHeader));
19
87
  const signingInputBytes = textEncoder.encode(`${protectedHeaderEncoded}.${payloadEncoded}`);
20
- const wireSig = {
21
- protected: protectedHeaderEncoded,
22
- signature: base64UrlEncode(await sign$1(alg, await resolveSigningKey(alg, await importKey(signer.key, {
88
+ let signatureBytes;
89
+ try {
90
+ const signingKey = await resolveSigningKey(alg, await importKey(signer.key, {
23
91
  alg,
24
92
  expect: "private"
25
- }), "sign"), signingInputBytes))
93
+ }), "sign");
94
+ signatureBytes = await sign$1(alg, signingKey, signingInputBytes);
95
+ } catch (error) {
96
+ throw asKeyUsageError(error, alg);
97
+ }
98
+ const wireSig = {
99
+ protected: protectedHeaderEncoded,
100
+ signature: base64UrlStringify(signatureBytes)
26
101
  };
27
102
  if (unprotectedHeader && Object.keys(unprotectedHeader).length > 0) wireSig.header = unprotectedHeader;
28
103
  wireSignatures.push(wireSig);
@@ -33,54 +108,83 @@ async function signMulti(payload, signers, options = {}) {
33
108
  };
34
109
  }
35
110
  async function verifyMulti(jws, keyOrLookup, options = {}) {
36
- const general = _normalizeSerialization(jws);
37
- if (typeof general.payload !== "string" || !Array.isArray(general.signatures) || general.signatures.length === 0) throw new JWTError("Invalid JWS JSON Serialization: missing payload or signatures.", "ERR_JWS_INVALID_SERIALIZATION");
111
+ assertConsumerOptions(options);
112
+ const payloadMode = resolvePayloadMode(options);
113
+ const signatureLimit = resolveLimit(options.limits, "signatures");
114
+ const keyCandidates = resolveLimit(options.limits, "keyCandidates");
115
+ const general = _readGeneralSerialization(jws, signatureLimit);
38
116
  let lastError;
39
117
  let cryptoAttempted = false;
40
118
  for (let i = 0; i < general.signatures.length; i++) {
41
- const wireSig = general.signatures[i];
42
- if (typeof wireSig.signature !== "string") {
43
- lastError = new JWTError(`Signature[${i}] is missing the "signature" field.`, "ERR_JWS_INVALID");
119
+ const read = _readSignatureHeaders(general.signatures[i], i);
120
+ if (read.error) {
121
+ lastError = read.error;
44
122
  continue;
45
123
  }
46
- let protectedHeader;
47
- try {
48
- protectedHeader = decodeProtectedHeader(wireSig.protected, "JWS");
49
- } catch (err) {
50
- lastError = err instanceof JWTError ? err : void 0;
124
+ const { wireSig, protectedHeader, unprotectedHeader, effective, alg } = read;
125
+ const typError = checkProtectedTyp(options.typ, protectedHeader, {
126
+ family: "JWS",
127
+ label: `signature[${i}]`
128
+ });
129
+ if (typError) {
130
+ lastError = typError;
51
131
  continue;
52
132
  }
53
- if (!protectedHeader.alg) {
54
- lastError = new JWTError(`Invalid JWS: Signature[${i}] protected header must have an "alg" property.`, "ERR_JWS_INVALID");
133
+ let rawKeyMaterial;
134
+ try {
135
+ rawKeyMaterial = await resolveKeyInput(keyOrLookup, {
136
+ header: effective,
137
+ token: wireSig.protected ?? "",
138
+ what: `signature[${i}]`
139
+ });
140
+ } catch (err) {
141
+ lastError = asKeyLookupError(err, `signature[${i}]`);
55
142
  continue;
56
143
  }
57
- const unprotectedHeader = sanitizeObjectCopy(wireSig.header);
58
- _assertDisjointHeaders(protectedHeader, unprotectedHeader);
59
- const effective = _mergeHeaders(protectedHeader, unprotectedHeader);
60
- const alg = protectedHeader.alg;
61
- if (options.typ && protectedHeader.typ !== options.typ) {
62
- lastError = new JWTError(`Invalid JWS: "typ" Header Parameter mismatch on signature[${i}]. Expected "${options.typ}", got "${protectedHeader.typ}".`, "ERR_JWS_INVALID");
144
+ if (typeof rawKeyMaterial === "string") {
145
+ lastError = stringKeyError();
63
146
  continue;
64
147
  }
65
- const rawKeyMaterial = typeof keyOrLookup === "function" ? await keyOrLookup(effective, wireSig.protected ?? "") : keyOrLookup;
66
- const algError = checkAlgAllowed(alg, rawKeyMaterial, options.algorithms, JWS_ALG_CTX);
148
+ const algError = checkAlgAllowed(alg, rawKeyMaterial, {
149
+ ctx: JWS_ALG_CTX,
150
+ algorithms: options.algorithms,
151
+ allowSymmetric: options.allowSymmetricJWKSet
152
+ });
67
153
  if (algError) {
68
154
  lastError = algError;
69
155
  continue;
70
156
  }
71
- if (options.strictSignerMatch && !_signerKeyMatches(effective, rawKeyMaterial)) continue;
72
- const signatureBytes = base64UrlDecode(wireSig.signature, { returnAs: "uint8array" });
157
+ if (options.strictSignerMatch && !keyMatchesHeader(effective, rawKeyMaterial, {
158
+ ctx: JWS_ALG_CTX,
159
+ allowSymmetric: options.allowSymmetricJWKSet
160
+ })) continue;
161
+ let signatureBytes;
162
+ try {
163
+ signatureBytes = decodeSegment(wireSig.signature, {
164
+ family: "JWS",
165
+ what: "Signature"
166
+ });
167
+ } catch (err) {
168
+ lastError = asJWTError(err, INVALID_JWS);
169
+ continue;
170
+ }
73
171
  const signingInputBytes = textEncoder.encode(`${wireSig.protected ?? ""}.${general.payload}`);
74
172
  cryptoAttempted = true;
75
173
  try {
76
- if (!await _verifyWithKey(alg, rawKeyMaterial, signatureBytes, signingInputBytes, effective.kid)) {
174
+ if (!await verifyWithKey(alg, rawKeyMaterial, {
175
+ signature: signatureBytes,
176
+ signingInput: signingInputBytes,
177
+ kid: effective.kid,
178
+ allowSymmetric: options.allowSymmetricJWKSet,
179
+ keyCandidates
180
+ })) {
77
181
  lastError = new JWTError("JWS signature verification failed.", "ERR_JWS_SIGNATURE_INVALID");
78
182
  continue;
79
183
  }
80
- const useB64 = protectedHeader.b64 !== false;
81
- const payload = decodeJWSPayload(general.payload, useB64, options.forceUint8Array);
82
184
  validateCriticalHeadersJWS(protectedHeader, options.recognizedHeaders);
83
- validateJwtClaimsIfJsonPayload(payload, options);
185
+ const useB64 = protectedHeader.b64 !== false;
186
+ const payload = decodeJWSPayload(general.payload, useB64, payloadMode);
187
+ validateClaimsIfJson(payload, payloadMode, options);
84
188
  const result = {
85
189
  payload,
86
190
  protectedHeader,
@@ -89,21 +193,31 @@ async function verifyMulti(jws, keyOrLookup, options = {}) {
89
193
  if (unprotectedHeader) result.signerHeader = unprotectedHeader;
90
194
  return result;
91
195
  } catch (err) {
92
- if (err instanceof JWTError && err.code !== "ERR_JWS_SIGNATURE_INVALID" && err.code !== "ERR_JWK_KEY_NOT_FOUND") throw err;
93
- lastError = err instanceof JWTError ? err : void 0;
196
+ const error = asJWTError(err, INVALID_SIGNATURE);
197
+ if (!PER_SIGNATURE_ERROR_CODES.has(error.code)) throw error;
198
+ lastError = error;
94
199
  }
95
200
  }
96
201
  if (options.strictSignerMatch && !cryptoAttempted) throw new JWTError("No signature matched the provided key under strictSignerMatch.", "ERR_JWS_NO_MATCHING_SIGNER");
97
202
  throw lastError ?? new JWTError("JWS signature verification failed.", "ERR_JWS_SIGNATURE_INVALID");
98
203
  }
99
204
  async function verifyMultiAll(jws, keyResolver, options = {}) {
100
- const general = _normalizeSerialization(jws);
101
- if (typeof general.payload !== "string" || !Array.isArray(general.signatures) || general.signatures.length === 0) throw new JWTError("Invalid JWS JSON Serialization: missing payload or signatures.", "ERR_JWS_INVALID_SERIALIZATION");
205
+ assertConsumerOptions(options);
206
+ const payloadMode = resolvePayloadMode(options);
207
+ const signatureLimit = resolveLimit(options.limits, "signatures");
208
+ const keyCandidates = resolveLimit(options.limits, "keyCandidates");
209
+ const general = _readGeneralSerialization(jws, signatureLimit);
102
210
  const outcomes = [];
103
- for (let i = 0; i < general.signatures.length; i++) outcomes.push(await _verifyOneSignature(general.payload, general.signatures[i], i, keyResolver, options));
211
+ for (let i = 0; i < general.signatures.length; i++) outcomes.push(await _verifyOneSignature(general.payload, general.signatures[i], i, keyResolver, {
212
+ options,
213
+ payloadMode,
214
+ keyCandidates
215
+ }));
104
216
  return outcomes;
105
217
  }
106
218
  function generalToFlattenedJWS(jws) {
219
+ assertSerializationObject(jws, "JWS");
220
+ if (!Array.isArray(jws.signatures)) throw new JWTError("Invalid JWS JSON Serialization: missing \"signatures\".", "ERR_JWS_INVALID_SERIALIZATION");
107
221
  if (jws.signatures.length !== 1) throw new JWTError(`Flattened JWS Serialization requires exactly one signature, got ${jws.signatures.length}.`, "ERR_JWS_INVALID_SERIALIZATION");
108
222
  const [signature] = jws.signatures;
109
223
  const flattened = {
@@ -114,13 +228,86 @@ function generalToFlattenedJWS(jws) {
114
228
  if (signature.header !== void 0) flattened.header = signature.header;
115
229
  return flattened;
116
230
  }
117
- function _resolveSignerAlg(key, index) {
118
- if (!isJWK(key)) throw new JWTError(`Signer[${index}] key is not a JWK.`, "ERR_JWS_SIGNER_ALG_INFERENCE");
119
- const alg = key.alg;
120
- if (!alg) throw new JWTError(`Cannot infer "alg" for signer[${index}]: the JWK has no "alg" property.`, "ERR_JWS_SIGNER_ALG_INFERENCE");
121
- if (alg === "none") throw new JWTError("\"none\" is not a valid signing algorithm", "ERR_JWS_ALG_NOT_ALLOWED");
231
+ const INVALID_JWS = {
232
+ code: "ERR_JWS_INVALID",
233
+ message: "Invalid JWS."
234
+ };
235
+ const INVALID_SIGNATURE = {
236
+ code: "ERR_JWS_SIGNATURE_INVALID",
237
+ message: "JWS signature verification failed."
238
+ };
239
+ const INVALID_CLAIMS = {
240
+ code: "ERR_JWT_CLAIM_INVALID",
241
+ message: "JWT claim validation failed."
242
+ };
243
+ const PER_SIGNATURE_ERROR_CODES = /* @__PURE__ */ new Set([
244
+ "ERR_JWS_SIGNATURE_INVALID",
245
+ "ERR_JWK_KEY_NOT_FOUND",
246
+ "ERR_JWK_INVALID"
247
+ ]);
248
+ function _checkUnprotectedHeader(header) {
249
+ if (!header) return void 0;
250
+ if (Object.hasOwn(header, "crit")) return new JWTError("Invalid JWS: \"crit\" must be integrity protected (RFC 7515 §4.1.11).", "ERR_JWS_INVALID");
251
+ if (Object.hasOwn(header, "b64")) return new JWTError("Invalid JWS: \"b64\" must occur only within the protected header (RFC 7797 §3).", "ERR_JWS_INVALID");
252
+ }
253
+ function _readSignatureHeaders(entry, index) {
254
+ const shapeError = checkSerializationEntry(entry, {
255
+ family: "JWS",
256
+ label: `Signature[${index}]`,
257
+ objects: ["header"],
258
+ strings: ["protected"]
259
+ });
260
+ if (shapeError) return { error: shapeError };
261
+ const wireSig = entry;
262
+ if (typeof wireSig.signature !== "string") return { error: new JWTError(`Signature[${index}] is missing the "signature" field.`, "ERR_JWS_INVALID") };
263
+ let protectedHeader;
264
+ try {
265
+ protectedHeader = decodeProtectedHeader(wireSig.protected, "JWS");
266
+ } catch (err) {
267
+ return { error: asJWTError(err, INVALID_JWS) };
268
+ }
269
+ const unprotectedHeader = sanitizeObjectCopy(wireSig.header);
270
+ const failed = (error) => ({
271
+ error,
272
+ protectedHeader,
273
+ ...unprotectedHeader ? { unprotectedHeader } : {}
274
+ });
275
+ const unprotectedError = _checkUnprotectedHeader(unprotectedHeader);
276
+ if (unprotectedError) return failed(unprotectedError);
277
+ try {
278
+ _assertDisjointHeaders(protectedHeader, unprotectedHeader);
279
+ } catch (err) {
280
+ return failed(asJWTError(err, INVALID_JWS));
281
+ }
282
+ const effective = _mergeHeaders(protectedHeader, unprotectedHeader);
283
+ const alg = effective.alg;
284
+ if (!alg) return failed(new JWTError(`Invalid JWS: Signature[${index}] has no "alg" in its protected or unprotected header.`, "ERR_JWS_INVALID"));
285
+ const algError = checkJWSAlgSupported(alg) ?? checkJWSB64(protectedHeader);
286
+ if (algError) return failed(algError);
287
+ return {
288
+ wireSig,
289
+ protectedHeader,
290
+ unprotectedHeader,
291
+ effective,
292
+ alg
293
+ };
294
+ }
295
+ function _resolveSignerAlg(signer, index) {
296
+ const alg = signer.alg ?? _inferSignerAlg(signer.key, index);
297
+ const unsupported = checkJWSAlgSupported(alg);
298
+ if (unsupported) throw unsupported;
122
299
  return alg;
123
300
  }
301
+ function _inferSignerAlg(key, index) {
302
+ if (isJWK(key)) {
303
+ const alg = key.alg;
304
+ if (!alg) throw new JWTError(`Cannot infer "alg" for signer[${index}]: the JWK has no "alg" property.`, "ERR_JWS_SIGNER_ALG_INFERENCE");
305
+ return alg;
306
+ }
307
+ const inferred = inferJWSAllowedAlgorithms(key);
308
+ if (!inferred?.length) throw new JWTError(`Cannot infer "alg" for signer[${index}]: pass it explicitly on the signer.`, "ERR_JWS_SIGNER_ALG_INFERENCE");
309
+ return inferred[0];
310
+ }
124
311
  function _resolveB64(signers) {
125
312
  let decided;
126
313
  for (let i = 0; i < signers.length; i++) {
@@ -131,17 +318,6 @@ function _resolveB64(signers) {
131
318
  }
132
319
  return decided ?? true;
133
320
  }
134
- function _buildSignerProtectedHeader(signer, alg, payload, b64) {
135
- const userHeader = sanitizeObjectCopy(signer.protectedHeader);
136
- const header = {};
137
- if (signer.key.kid) header.kid = signer.key.kid;
138
- if (userHeader) Object.assign(header, userHeader);
139
- header.alg = alg;
140
- applyTypCtyDefaults(header, payload);
141
- if (!b64) header.b64 = false;
142
- else delete header.b64;
143
- return header;
144
- }
145
321
  function _assertDisjointHeaders(protectedHeader, unprotectedHeader) {
146
322
  if (!protectedHeader || !unprotectedHeader) return;
147
323
  for (const k of Object.keys(unprotectedHeader)) if (k in protectedHeader) throw new JWTError(`JWS header parameter "${k}" appears in both protected and unprotected headers of a signature; they must be disjoint (RFC 7515 §7.2.1).`, "ERR_JWS_HEADER_PARAMS_NOT_DISJOINT");
@@ -153,12 +329,23 @@ function _mergeHeaders(protectedHeader, unprotectedHeader) {
153
329
  ...unprotectedHeader
154
330
  };
155
331
  }
332
+ function _readGeneralSerialization(input, signatureLimit) {
333
+ const general = _normalizeSerialization(input);
334
+ if (typeof general.payload !== "string" || !Array.isArray(general.signatures) || general.signatures.length === 0) throw new JWTError("Invalid JWS JSON Serialization: missing payload or signatures.", "ERR_JWS_INVALID_SERIALIZATION");
335
+ assertEntryLimit(general.signatures.length, signatureLimit, {
336
+ family: "JWS",
337
+ what: "signatures"
338
+ });
339
+ return general;
340
+ }
156
341
  function _normalizeSerialization(input) {
157
- if (!input || typeof input !== "object") throw new JWTError("Invalid JWS JSON Serialization: expected an object.", "ERR_JWS_INVALID_SERIALIZATION");
158
- const obj = input;
159
- if (Array.isArray(obj.signatures)) return obj;
160
- if ("signature" in obj) {
161
- const flattened = obj;
342
+ assertSerializationObject(input, "JWS");
343
+ if ("signatures" in input && Array.isArray(input.signatures)) {
344
+ if ("signature" in input || "protected" in input) throw new JWTError("Invalid JWS JSON Serialization: \"signatures\" must not be combined with a top-level \"signature\" or \"protected\".", "ERR_JWS_INVALID_SERIALIZATION");
345
+ return input;
346
+ }
347
+ if ("signature" in input) {
348
+ const flattened = input;
162
349
  const signature = { signature: flattened.signature };
163
350
  if (flattened.protected !== void 0) signature.protected = flattened.protected;
164
351
  if (flattened.header !== void 0) signature.header = flattened.header;
@@ -169,34 +356,8 @@ function _normalizeSerialization(input) {
169
356
  }
170
357
  throw new JWTError("Invalid JWS JSON Serialization: missing both signatures[] and flattened signature.", "ERR_JWS_INVALID_SERIALIZATION");
171
358
  }
172
- function _signerKeyMatches(effectiveHeader, keyMaterial) {
173
- const headerKid = effectiveHeader.kid;
174
- if (isJWKSet(keyMaterial)) return keyMaterial.keys.some((k) => !headerKid || k.kid === headerKid);
175
- if (isJWK(keyMaterial)) {
176
- if (headerKid && keyMaterial.kid && headerKid !== keyMaterial.kid) return false;
177
- return true;
178
- }
179
- return !headerKid;
180
- }
181
- async function _verifyWithKey(alg, rawKeyMaterial, signatureBytes, signingInputBytes, headerKid) {
182
- if (isJWKSet(rawKeyMaterial)) {
183
- const candidates = getJWKsFromSet(rawKeyMaterial, buildJWKSetFilter({
184
- kid: headerKid,
185
- alg
186
- }));
187
- if (candidates.length === 0) throw new JWTError("No key found in JWK Set.", "ERR_JWK_KEY_NOT_FOUND");
188
- for (const candidate of candidates) if (await verify$1(alg, await resolveSigningKey(alg, await importKey(candidate, {
189
- alg,
190
- expect: "public"
191
- }), "verify"), signatureBytes, signingInputBytes)) return true;
192
- return false;
193
- }
194
- return verify$1(alg, await resolveSigningKey(alg, await importKey(rawKeyMaterial, {
195
- alg,
196
- expect: "public"
197
- }), "verify"), signatureBytes, signingInputBytes);
198
- }
199
- async function _verifyOneSignature(payload, wireSig, index, keyResolver, options) {
359
+ async function _verifyOneSignature(payload, entry, index, keyResolver, context) {
360
+ const { options, payloadMode, keyCandidates } = context;
200
361
  const fail = (error, protectedHeader, signerHeader) => {
201
362
  const outcome = {
202
363
  signerIndex: index,
@@ -207,51 +368,70 @@ async function _verifyOneSignature(payload, wireSig, index, keyResolver, options
207
368
  if (signerHeader) outcome.signerHeader = signerHeader;
208
369
  return outcome;
209
370
  };
210
- if (typeof wireSig.signature !== "string") return fail(new JWTError(`Signature[${index}] is missing the "signature" field.`, "ERR_JWS_INVALID"));
211
- let protectedHeader;
212
- try {
213
- protectedHeader = decodeProtectedHeader(wireSig.protected, "JWS");
214
- } catch (err) {
215
- return fail(_asJWTError(err, "ERR_JWS_INVALID"));
216
- }
217
- if (!protectedHeader.alg) return fail(new JWTError(`Invalid JWS: Signature[${index}] protected header must have an "alg" property.`, "ERR_JWS_INVALID"), protectedHeader);
218
- const unprotectedHeader = sanitizeObjectCopy(wireSig.header);
371
+ const read = _readSignatureHeaders(entry, index);
372
+ if (read.error) return fail(read.error, read.protectedHeader, read.unprotectedHeader);
373
+ const { wireSig, protectedHeader, unprotectedHeader, effective, alg } = read;
374
+ const typError = checkProtectedTyp(options.typ, protectedHeader, {
375
+ family: "JWS",
376
+ label: `signature[${index}]`
377
+ });
378
+ if (typError) return fail(typError, protectedHeader, unprotectedHeader);
379
+ let rawKeyMaterial;
219
380
  try {
220
- _assertDisjointHeaders(protectedHeader, unprotectedHeader);
381
+ rawKeyMaterial = await resolveKeyInput(keyResolver, {
382
+ header: effective,
383
+ token: wireSig.protected ?? "",
384
+ what: `signature[${index}]`
385
+ });
221
386
  } catch (err) {
222
- return fail(_asJWTError(err, "ERR_JWS_INVALID"), protectedHeader, unprotectedHeader);
387
+ return fail(asKeyLookupError(err, `signature[${index}]`), protectedHeader, unprotectedHeader);
223
388
  }
224
- const effective = _mergeHeaders(protectedHeader, unprotectedHeader);
225
- const alg = protectedHeader.alg;
226
- if (options.typ && protectedHeader.typ !== options.typ) return fail(new JWTError(`Invalid JWS: "typ" Header Parameter mismatch on signature[${index}]. Expected "${options.typ}", got "${protectedHeader.typ}".`, "ERR_JWS_INVALID"), protectedHeader, unprotectedHeader);
227
- let rawKeyMaterial;
389
+ if (typeof rawKeyMaterial === "string") return fail(stringKeyError(), protectedHeader, unprotectedHeader);
390
+ const algError = checkAlgAllowed(alg, rawKeyMaterial, {
391
+ ctx: JWS_ALG_CTX,
392
+ algorithms: options.algorithms,
393
+ allowSymmetric: options.allowSymmetricJWKSet
394
+ });
395
+ if (algError) return fail(algError, protectedHeader, unprotectedHeader);
396
+ let signatureBytes;
228
397
  try {
229
- rawKeyMaterial = await keyResolver(effective, wireSig.protected ?? "");
398
+ signatureBytes = decodeSegment(wireSig.signature, {
399
+ family: "JWS",
400
+ what: "Signature"
401
+ });
230
402
  } catch (err) {
231
- return fail(_asJWTError(err, "ERR_JWK_KEY_NOT_FOUND", `Key resolver failed for signature[${index}]`), protectedHeader, unprotectedHeader);
403
+ return fail(asJWTError(err, INVALID_JWS), protectedHeader, unprotectedHeader);
232
404
  }
233
- const algError = checkAlgAllowed(alg, rawKeyMaterial, options.algorithms, JWS_ALG_CTX);
234
- if (algError) return fail(algError, protectedHeader, unprotectedHeader);
235
- const signatureBytes = base64UrlDecode(wireSig.signature, { returnAs: "uint8array" });
236
405
  const signingInputBytes = textEncoder.encode(`${wireSig.protected ?? ""}.${payload}`);
237
406
  let verified;
238
407
  try {
239
- verified = await _verifyWithKey(alg, rawKeyMaterial, signatureBytes, signingInputBytes, effective.kid);
408
+ verified = await verifyWithKey(alg, rawKeyMaterial, {
409
+ signature: signatureBytes,
410
+ signingInput: signingInputBytes,
411
+ kid: effective.kid,
412
+ allowSymmetric: options.allowSymmetricJWKSet,
413
+ keyCandidates
414
+ });
240
415
  } catch (err) {
241
- return fail(_asJWTError(err, "ERR_JWS_SIGNATURE_INVALID"), protectedHeader, unprotectedHeader);
416
+ return fail(asJWTError(err, INVALID_SIGNATURE), protectedHeader, unprotectedHeader);
242
417
  }
243
418
  if (!verified) return fail(new JWTError(`JWS signature[${index}] verification failed.`, "ERR_JWS_SIGNATURE_INVALID"), protectedHeader, unprotectedHeader);
419
+ try {
420
+ validateCriticalHeadersJWS(protectedHeader, options.recognizedHeaders);
421
+ } catch (err) {
422
+ return fail(asJWTError(err, INVALID_JWS), protectedHeader, unprotectedHeader);
423
+ }
244
424
  let decodedPayload;
245
425
  try {
246
- decodedPayload = decodeJWSPayload(payload, protectedHeader.b64 !== false, options.forceUint8Array);
426
+ const useB64 = protectedHeader.b64 !== false;
427
+ decodedPayload = decodeJWSPayload(payload, useB64, payloadMode);
247
428
  } catch (err) {
248
- return fail(_asJWTError(err, "ERR_JWS_INVALID"), protectedHeader, unprotectedHeader);
429
+ return fail(asJWTError(err, INVALID_JWS), protectedHeader, unprotectedHeader);
249
430
  }
250
431
  try {
251
- validateCriticalHeadersJWS(protectedHeader, options.recognizedHeaders);
252
- validateJwtClaimsIfJsonPayload(decodedPayload, options);
432
+ validateClaimsIfJson(decodedPayload, payloadMode, options);
253
433
  } catch (err) {
254
- return fail(_asJWTError(err, "ERR_JWT_CLAIM_INVALID"), protectedHeader, unprotectedHeader);
434
+ return fail(asJWTError(err, INVALID_CLAIMS), protectedHeader, unprotectedHeader);
255
435
  }
256
436
  const outcome = {
257
437
  signerIndex: index,
@@ -262,88 +442,96 @@ async function _verifyOneSignature(payload, wireSig, index, keyResolver, options
262
442
  if (unprotectedHeader) outcome.signerHeader = unprotectedHeader;
263
443
  return outcome;
264
444
  }
265
- function _asJWTError(err, fallbackCode, fallbackMessage) {
266
- if (err instanceof JWTError) return err;
267
- return new JWTError(fallbackMessage ?? (err instanceof Error ? err.message : typeof err === "string" ? err : "Unknown error"), fallbackCode, err);
268
- }
269
445
  async function sign(payload, key, options = {}) {
446
+ assertOptionEntry(options, {
447
+ label: "options",
448
+ objects: ["protectedHeader"]
449
+ });
450
+ assertNotStringKey(key);
270
451
  const { protectedHeader: userHeader } = options;
271
452
  let { alg } = options;
272
- if (!alg) if (isJWK(key) && key.alg) alg = key.alg;
273
- else throw new JWTError("JWS \"alg\" (Algorithm) must be provided in options", "ERR_JWS_ALG_MISSING");
274
- if (alg === "none") throw new JWTError("\"none\" is not a valid signing algorithm", "ERR_JWS_ALG_NOT_ALLOWED");
275
- const signingKey = await resolveSigningKey(alg, await importKey(key, {
276
- alg,
277
- expect: "private"
278
- }), "sign");
279
- const protectedHeader = _buildJWSHeader(alg, key, userHeader, payload);
453
+ if (!alg) {
454
+ if (isJWK(key) && key.alg) alg = key.alg;
455
+ else throw new JWTError("JWS \"alg\" (Algorithm) must be provided in options", "ERR_JWS_ALG_MISSING");
456
+ }
457
+ const algSupportError = checkJWSAlgSupported(alg);
458
+ if (algSupportError) throw algSupportError;
459
+ let signingKey;
460
+ try {
461
+ signingKey = await resolveSigningKey(alg, await importKey(key, {
462
+ alg,
463
+ expect: "private"
464
+ }), "sign");
465
+ } catch (error) {
466
+ throw asKeyUsageError(error, alg);
467
+ }
468
+ const protectedHeader = buildJWSProtectedHeader(alg, key, userHeader, { payload });
280
469
  const computedPayload = computeJwtTimeClaims(payload, options);
281
- const protectedHeaderEncoded = base64UrlEncode(JSON.stringify(protectedHeader));
282
- let payloadBytes;
283
- if (payload instanceof Uint8Array) payloadBytes = payload;
284
- else if (typeof payload === "string") payloadBytes = textEncoder.encode(payload);
285
- else if (typeof payload === "object" && payload !== null) payloadBytes = textEncoder.encode(JSON.stringify(computedPayload || payload));
286
- else throw new TypeError("Payload must be a string, Uint8Array, or a JSON-serializable object.");
287
- const signingInputString = `${protectedHeaderEncoded}.${protectedHeader.b64 !== false ? base64UrlEncode(payloadBytes) : textDecoder.decode(payloadBytes)}`;
470
+ const protectedHeaderString = serializeProtectedHeader(protectedHeader);
471
+ const protectedHeaderEncoded = base64UrlStringify(protectedHeaderString);
472
+ const payloadBytes = getPlaintextBytes(computedPayload || payload);
473
+ const useB64 = protectedHeader.b64 !== false;
474
+ let payloadEncoded;
475
+ if (useB64) payloadEncoded = base64UrlStringify(payloadBytes);
476
+ else {
477
+ payloadEncoded = parsePayloadText(payloadBytes);
478
+ if (payloadEncoded.includes(".")) throw new JWTError("Invalid JWS: unencoded compact payload must not contain \".\" (RFC 7797 §5.2).", "ERR_JWS_INVALID");
479
+ }
480
+ const signingInputString = `${protectedHeaderEncoded}.${payloadEncoded}`;
288
481
  const signingInputBytes = textEncoder.encode(signingInputString);
289
- return `${signingInputString}.${base64UrlEncode(await sign$1(alg, signingKey, signingInputBytes))}`;
482
+ let signatureBytes;
483
+ try {
484
+ signatureBytes = await sign$1(alg, signingKey, signingInputBytes);
485
+ } catch (error) {
486
+ throw asKeyUsageError(error, alg);
487
+ }
488
+ return `${signingInputString}.${base64UrlStringify(signatureBytes)}`;
290
489
  }
291
490
  async function verify(jws, key, options = {}) {
491
+ assertCompactToken(jws, "JWS");
492
+ assertConsumerOptions(options);
493
+ const payloadMode = resolvePayloadMode(options);
494
+ const keyCandidateLimit = resolveLimit(options.limits, "keyCandidates");
292
495
  const parts = jws.split(".");
293
496
  if (parts.length !== 3) throw new JWTError("Invalid JWS: Must contain three parts separated by dots.", "ERR_JWS_INVALID");
294
497
  const [protectedHeaderEncoded, payloadEncoded, signatureEncoded] = parts;
295
498
  const protectedHeader = decodeProtectedHeader(protectedHeaderEncoded, "JWS");
296
499
  if (!protectedHeader.alg) throw new JWTError("Invalid JWS: Protected header must be an object with an \"alg\" property.", "ERR_JWS_INVALID");
297
500
  const alg = protectedHeader.alg;
298
- if (options.typ && protectedHeader.typ !== options.typ) throw new JWTError(`Invalid JWS: "typ" (Type) Header Parameter mismatch. Expected "${options.typ}", got "${protectedHeader.typ}".`, "ERR_JWS_INVALID");
299
- let signatureBytes;
300
- try {
301
- signatureBytes = base64UrlDecode(signatureEncoded, { returnAs: "uint8array" });
302
- } catch {
303
- throw new JWTError("Invalid JWS: Signature could not be decoded.", "ERR_JWS_INVALID");
304
- }
305
- const keyInput = typeof key === "function" ? await key(protectedHeader, jws) : key;
306
- const algError = checkAlgAllowed(alg, keyInput, options.algorithms, JWS_ALG_CTX);
501
+ const algSupportError = checkJWSAlgSupported(alg);
502
+ if (algSupportError) throw algSupportError;
503
+ const headerError = checkJWSB64(protectedHeader) ?? checkProtectedTyp(options.typ, protectedHeader, { family: "JWS" });
504
+ if (headerError) throw headerError;
505
+ const signatureBytes = decodeSegment(signatureEncoded, {
506
+ family: "JWS",
507
+ what: "Signature"
508
+ });
509
+ const keyInput = await resolveKeyInput(key, {
510
+ header: protectedHeader,
511
+ token: jws,
512
+ what: "the JWS"
513
+ });
514
+ assertNotStringKey(keyInput);
515
+ const algError = checkAlgAllowed(alg, keyInput, {
516
+ ctx: JWS_ALG_CTX,
517
+ algorithms: options.algorithms,
518
+ allowSymmetric: options.allowSymmetricJWKSet
519
+ });
307
520
  if (algError) throw algError;
308
- const signingInputBytes = textEncoder.encode(`${protectedHeaderEncoded}.${payloadEncoded}`);
309
- if (isJWKSet(keyInput)) {
310
- const candidates = getJWKsFromSet(keyInput, buildJWKSetFilter(protectedHeader));
311
- if (candidates.length === 0) throw new JWTError(`No key found in JWK Set${protectedHeader.kid ? ` with kid "${protectedHeader.kid}"` : ""}.`, "ERR_JWK_KEY_NOT_FOUND");
312
- let verified = false;
313
- for (const candidate of candidates) if (await verify$1(alg, await resolveSigningKey(alg, await importKey(candidate, {
314
- alg,
315
- expect: "public"
316
- }), "verify"), signatureBytes, signingInputBytes)) {
317
- verified = true;
318
- break;
319
- }
320
- if (!verified) throw new JWTError("JWS signature verification failed.", "ERR_JWS_SIGNATURE_INVALID");
321
- } else if (!await verify$1(alg, await resolveSigningKey(alg, await importKey(keyInput, {
322
- alg,
323
- expect: "public"
324
- }), "verify"), signatureBytes, signingInputBytes)) throw new JWTError("JWS signature verification failed.", "ERR_JWS_SIGNATURE_INVALID");
325
- const useB64 = protectedHeader.b64 !== false;
326
- let payload;
327
- try {
328
- payload = decodeJWSPayload(payloadEncoded, useB64, options.forceUint8Array);
329
- } catch (error_) {
330
- throw new JWTError(`Invalid JWS: Payload decoding failed (${error_ instanceof Error ? error_.message : error_})`, "ERR_JWS_INVALID");
331
- }
521
+ if (!await verifyWithKey(alg, keyInput, {
522
+ signature: signatureBytes,
523
+ signingInput: textEncoder.encode(`${protectedHeaderEncoded}.${payloadEncoded}`),
524
+ kid: protectedHeader.kid,
525
+ allowSymmetric: options.allowSymmetricJWKSet,
526
+ keyCandidates: keyCandidateLimit
527
+ })) throw new JWTError("JWS signature verification failed.", "ERR_JWS_SIGNATURE_INVALID");
332
528
  validateCriticalHeadersJWS(protectedHeader, options.recognizedHeaders);
333
- validateJwtClaimsIfJsonPayload(payload, options);
529
+ const useB64 = protectedHeader.b64 !== false;
530
+ const payload = decodeJWSPayload(payloadEncoded, useB64, payloadMode);
531
+ validateClaimsIfJson(payload, payloadMode, options);
334
532
  return {
335
533
  payload,
336
534
  protectedHeader
337
535
  };
338
536
  }
339
- function _buildJWSHeader(alg, key, userHeader, payload) {
340
- const safeHeader = sanitizeObjectCopy(userHeader);
341
- const header = {};
342
- if (isJWK(key) && key.kid) header.kid = key.kid;
343
- if (safeHeader) Object.assign(header, safeHeader);
344
- header.alg = alg;
345
- applyTypCtyDefaults(header, payload);
346
- if (header.b64 === true) delete header.b64;
347
- return header;
348
- }
349
537
  export { generalToFlattenedJWS, sign, signMulti, verify, verifyMulti, verifyMultiAll };