diff options
| author | Gyorgy Sarvari <skandigraun@gmail.com> | 2026-01-06 08:33:28 +0100 |
|---|---|---|
| committer | Gyorgy Sarvari <skandigraun@gmail.com> | 2026-01-08 22:03:03 +0100 |
| commit | 304c0c66430e6aee14793016d9b2236f577de771 (patch) | |
| tree | 717f3f993d389955808d523c15c487100463732c /meta-python | |
| parent | f6d4f623c19ef65ee2a536c5c689b763b00e83de (diff) | |
| download | meta-openembedded-304c0c66430e6aee14793016d9b2236f577de771.tar.gz | |
python3-pyjwt: patch CVE-2022-29217
Details: https://nvd.nist.gov/vuln/detail/CVE-2022-29217
Pick the patch referenced by the NVD advsory.
Signed-off-by: Gyorgy Sarvari <skandigraun@gmail.com>
Diffstat (limited to 'meta-python')
| -rw-r--r-- | meta-python/recipes-devtools/python/python3-pyjwt/CVE-2022-29217.patch | 295 | ||||
| -rw-r--r-- | meta-python/recipes-devtools/python/python3-pyjwt_2.3.0.bb | 1 |
2 files changed, 296 insertions, 0 deletions
diff --git a/meta-python/recipes-devtools/python/python3-pyjwt/CVE-2022-29217.patch b/meta-python/recipes-devtools/python/python3-pyjwt/CVE-2022-29217.patch new file mode 100644 index 0000000000..53334ec6db --- /dev/null +++ b/meta-python/recipes-devtools/python/python3-pyjwt/CVE-2022-29217.patch | |||
| @@ -0,0 +1,295 @@ | |||
| 1 | From 0ab93eb55a182f190dea55cc048dcb50bf97724c Mon Sep 17 00:00:00 2001 | ||
| 2 | From: =?UTF-8?q?Jos=C3=A9=20Padilla?= <jpadilla@webapplicate.com> | ||
| 3 | Date: Thu, 12 May 2022 14:31:00 -0400 | ||
| 4 | Subject: [PATCH] Merge pull request from GHSA-ffqj-6fqr-9h24 | ||
| 5 | MIME-Version: 1.0 | ||
| 6 | Content-Type: text/plain; charset=UTF-8 | ||
| 7 | Content-Transfer-Encoding: 8bit | ||
| 8 | |||
| 9 | Co-authored-by: José Padilla <jpadilla@users.noreply.github.com> | ||
| 10 | |||
| 11 | CVE: CVE-2022-29217 | ||
| 12 | Upstream-Status: Backport [https://github.com/jpadilla/pyjwt/commit/9c528670c455b8d948aff95ed50e22940d1ad3fc] | ||
| 13 | Signed-off-by: Gyorgy Sarvari <skandigraun@gmail.com> | ||
| 14 | --- | ||
| 15 | jwt/algorithms.py | 39 +++++++------- | ||
| 16 | jwt/utils.py | 61 ++++++++++++++++++++++ | ||
| 17 | tests/test_advisory.py | 109 +++++++++++++++++++++++++++++++++++++++ | ||
| 18 | tests/test_algorithms.py | 2 +- | ||
| 19 | 4 files changed, 189 insertions(+), 22 deletions(-) | ||
| 20 | create mode 100644 tests/test_advisory.py | ||
| 21 | |||
| 22 | diff --git a/jwt/algorithms.py b/jwt/algorithms.py | ||
| 23 | index 1f8865a..1aa30ed 100644 | ||
| 24 | --- a/jwt/algorithms.py | ||
| 25 | +++ b/jwt/algorithms.py | ||
| 26 | @@ -9,6 +9,8 @@ from .utils import ( | ||
| 27 | der_to_raw_signature, | ||
| 28 | force_bytes, | ||
| 29 | from_base64url_uint, | ||
| 30 | + is_pem_format, | ||
| 31 | + is_ssh_key, | ||
| 32 | raw_to_der_signature, | ||
| 33 | to_base64url_uint, | ||
| 34 | ) | ||
| 35 | @@ -183,14 +185,7 @@ class HMACAlgorithm(Algorithm): | ||
| 36 | def prepare_key(self, key): | ||
| 37 | key = force_bytes(key) | ||
| 38 | |||
| 39 | - invalid_strings = [ | ||
| 40 | - b"-----BEGIN PUBLIC KEY-----", | ||
| 41 | - b"-----BEGIN CERTIFICATE-----", | ||
| 42 | - b"-----BEGIN RSA PUBLIC KEY-----", | ||
| 43 | - b"ssh-rsa", | ||
| 44 | - ] | ||
| 45 | - | ||
| 46 | - if any(string_value in key for string_value in invalid_strings): | ||
| 47 | + if is_pem_format(key) or is_ssh_key(key): | ||
| 48 | raise InvalidKeyError( | ||
| 49 | "The specified key is an asymmetric key or x509 certificate and" | ||
| 50 | " should not be used as an HMAC secret." | ||
| 51 | @@ -545,26 +540,28 @@ if has_crypto: | ||
| 52 | pass | ||
| 53 | |||
| 54 | def prepare_key(self, key): | ||
| 55 | - | ||
| 56 | - if isinstance( | ||
| 57 | - key, | ||
| 58 | - (Ed25519PrivateKey, Ed25519PublicKey, Ed448PrivateKey, Ed448PublicKey), | ||
| 59 | - ): | ||
| 60 | - return key | ||
| 61 | - | ||
| 62 | if isinstance(key, (bytes, str)): | ||
| 63 | if isinstance(key, str): | ||
| 64 | key = key.encode("utf-8") | ||
| 65 | str_key = key.decode("utf-8") | ||
| 66 | |||
| 67 | if "-----BEGIN PUBLIC" in str_key: | ||
| 68 | - return load_pem_public_key(key) | ||
| 69 | - if "-----BEGIN PRIVATE" in str_key: | ||
| 70 | - return load_pem_private_key(key, password=None) | ||
| 71 | - if str_key[0:4] == "ssh-": | ||
| 72 | - return load_ssh_public_key(key) | ||
| 73 | + key = load_pem_public_key(key) | ||
| 74 | + elif "-----BEGIN PRIVATE" in str_key: | ||
| 75 | + key = load_pem_private_key(key, password=None) | ||
| 76 | + elif str_key[0:4] == "ssh-": | ||
| 77 | + key = load_ssh_public_key(key) | ||
| 78 | + | ||
| 79 | + # Explicit check the key to prevent confusing errors from cryptography | ||
| 80 | + if not isinstance( | ||
| 81 | + key, | ||
| 82 | + (Ed25519PrivateKey, Ed25519PublicKey, Ed448PrivateKey, Ed448PublicKey), | ||
| 83 | + ): | ||
| 84 | + raise InvalidKeyError( | ||
| 85 | + "Expecting a EllipticCurvePrivateKey/EllipticCurvePublicKey. Wrong key provided for EdDSA algorithms" | ||
| 86 | + ) | ||
| 87 | |||
| 88 | - raise TypeError("Expecting a PEM-formatted or OpenSSH key.") | ||
| 89 | + return key | ||
| 90 | |||
| 91 | def sign(self, msg, key): | ||
| 92 | """ | ||
| 93 | diff --git a/jwt/utils.py b/jwt/utils.py | ||
| 94 | index 9dde10c..8ab73b4 100644 | ||
| 95 | --- a/jwt/utils.py | ||
| 96 | +++ b/jwt/utils.py | ||
| 97 | @@ -1,5 +1,6 @@ | ||
| 98 | import base64 | ||
| 99 | import binascii | ||
| 100 | +import re | ||
| 101 | from typing import Any, Union | ||
| 102 | |||
| 103 | try: | ||
| 104 | @@ -97,3 +98,63 @@ def raw_to_der_signature(raw_sig: bytes, curve: EllipticCurve) -> bytes: | ||
| 105 | s = bytes_to_number(raw_sig[num_bytes:]) | ||
| 106 | |||
| 107 | return encode_dss_signature(r, s) | ||
| 108 | + | ||
| 109 | + | ||
| 110 | +# Based on https://github.com/hynek/pem/blob/7ad94db26b0bc21d10953f5dbad3acfdfacf57aa/src/pem/_core.py#L224-L252 | ||
| 111 | +_PEMS = { | ||
| 112 | + b"CERTIFICATE", | ||
| 113 | + b"TRUSTED CERTIFICATE", | ||
| 114 | + b"PRIVATE KEY", | ||
| 115 | + b"PUBLIC KEY", | ||
| 116 | + b"ENCRYPTED PRIVATE KEY", | ||
| 117 | + b"OPENSSH PRIVATE KEY", | ||
| 118 | + b"DSA PRIVATE KEY", | ||
| 119 | + b"RSA PRIVATE KEY", | ||
| 120 | + b"RSA PUBLIC KEY", | ||
| 121 | + b"EC PRIVATE KEY", | ||
| 122 | + b"DH PARAMETERS", | ||
| 123 | + b"NEW CERTIFICATE REQUEST", | ||
| 124 | + b"CERTIFICATE REQUEST", | ||
| 125 | + b"SSH2 PUBLIC KEY", | ||
| 126 | + b"SSH2 ENCRYPTED PRIVATE KEY", | ||
| 127 | + b"X509 CRL", | ||
| 128 | +} | ||
| 129 | + | ||
| 130 | +_PEM_RE = re.compile( | ||
| 131 | + b"----[- ]BEGIN (" | ||
| 132 | + + b"|".join(_PEMS) | ||
| 133 | + + b""")[- ]----\r? | ||
| 134 | +.+?\r? | ||
| 135 | +----[- ]END \\1[- ]----\r?\n?""", | ||
| 136 | + re.DOTALL, | ||
| 137 | +) | ||
| 138 | + | ||
| 139 | + | ||
| 140 | +def is_pem_format(key: bytes) -> bool: | ||
| 141 | + return bool(_PEM_RE.search(key)) | ||
| 142 | + | ||
| 143 | + | ||
| 144 | +# Based on https://github.com/pyca/cryptography/blob/bcb70852d577b3f490f015378c75cba74986297b/src/cryptography/hazmat/primitives/serialization/ssh.py#L40-L46 | ||
| 145 | +_CERT_SUFFIX = b"-cert-v01@openssh.com" | ||
| 146 | +_SSH_PUBKEY_RC = re.compile(br"\A(\S+)[ \t]+(\S+)") | ||
| 147 | +_SSH_KEY_FORMATS = [ | ||
| 148 | + b"ssh-ed25519", | ||
| 149 | + b"ssh-rsa", | ||
| 150 | + b"ssh-dss", | ||
| 151 | + b"ecdsa-sha2-nistp256", | ||
| 152 | + b"ecdsa-sha2-nistp384", | ||
| 153 | + b"ecdsa-sha2-nistp521", | ||
| 154 | +] | ||
| 155 | + | ||
| 156 | + | ||
| 157 | +def is_ssh_key(key: bytes) -> bool: | ||
| 158 | + if any(string_value in key for string_value in _SSH_KEY_FORMATS): | ||
| 159 | + return True | ||
| 160 | + | ||
| 161 | + ssh_pubkey_match = _SSH_PUBKEY_RC.match(key) | ||
| 162 | + if ssh_pubkey_match: | ||
| 163 | + key_type = ssh_pubkey_match.group(1) | ||
| 164 | + if _CERT_SUFFIX == key_type[-len(_CERT_SUFFIX) :]: | ||
| 165 | + return True | ||
| 166 | + | ||
| 167 | + return False | ||
| 168 | diff --git a/tests/test_advisory.py b/tests/test_advisory.py | ||
| 169 | new file mode 100644 | ||
| 170 | index 0000000..f70f54b | ||
| 171 | --- /dev/null | ||
| 172 | +++ b/tests/test_advisory.py | ||
| 173 | @@ -0,0 +1,109 @@ | ||
| 174 | +import jwt | ||
| 175 | +import pytest | ||
| 176 | +from jwt.exceptions import InvalidKeyError | ||
| 177 | + | ||
| 178 | +priv_key_bytes = b'''-----BEGIN PRIVATE KEY----- | ||
| 179 | +MC4CAQAwBQYDK2VwBCIEIIbBhdo2ah7X32i50GOzrCr4acZTe6BezUdRIixjTAdL | ||
| 180 | +-----END PRIVATE KEY-----''' | ||
| 181 | + | ||
| 182 | +pub_key_bytes = b'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPL1I9oiq+B8crkmuV4YViiUnhdLjCp3hvy1bNGuGfNL' | ||
| 183 | + | ||
| 184 | +ssh_priv_key_bytes = b"""-----BEGIN EC PRIVATE KEY----- | ||
| 185 | +MHcCAQEEIOWc7RbaNswMtNtc+n6WZDlUblMr2FBPo79fcGXsJlGQoAoGCCqGSM49 | ||
| 186 | +AwEHoUQDQgAElcy2RSSSgn2RA/xCGko79N+7FwoLZr3Z0ij/ENjow2XpUDwwKEKk | ||
| 187 | +Ak3TDXC9U8nipMlGcY7sDpXp2XyhHEM+Rw== | ||
| 188 | +-----END EC PRIVATE KEY-----""" | ||
| 189 | + | ||
| 190 | +ssh_key_bytes = b"""ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJXMtkUkkoJ9kQP8QhpKO/TfuxcKC2a92dIo/xDY6MNl6VA8MChCpAJN0w1wvVPJ4qTJRnGO7A6V6dl8oRxDPkc=""" | ||
| 191 | + | ||
| 192 | + | ||
| 193 | +class TestAdvisory: | ||
| 194 | + def test_ghsa_ffqj_6fqr_9h24(self): | ||
| 195 | + # Generate ed25519 private key | ||
| 196 | + # private_key = ed25519.Ed25519PrivateKey.generate() | ||
| 197 | + | ||
| 198 | + # Get private key bytes as they would be stored in a file | ||
| 199 | + # priv_key_bytes = private_key.private_bytes( | ||
| 200 | + # encoding=serialization.Encoding.PEM, | ||
| 201 | + # format=serialization.PrivateFormat.PKCS8, | ||
| 202 | + # encryption_algorithm=serialization.NoEncryption(), | ||
| 203 | + # ) | ||
| 204 | + | ||
| 205 | + # Get public key bytes as they would be stored in a file | ||
| 206 | + # pub_key_bytes = private_key.public_key().public_bytes( | ||
| 207 | + # encoding=serialization.Encoding.OpenSSH, | ||
| 208 | + # format=serialization.PublicFormat.OpenSSH, | ||
| 209 | + # ) | ||
| 210 | + | ||
| 211 | + # Making a good jwt token that should work by signing it | ||
| 212 | + # with the private key | ||
| 213 | + # encoded_good = jwt.encode({"test": 1234}, priv_key_bytes, algorithm="EdDSA") | ||
| 214 | + encoded_good = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJ0ZXN0IjoxMjM0fQ.M5y1EEavZkHSlj9i8yi9nXKKyPBSAUhDRTOYZi3zZY11tZItDaR3qwAye8pc74_lZY3Ogt9KPNFbVOSGnUBHDg' | ||
| 215 | + | ||
| 216 | + # Using HMAC with the public key to trick the receiver to think that the | ||
| 217 | + # public key is a HMAC secret | ||
| 218 | + encoded_bad = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0ZXN0IjoxMjM0fQ.6ulDpqSlbHmQ8bZXhZRLFko9SwcHrghCwh8d-exJEE4' | ||
| 219 | + | ||
| 220 | + # Both of the jwt tokens are validated as valid | ||
| 221 | + jwt.decode( | ||
| 222 | + encoded_good, | ||
| 223 | + pub_key_bytes, | ||
| 224 | + algorithms=jwt.algorithms.get_default_algorithms(), | ||
| 225 | + ) | ||
| 226 | + | ||
| 227 | + with pytest.raises(InvalidKeyError): | ||
| 228 | + jwt.decode( | ||
| 229 | + encoded_bad, | ||
| 230 | + pub_key_bytes, | ||
| 231 | + algorithms=jwt.algorithms.get_default_algorithms(), | ||
| 232 | + ) | ||
| 233 | + | ||
| 234 | + # Of course the receiver should specify ed25519 algorithm to be used if | ||
| 235 | + # they specify ed25519 public key. However, if other algorithms are used, | ||
| 236 | + # the POC does not work | ||
| 237 | + # HMAC specifies illegal strings for the HMAC secret in jwt/algorithms.py | ||
| 238 | + # | ||
| 239 | + # invalid_str ings = [ | ||
| 240 | + # b"-----BEGIN PUBLIC KEY-----", | ||
| 241 | + # b"-----BEGIN CERTIFICATE-----", | ||
| 242 | + # b"-----BEGIN RSA PUBLIC KEY-----", | ||
| 243 | + # b"ssh-rsa", | ||
| 244 | + # ] | ||
| 245 | + # | ||
| 246 | + # However, OKPAlgorithm (ed25519) accepts the following in jwt/algorithms.py: | ||
| 247 | + # | ||
| 248 | + # if "-----BEGIN PUBLIC" in str_key: | ||
| 249 | + # return load_pem_public_key(key) | ||
| 250 | + # if "-----BEGIN PRIVATE" in str_key: | ||
| 251 | + # return load_pem_private_key(key, password=None) | ||
| 252 | + # if str_key[0:4] == "ssh-": | ||
| 253 | + # return load_ssh_public_key(key) | ||
| 254 | + # | ||
| 255 | + # These should most likely made to match each other to prevent this behavior | ||
| 256 | + | ||
| 257 | + # POC for the ecdsa-sha2-nistp256 format. | ||
| 258 | + # openssl ecparam -genkey -name prime256v1 -noout -out ec256-key-priv.pem | ||
| 259 | + # openssl ec -in ec256-key-priv.pem -pubout > ec256-key-pub.pem | ||
| 260 | + # ssh-keygen -y -f ec256-key-priv.pem > ec256-key-ssh.pub | ||
| 261 | + | ||
| 262 | + # Making a good jwt token that should work by signing it with the private key | ||
| 263 | + # encoded_good = jwt.encode({"test": 1234}, ssh_priv_key_bytes, algorithm="ES256") | ||
| 264 | + encoded_good = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoxMjM0fQ.NX42mS8cNqYoL3FOW9ZcKw8Nfq2mb6GqJVADeMA1-kyHAclilYo_edhdM_5eav9tBRQTlL0XMeu_WFE_mz3OXg" | ||
| 265 | + | ||
| 266 | + # Using HMAC with the ssh public key to trick the receiver to think that the public key is a HMAC secret | ||
| 267 | + # encoded_bad = jwt.encode({"test": 1234}, ssh_key_bytes, algorithm="HS256") | ||
| 268 | + encoded_bad = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoxMjM0fQ.5eYfbrbeGYmWfypQ6rMWXNZ8bdHcqKng5GPr9MJZITU" | ||
| 269 | + | ||
| 270 | + # Both of the jwt tokens are validated as valid | ||
| 271 | + jwt.decode( | ||
| 272 | + encoded_good, | ||
| 273 | + ssh_key_bytes, | ||
| 274 | + algorithms=jwt.algorithms.get_default_algorithms() | ||
| 275 | + ) | ||
| 276 | + | ||
| 277 | + with pytest.raises(InvalidKeyError): | ||
| 278 | + jwt.decode( | ||
| 279 | + encoded_bad, | ||
| 280 | + ssh_key_bytes, | ||
| 281 | + algorithms=jwt.algorithms.get_default_algorithms() | ||
| 282 | + ) | ||
| 283 | diff --git a/tests/test_algorithms.py b/tests/test_algorithms.py | ||
| 284 | index b6a73fc..777c108 100644 | ||
| 285 | --- a/tests/test_algorithms.py | ||
| 286 | +++ b/tests/test_algorithms.py | ||
| 287 | @@ -669,7 +669,7 @@ class TestOKPAlgorithms: | ||
| 288 | def test_okp_ed25519_should_reject_non_string_key(self): | ||
| 289 | algo = OKPAlgorithm() | ||
| 290 | |||
| 291 | - with pytest.raises(TypeError): | ||
| 292 | + with pytest.raises(InvalidKeyError): | ||
| 293 | algo.prepare_key(None) | ||
| 294 | |||
| 295 | with open(key_path("testkey_ed25519")) as keyfile: | ||
diff --git a/meta-python/recipes-devtools/python/python3-pyjwt_2.3.0.bb b/meta-python/recipes-devtools/python/python3-pyjwt_2.3.0.bb index 19ba30780e..ad84e59d57 100644 --- a/meta-python/recipes-devtools/python/python3-pyjwt_2.3.0.bb +++ b/meta-python/recipes-devtools/python/python3-pyjwt_2.3.0.bb | |||
| @@ -5,6 +5,7 @@ HOMEPAGE = "http://github.com/jpadilla/pyjwt" | |||
| 5 | LICENSE = "MIT" | 5 | LICENSE = "MIT" |
| 6 | LIC_FILES_CHKSUM = "file://LICENSE;md5=68626705a7b513ca8d5f44a3e200ed0c" | 6 | LIC_FILES_CHKSUM = "file://LICENSE;md5=68626705a7b513ca8d5f44a3e200ed0c" |
| 7 | 7 | ||
| 8 | SRC_URI += "file://CVE-2022-29217.patch" | ||
| 8 | SRC_URI[sha256sum] = "b888b4d56f06f6dcd777210c334e69c737be74755d3e5e9ee3fe67dc18a0ee41" | 9 | SRC_URI[sha256sum] = "b888b4d56f06f6dcd777210c334e69c737be74755d3e5e9ee3fe67dc18a0ee41" |
| 9 | 10 | ||
| 10 | PYPI_PACKAGE = "PyJWT" | 11 | PYPI_PACKAGE = "PyJWT" |
