summaryrefslogtreecommitdiffstats
path: root/meta-python
diff options
context:
space:
mode:
authorGyorgy Sarvari <skandigraun@gmail.com>2026-01-06 08:33:28 +0100
committerGyorgy Sarvari <skandigraun@gmail.com>2026-01-08 22:03:03 +0100
commit304c0c66430e6aee14793016d9b2236f577de771 (patch)
tree717f3f993d389955808d523c15c487100463732c /meta-python
parentf6d4f623c19ef65ee2a536c5c689b763b00e83de (diff)
downloadmeta-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.patch295
-rw-r--r--meta-python/recipes-devtools/python/python3-pyjwt_2.3.0.bb1
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 @@
1From 0ab93eb55a182f190dea55cc048dcb50bf97724c Mon Sep 17 00:00:00 2001
2From: =?UTF-8?q?Jos=C3=A9=20Padilla?= <jpadilla@webapplicate.com>
3Date: Thu, 12 May 2022 14:31:00 -0400
4Subject: [PATCH] Merge pull request from GHSA-ffqj-6fqr-9h24
5MIME-Version: 1.0
6Content-Type: text/plain; charset=UTF-8
7Content-Transfer-Encoding: 8bit
8
9Co-authored-by: José Padilla <jpadilla@users.noreply.github.com>
10
11CVE: CVE-2022-29217
12Upstream-Status: Backport [https://github.com/jpadilla/pyjwt/commit/9c528670c455b8d948aff95ed50e22940d1ad3fc]
13Signed-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
22diff --git a/jwt/algorithms.py b/jwt/algorithms.py
23index 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 """
93diff --git a/jwt/utils.py b/jwt/utils.py
94index 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
168diff --git a/tests/test_advisory.py b/tests/test_advisory.py
169new file mode 100644
170index 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+ )
283diff --git a/tests/test_algorithms.py b/tests/test_algorithms.py
284index 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"
5LICENSE = "MIT" 5LICENSE = "MIT"
6LIC_FILES_CHKSUM = "file://LICENSE;md5=68626705a7b513ca8d5f44a3e200ed0c" 6LIC_FILES_CHKSUM = "file://LICENSE;md5=68626705a7b513ca8d5f44a3e200ed0c"
7 7
8SRC_URI += "file://CVE-2022-29217.patch"
8SRC_URI[sha256sum] = "b888b4d56f06f6dcd777210c334e69c737be74755d3e5e9ee3fe67dc18a0ee41" 9SRC_URI[sha256sum] = "b888b4d56f06f6dcd777210c334e69c737be74755d3e5e9ee3fe67dc18a0ee41"
9 10
10PYPI_PACKAGE = "PyJWT" 11PYPI_PACKAGE = "PyJWT"