Skip to content

Commit 8e1f521

Browse files
Fix for CVE-2024-33664. JWE limited to 250K (mpdavis#352)
* Fix for CVE-2024-33664. JWE limited to 250K * Removed Py3.7 from Ubuntu latest. Linting on Py3.10 * Changed exception message * Test now uses monkeypatch and checks error message text * Update .github/workflows/ci.yml * Update .github/workflows/ci.yml * Update jose/jwe.py * Update tests/test_jwe.py * fmt --------- Co-authored-by: Asher Foa <[email protected]>
1 parent c9403b5 commit 8e1f521

File tree

4 files changed

+48
-9
lines changed

4 files changed

+48
-9
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
strategy:
1616
fail-fast: false
1717
matrix:
18-
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "pypy3.9"]
18+
python-version: ["3.8", "3.9", "3.10", "3.11", "pypy3.9"]
1919
os: [ubuntu-latest, macos-latest, windows-latest]
2020
exclude:
2121
- os: macos-latest
@@ -56,7 +56,7 @@ jobs:
5656
- uses: actions/checkout@v3
5757
- uses: actions/setup-python@v4
5858
with:
59-
python-version: 3.9
59+
python-version: "3.10"
6060
- name: Install dependencies
6161
run: |
6262
pip install -U setuptools

jose/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,5 @@ class Zips:
9696

9797

9898
ZIPS = Zips()
99+
100+
JWE_SIZE_LIMIT = 250 * 1024

jose/jwe.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from . import jwk
88
from .backends import get_random_bytes
9-
from .constants import ALGORITHMS, ZIPS
9+
from .constants import ALGORITHMS, JWE_SIZE_LIMIT, ZIPS
1010
from .exceptions import JWEError, JWEParseError
1111
from .utils import base64url_decode, base64url_encode, ensure_binary
1212

@@ -76,6 +76,13 @@ def decrypt(jwe_str, key):
7676
>>> jwe.decrypt(jwe_string, 'asecret128bitkey')
7777
'Hello, World!'
7878
"""
79+
80+
# Limit the token size - if the data is compressed then decompressing the
81+
# data could lead to large memory usage. This helps address This addresses
82+
# CVE-2024-33664. Also see _decompress()
83+
if len(jwe_str) > JWE_SIZE_LIMIT:
84+
raise JWEError(f"JWE string {len(jwe_str)} bytes exceeds {JWE_SIZE_LIMIT} bytes")
85+
7986
header, encoded_header, encrypted_key, iv, cipher_text, auth_tag = _jwe_compact_deserialize(jwe_str)
8087

8188
# Verify that the implementation understands and can process all
@@ -424,13 +431,13 @@ def _compress(zip, plaintext):
424431
(bytes): Compressed plaintext
425432
"""
426433
if zip not in ZIPS.SUPPORTED:
427-
raise NotImplementedError("ZIP {} is not supported!")
434+
raise NotImplementedError(f"ZIP {zip} is not supported!")
428435
if zip is None:
429436
compressed = plaintext
430437
elif zip == ZIPS.DEF:
431438
compressed = zlib.compress(plaintext)
432439
else:
433-
raise NotImplementedError("ZIP {} is not implemented!")
440+
raise NotImplementedError(f"ZIP {zip} is not implemented!")
434441
return compressed
435442

436443

@@ -446,13 +453,18 @@ def _decompress(zip, compressed):
446453
(bytes): Compressed plaintext
447454
"""
448455
if zip not in ZIPS.SUPPORTED:
449-
raise NotImplementedError("ZIP {} is not supported!")
456+
raise NotImplementedError(f"ZIP {zip} is not supported!")
450457
if zip is None:
451458
decompressed = compressed
452459
elif zip == ZIPS.DEF:
453-
decompressed = zlib.decompress(compressed)
460+
# If, during decompression, there is more data than expected, the
461+
# decompression halts and raise an error. This addresses CVE-2024-33664
462+
decompressor = zlib.decompressobj()
463+
decompressed = decompressor.decompress(compressed, max_length=JWE_SIZE_LIMIT)
464+
if decompressor.unconsumed_tail:
465+
raise JWEError(f"Decompressed JWE string exceeds {JWE_SIZE_LIMIT} bytes")
454466
else:
455-
raise NotImplementedError("ZIP {} is not implemented!")
467+
raise NotImplementedError(f"ZIP {zip} is not implemented!")
456468
return decompressed
457469

458470

tests/test_jwe.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import jose.backends
66
from jose import jwe
77
from jose.constants import ALGORITHMS, ZIPS
8-
from jose.exceptions import JWEParseError
8+
from jose.exceptions import JWEError, JWEParseError
99
from jose.jwk import AESKey, RSAKey
1010
from jose.utils import base64url_decode
1111

@@ -525,3 +525,28 @@ def test_kid_header_not_present_when_not_provided(self):
525525
encrypted = jwe.encrypt("Text", PUBLIC_KEY_PEM, enc, alg)
526526
header = json.loads(base64url_decode(encrypted.split(b".")[0]))
527527
assert "kid" not in header
528+
529+
@pytest.mark.skipif(AESKey is None, reason="No AES backend")
530+
def test_jwe_with_excessive_data(self, monkeypatch):
531+
enc = ALGORITHMS.A256CBC_HS512
532+
alg = ALGORITHMS.RSA_OAEP_256
533+
monkeypatch.setattr("jose.constants.JWE_SIZE_LIMIT", 1024)
534+
encrypted = jwe.encrypt(b"Text" * 64 * 1024, PUBLIC_KEY_PEM, enc, alg)
535+
header = json.loads(base64url_decode(encrypted.split(b".")[0]))
536+
with pytest.raises(JWEError) as excinfo:
537+
actual = jwe.decrypt(encrypted, PRIVATE_KEY_PEM)
538+
assert "JWE string" in str(excinfo.value)
539+
assert "bytes exceeds" in str(excinfo.value)
540+
541+
@pytest.mark.skipif(AESKey is None, reason="No AES backend")
542+
def test_jwe_zip_with_excessive_data(self, monkeypatch):
543+
# Test that a fix for CVE-2024-33664 is in place.
544+
enc = ALGORITHMS.A256CBC_HS512
545+
alg = ALGORITHMS.RSA_OAEP_256
546+
monkeypatch.setattr("jose.constants.JWE_SIZE_LIMIT", 1024)
547+
encrypted = jwe.encrypt(b"Text" * 64 * 1024, PUBLIC_KEY_PEM, enc, alg, zip=ZIPS.DEF)
548+
assert len(encrypted) < jose.constants.JWE_SIZE_LIMIT
549+
header = json.loads(base64url_decode(encrypted.split(b".")[0]))
550+
with pytest.raises(JWEError) as excinfo:
551+
actual = jwe.decrypt(encrypted, PRIVATE_KEY_PEM)
552+
assert "Decompressed JWE string exceeds" in str(excinfo.value)

0 commit comments

Comments
 (0)