diff --git a/CVE-2024-33663.patch b/CVE-2024-33663.patch deleted file mode 100644 index 2bd58a9..0000000 --- a/CVE-2024-33663.patch +++ /dev/null @@ -1,599 +0,0 @@ -From 34bd82c43ea31da5b9deaa25ff591905a180bdf7 Mon Sep 17 00:00:00 2001 -From: Daniel Garcia Moreno -Date: Thu, 2 May 2024 09:29:54 +0200 -Subject: [PATCH 1/4] Improve asymmetric key check in CryptographyHMACKey - -This change should fix https://github.com/mpdavis/python-jose/issues/346 -security issue. - -The code is based on pyjwt change: -https://github.com/jpadilla/pyjwt/commit/9c528670c455b8d948aff95ed50e22940d1ad3fc ---- - jose/backends/cryptography_backend.py | 72 ++++++++++++++++++++++++--- - tests/test_jwt.py | 35 ++++++++++++- - 2 files changed, 98 insertions(+), 9 deletions(-) - -Index: python-jose-3.3.0/jose/backends/cryptography_backend.py -=================================================================== ---- python-jose-3.3.0.orig/jose/backends/cryptography_backend.py -+++ python-jose-3.3.0/jose/backends/cryptography_backend.py -@@ -17,6 +17,7 @@ from cryptography.x509 import load_pem_x - from ..constants import ALGORITHMS - from ..exceptions import JWEError, JWKError - from ..utils import base64_to_long, base64url_decode, base64url_encode, ensure_binary, long_to_base64 -+from ..utils import is_pem_format, is_ssh_key - from .base import Key - - _binding = None -@@ -552,14 +553,7 @@ class CryptographyHMACKey(Key): - if isinstance(key, str): - key = key.encode("utf-8") - -- invalid_strings = [ -- b"-----BEGIN PUBLIC KEY-----", -- b"-----BEGIN RSA PUBLIC KEY-----", -- b"-----BEGIN CERTIFICATE-----", -- b"ssh-rsa", -- ] -- -- if any(string_value in key for string_value in invalid_strings): -+ if is_pem_format(key) or is_ssh_key(key): - raise JWKError( - "The specified key is an asymmetric key or x509 certificate and" - " should not be used as an HMAC secret." -Index: python-jose-3.3.0/tests/test_jwt.py -=================================================================== ---- python-jose-3.3.0.orig/tests/test_jwt.py -+++ python-jose-3.3.0/tests/test_jwt.py -@@ -5,7 +5,8 @@ from datetime import datetime, timedelta - import pytest - - from jose import jws, jwt --from jose.exceptions import JWTError -+from jose.constants import ALGORITHMS -+from jose.exceptions import JWTError, JWKError - - - @pytest.fixture -@@ -56,7 +57,7 @@ class TestJWT: - ], - ) - def test_numeric_key(self, key, token): -- token_info = jwt.decode(token, key) -+ token_info = jwt.decode(token, key, algorithms=ALGORITHMS.SUPPORTED) - assert token_info == {"name": "test"} - - def test_invalid_claims_json(self): -@@ -108,7 +109,7 @@ class TestJWT: - - def test_non_default_headers(self, claims, key, headers): - encoded = jwt.encode(claims, key, headers=headers) -- decoded = jwt.decode(encoded, key) -+ decoded = jwt.decode(encoded, key, algorithms=ALGORITHMS.HS256) - assert claims == decoded - all_headers = jwt.get_unverified_headers(encoded) - for k, v in headers.items(): -@@ -161,7 +162,7 @@ class TestJWT: - - token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" ".eyJhIjoiYiJ9" ".jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8" - -- decoded = jwt.decode(token, key) -+ decoded = jwt.decode(token, key, algorithms=ALGORITHMS.SUPPORTED) - - assert decoded == claims - -@@ -193,7 +194,7 @@ class TestJWT: - options = {"leeway": leeway} - - token = jwt.encode(claims, key) -- jwt.decode(token, key, options=options) -+ jwt.decode(token, key, options=options, algorithms=ALGORITHMS.HS256) - - def test_iat_not_int(self, key): - -@@ -202,7 +203,7 @@ class TestJWT: - token = jwt.encode(claims, key) - - with pytest.raises(JWTError): -- jwt.decode(token, key) -+ jwt.decode(token, key, algorithms=ALGORITHMS.HS256) - - def test_nbf_not_int(self, key): - -@@ -211,7 +212,7 @@ class TestJWT: - token = jwt.encode(claims, key) - - with pytest.raises(JWTError): -- jwt.decode(token, key) -+ jwt.decode(token, key, algorithms=ALGORITHMS.HS256) - - def test_nbf_datetime(self, key): - -@@ -220,7 +221,7 @@ class TestJWT: - claims = {"nbf": nbf} - - token = jwt.encode(claims, key) -- jwt.decode(token, key) -+ jwt.decode(token, key, algorithms=ALGORITHMS.HS256) - - def test_nbf_with_leeway(self, key): - -@@ -233,7 +234,7 @@ class TestJWT: - options = {"leeway": 10} - - token = jwt.encode(claims, key) -- jwt.decode(token, key, options=options) -+ jwt.decode(token, key, options=options, algorithms=ALGORITHMS.HS256) - - def test_nbf_in_future(self, key): - -@@ -244,7 +245,7 @@ class TestJWT: - token = jwt.encode(claims, key) - - with pytest.raises(JWTError): -- jwt.decode(token, key) -+ jwt.decode(token, key, algorithms=ALGORITHMS.HS256) - - def test_nbf_skip(self, key): - -@@ -255,11 +256,11 @@ class TestJWT: - token = jwt.encode(claims, key) - - with pytest.raises(JWTError): -- jwt.decode(token, key) -+ jwt.decode(token, key, algorithms=ALGORITHMS.HS256) - - options = {"verify_nbf": False} - -- jwt.decode(token, key, options=options) -+ jwt.decode(token, key, options=options, algorithms=ALGORITHMS.HS256) - - def test_exp_not_int(self, key): - -@@ -268,7 +269,7 @@ class TestJWT: - token = jwt.encode(claims, key) - - with pytest.raises(JWTError): -- jwt.decode(token, key) -+ jwt.decode(token, key, algorithms=ALGORITHMS.HS256) - - def test_exp_datetime(self, key): - -@@ -277,7 +278,7 @@ class TestJWT: - claims = {"exp": exp} - - token = jwt.encode(claims, key) -- jwt.decode(token, key) -+ jwt.decode(token, key, algorithms=ALGORITHMS.HS256) - - def test_exp_with_leeway(self, key): - -@@ -290,7 +291,7 @@ class TestJWT: - options = {"leeway": 10} - - token = jwt.encode(claims, key) -- jwt.decode(token, key, options=options) -+ jwt.decode(token, key, options=options, algorithms=ALGORITHMS.HS256) - - def test_exp_in_past(self, key): - -@@ -301,7 +302,7 @@ class TestJWT: - token = jwt.encode(claims, key) - - with pytest.raises(JWTError): -- jwt.decode(token, key) -+ jwt.decode(token, key, algorithms=ALGORITHMS.HS256) - - def test_exp_skip(self, key): - -@@ -312,11 +313,11 @@ class TestJWT: - token = jwt.encode(claims, key) - - with pytest.raises(JWTError): -- jwt.decode(token, key) -+ jwt.decode(token, key, algorithms=ALGORITHMS.HS256) - - options = {"verify_exp": False} - -- jwt.decode(token, key, options=options) -+ jwt.decode(token, key, options=options, algorithms=ALGORITHMS.HS256) - - def test_aud_string(self, key): - -@@ -325,7 +326,7 @@ class TestJWT: - claims = {"aud": aud} - - token = jwt.encode(claims, key) -- jwt.decode(token, key, audience=aud) -+ jwt.decode(token, key, audience=aud, algorithms=ALGORITHMS.HS256) - - def test_aud_list(self, key): - -@@ -334,7 +335,7 @@ class TestJWT: - claims = {"aud": [aud]} - - token = jwt.encode(claims, key) -- jwt.decode(token, key, audience=aud) -+ jwt.decode(token, key, audience=aud, algorithms=ALGORITHMS.HS256) - - def test_aud_list_multiple(self, key): - -@@ -343,7 +344,7 @@ class TestJWT: - claims = {"aud": [aud, "another"]} - - token = jwt.encode(claims, key) -- jwt.decode(token, key, audience=aud) -+ jwt.decode(token, key, audience=aud, algorithms=ALGORITHMS.HS256) - - def test_aud_list_is_strings(self, key): - -@@ -353,7 +354,7 @@ class TestJWT: - - token = jwt.encode(claims, key) - with pytest.raises(JWTError): -- jwt.decode(token, key, audience=aud) -+ jwt.decode(token, key, audience=aud, algorithms=ALGORITHMS.HS256) - - def test_aud_case_sensitive(self, key): - -@@ -363,14 +364,14 @@ class TestJWT: - - token = jwt.encode(claims, key) - with pytest.raises(JWTError): -- jwt.decode(token, key, audience="AUDIENCE") -+ jwt.decode(token, key, audience="AUDIENCE", algorithms=ALGORITHMS.HS256) - - def test_aud_empty_claim(self, claims, key): - - aud = "audience" - - token = jwt.encode(claims, key) -- jwt.decode(token, key, audience=aud) -+ jwt.decode(token, key, audience=aud, algorithms=ALGORITHMS.HS256) - - def test_aud_not_string_or_list(self, key): - -@@ -380,7 +381,7 @@ class TestJWT: - - token = jwt.encode(claims, key) - with pytest.raises(JWTError): -- jwt.decode(token, key) -+ jwt.decode(token, key, algorithms=ALGORITHMS.HS256) - - def test_aud_given_number(self, key): - -@@ -390,7 +391,7 @@ class TestJWT: - - token = jwt.encode(claims, key) - with pytest.raises(JWTError): -- jwt.decode(token, key, audience=1) -+ jwt.decode(token, key, audience=1, algorithms=ALGORITHMS.HS256) - - def test_iss_string(self, key): - -@@ -399,7 +400,7 @@ class TestJWT: - claims = {"iss": iss} - - token = jwt.encode(claims, key) -- jwt.decode(token, key, issuer=iss) -+ jwt.decode(token, key, issuer=iss, algorithms=ALGORITHMS.HS256) - - def test_iss_list(self, key): - -@@ -408,7 +409,7 @@ class TestJWT: - claims = {"iss": iss} - - token = jwt.encode(claims, key) -- jwt.decode(token, key, issuer=["https://issuer", "issuer"]) -+ jwt.decode(token, key, issuer=["https://issuer", "issuer"], algorithms=ALGORITHMS.HS256) - - def test_iss_tuple(self, key): - -@@ -417,7 +418,7 @@ class TestJWT: - claims = {"iss": iss} - - token = jwt.encode(claims, key) -- jwt.decode(token, key, issuer=("https://issuer", "issuer")) -+ jwt.decode(token, key, issuer=("https://issuer", "issuer"), algorithms=ALGORITHMS.HS256) - - def test_iss_invalid(self, key): - -@@ -427,7 +428,7 @@ class TestJWT: - - token = jwt.encode(claims, key) - with pytest.raises(JWTError): -- jwt.decode(token, key, issuer="another") -+ jwt.decode(token, key, issuer="another", algorithms=ALGORITHMS.HS256) - - def test_sub_string(self, key): - -@@ -436,7 +437,7 @@ class TestJWT: - claims = {"sub": sub} - - token = jwt.encode(claims, key) -- jwt.decode(token, key) -+ jwt.decode(token, key, algorithms=ALGORITHMS.HS256) - - def test_sub_invalid(self, key): - -@@ -446,7 +447,7 @@ class TestJWT: - - token = jwt.encode(claims, key) - with pytest.raises(JWTError): -- jwt.decode(token, key) -+ jwt.decode(token, key, algorithms=ALGORITHMS.HS256) - - def test_sub_correct(self, key): - -@@ -455,7 +456,7 @@ class TestJWT: - claims = {"sub": sub} - - token = jwt.encode(claims, key) -- jwt.decode(token, key, subject=sub) -+ jwt.decode(token, key, subject=sub, algorithms=ALGORITHMS.HS256) - - def test_sub_incorrect(self, key): - -@@ -465,7 +466,7 @@ class TestJWT: - - token = jwt.encode(claims, key) - with pytest.raises(JWTError): -- jwt.decode(token, key, subject="another") -+ jwt.decode(token, key, subject="another", algorithms=ALGORITHMS.HS256) - - def test_jti_string(self, key): - -@@ -474,7 +475,7 @@ class TestJWT: - claims = {"jti": jti} - - token = jwt.encode(claims, key) -- jwt.decode(token, key) -+ jwt.decode(token, key, algorithms=ALGORITHMS.HS256) - - def test_jti_invalid(self, key): - -@@ -484,33 +485,33 @@ class TestJWT: - - token = jwt.encode(claims, key) - with pytest.raises(JWTError): -- jwt.decode(token, key) -+ jwt.decode(token, key, algorithms=ALGORITHMS.HS256) - - def test_at_hash(self, claims, key): - access_token = "" - token = jwt.encode(claims, key, access_token=access_token) -- payload = jwt.decode(token, key, access_token=access_token) -+ payload = jwt.decode(token, key, access_token=access_token, algorithms=ALGORITHMS.HS256) - assert "at_hash" in payload - - def test_at_hash_invalid(self, claims, key): - token = jwt.encode(claims, key, access_token="") - with pytest.raises(JWTError): -- jwt.decode(token, key, access_token="") -+ jwt.decode(token, key, access_token="", algorithms=ALGORITHMS.HS256) - - def test_at_hash_missing_access_token(self, claims, key): - token = jwt.encode(claims, key, access_token="") - with pytest.raises(JWTError): -- jwt.decode(token, key) -+ jwt.decode(token, key, algorithms=ALGORITHMS.HS256) - - def test_at_hash_missing_claim(self, claims, key): - token = jwt.encode(claims, key) -- payload = jwt.decode(token, key, access_token="") -+ payload = jwt.decode(token, key, access_token="", algorithms=ALGORITHMS.HS256) - assert "at_hash" not in payload - - def test_at_hash_unable_to_calculate(self, claims, key): - token = jwt.encode(claims, key, access_token="") - with pytest.raises(JWTError): -- jwt.decode(token, key, access_token="\xe2") -+ jwt.decode(token, key, access_token="\xe2", algorithms=ALGORITHMS.HS256) - - def test_bad_claims(self): - bad_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.iOJ5SiNfaNO_pa2J4Umtb3b3zmk5C18-mhTCVNsjnck" -@@ -548,9 +549,48 @@ class TestJWT: - - token = jwt.encode(claims, key) - with pytest.raises(JWTError): -- jwt.decode(token, key, options=options, audience=str(value)) -+ jwt.decode(token, key, options=options, audience=str(value), algorithms=ALGORITHMS.HS256) - - new_claims = dict(claims) - new_claims[claim] = value - token = jwt.encode(new_claims, key) -- jwt.decode(token, key, options=options, audience=str(value)) -+ jwt.decode(token, key, options=options, audience=str(value), algorithms=ALGORITHMS.HS256) -+ -+ def test_CVE_2024_33663(self): -+ """Test based on https://github.com/mpdavis/python-jose/issues/346""" -+ try: -+ from Crypto.PublicKey import ECC -+ from Crypto.Hash import HMAC, SHA256 -+ except ModuleNotFoundError: -+ pytest.skip("pycryptodome module not installed") -+ -+ # ----- SETUP ----- -+ # generate an asymmetric ECC keypair -+ # !! signing should only be possible with the private key !! -+ KEY = ECC.generate(curve='P-256') -+ -+ # PUBLIC KEY, AVAILABLE TO USER -+ # CAN BE RECOVERED THROUGH E.G. PUBKEY RECOVERY WITH TWO SIGNATURES: -+ # https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm#Public_key_recovery -+ # https://github.com/FlorianPicca/JWT-Key-Recovery -+ PUBKEY = KEY.public_key().export_key(format='OpenSSH').encode() -+ -+ # ---- CLIENT SIDE ----- -+ # without knowing the private key, a valid token can be constructed -+ # YIKES!! -+ -+ b64 = lambda x:base64.urlsafe_b64encode(x).replace(b'=',b'') -+ payload = b64(b'{"alg":"HS256"}') + b'.' + b64(b'{"pwned":true}') -+ hasher = HMAC.new(PUBKEY, digestmod=SHA256) -+ hasher.update(payload) -+ evil_token = payload + b'.' + b64(hasher.digest()) -+ -+ # ---- SERVER SIDE ----- -+ # verify and decode the token using the public key, as is custom -+ # algorithm field is left unspecified -+ # but the library will happily still verify without warning, trusting the user-controlled alg field of the token header -+ with pytest.raises(JWKError): -+ data = jwt.decode(evil_token, PUBKEY, algorithms=ALGORITHMS.HS256) -+ -+ with pytest.raises(JWTError, match='.*required.*"algorithms".*'): -+ data = jwt.decode(evil_token, PUBKEY) -Index: python-jose-3.3.0/jose/jwt.py -=================================================================== ---- python-jose-3.3.0.orig/jose/jwt.py -+++ python-jose-3.3.0/jose/jwt.py -@@ -138,6 +138,14 @@ def decode(token, key, algorithms=None, - - verify_signature = defaults.get("verify_signature", True) - -+ # Forbid the usage of the jwt.decode without alogrightms parameter -+ # See https://github.com/mpdavis/python-jose/issues/346 for more -+ # information CVE-2024-33663 -+ if verify_signature and algorithms is None: -+ raise JWTError("It is required that you pass in a value for " -+ 'the "algorithms" argument when calling ' -+ "decode().") -+ - try: - payload = jws.verify(token, key, algorithms, verify=verify_signature) - except JWSError as e: -Index: python-jose-3.3.0/jose/backends/native.py -=================================================================== ---- python-jose-3.3.0.orig/jose/backends/native.py -+++ python-jose-3.3.0/jose/backends/native.py -@@ -6,6 +6,7 @@ from jose.backends.base import Key - from jose.constants import ALGORITHMS - from jose.exceptions import JWKError - from jose.utils import base64url_decode, base64url_encode -+from jose.utils import is_pem_format, is_ssh_key - - - def get_random_bytes(num_bytes): -@@ -36,14 +37,7 @@ class HMACKey(Key): - if isinstance(key, str): - key = key.encode("utf-8") - -- invalid_strings = [ -- b"-----BEGIN PUBLIC KEY-----", -- b"-----BEGIN RSA PUBLIC KEY-----", -- b"-----BEGIN CERTIFICATE-----", -- b"ssh-rsa", -- ] -- -- if any(string_value in key for string_value in invalid_strings): -+ if is_pem_format(key) or is_ssh_key(key): - raise JWKError( - "The specified key is an asymmetric key or x509 certificate and" - " should not be used as an HMAC secret." -Index: python-jose-3.3.0/jose/utils.py -=================================================================== ---- python-jose-3.3.0.orig/jose/utils.py -+++ python-jose-3.3.0/jose/utils.py -@@ -1,3 +1,4 @@ -+import re - import base64 - import struct - -@@ -106,3 +107,75 @@ def ensure_binary(s): - if isinstance(s, str): - return s.encode("utf-8", "strict") - raise TypeError(f"not expecting type '{type(s)}'") -+ -+ -+# Based on https://github.com/jpadilla/pyjwt/commit/9c528670c455b8d948aff95ed50e22940d1ad3fc -+# Based on https://github.com/hynek/pem/blob/7ad94db26b0bc21d10953f5dbad3acfdfacf57aa/src/pem/_core.py#L224-L252 -+_PEMS = { -+ b"CERTIFICATE", -+ b"TRUSTED CERTIFICATE", -+ b"PRIVATE KEY", -+ b"PUBLIC KEY", -+ b"ENCRYPTED PRIVATE KEY", -+ b"OPENSSH PRIVATE KEY", -+ b"DSA PRIVATE KEY", -+ b"RSA PRIVATE KEY", -+ b"RSA PUBLIC KEY", -+ b"EC PRIVATE KEY", -+ b"DH PARAMETERS", -+ b"NEW CERTIFICATE REQUEST", -+ b"CERTIFICATE REQUEST", -+ b"SSH2 PUBLIC KEY", -+ b"SSH2 ENCRYPTED PRIVATE KEY", -+ b"X509 CRL", -+} -+ -+ -+_PEM_RE = re.compile( -+ b"----[- ]BEGIN (" -+ + b"|".join(_PEMS) -+ + b""")[- ]----\r? -+.+?\r? -+----[- ]END \\1[- ]----\r?\n?""", -+ re.DOTALL, -+) -+ -+ -+def is_pem_format(key): -+ """ -+ Return True if the key is PEM format -+ This function uses the list of valid PEM headers defined in -+ _PEMS dict. -+ """ -+ return bool(_PEM_RE.search(key)) -+ -+ -+# Based on https://github.com/pyca/cryptography/blob/bcb70852d577b3f490f015378c75cba74986297b/src/cryptography/hazmat/primitives/serialization/ssh.py#L40-L46 -+_CERT_SUFFIX = b"-cert-v01@openssh.com" -+_SSH_PUBKEY_RC = re.compile(br"\A(\S+)[ \t]+(\S+)") -+_SSH_KEY_FORMATS = [ -+ b"ssh-ed25519", -+ b"ssh-rsa", -+ b"ssh-dss", -+ b"ecdsa-sha2-nistp256", -+ b"ecdsa-sha2-nistp384", -+ b"ecdsa-sha2-nistp521", -+] -+ -+ -+def is_ssh_key(key): -+ """ -+ Return True if the key is a SSH key -+ This function uses the list of valid SSH key format defined in -+ _SSH_KEY_FORMATS dict. -+ """ -+ if any(string_value in key for string_value in _SSH_KEY_FORMATS): -+ return True -+ -+ ssh_pubkey_match = _SSH_PUBKEY_RC.match(key) -+ if ssh_pubkey_match: -+ key_type = ssh_pubkey_match.group(1) -+ if _CERT_SUFFIX == key_type[-len(_CERT_SUFFIX) :]: -+ return True -+ -+ return False -Index: python-jose-3.3.0/tests/algorithms/test_HMAC.py -=================================================================== ---- python-jose-3.3.0.orig/tests/algorithms/test_HMAC.py -+++ python-jose-3.3.0/tests/algorithms/test_HMAC.py -@@ -14,14 +14,17 @@ class TestHMACAlgorithm: - - def test_RSA_key(self): - key = "-----BEGIN PUBLIC KEY-----" -+ key += "\n\n\n-----END PUBLIC KEY-----" - with pytest.raises(JOSEError): - HMACKey(key, ALGORITHMS.HS256) - - key = "-----BEGIN RSA PUBLIC KEY-----" -+ key += "\n\n\n-----END RSA PUBLIC KEY-----" - with pytest.raises(JOSEError): - HMACKey(key, ALGORITHMS.HS256) - - key = "-----BEGIN CERTIFICATE-----" -+ key += "\n\n\n-----END CERTIFICATE-----" - with pytest.raises(JOSEError): - HMACKey(key, ALGORITHMS.HS256) - diff --git a/CVE-2024-33664.patch b/CVE-2024-33664.patch deleted file mode 100644 index e04b6a9..0000000 --- a/CVE-2024-33664.patch +++ /dev/null @@ -1,135 +0,0 @@ -From ff3357d9f91b93bc957aac9bc5a447c5c0bb74da Mon Sep 17 00:00:00 2001 -From: "alistair.watts@groupbc.com" -Date: Tue, 7 May 2024 14:50:53 +0100 -Subject: [PATCH] Fix for CVE-2024-33664. JWE limited to 250K - ---- - jose/constants.py | 2 ++ - jose/jwe.py | 24 ++++++++++++++++++------ - tests/test_jwe.py | 34 +++++++++++++++++++++++++++++++++- - 3 files changed, 53 insertions(+), 7 deletions(-) - -diff --git a/jose/constants.py b/jose/constants.py -index ab4d74d3..58787d46 100644 ---- a/jose/constants.py -+++ b/jose/constants.py -@@ -96,3 +96,5 @@ class Zips: - - - ZIPS = Zips() -+ -+JWE_SIZE_LIMIT = 250 * 1024 -diff --git a/jose/jwe.py b/jose/jwe.py -index 2c387ff4..04923873 100644 ---- a/jose/jwe.py -+++ b/jose/jwe.py -@@ -6,7 +6,7 @@ - - from . import jwk - from .backends import get_random_bytes --from .constants import ALGORITHMS, ZIPS -+from .constants import ALGORITHMS, ZIPS, JWE_SIZE_LIMIT - from .exceptions import JWEError, JWEParseError - from .utils import base64url_decode, base64url_encode, ensure_binary - -@@ -76,6 +76,13 @@ def decrypt(jwe_str, key): - >>> jwe.decrypt(jwe_string, 'asecret128bitkey') - 'Hello, World!' - """ -+ -+ # Limit the token size - if the data is compressed then decompressing the -+ # data could lead to large memory usage. This helps address This addresses -+ # CVE-2024-33664. Also see _decompress() -+ if len(jwe_str) > JWE_SIZE_LIMIT: -+ raise JWEError("JWE string exceeds {JWE_SIZE_LIMIT} bytes") -+ - header, encoded_header, encrypted_key, iv, cipher_text, auth_tag = _jwe_compact_deserialize(jwe_str) - - # Verify that the implementation understands and can process all -@@ -424,13 +431,13 @@ def _compress(zip, plaintext): - (bytes): Compressed plaintext - """ - if zip not in ZIPS.SUPPORTED: -- raise NotImplementedError("ZIP {} is not supported!") -+ raise NotImplementedError(f"ZIP {zip} is not supported!") - if zip is None: - compressed = plaintext - elif zip == ZIPS.DEF: - compressed = zlib.compress(plaintext) - else: -- raise NotImplementedError("ZIP {} is not implemented!") -+ raise NotImplementedError(f"ZIP {zip} is not implemented!") - return compressed - - -@@ -446,13 +453,18 @@ def _decompress(zip, compressed): - (bytes): Compressed plaintext - """ - if zip not in ZIPS.SUPPORTED: -- raise NotImplementedError("ZIP {} is not supported!") -+ raise NotImplementedError(f"ZIP {zip} is not supported!") - if zip is None: - decompressed = compressed - elif zip == ZIPS.DEF: -- decompressed = zlib.decompress(compressed) -+ # If, during decompression, there is more data than expected, the -+ # decompression halts and raise an error. This addresses CVE-2024-33664 -+ decompressor = zlib.decompressobj() -+ decompressed = decompressor.decompress(compressed, max_length=JWE_SIZE_LIMIT) -+ if decompressor.unconsumed_tail: -+ raise JWEError(f"Decompressed JWE string exceeds {JWE_SIZE_LIMIT} bytes") - else: -- raise NotImplementedError("ZIP {} is not implemented!") -+ raise NotImplementedError(f"ZIP {zip} is not implemented!") - return decompressed - - -diff --git a/tests/test_jwe.py b/tests/test_jwe.py -index f089d565..8c5ff387 100644 ---- a/tests/test_jwe.py -+++ b/tests/test_jwe.py -@@ -5,7 +5,7 @@ - import jose.backends - from jose import jwe - from jose.constants import ALGORITHMS, ZIPS --from jose.exceptions import JWEParseError -+from jose.exceptions import JWEParseError, JWEError - from jose.jwk import AESKey, RSAKey - from jose.utils import base64url_decode - -@@ -525,3 +525,35 @@ def test_kid_header_not_present_when_not_provided(self): - encrypted = jwe.encrypt("Text", PUBLIC_KEY_PEM, enc, alg) - header = json.loads(base64url_decode(encrypted.split(b".")[0])) - assert "kid" not in header -+ -+ @pytest.mark.skipif(AESKey is None, reason="No AES backend") -+ def test_jwe_with_excessive_data(self): -+ enc = ALGORITHMS.A256CBC_HS512 -+ alg = ALGORITHMS.RSA_OAEP_256 -+ import jose.constants -+ old_limit = jose.constants.JWE_SIZE_LIMIT -+ try: -+ jose.constants.JWE_SIZE_LIMIT = 1024 -+ encrypted = jwe.encrypt(b"Text"*64*1024, PUBLIC_KEY_PEM, enc, alg) -+ header = json.loads(base64url_decode(encrypted.split(b".")[0])) -+ with pytest.raises(JWEError): -+ actual = jwe.decrypt(encrypted, PRIVATE_KEY_PEM) -+ finally: -+ jose.constants.JWE_SIZE_LIMIT = old_limit -+ -+ @pytest.mark.skipif(AESKey is None, reason="No AES backend") -+ def test_jwe_zip_with_excessive_data(self): -+ # Test that a fix for CVE-2024-33664 is in place. -+ enc = ALGORITHMS.A256CBC_HS512 -+ alg = ALGORITHMS.RSA_OAEP_256 -+ import jose.constants -+ old_limit = jose.constants.JWE_SIZE_LIMIT -+ try: -+ jose.constants.JWE_SIZE_LIMIT = 1024 -+ encrypted = jwe.encrypt(b"Text"*64*1024, PUBLIC_KEY_PEM, enc, alg, zip=ZIPS.DEF) -+ assert len(encrypted) < jose.constants.JWE_SIZE_LIMIT -+ header = json.loads(base64url_decode(encrypted.split(b".")[0])) -+ with pytest.raises(JWEError): -+ actual = jwe.decrypt(encrypted, PRIVATE_KEY_PEM) -+ finally: -+ jose.constants.JWE_SIZE_LIMIT = old_limit diff --git a/fix-tests-ecdsa-019.patch b/fix-tests-ecdsa-019.patch deleted file mode 100644 index 147481a..0000000 --- a/fix-tests-ecdsa-019.patch +++ /dev/null @@ -1,60 +0,0 @@ -From ec5c62249b4f67b15376d3cbc96d2b1d272d0552 Mon Sep 17 00:00:00 2001 -From: Daniel Garcia Moreno -Date: Thu, 2 May 2024 18:47:43 +0200 -Subject: [PATCH] test: Fix tests with ecdsa 0.19.0 - -Fix https://github.com/mpdavis/python-jose/issues/348 ---- - tests/algorithms/test_EC_compat.py | 8 ++++---- - 1 file changed, 4 insertions(+), 4 deletions(-) - -Index: python-jose-3.3.0/tests/algorithms/test_EC_compat.py -=================================================================== ---- python-jose-3.3.0.orig/tests/algorithms/test_EC_compat.py -+++ python-jose-3.3.0/tests/algorithms/test_EC_compat.py -@@ -37,7 +37,7 @@ class TestBackendEcdsaCompatibility: - key = BackendFrom(private_key, ALGORITHMS.ES256) - key2 = BackendTo(private_key, ALGORITHMS.ES256) - -- assert key.public_key().to_pem().strip() == key2.public_key().to_pem().strip() -+ assert key.public_key().to_pem().strip().replace(b"\n", b"") == key2.public_key().to_pem().strip().replace(b"\n", b"") - - @pytest.mark.parametrize("BackendFrom", [ECDSAECKey, CryptographyECKey]) - @pytest.mark.parametrize("BackendTo", [ECDSAECKey, CryptographyECKey]) -@@ -45,7 +45,7 @@ class TestBackendEcdsaCompatibility: - key = BackendFrom(private_key, ALGORITHMS.ES256) - key2 = BackendTo(private_key, ALGORITHMS.ES256) - -- assert key.to_pem().strip() == key2.to_pem().strip() -+ assert key.to_pem().strip().replace(b"\n", b"") == key2.to_pem().strip().replace(b"\n", b"") - - @pytest.mark.parametrize("BackendFrom", [ECDSAECKey, CryptographyECKey]) - @pytest.mark.parametrize("BackendTo", [ECDSAECKey, CryptographyECKey]) -@@ -57,7 +57,7 @@ class TestBackendEcdsaCompatibility: - - pub_target = BackendTo(pub_pem_source, ALGORITHMS.ES256) - -- assert pub_pem_source == pub_target.to_pem().strip() -+ assert pub_pem_source.replace(b"\n", b"") == pub_target.to_pem().strip().replace(b"\n", b"") - - @pytest.mark.parametrize("BackendFrom", [ECDSAECKey, CryptographyECKey]) - @pytest.mark.parametrize("BackendTo", [ECDSAECKey, CryptographyECKey]) -@@ -68,4 +68,4 @@ class TestBackendEcdsaCompatibility: - - target = BackendTo(pem_source, ALGORITHMS.ES256) - -- assert pem_source == target.to_pem().strip() -+ assert pem_source.replace(b"\n", b"") == target.to_pem().strip().replace(b"\n", b"") -Index: python-jose-3.3.0/tests/algorithms/test_EC.py -=================================================================== ---- python-jose-3.3.0.orig/tests/algorithms/test_EC.py -+++ python-jose-3.3.0/tests/algorithms/test_EC.py -@@ -104,7 +104,7 @@ class TestECAlgorithm: - def test_to_pem(self): - key = ECKey(private_key, ALGORITHMS.ES256) - assert not key.is_public() -- assert key.to_pem().strip() == private_key.strip().encode("utf-8") -+ assert key.to_pem().strip().replace(b"\n", b"") == private_key.strip().encode("utf-8").replace(b"\n", b"") - - public_pem = key.public_key().to_pem() - assert ECKey(public_pem, ALGORITHMS.ES256).is_public() diff --git a/python-jose-3.3.0.tar.gz b/python-jose-3.3.0.tar.gz deleted file mode 100644 index 8459b6e..0000000 --- a/python-jose-3.3.0.tar.gz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:55779b5e6ad599c6336191246e95eb2293a9ddebd555f796a65f838f07e5d78a -size 129068 diff --git a/python-jose-3.4.0.tar.gz b/python-jose-3.4.0.tar.gz new file mode 100644 index 0000000..4b4fccb --- /dev/null +++ b/python-jose-3.4.0.tar.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9a9a40f418ced8ecaf7e3b28d69887ceaa76adad3bcaa6dae0d9e596fec1d680 +size 92145 diff --git a/python-python-jose.changes b/python-python-jose.changes index 2535e6f..15fe472 100644 --- a/python-python-jose.changes +++ b/python-python-jose.changes @@ -1,3 +1,19 @@ +------------------------------------------------------------------- +Tue May 6 17:27:12 UTC 2025 - Guang Yee + +- Update to 3.4.0 + * Remove support for Python 3.6 and 3.7 + * Added support for Python 3.10 and 3.11 + * Updating CryptographyAESKey::encrypt to generate 96 bit IVs for + GCM block cipher mode + * Fix for PEM key comparisons caused by line lengths and new lines + * Fix for CVE-2024-33664 - JWE limited to 250KiB + * Fix for CVE-2024-33663 - signing JWT with public key is now forbidden + * Replace usage of deprecated datetime.utcnow() with datetime.now(UTC) +- Removed patches CVE-2024-33663.patch, CVE-2024-33664.patch, + fix-tests-ecdsa-019.patch, and unpin-deps.patch as they have been + incorporated into release 3.4.0 + ------------------------------------------------------------------- Mon Jun 3 07:38:00 UTC 2024 - Daniel Garcia diff --git a/python-python-jose.spec b/python-python-jose.spec index 9b8567e..441582e 100644 --- a/python-python-jose.spec +++ b/python-python-jose.spec @@ -1,7 +1,7 @@ # # spec file for package python-python-jose # -# Copyright (c) 2024 SUSE LLC +# Copyright (c) 2025 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -38,19 +38,12 @@ %{?sle15_python_module_pythons} Name: python-python-jose%{psuffix} -Version: 3.3.0 +Version: 3.4.0 Release: 0 Summary: JOSE implementation in Python License: MIT URL: https://github.com/mpdavis/python-jose Source: https://files.pythonhosted.org/packages/source/p/python-jose/python-jose-%{version}.tar.gz -Patch0: unpin-deps.patch -# PATCH-FIX-UPSTREAM CVE-2024-33664.patch gh#mpdavis/python-jose#352 -Patch1: CVE-2024-33664.patch -# PATCH-FIX-UPSTREAM CVE-2024-33663.patch gh#mpdavis/python-jose#349 -Patch2: CVE-2024-33663.patch -# PATCH-FIX-UPSTREAM fix-tests-ecdsa-019.patch gh#mpdavis/python-jose#350 -Patch3: fix-tests-ecdsa-019.patch BuildRequires: %{python_module setuptools >= 39.2.0} BuildRequires: fdupes BuildRequires: python-rpm-macros diff --git a/unpin-deps.patch b/unpin-deps.patch deleted file mode 100644 index dfdd82d..0000000 --- a/unpin-deps.patch +++ /dev/null @@ -1,32 +0,0 @@ -Index: python-jose-3.3.0/setup.py -=================================================================== ---- python-jose-3.3.0.orig/setup.py -+++ python-jose-3.3.0/setup.py -@@ -23,11 +23,9 @@ def get_packages(package): - pyasn1 = ["pyasn1"] - extras_require = { - "cryptography": ["cryptography>=3.4.0"], -- "pycrypto": ["pycrypto >=2.6.0, <2.7.0"] + pyasn1, -- "pycryptodome": ["pycryptodome >=3.3.1, <4.0.0"] + pyasn1, - } - # TODO: work this into the extras selection instead. --install_requires = ["ecdsa != 0.15", "rsa"] + pyasn1 -+install_requires = ["ecdsa >= 0.16", "rsa"] + pyasn1 - - - setup( -@@ -63,14 +61,11 @@ setup( - ], - extras_require=extras_require, - setup_requires=[ -- "pytest-runner", - "setuptools>=39.2.0", - ], - tests_require=[ - "ecdsa != 0.15", - "pytest", -- "pytest-cov", -- "pytest-runner", - ], - install_requires=install_requires, - )