From 1c56ae2139769798cc582ef6dd9cb770404f751b Mon Sep 17 00:00:00 2001 From: shixuantong <1726671442@qq.com> Date: Tue, 14 Jun 2022 11:03:51 +0800 Subject: [PATCH] fix CVE-2022-29217 --- backport-CVE-2022-29217.patch | 239 ++++++++++++++++++++++++++++++++++ python-jwt.spec | 13 +- 2 files changed, 250 insertions(+), 2 deletions(-) create mode 100644 backport-CVE-2022-29217.patch diff --git a/backport-CVE-2022-29217.patch b/backport-CVE-2022-29217.patch new file mode 100644 index 0000000..406264f --- /dev/null +++ b/backport-CVE-2022-29217.patch @@ -0,0 +1,239 @@ +From 9c528670c455b8d948aff95ed50e22940d1ad3fc Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jos=C3=A9=20Padilla?= +Date: Thu, 12 May 2022 14:31:00 -0400 +Subject: [PATCH] Merge pull request from GHSA-ffqj-6fqr-9h24 +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Co-authored-by: José Padilla +--- + jwt/algorithms.py | 11 +---- + jwt/utils.py | 61 +++++++++++++++++++++++++++ + tests/test_advisory.py | 109 +++++++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 172 insertions(+), 9 deletions(-) + create mode 100644 tests/test_advisory.py + +diff --git a/jwt/algorithms.py b/jwt/algorithms.py +index 1343688..8e4d739 100644 +--- a/jwt/algorithms.py ++++ b/jwt/algorithms.py +@@ -8,7 +8,7 @@ from .exceptions import InvalidKeyError + from .utils import ( + base64url_decode, base64url_encode, der_to_raw_signature, + force_bytes, force_unicode, from_base64url_uint, raw_to_der_signature, +- to_base64url_uint ++ to_base64url_uint, is_pem_format, is_ssh_key + ) + + try: +@@ -139,14 +139,7 @@ class HMACAlgorithm(Algorithm): + def prepare_key(self, key): + key = force_bytes(key) + +- invalid_strings = [ +- b'-----BEGIN PUBLIC KEY-----', +- b'-----BEGIN CERTIFICATE-----', +- b'-----BEGIN RSA PUBLIC KEY-----', +- 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 InvalidKeyError( + 'The specified key is an asymmetric key or x509 certificate and' + ' should not be used as an HMAC secret.') +diff --git a/jwt/utils.py b/jwt/utils.py +index b33c7a2..04b4e19 100644 +--- a/jwt/utils.py ++++ b/jwt/utils.py +@@ -1,6 +1,7 @@ + import base64 + import binascii + import struct ++import re + + from .compat import binary_type, bytes_from_int, text_type + +@@ -111,3 +112,63 @@ def raw_to_der_signature(raw_sig, curve): + s = bytes_to_number(raw_sig[num_bytes:]) + + return encode_dss_signature(r, s) ++ ++ ++# 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(bytes): ++ 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): ++ 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 +diff --git a/tests/test_advisory.py b/tests/test_advisory.py +new file mode 100644 +index 0000000..e8065d4 +--- /dev/null ++++ b/tests/test_advisory.py +@@ -0,0 +1,109 @@ ++import jwt ++import pytest ++from jwt.exceptions import InvalidKeyError ++ ++priv_key_bytes = b'''-----BEGIN PRIVATE KEY----- ++MC4CAQAwBQYDK2VwBCIEIIbBhdo2ah7X32i50GOzrCr4acZTe6BezUdRIixjTAdL ++-----END PRIVATE KEY-----''' ++ ++pub_key_bytes = b'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPL1I9oiq+B8crkmuV4YViiUnhdLjCp3hvy1bNGuGfNL' ++ ++ssh_priv_key_bytes = b"""-----BEGIN EC PRIVATE KEY----- ++MHcCAQEEIOWc7RbaNswMtNtc+n6WZDlUblMr2FBPo79fcGXsJlGQoAoGCCqGSM49 ++AwEHoUQDQgAElcy2RSSSgn2RA/xCGko79N+7FwoLZr3Z0ij/ENjow2XpUDwwKEKk ++Ak3TDXC9U8nipMlGcY7sDpXp2XyhHEM+Rw== ++-----END EC PRIVATE KEY-----""" ++ ++ssh_key_bytes = b"""ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJXMtkUkkoJ9kQP8QhpKO/TfuxcKC2a92dIo/xDY6MNl6VA8MChCpAJN0w1wvVPJ4qTJRnGO7A6V6dl8oRxDPkc=""" ++ ++ ++class TestAdvisory: ++ def test_ghsa_ffqj_6fqr_9h24(self): ++ # Generate ed25519 private key ++ # private_key = ed25519.Ed25519PrivateKey.generate() ++ ++ # Get private key bytes as they would be stored in a file ++ # priv_key_bytes = private_key.private_bytes( ++ # encoding=serialization.Encoding.PEM, ++ # format=serialization.PrivateFormat.PKCS8, ++ # encryption_algorithm=serialization.NoEncryption(), ++ # ) ++ ++ # Get public key bytes as they would be stored in a file ++ # pub_key_bytes = private_key.public_key().public_bytes( ++ # encoding=serialization.Encoding.OpenSSH, ++ # format=serialization.PublicFormat.OpenSSH, ++ # ) ++ ++ # Making a good jwt token that should work by signing it ++ # with the private key ++ # encoded_good = jwt.encode({"test": 1234}, priv_key_bytes, algorithm="EdDSA") ++ # encoded_good = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJ0ZXN0IjoxMjM0fQ.M5y1EEavZkHSlj9i8yi9nXKKyPBSAUhDRTOYZi3zZY11tZItDaR3qwAye8pc74_lZY3Ogt9KPNFbVOSGnUBHDg' ++ ++ # Using HMAC with the public key to trick the receiver to think that the ++ # public key is a HMAC secret ++ # encoded_bad = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0ZXN0IjoxMjM0fQ.6ulDpqSlbHmQ8bZXhZRLFko9SwcHrghCwh8d-exJEE4' ++ ++ # Both of the jwt tokens are validated as valid ++ # jwt.decode( ++ # encoded_good, ++ # pub_key_bytes, ++ # algorithms=jwt.algorithms.get_default_algorithms(), ++ # ) ++ ++ # with pytest.raises(InvalidKeyError): ++ # jwt.decode( ++ # encoded_bad, ++ # pub_key_bytes, ++ # algorithms=jwt.algorithms.get_default_algorithms(), ++ # ) ++ ++ # Of course the receiver should specify ed25519 algorithm to be used if ++ # they specify ed25519 public key. However, if other algorithms are used, ++ # the POC does not work ++ # HMAC specifies illegal strings for the HMAC secret in jwt/algorithms.py ++ # ++ # invalid_str ings = [ ++ # b"-----BEGIN PUBLIC KEY-----", ++ # b"-----BEGIN CERTIFICATE-----", ++ # b"-----BEGIN RSA PUBLIC KEY-----", ++ # b"ssh-rsa", ++ # ] ++ # ++ # However, OKPAlgorithm (ed25519) accepts the following in jwt/algorithms.py: ++ # ++ # if "-----BEGIN PUBLIC" in str_key: ++ # return load_pem_public_key(key) ++ # if "-----BEGIN PRIVATE" in str_key: ++ # return load_pem_private_key(key, password=None) ++ # if str_key[0:4] == "ssh-": ++ # return load_ssh_public_key(key) ++ # ++ # These should most likely made to match each other to prevent this behavior ++ ++ # POC for the ecdsa-sha2-nistp256 format. ++ # openssl ecparam -genkey -name prime256v1 -noout -out ec256-key-priv.pem ++ # openssl ec -in ec256-key-priv.pem -pubout > ec256-key-pub.pem ++ # ssh-keygen -y -f ec256-key-priv.pem > ec256-key-ssh.pub ++ ++ # Making a good jwt token that should work by signing it with the private key ++ # encoded_good = jwt.encode({"test": 1234}, ssh_priv_key_bytes, algorithm="ES256") ++ encoded_good = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoxMjM0fQ.NX42mS8cNqYoL3FOW9ZcKw8Nfq2mb6GqJVADeMA1-kyHAclilYo_edhdM_5eav9tBRQTlL0XMeu_WFE_mz3OXg" ++ ++ # Using HMAC with the ssh public key to trick the receiver to think that the public key is a HMAC secret ++ # encoded_bad = jwt.encode({"test": 1234}, ssh_key_bytes, algorithm="HS256") ++ encoded_bad = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoxMjM0fQ.5eYfbrbeGYmWfypQ6rMWXNZ8bdHcqKng5GPr9MJZITU" ++ ++ # Both of the jwt tokens are validated as valid ++ jwt.decode( ++ encoded_good, ++ ssh_key_bytes, ++ algorithms=jwt.algorithms.get_default_algorithms() ++ ) ++ ++ with pytest.raises(InvalidKeyError): ++ jwt.decode( ++ encoded_bad, ++ ssh_key_bytes, ++ algorithms=jwt.algorithms.get_default_algorithms() ++ ) +-- +1.8.3.1 + diff --git a/python-jwt.spec b/python-jwt.spec index 98de763..bda1b58 100644 --- a/python-jwt.spec +++ b/python-jwt.spec @@ -2,12 +2,15 @@ %bcond_with tests Name: python-jwt Version: 1.7.1 -Release: 2 +Release: 3 Summary: JSON Web Token implementation in Python License: MIT URL: https://github.com/jpadilla/pyjwt Source0: https://files.pythonhosted.org/packages/source/P/PyJWT/PyJWT-1.7.1.tar.gz BuildArch: noarch + +Patch6000: backport-CVE-2022-29217.patch + BuildRequires: python2-devel python2-setuptools python2-cryptography %if %{with tests} BuildRequires: python2-pytest python-pytest-cov python-pytest-runner @@ -37,7 +40,7 @@ Requires: python%{python3_pkgversion}-cryptography %package_help %prep -%autosetup -n PyJWT-%{version} +%autosetup -n PyJWT-%{version} -p1 %build %py2_build @@ -68,6 +71,12 @@ Requires: python%{python3_pkgversion}-cryptography %doc README.rst %changelog +* Tue Jun 14 2022 shixuantong - 1.7.1-3 +- Type:CVE +- ID:CVE-2022-29217 +- SUG:NA +- DESC:fix CVE-2022-29217 + * Fri Oct 11 2019 yefei - 1.7.1-2 - Type:enhancement - ID:NA -- Gitee