jose 6.1.3 → 6.2.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.
@@ -16,6 +16,13 @@ export interface GeneralDecryptGetKey extends types.GetKeyFunction<types.JWEHead
16
16
  * This function is exported (as a named export) from the main `'jose'` module entry point as well
17
17
  * as from its subpath export `'jose/jwe/general/decrypt'`.
18
18
  *
19
+ * > [!NOTE]\
20
+ * > The function iterates over the `recipients` array in the General JWE and returns the decryption
21
+ * > result of the first recipient entry that can be successfully decrypted. The result only contains
22
+ * > the plaintext and headers of that successfully decrypted recipient entry. Other recipient entries
23
+ * > in the General JWE are not validated, and their headers are not included in the returned result.
24
+ * > Recipients of a General JWE should only rely on the returned (decrypted) data.
25
+ *
19
26
  * @param jwe General JWE.
20
27
  * @param key Private Key or Secret to decrypt the JWE with. See
21
28
  * {@link https://github.com/panva/jose/issues/210#jwe-alg Algorithm Key Requirements}.
@@ -18,6 +18,14 @@ export interface GeneralVerifyGetKey extends types.GenericGetKeyFunction<types.J
18
18
  * This function is exported (as a named export) from the main `'jose'` module entry point as well
19
19
  * as from its subpath export `'jose/jws/general/verify'`.
20
20
  *
21
+ * > [!NOTE]\
22
+ * > The function iterates over the `signatures` array in the General JWS and returns the verification
23
+ * > result of the first signature entry that can be successfully verified. The result only contains
24
+ * > the payload, protected header, and unprotected header of that successfully verified signature
25
+ * > entry. Other signature entries in the General JWS are not validated, and their headers are not
26
+ * > included in the returned result. Recipients of a General JWS should only rely on the returned
27
+ * > (verified) data.
28
+ *
21
29
  * @param jws General JWS.
22
30
  * @param key Key to verify the JWS with. See
23
31
  * {@link https://github.com/panva/jose/issues/210#jws-alg Algorithm Key Requirements}.
@@ -415,12 +415,12 @@ export interface JWEHeaderParameters extends JoseHeaderParameters {
415
415
  crit?: string[]
416
416
 
417
417
  /**
418
- * JWE "zip" (Compression Algorithm) Header Parameter. This parameter is not supported anymore.
418
+ * JWE "zip" (Compression Algorithm) Header Parameter.
419
419
  *
420
- * @deprecated Compression of data SHOULD NOT be done before encryption, because such compressed
421
- * data often reveals information about the plaintext.
420
+ * The only supported value is `"DEF"` (DEFLATE). Requires the `CompressionStream` /
421
+ * `DecompressionStream` APIs to be available in the runtime.
422
422
  *
423
- * @see {@link https://www.rfc-editor.org/rfc/rfc8725#name-avoid-compression-of-encryp Avoid Compression of Encryption Inputs}
423
+ * @see {@link https://www.rfc-editor.org/rfc/rfc7516#section-4.1.3 JWE "zip" Header Parameter}
424
424
  */
425
425
  zip?: string
426
426
 
@@ -474,6 +474,17 @@ export interface DecryptOptions extends CritOption {
474
474
  * this value is set to 10000.
475
475
  */
476
476
  maxPBES2Count?: number
477
+
478
+ /**
479
+ * Maximum allowed size (in bytes) of the decompressed plaintext when the JWE `"zip"` (Compression
480
+ * Algorithm) Header Parameter is present. By default this value is set to 250000 (250 KB). The
481
+ * value must be `0`, a positive safe integer, or `Infinity`.
482
+ *
483
+ * Set to `0` to reject all compressed JWEs during decryption.
484
+ *
485
+ * Set to `Infinity` to disable the decompressed size limit.
486
+ */
487
+ maxDecompressedLength?: number
477
488
  }
478
489
 
479
490
  /** JWE Encryption options. */
@@ -10,6 +10,7 @@ import { validateCrit } from '../../lib/validate_crit.js';
10
10
  import { validateAlgorithms } from '../../lib/validate_algorithms.js';
11
11
  import { normalizeKey } from '../../lib/normalize_key.js';
12
12
  import { checkKeyType } from '../../lib/check_key_type.js';
13
+ import { decompress } from '../../lib/deflate.js';
13
14
  export async function flattenedDecrypt(jwe, key, options) {
14
15
  if (!isObject(jwe)) {
15
16
  throw new JWEInvalid('Flattened JWE must be an object');
@@ -60,8 +61,11 @@ export async function flattenedDecrypt(jwe, key, options) {
60
61
  ...jwe.unprotected,
61
62
  };
62
63
  validateCrit(JWEInvalid, new Map(), options?.crit, parsedProt, joseHeader);
63
- if (joseHeader.zip !== undefined) {
64
- throw new JOSENotSupported('JWE "zip" (Compression Algorithm) Header Parameter is not supported.');
64
+ if (joseHeader.zip !== undefined && joseHeader.zip !== 'DEF') {
65
+ throw new JOSENotSupported('Unsupported JWE "zip" (Compression Algorithm) Header Parameter value.');
66
+ }
67
+ if (joseHeader.zip !== undefined && !parsedProt?.zip) {
68
+ throw new JWEInvalid('JWE "zip" (Compression Algorithm) Header Parameter MUST be in a protected header.');
65
69
  }
66
70
  const { alg, enc } = joseHeader;
67
71
  if (typeof alg !== 'string' || !alg) {
@@ -141,6 +145,17 @@ export async function flattenedDecrypt(jwe, key, options) {
141
145
  }
142
146
  const plaintext = await decrypt(enc, cek, ciphertext, iv, tag, additionalData);
143
147
  const result = { plaintext };
148
+ if (joseHeader.zip === 'DEF') {
149
+ const maxDecompressedLength = options?.maxDecompressedLength ?? 250_000;
150
+ if (maxDecompressedLength === 0) {
151
+ throw new JOSENotSupported('JWE "zip" (Compression Algorithm) Header Parameter is not supported.');
152
+ }
153
+ if (maxDecompressedLength !== Infinity &&
154
+ (!Number.isSafeInteger(maxDecompressedLength) || maxDecompressedLength < 1)) {
155
+ throw new TypeError('maxDecompressedLength must be 0, a positive safe integer, or Infinity');
156
+ }
157
+ result.plaintext = await decompress(plaintext, maxDecompressedLength);
158
+ }
144
159
  if (jwe.protected !== undefined) {
145
160
  result.protectedHeader = parsedProt;
146
161
  }
@@ -8,6 +8,7 @@ import { concat, encode } from '../../lib/buffer_utils.js';
8
8
  import { validateCrit } from '../../lib/validate_crit.js';
9
9
  import { normalizeKey } from '../../lib/normalize_key.js';
10
10
  import { checkKeyType } from '../../lib/check_key_type.js';
11
+ import { compress } from '../../lib/deflate.js';
11
12
  export class FlattenedEncrypt {
12
13
  #plaintext;
13
14
  #protectedHeader;
@@ -82,8 +83,11 @@ export class FlattenedEncrypt {
82
83
  ...this.#sharedUnprotectedHeader,
83
84
  };
84
85
  validateCrit(JWEInvalid, new Map(), options?.crit, this.#protectedHeader, joseHeader);
85
- if (joseHeader.zip !== undefined) {
86
- throw new JOSENotSupported('JWE "zip" (Compression Algorithm) Header Parameter is not supported.');
86
+ if (joseHeader.zip !== undefined && joseHeader.zip !== 'DEF') {
87
+ throw new JOSENotSupported('Unsupported JWE "zip" (Compression Algorithm) Header Parameter value.');
88
+ }
89
+ if (joseHeader.zip !== undefined && !this.#protectedHeader?.zip) {
90
+ throw new JWEInvalid('JWE "zip" (Compression Algorithm) Header Parameter MUST be in a protected header.');
87
91
  }
88
92
  const { alg, enc } = joseHeader;
89
93
  if (typeof alg !== 'string' || !alg) {
@@ -139,7 +143,11 @@ export class FlattenedEncrypt {
139
143
  else {
140
144
  additionalData = protectedHeaderB;
141
145
  }
142
- const { ciphertext, tag, iv } = await encrypt(enc, this.#plaintext, cek, this.#iv, additionalData);
146
+ let plaintext = this.#plaintext;
147
+ if (joseHeader.zip === 'DEF') {
148
+ plaintext = await compress(plaintext);
149
+ }
150
+ const { ciphertext, tag, iv } = await encrypt(enc, plaintext, cek, this.#iv, additionalData);
143
151
  const jwe = {
144
152
  ciphertext: b64u(ciphertext),
145
153
  };
@@ -133,8 +133,11 @@ export class GeneralEncrypt {
133
133
  throw new JWEInvalid('JWE "enc" (Encryption Algorithm) Header Parameter must be the same for all recipients');
134
134
  }
135
135
  validateCrit(JWEInvalid, new Map(), recipient.options.crit, this.#protectedHeader, joseHeader);
136
- if (joseHeader.zip !== undefined) {
137
- throw new JOSENotSupported('JWE "zip" (Compression Algorithm) Header Parameter is not supported.');
136
+ if (joseHeader.zip !== undefined && joseHeader.zip !== 'DEF') {
137
+ throw new JOSENotSupported('Unsupported JWE "zip" (Compression Algorithm) Header Parameter value.');
138
+ }
139
+ if (joseHeader.zip !== undefined && !this.#protectedHeader?.zip) {
140
+ throw new JWEInvalid('JWE "zip" (Compression Algorithm) Header Parameter MUST be in a protected header.');
138
141
  }
139
142
  }
140
143
  const cek = generateCek(enc);
@@ -9,7 +9,7 @@ function isCloudflareWorkers() {
9
9
  let USER_AGENT;
10
10
  if (typeof navigator === 'undefined' || !navigator.userAgent?.startsWith?.('Mozilla/5.0 ')) {
11
11
  const NAME = 'jose';
12
- const VERSION = 'v6.1.3';
12
+ const VERSION = 'v6.2.0';
13
13
  USER_AGENT = `${NAME}/${VERSION}`;
14
14
  }
15
15
  export const customFetch = Symbol();
@@ -0,0 +1,44 @@
1
+ import { JOSENotSupported, JWEInvalid } from '../util/errors.js';
2
+ import { concat } from './buffer_utils.js';
3
+ function supported(name) {
4
+ if (typeof globalThis[name] === 'undefined') {
5
+ throw new JOSENotSupported(`JWE "zip" (Compression Algorithm) Header Parameter requires the ${name} API.`);
6
+ }
7
+ }
8
+ export async function compress(input) {
9
+ supported('CompressionStream');
10
+ const cs = new CompressionStream('deflate-raw');
11
+ const writer = cs.writable.getWriter();
12
+ writer.write(input);
13
+ writer.close();
14
+ const chunks = [];
15
+ const reader = cs.readable.getReader();
16
+ for (;;) {
17
+ const { value, done } = await reader.read();
18
+ if (done)
19
+ break;
20
+ chunks.push(value);
21
+ }
22
+ return concat(...chunks);
23
+ }
24
+ export async function decompress(input, maxLength) {
25
+ supported('DecompressionStream');
26
+ const ds = new DecompressionStream('deflate-raw');
27
+ const writer = ds.writable.getWriter();
28
+ writer.write(input);
29
+ writer.close();
30
+ const chunks = [];
31
+ let length = 0;
32
+ const reader = ds.readable.getReader();
33
+ for (;;) {
34
+ const { value, done } = await reader.read();
35
+ if (done)
36
+ break;
37
+ chunks.push(value);
38
+ length += value.byteLength;
39
+ if (maxLength !== Infinity && length > maxLength) {
40
+ throw new JWEInvalid('Decompressed plaintext exceeded the configured limit');
41
+ }
42
+ }
43
+ return concat(...chunks);
44
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jose",
3
- "version": "6.1.3",
3
+ "version": "6.2.0",
4
4
  "description": "JWA, JWS, JWE, JWT, JWK, JWKS for Node.js, Browser, Cloudflare Workers, Deno, Bun, and other Web-interoperable runtimes",
5
5
  "keywords": [
6
6
  "akp",