unjwt 0.8.3 → 0.8.5

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 (54) hide show
  1. package/README.md +3 -9
  2. package/dist/_chunks/algorithms.mjs +1541 -0
  3. package/dist/_chunks/{_codec-jwe.d.mts → codec-jwe.d.mts} +40 -9
  4. package/dist/_chunks/{_codec-jws.mjs → codec-jws.mjs} +148 -95
  5. package/dist/_chunks/cookie.mjs +50 -0
  6. package/dist/_chunks/digest.mjs +10 -3
  7. package/dist/_chunks/error.d.mts +103 -0
  8. package/dist/_chunks/export.mjs +97 -0
  9. package/dist/_chunks/index.d.mts +5 -103
  10. package/dist/_chunks/index2.d.mts +206 -12
  11. package/dist/_chunks/index3.d.mts +12 -109
  12. package/dist/_chunks/{jwe.d.mts → index4.d.mts} +3 -1
  13. package/dist/_chunks/{jws.d.mts → index5.d.mts} +6 -2
  14. package/dist/_chunks/index6.d.mts +113 -0
  15. package/dist/_chunks/jwe.mjs +208 -74
  16. package/dist/_chunks/jwk.mjs +214 -2237
  17. package/dist/_chunks/jws.mjs +289 -202
  18. package/dist/_chunks/jwt.d.mts +2 -1
  19. package/dist/_chunks/{_jwt-internal.mjs → jwt.mjs} +124 -68
  20. package/dist/_chunks/libs/unsecure.d.mts +35 -25
  21. package/dist/_chunks/libs/unsecure.mjs +148 -302
  22. package/dist/_chunks/{_internal.d.mts → limits.d.mts} +1 -1
  23. package/dist/_chunks/payload.mjs +23 -0
  24. package/dist/_chunks/wrap.mjs +209 -0
  25. package/dist/adapters/elysia/index.d.mts +4 -4
  26. package/dist/adapters/elysia/index.mjs +5 -3
  27. package/dist/adapters/h3/index.d.mts +13 -13
  28. package/dist/adapters/h3/index.mjs +8 -10
  29. package/dist/adapters/h3v1/index.d.mts +5 -5
  30. package/dist/adapters/h3v1/index.mjs +6 -8
  31. package/dist/core/internal.mjs +1 -1
  32. package/dist/core/{jwe.d.mts → jwe/index.d.mts} +5 -4
  33. package/dist/core/jwe/index.mjs +5 -0
  34. package/dist/core/jwk/index.d.mts +6 -0
  35. package/dist/core/jwk/index.mjs +7 -0
  36. package/dist/core/{jws.d.mts → jws/index.d.mts} +5 -4
  37. package/dist/core/jws/index.mjs +5 -0
  38. package/dist/dpop/index.d.mts +73 -0
  39. package/dist/dpop/index.mjs +296 -0
  40. package/dist/error.d.mts +3 -0
  41. package/dist/error.mjs +20 -0
  42. package/dist/index.d.mts +10 -9
  43. package/dist/index.mjs +7 -3
  44. package/dist/session/index.d.mts +4 -4
  45. package/dist/session/index.mjs +6 -46
  46. package/dist/utils/index.d.mts +7 -0
  47. package/dist/{core/utils → utils}/index.mjs +4 -3
  48. package/package.json +22 -10
  49. package/dist/core/jwe.mjs +0 -5
  50. package/dist/core/jwk.d.mts +0 -207
  51. package/dist/core/jwk.mjs +0 -4
  52. package/dist/core/jws.mjs +0 -5
  53. package/dist/core/utils/index.d.mts +0 -6
  54. /package/dist/_chunks/{_cookie-attributes.d.mts → cookie-attributes.d.mts} +0 -0
@@ -1,9 +1,94 @@
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 "./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 "./jwk.mjs";
1
+ import { textEncoder } from "./libs/unsecure.mjs";
2
+ import { JWTError } from "../error.mjs";
3
+ import { applyTypDefault, base64UrlStringify, computeJwtTimeClaims, getPlaintextBytes, isJWK, isJWKSet, parsePayloadBytes, parsePayloadSegment, parsePayloadText, validateCriticalHeadersJWS } from "./jwt.mjs";
4
+ import { JWS_ALG_CTX, asJWTError, asKeyLookupError, assertCompactToken, assertConsumerOptions, assertEntryLimit, assertJWKSetCandidates, assertOctKeyLength, assertOptionEntry, assertProducerConfirmation, assertProducerCrit, assertProducerKeyHeaders, assertProtectedOnlyOption, assertSerializationObject, buildJWKSetFilter, checkAlgAllowed, checkAlgSupported, checkProtectedTyp, checkSerializationEntry, copyJsonObject, decodeProtectedHeader, decodeSegment, getJWKsFromSet, importKey, keyMatchesHeader, resolveKeyInput, resolveLimit, serializeProtectedHeader, sign as sign$1, stringKeyError, verify as verify$1 } from "./algorithms.mjs";
5
+ import { inferJWSAllowedAlgorithms, isJWSAlgorithm, jwsHashOf, modulusLengthOf } from "./digest.mjs";
6
+ import { resolvePayloadMode, validateClaimsIfJson } from "./payload.mjs";
7
+ async function resolveSigningKey(alg, key, usage) {
8
+ if (key instanceof Uint8Array) {
9
+ if (alg !== "HS256" && alg !== "HS384" && alg !== "HS512") throw new JWTError(`${alg} needs an asymmetric key, not raw bytes.`, "ERR_JWK_INVALID");
10
+ assertOctKeyLength(key, alg);
11
+ return crypto.subtle.importKey("raw", key, {
12
+ name: "HMAC",
13
+ hash: jwsHashOf(alg)
14
+ }, false, [usage]);
15
+ }
16
+ if (RSA_SIGNATURE_ALGORITHMS.has(alg)) {
17
+ const modulusLength = modulusLengthOf(key.algorithm);
18
+ if (typeof modulusLength !== "number" || modulusLength < 2048) throw new JWTError(`${alg} requires a key modulusLength of at least 2048 bits`, "ERR_JWK_INVALID");
19
+ }
20
+ return key;
21
+ }
22
+ function assertNotStringKey(key) {
23
+ if (typeof key === "string") throw stringKeyError();
24
+ }
25
+ function asKeyUsageError(error, alg) {
26
+ return asJWTError(error, {
27
+ code: "ERR_JWK_INVALID",
28
+ message: `Key is not usable for ${alg}.`
29
+ });
30
+ }
31
+ const RSA_SIGNATURE_ALGORITHMS = /* @__PURE__ */ new Set([
32
+ "RS256",
33
+ "RS384",
34
+ "RS512",
35
+ "PS256",
36
+ "PS384",
37
+ "PS512"
38
+ ]);
39
+ function assertJWSAlgSupported(alg) {
40
+ const error = checkJWSAlgSupported(alg);
41
+ if (error) throw error;
42
+ }
43
+ function checkJWSAlgSupported(alg) {
44
+ return checkAlgSupported(alg, {
45
+ supported: isJWSAlgorithm,
46
+ code: "ERR_JWS_ALG_NOT_ALLOWED",
47
+ label: "Algorithm",
48
+ role: "signing algorithm"
49
+ });
50
+ }
51
+ function checkJWSB64(header) {
52
+ if (Object.hasOwn(header, "b64") && typeof header.b64 !== "boolean") return new JWTError("Invalid JWS: \"b64\" must be a boolean.", "ERR_JWS_INVALID");
53
+ if (header.b64 !== false) return void 0;
54
+ if (Array.isArray(header.crit) && header.crit.includes("b64")) return void 0;
55
+ return new JWTError("Invalid JWS: \"b64\" must be listed in \"crit\".", "ERR_JWS_INVALID");
56
+ }
57
+ function assertB64Option(header) {
58
+ if (!header || !Object.hasOwn(header, "b64") || typeof header.b64 === "boolean") return;
59
+ throw new JWTError("\"b64\" must be a boolean.", "ERR_INVALID_OPTION");
60
+ }
61
+ function applyB64Header(header) {
62
+ assertB64Option(header);
63
+ if (header.b64 === true) {
64
+ if (!(Array.isArray(header.crit) && header.crit.includes("b64"))) delete header.b64;
65
+ return;
66
+ }
67
+ if (header.b64 !== false) return;
68
+ const crit = header.crit;
69
+ if (crit === void 0) header.crit = ["b64"];
70
+ else if (Array.isArray(crit) && !crit.includes("b64")) header.crit = [...crit, "b64"];
71
+ }
72
+ function decodeJWSPayload(payloadSegment, useB64, mode) {
73
+ if (!useB64) return mode === "string" ? payloadSegment : parsePayloadBytes(textEncoder.encode(payloadSegment), {
74
+ payload: mode,
75
+ family: "JWS"
76
+ });
77
+ try {
78
+ return parsePayloadSegment(payloadSegment, {
79
+ payload: mode,
80
+ family: "JWS"
81
+ });
82
+ } catch (error) {
83
+ if (error instanceof JWTError) throw error;
84
+ throw new JWTError("Invalid JWS: Payload could not be decoded.", "ERR_JWS_INVALID", error);
85
+ }
86
+ }
5
87
  function buildJWSProtectedHeader(alg, key, userHeader, options) {
6
- const safeHeader = sanitizeObjectCopy(userHeader);
88
+ const safeHeader = copyJsonObject(userHeader, {
89
+ code: "ERR_INVALID_OPTION",
90
+ message: "\"protectedHeader\" must hold only JSON values."
91
+ });
7
92
  const header = {};
8
93
  if (isJWK(key) && key.kid) header.kid = key.kid;
9
94
  if (safeHeader) Object.assign(header, safeHeader);
@@ -54,189 +139,6 @@ async function verifyWithKeyMaterial(alg, rawKeyMaterial, options) {
54
139
  }), "verify");
55
140
  return verify$1(alg, verificationKey, signature, signingInput);
56
141
  }
57
- async function signMulti(payload, signers, options = {}) {
58
- if (!Array.isArray(signers) || signers.length === 0) throw new JWTError("signMulti requires at least one signer.", "ERR_JWS_INVALID");
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
- assertProducerKeyHeaders(signer.protectedHeader, {
70
- tier: "protected",
71
- family: "JWS"
72
- });
73
- assertProducerKeyHeaders(signer.unprotectedHeader, {
74
- tier: "unprotected",
75
- family: "JWS"
76
- });
77
- assertB64Option(signer.protectedHeader);
78
- }
79
- assertProducerConfirmation(payload);
80
- const resolvedAlgs = signers.map((s, i) => _resolveSignerAlg(s, i));
81
- const b64 = _resolveB64(signers);
82
- const computedPayload = computeJwtTimeClaims(payload, options);
83
- const payloadBytes = getPlaintextBytes(computedPayload || payload);
84
- const payloadEncoded = b64 ? base64UrlStringify(payloadBytes) : parsePayloadText(payloadBytes);
85
- const wireSignatures = [];
86
- for (let i = 0; i < signers.length; i++) {
87
- const signer = signers[i];
88
- const alg = resolvedAlgs[i];
89
- const protectedHeader = buildJWSProtectedHeader(alg, signer.key, signer.protectedHeader, {
90
- payload,
91
- b64
92
- });
93
- const unprotectedHeader = sanitizeObjectCopy(signer.unprotectedHeader);
94
- _assertDisjointHeaders(protectedHeader, unprotectedHeader);
95
- const protectedHeaderEncoded = base64UrlStringify(serializeProtectedHeader(protectedHeader));
96
- const signingInputBytes = textEncoder.encode(`${protectedHeaderEncoded}.${payloadEncoded}`);
97
- let signatureBytes;
98
- try {
99
- const signingKey = await resolveSigningKey(alg, await importKey(signer.key, {
100
- alg,
101
- expect: "private"
102
- }), "sign");
103
- signatureBytes = await sign$1(alg, signingKey, signingInputBytes);
104
- } catch (error) {
105
- throw asKeyUsageError(error, alg);
106
- }
107
- const wireSig = {
108
- protected: protectedHeaderEncoded,
109
- signature: base64UrlStringify(signatureBytes)
110
- };
111
- if (unprotectedHeader && Object.keys(unprotectedHeader).length > 0) wireSig.header = unprotectedHeader;
112
- wireSignatures.push(wireSig);
113
- }
114
- return {
115
- payload: payloadEncoded,
116
- signatures: wireSignatures
117
- };
118
- }
119
- async function verifyMulti(jws, keyOrLookup, options = {}) {
120
- assertConsumerOptions(options);
121
- const payloadMode = resolvePayloadMode(options);
122
- const signatureLimit = resolveLimit(options.limits, "signatures");
123
- const keyCandidates = resolveLimit(options.limits, "keyCandidates");
124
- const general = _readGeneralSerialization(jws, signatureLimit);
125
- let lastError;
126
- let cryptoAttempted = false;
127
- for (let i = 0; i < general.signatures.length; i++) {
128
- const read = _readSignatureHeaders(general.signatures[i], i);
129
- if (read.error) {
130
- lastError = read.error;
131
- continue;
132
- }
133
- const { wireSig, protectedHeader, unprotectedHeader, effective, alg } = read;
134
- const typError = checkProtectedTyp(options.typ, protectedHeader, {
135
- family: "JWS",
136
- label: `signature[${i}]`
137
- });
138
- if (typError) {
139
- lastError = typError;
140
- continue;
141
- }
142
- let rawKeyMaterial;
143
- try {
144
- rawKeyMaterial = await resolveKeyInput(keyOrLookup, {
145
- header: effective,
146
- token: wireSig.protected ?? "",
147
- what: `signature[${i}]`
148
- });
149
- } catch (err) {
150
- lastError = asKeyLookupError(err, `signature[${i}]`);
151
- continue;
152
- }
153
- if (typeof rawKeyMaterial === "string") {
154
- lastError = stringKeyError();
155
- continue;
156
- }
157
- const algError = checkAlgAllowed(alg, rawKeyMaterial, {
158
- ctx: JWS_ALG_CTX,
159
- algorithms: options.algorithms,
160
- allowSymmetric: options.allowSymmetricJWKSet
161
- });
162
- if (algError) {
163
- lastError = algError;
164
- continue;
165
- }
166
- if (options.strictSignerMatch && !keyMatchesHeader(effective, rawKeyMaterial, {
167
- ctx: JWS_ALG_CTX,
168
- allowSymmetric: options.allowSymmetricJWKSet
169
- })) continue;
170
- let signatureBytes;
171
- try {
172
- signatureBytes = decodeSegment(wireSig.signature, {
173
- family: "JWS",
174
- what: "Signature"
175
- });
176
- } catch (err) {
177
- lastError = asJWTError(err, INVALID_JWS);
178
- continue;
179
- }
180
- const signingInputBytes = textEncoder.encode(`${wireSig.protected ?? ""}.${general.payload}`);
181
- cryptoAttempted = true;
182
- try {
183
- if (!await verifyWithKey(alg, rawKeyMaterial, {
184
- signature: signatureBytes,
185
- signingInput: signingInputBytes,
186
- kid: effective.kid,
187
- allowSymmetric: options.allowSymmetricJWKSet,
188
- keyCandidates
189
- })) {
190
- lastError = new JWTError("JWS signature verification failed.", "ERR_JWS_SIGNATURE_INVALID");
191
- continue;
192
- }
193
- validateCriticalHeadersJWS(protectedHeader, options.recognizedHeaders);
194
- const useB64 = protectedHeader.b64 !== false;
195
- const payload = decodeJWSPayload(general.payload, useB64, payloadMode);
196
- validateClaimsIfJson(payload, payloadMode, options);
197
- const result = {
198
- payload,
199
- protectedHeader,
200
- signerIndex: i
201
- };
202
- if (unprotectedHeader) result.signerHeader = unprotectedHeader;
203
- return result;
204
- } catch (err) {
205
- const error = asJWTError(err, INVALID_SIGNATURE);
206
- if (!PER_SIGNATURE_ERROR_CODES.has(error.code)) throw error;
207
- lastError = error;
208
- }
209
- }
210
- if (options.strictSignerMatch && !cryptoAttempted) throw new JWTError("No signature matched the provided key under strictSignerMatch.", "ERR_JWS_NO_MATCHING_SIGNER");
211
- throw lastError ?? new JWTError("JWS signature verification failed.", "ERR_JWS_SIGNATURE_INVALID");
212
- }
213
- async function verifyMultiAll(jws, keyResolver, options = {}) {
214
- assertConsumerOptions(options);
215
- const payloadMode = resolvePayloadMode(options);
216
- const signatureLimit = resolveLimit(options.limits, "signatures");
217
- const keyCandidates = resolveLimit(options.limits, "keyCandidates");
218
- const general = _readGeneralSerialization(jws, signatureLimit);
219
- const outcomes = [];
220
- for (let i = 0; i < general.signatures.length; i++) outcomes.push(await _verifyOneSignature(general.payload, general.signatures[i], i, keyResolver, {
221
- options,
222
- payloadMode,
223
- keyCandidates
224
- }));
225
- return outcomes;
226
- }
227
- function generalToFlattenedJWS(jws) {
228
- assertSerializationObject(jws, "JWS");
229
- if (!Array.isArray(jws.signatures)) throw new JWTError("Invalid JWS JSON Serialization: missing \"signatures\".", "ERR_JWS_INVALID_SERIALIZATION");
230
- if (jws.signatures.length !== 1) throw new JWTError(`Flattened JWS Serialization requires exactly one signature, got ${jws.signatures.length}.`, "ERR_JWS_INVALID_SERIALIZATION");
231
- const [signature] = jws.signatures;
232
- const flattened = {
233
- payload: jws.payload,
234
- signature: signature.signature
235
- };
236
- if (signature.protected !== void 0) flattened.protected = signature.protected;
237
- if (signature.header !== void 0) flattened.header = signature.header;
238
- return flattened;
239
- }
240
142
  const INVALID_JWS = {
241
143
  code: "ERR_JWS_INVALID",
242
144
  message: "Invalid JWS."
@@ -256,8 +158,8 @@ const PER_SIGNATURE_ERROR_CODES = /* @__PURE__ */ new Set([
256
158
  ]);
257
159
  function _checkUnprotectedHeader(header) {
258
160
  if (!header) return void 0;
259
- if (Object.hasOwn(header, "crit")) return new JWTError("Invalid JWS: \"crit\" must be integrity protected (RFC 7515 §4.1.11).", "ERR_JWS_INVALID");
260
- if (Object.hasOwn(header, "b64")) return new JWTError("Invalid JWS: \"b64\" must occur only within the protected header (RFC 7797 §3).", "ERR_JWS_INVALID");
161
+ if (Object.hasOwn(header, "crit")) return new JWTError("Invalid JWS: \"crit\" must be integrity protected.", "ERR_JWS_INVALID");
162
+ if (Object.hasOwn(header, "b64")) return new JWTError("Invalid JWS: \"b64\" must be in the protected header.", "ERR_JWS_INVALID");
261
163
  }
262
164
  function _readSignatureHeaders(entry, index) {
263
165
  const shapeError = checkSerializationEntry(entry, {
@@ -270,12 +172,13 @@ function _readSignatureHeaders(entry, index) {
270
172
  const wireSig = entry;
271
173
  if (typeof wireSig.signature !== "string") return { error: new JWTError(`Signature[${index}] is missing the "signature" field.`, "ERR_JWS_INVALID") };
272
174
  let protectedHeader;
175
+ let unprotectedHeader;
273
176
  try {
274
177
  protectedHeader = decodeProtectedHeader(wireSig.protected, "JWS");
178
+ unprotectedHeader = copyJsonObject(wireSig.header, INVALID_JWS);
275
179
  } catch (err) {
276
180
  return { error: asJWTError(err, INVALID_JWS) };
277
181
  }
278
- const unprotectedHeader = sanitizeObjectCopy(wireSig.header);
279
182
  const failed = (error) => ({
280
183
  error,
281
184
  protectedHeader,
@@ -290,7 +193,7 @@ function _readSignatureHeaders(entry, index) {
290
193
  }
291
194
  const effective = _mergeHeaders(protectedHeader, unprotectedHeader);
292
195
  const alg = effective.alg;
293
- if (!alg) return failed(new JWTError(`Invalid JWS: Signature[${index}] has no "alg" in its protected or unprotected header.`, "ERR_JWS_INVALID"));
196
+ if (!alg) return failed(new JWTError(`Invalid JWS: Signature[${index}] has no "alg" in its headers.`, "ERR_JWS_INVALID"));
294
197
  const algError = checkJWSAlgSupported(alg) ?? checkJWSB64(protectedHeader);
295
198
  if (algError) return failed(algError);
296
199
  return {
@@ -323,13 +226,13 @@ function _resolveB64(signers) {
323
226
  const raw = signers[i].protectedHeader?.b64;
324
227
  if (raw === void 0) continue;
325
228
  if (decided === void 0) decided = raw;
326
- else if (decided !== raw) throw new JWTError(`Inconsistent "b64" across signers (RFC 7797 §3 requires all signers use the same b64 value).`, "ERR_JWS_B64_INCONSISTENT");
229
+ else if (decided !== raw) throw new JWTError("Inconsistent \"b64\" across signers.", "ERR_JWS_B64_INCONSISTENT");
327
230
  }
328
231
  return decided ?? true;
329
232
  }
330
233
  function _assertDisjointHeaders(protectedHeader, unprotectedHeader) {
331
234
  if (!protectedHeader || !unprotectedHeader) return;
332
- 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");
235
+ 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
236
  }
334
237
  function _mergeHeaders(protectedHeader, unprotectedHeader) {
335
238
  if (!unprotectedHeader) return protectedHeader;
@@ -350,7 +253,7 @@ function _readGeneralSerialization(input, signatureLimit) {
350
253
  function _normalizeSerialization(input) {
351
254
  assertSerializationObject(input, "JWS");
352
255
  if ("signatures" in input && Array.isArray(input.signatures)) {
353
- 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");
256
+ 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
257
  return input;
355
258
  }
356
259
  if ("signature" in input) {
@@ -363,7 +266,7 @@ function _normalizeSerialization(input) {
363
266
  signatures: [signature]
364
267
  };
365
268
  }
366
- throw new JWTError("Invalid JWS JSON Serialization: missing both signatures[] and flattened signature.", "ERR_JWS_INVALID_SERIALIZATION");
269
+ throw new JWTError("Invalid JWS JSON Serialization: missing \"signatures\" or \"signature\".", "ERR_JWS_INVALID_SERIALIZATION");
367
270
  }
368
271
  async function _verifyOneSignature(payload, entry, index, keyResolver, context) {
369
272
  const { options, payloadMode, keyCandidates } = context;
@@ -432,8 +335,7 @@ async function _verifyOneSignature(payload, entry, index, keyResolver, context)
432
335
  }
433
336
  let decodedPayload;
434
337
  try {
435
- const useB64 = protectedHeader.b64 !== false;
436
- decodedPayload = decodeJWSPayload(payload, useB64, payloadMode);
338
+ decodedPayload = decodeJWSPayload(payload, protectedHeader.b64 !== false, payloadMode);
437
339
  } catch (err) {
438
340
  return fail(asJWTError(err, INVALID_JWS), protectedHeader, unprotectedHeader);
439
341
  }
@@ -451,6 +353,192 @@ async function _verifyOneSignature(payload, entry, index, keyResolver, context)
451
353
  if (unprotectedHeader) outcome.signerHeader = unprotectedHeader;
452
354
  return outcome;
453
355
  }
356
+ async function signMulti(payload, signers, options = {}) {
357
+ if (!Array.isArray(signers) || signers.length === 0) throw new JWTError("signMulti requires at least one signer.", "ERR_JWS_INVALID");
358
+ for (const [index, signer] of signers.entries()) {
359
+ assertOptionEntry(signer, {
360
+ label: `signers[${index}]`,
361
+ objects: ["protectedHeader", "unprotectedHeader"]
362
+ });
363
+ assertNotStringKey(signer.key);
364
+ assertProtectedOnlyOption(signer.unprotectedHeader, {
365
+ tier: "unprotected",
366
+ family: "JWS"
367
+ });
368
+ assertProducerKeyHeaders(signer.protectedHeader, {
369
+ tier: "protected",
370
+ family: "JWS"
371
+ });
372
+ assertProducerKeyHeaders(signer.unprotectedHeader, {
373
+ tier: "unprotected",
374
+ family: "JWS"
375
+ });
376
+ assertB64Option(signer.protectedHeader);
377
+ }
378
+ assertProducerConfirmation(payload);
379
+ const resolvedAlgs = signers.map((s, i) => _resolveSignerAlg(s, i));
380
+ const b64 = _resolveB64(signers);
381
+ const computedPayload = computeJwtTimeClaims(payload, options);
382
+ const payloadBytes = getPlaintextBytes(computedPayload || payload);
383
+ const payloadEncoded = b64 ? base64UrlStringify(payloadBytes) : parsePayloadText(payloadBytes);
384
+ const wireSignatures = [];
385
+ for (let i = 0; i < signers.length; i++) {
386
+ const signer = signers[i];
387
+ const alg = resolvedAlgs[i];
388
+ const protectedHeader = buildJWSProtectedHeader(alg, signer.key, signer.protectedHeader, {
389
+ payload,
390
+ b64
391
+ });
392
+ const unprotectedHeader = copyJsonObject(signer.unprotectedHeader, {
393
+ code: "ERR_INVALID_OPTION",
394
+ message: "\"unprotectedHeader\" must hold only JSON values."
395
+ });
396
+ _assertDisjointHeaders(protectedHeader, unprotectedHeader);
397
+ const protectedHeaderEncoded = base64UrlStringify(serializeProtectedHeader(protectedHeader));
398
+ const signingInputBytes = textEncoder.encode(`${protectedHeaderEncoded}.${payloadEncoded}`);
399
+ let signatureBytes;
400
+ try {
401
+ const signingKey = await resolveSigningKey(alg, await importKey(signer.key, {
402
+ alg,
403
+ expect: "private"
404
+ }), "sign");
405
+ signatureBytes = await sign$1(alg, signingKey, signingInputBytes);
406
+ } catch (error) {
407
+ throw asKeyUsageError(error, alg);
408
+ }
409
+ const wireSig = {
410
+ protected: protectedHeaderEncoded,
411
+ signature: base64UrlStringify(signatureBytes)
412
+ };
413
+ if (unprotectedHeader && Object.keys(unprotectedHeader).length > 0) wireSig.header = unprotectedHeader;
414
+ wireSignatures.push(wireSig);
415
+ }
416
+ return {
417
+ payload: payloadEncoded,
418
+ signatures: wireSignatures
419
+ };
420
+ }
421
+ async function verifyMulti(jws, keyOrLookup, options = {}) {
422
+ assertConsumerOptions(options);
423
+ const payloadMode = resolvePayloadMode(options);
424
+ const signatureLimit = resolveLimit(options.limits, "signatures");
425
+ const keyCandidates = resolveLimit(options.limits, "keyCandidates");
426
+ const general = _readGeneralSerialization(jws, signatureLimit);
427
+ let lastError;
428
+ let cryptoAttempted = false;
429
+ for (let i = 0; i < general.signatures.length; i++) {
430
+ const read = _readSignatureHeaders(general.signatures[i], i);
431
+ if (read.error) {
432
+ lastError = read.error;
433
+ continue;
434
+ }
435
+ const { wireSig, protectedHeader, unprotectedHeader, effective, alg } = read;
436
+ const typError = checkProtectedTyp(options.typ, protectedHeader, {
437
+ family: "JWS",
438
+ label: `signature[${i}]`
439
+ });
440
+ if (typError) {
441
+ lastError = typError;
442
+ continue;
443
+ }
444
+ let rawKeyMaterial;
445
+ try {
446
+ rawKeyMaterial = await resolveKeyInput(keyOrLookup, {
447
+ header: effective,
448
+ token: wireSig.protected ?? "",
449
+ what: `signature[${i}]`
450
+ });
451
+ } catch (err) {
452
+ lastError = asKeyLookupError(err, `signature[${i}]`);
453
+ continue;
454
+ }
455
+ if (typeof rawKeyMaterial === "string") {
456
+ lastError = stringKeyError();
457
+ continue;
458
+ }
459
+ const algError = checkAlgAllowed(alg, rawKeyMaterial, {
460
+ ctx: JWS_ALG_CTX,
461
+ algorithms: options.algorithms,
462
+ allowSymmetric: options.allowSymmetricJWKSet
463
+ });
464
+ if (algError) {
465
+ lastError = algError;
466
+ continue;
467
+ }
468
+ if (options.strictSignerMatch && !keyMatchesHeader(effective, rawKeyMaterial, {
469
+ ctx: JWS_ALG_CTX,
470
+ allowSymmetric: options.allowSymmetricJWKSet
471
+ })) continue;
472
+ let signatureBytes;
473
+ try {
474
+ signatureBytes = decodeSegment(wireSig.signature, {
475
+ family: "JWS",
476
+ what: "Signature"
477
+ });
478
+ } catch (err) {
479
+ lastError = asJWTError(err, INVALID_JWS);
480
+ continue;
481
+ }
482
+ const signingInputBytes = textEncoder.encode(`${wireSig.protected ?? ""}.${general.payload}`);
483
+ cryptoAttempted = true;
484
+ try {
485
+ if (!await verifyWithKey(alg, rawKeyMaterial, {
486
+ signature: signatureBytes,
487
+ signingInput: signingInputBytes,
488
+ kid: effective.kid,
489
+ allowSymmetric: options.allowSymmetricJWKSet,
490
+ keyCandidates
491
+ })) {
492
+ lastError = new JWTError("JWS signature verification failed.", "ERR_JWS_SIGNATURE_INVALID");
493
+ continue;
494
+ }
495
+ validateCriticalHeadersJWS(protectedHeader, options.recognizedHeaders);
496
+ const useB64 = protectedHeader.b64 !== false;
497
+ const payload = decodeJWSPayload(general.payload, useB64, payloadMode);
498
+ validateClaimsIfJson(payload, payloadMode, options);
499
+ const result = {
500
+ payload,
501
+ protectedHeader,
502
+ signerIndex: i
503
+ };
504
+ if (unprotectedHeader) result.signerHeader = unprotectedHeader;
505
+ return result;
506
+ } catch (err) {
507
+ const error = asJWTError(err, INVALID_SIGNATURE);
508
+ if (!PER_SIGNATURE_ERROR_CODES.has(error.code)) throw error;
509
+ lastError = error;
510
+ }
511
+ }
512
+ if (options.strictSignerMatch && !cryptoAttempted) throw new JWTError("No signature matched the provided key under strictSignerMatch.", "ERR_JWS_NO_MATCHING_SIGNER");
513
+ throw lastError ?? new JWTError("JWS signature verification failed.", "ERR_JWS_SIGNATURE_INVALID");
514
+ }
515
+ async function verifyMultiAll(jws, keyResolver, options = {}) {
516
+ assertConsumerOptions(options);
517
+ const payloadMode = resolvePayloadMode(options);
518
+ const signatureLimit = resolveLimit(options.limits, "signatures");
519
+ const keyCandidates = resolveLimit(options.limits, "keyCandidates");
520
+ const general = _readGeneralSerialization(jws, signatureLimit);
521
+ const outcomes = [];
522
+ for (let i = 0; i < general.signatures.length; i++) outcomes.push(await _verifyOneSignature(general.payload, general.signatures[i], i, keyResolver, {
523
+ options,
524
+ payloadMode,
525
+ keyCandidates
526
+ }));
527
+ return outcomes;
528
+ }
529
+ function generalToFlattenedJWS(jws) {
530
+ assertSerializationObject(jws, "JWS");
531
+ if (!Array.isArray(jws.signatures)) throw new JWTError("Invalid JWS JSON Serialization: missing \"signatures\".", "ERR_JWS_INVALID_SERIALIZATION");
532
+ if (jws.signatures.length !== 1) throw new JWTError(`Flattened JWS Serialization requires exactly one signature, got ${jws.signatures.length}.`, "ERR_JWS_INVALID_SERIALIZATION");
533
+ const [signature] = jws.signatures;
534
+ const flattened = {
535
+ payload: jws.payload,
536
+ signature: signature.signature
537
+ };
538
+ if (signature.protected !== void 0) flattened.protected = signature.protected;
539
+ if (signature.header !== void 0) flattened.header = signature.header;
540
+ return flattened;
541
+ }
454
542
  async function sign(payload, key, options = {}) {
455
543
  assertOptionEntry(options, {
456
544
  label: "options",
@@ -466,7 +554,7 @@ async function sign(payload, key, options = {}) {
466
554
  let { alg } = options;
467
555
  if (!alg) {
468
556
  if (isJWK(key) && key.alg) alg = key.alg;
469
- else throw new JWTError("JWS \"alg\" (Algorithm) must be provided in options", "ERR_JWS_ALG_MISSING");
557
+ else throw new JWTError("JWS \"alg\" must be provided in options", "ERR_JWS_ALG_MISSING");
470
558
  }
471
559
  assertJWSAlgSupported(alg);
472
560
  let signingKey;
@@ -488,7 +576,7 @@ async function sign(payload, key, options = {}) {
488
576
  if (useB64) payloadEncoded = base64UrlStringify(payloadBytes);
489
577
  else {
490
578
  payloadEncoded = parsePayloadText(payloadBytes);
491
- if (payloadEncoded.includes(".")) throw new JWTError("Invalid JWS: unencoded compact payload must not contain \".\" (RFC 7797 §5.2).", "ERR_JWS_INVALID");
579
+ if (payloadEncoded.includes(".")) throw new JWTError("Invalid JWS: unencoded compact payload must not contain \".\".", "ERR_JWS_INVALID");
492
580
  }
493
581
  const signingInputString = `${protectedHeaderEncoded}.${payloadEncoded}`;
494
582
  const signingInputBytes = textEncoder.encode(signingInputString);
@@ -538,8 +626,7 @@ async function verify(jws, key, options = {}) {
538
626
  keyCandidates: keyCandidateLimit
539
627
  })) throw new JWTError("JWS signature verification failed.", "ERR_JWS_SIGNATURE_INVALID");
540
628
  validateCriticalHeadersJWS(protectedHeader, options.recognizedHeaders);
541
- const useB64 = protectedHeader.b64 !== false;
542
- const payload = decodeJWSPayload(payloadEncoded, useB64, payloadMode);
629
+ const payload = decodeJWSPayload(payloadEncoded, protectedHeader.b64 !== false, payloadMode);
543
630
  validateClaimsIfJson(payload, payloadMode, options);
544
631
  return {
545
632
  payload,
@@ -1,4 +1,5 @@
1
- import { Duration, JOSEPayload, JOSEPayloadMode, JWTClaimValidationOptions, JWTClaims, JWTErrorCode, JWTTimeClaimOptions, PayloadOf } from "./index.mjs";
1
+ import { JWTErrorCode } from "./error.mjs";
2
+ import { Duration, JOSEPayload, JOSEPayloadMode, JWTClaimValidationOptions, JWTClaims, JWTTimeClaimOptions, PayloadOf } from "./index.mjs";
2
3
  /** Set `typ: "JWT"` on a header without `typ` when the payload is an object (not a `Uint8Array`). */
3
4
  export declare function applyTypDefault<T extends {
4
5
  typ?: string;