python-urllib3/5103.patch
Miro Hrončok d75d492045 Fix test failures (OpenSSL 4 and pyOpenSSL 26.3)
- Fixes: rhbz#2504593

Used LLM to identify and rebase upstream patch #5103

Assisted-By: Claude Opus 4.6
2026-07-27 22:45:42 +02:00

83 lines
3.6 KiB
Diff

From 13fa1e033b37405b228599ee590af485dc3e38aa Mon Sep 17 00:00:00 2001
From: Illia Volochii <illia.volochii@gmail.com>
Date: Sat, 18 Jul 2026 13:25:41 +0200
Subject: [PATCH] Replace deprecated pyOpenSSL `X509.get_subject` and
`Context.set_passwd_cb` methods (#5103)
Rebased on top of urllib3-2.7.0.
---
src/urllib3/contrib/pyopenssl.py | 35 ++++++++++++++++++++++++++-------
1 file changed, 28 insertions(+), 7 deletions(-)
diff --git a/src/urllib3/contrib/pyopenssl.py b/src/urllib3/contrib/pyopenssl.py
index 76a225b7bf..a1b2c3d4e5 100644
--- a/src/urllib3/contrib/pyopenssl.py
+++ b/src/urllib3/contrib/pyopenssl.py
@@ -42,6 +42,8 @@
import OpenSSL.SSL # type: ignore[import-not-found]
from cryptography import x509
+from cryptography.hazmat.primitives.serialization import load_pem_private_key
+from cryptography.x509.oid import NameOID
try:
from cryptography.x509 import UnsupportedExtension # type: ignore[attr-defined]
@@ -272,6 +274,15 @@
return names
+def _get_common_name(peer_cert: X509) -> str | None:
+ """
+ Given a pyOpenSSL certificate, return the subject's common name.
+ """
+ cert = peer_cert.to_cryptography()
+ names = cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)
+ return typing.cast(str, names[0].value) if names else None
+
+
class WrappedSocket:
"""API-compatibility wrapper for Python OpenSSL's Connection-class."""
@@ -396,7 +407,7 @@
return OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_ASN1, x509) # type: ignore[no-any-return]
return {
- "subject": ((("commonName", x509.get_subject().CN),),), # type: ignore[dict-item]
+ "subject": ((("commonName", _get_common_name(x509)),),), # type: ignore[dict-item]
"subjectAltName": get_subj_alt_name(x509),
}
@@ -482,16 +493,28 @@
self,
certfile: str,
keyfile: str | None = None,
- password: str | None = None,
+ password: str | bytes | None = None,
) -> None:
try:
self._ctx.use_certificate_chain_file(certfile)
if password is not None:
if not isinstance(password, bytes):
- password = password.encode("utf-8") # type: ignore[assignment]
- self._ctx.set_passwd_cb(lambda *_: password)
- self._ctx.use_privatekey_file(keyfile or certfile)
- except OpenSSL.SSL.Error as e:
+ password = password.encode("utf-8")
+ # pyOpenSSL added cryptography-key support in 24.3.0.
+ # Keep using the older password-callback path until 2026's
+ # versions because set_passwd_cb() became deprecated in 26.3.0.
+ if int(OpenSSL.__version__.split(".")[0]) >= 26:
+ with open(keyfile or certfile, "rb") as key_file:
+ private_key = load_pem_private_key(key_file.read(), password)
+ # cryptography's loader returns a wider private-key union
+ # than pyOpenSSL accepts, so we add `type: ignore` here.
+ self._ctx.use_privatekey(private_key) # type: ignore[arg-type]
+ else:
+ self._ctx.set_passwd_cb(lambda *_: password)
+ self._ctx.use_privatekey_file(keyfile or certfile)
+ else:
+ self._ctx.use_privatekey_file(keyfile or certfile)
+ except (OpenSSL.SSL.Error, TypeError, ValueError) as e:
raise ssl.SSLError(f"Unable to load certificate chain: {e!r}") from e
def set_alpn_protocols(self, protocols: list[bytes | str]) -> None: