From 13fa1e033b37405b228599ee590af485dc3e38aa Mon Sep 17 00:00:00 2001 From: Illia Volochii 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: