Skip to content

Commit 07408ce

Browse files
authored
Isolate HTTPS proxy TLS settings from target TLS (#5093)
1 parent e6cc4bf commit 07408ce

4 files changed

Lines changed: 212 additions & 46 deletions

File tree

‎changelog/5093.bugfix.rst‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Fixed ``HTTPSConnection.connect()`` overriding
2+
``ProxyConfig.ssl_context``'s certificate policy and proxy identity checks with
3+
the target connection's TLS settings when forwarding through an HTTPS proxy.
4+
5+
``HTTPSConnection`` no longer applies target SNI, assertions, or client
6+
credentials to forwarding proxy handshakes and continues to use its
7+
``ssl_context`` as a fallback when an HTTPS proxy forwards an HTTP target.

‎src/urllib3/connection.py‎

Lines changed: 58 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -838,30 +838,30 @@ def connect(self) -> None:
838838
# fall back to using the connection's SSL context until
839839
# urllib3 v3.0. Appropriate warning is emitted in
840840
# ``ProxyManager.__init__``.
841+
wrapped_socket: ssl.SSLSocket | SSLTransport
841842
if self.proxy_is_forwarding and self.proxy_config is not None:
842-
ssl_context = self.proxy_config.ssl_context
843+
wrapped_socket = self._connect_tls_proxy(self.host, sock)
844+
is_verified = self.proxy_is_verified is True
843845
else:
844-
ssl_context = self.ssl_context
845-
846-
sock_and_verified = _ssl_wrap_socket_and_match_hostname(
847-
sock=sock,
848-
cert_reqs=self.cert_reqs,
849-
ssl_version=self.ssl_version,
850-
ssl_minimum_version=self.ssl_minimum_version,
851-
ssl_maximum_version=self.ssl_maximum_version,
852-
ca_certs=self.ca_certs,
853-
ca_cert_dir=self.ca_cert_dir,
854-
ca_cert_data=self.ca_cert_data,
855-
cert_file=self.cert_file,
856-
key_file=self.key_file,
857-
key_password=self.key_password,
858-
server_hostname=server_hostname_rm_dot,
859-
ssl_context=ssl_context,
860-
tls_in_tls=tls_in_tls,
861-
assert_hostname=self.assert_hostname,
862-
assert_fingerprint=self.assert_fingerprint,
863-
)
864-
self.sock = sock_and_verified.socket
846+
wrapped_socket, is_verified = _ssl_wrap_socket_and_match_hostname(
847+
sock=sock,
848+
cert_reqs=self.cert_reqs,
849+
ssl_version=self.ssl_version,
850+
ssl_minimum_version=self.ssl_minimum_version,
851+
ssl_maximum_version=self.ssl_maximum_version,
852+
ca_certs=self.ca_certs,
853+
ca_cert_dir=self.ca_cert_dir,
854+
ca_cert_data=self.ca_cert_data,
855+
cert_file=self.cert_file,
856+
key_file=self.key_file,
857+
key_password=self.key_password,
858+
server_hostname=server_hostname_rm_dot,
859+
ssl_context=self.ssl_context,
860+
tls_in_tls=tls_in_tls,
861+
assert_hostname=self.assert_hostname,
862+
assert_fingerprint=self.assert_fingerprint,
863+
)
864+
self.sock = wrapped_socket
865865

866866
# If an error occurs during connection/handshake we may need to release
867867
# our lock so another connection can probe the origin.
@@ -882,7 +882,7 @@ def connect(self) -> None:
882882
# If this connection doesn't know if the origin supports HTTP/2
883883
# we report back to the HTTP/2 probe our result.
884884
if target_supports_http2 is None:
885-
supports_http2 = sock_and_verified.socket.selected_alpn_protocol() == "h2"
885+
supports_http2 = wrapped_socket.selected_alpn_protocol() == "h2"
886886
http2_probe.set_and_release(
887887
host=probe_http2_host,
888888
port=probe_http2_port,
@@ -896,7 +896,7 @@ def connect(self) -> None:
896896
if self.proxy_is_forwarding:
897897
self.is_verified = False
898898
else:
899-
self.is_verified = sock_and_verified.is_verified
899+
self.is_verified = is_verified
900900

901901
# If there's a proxy to be connected to we are fully connected.
902902
# This is set twice (once above and here) due to forwarding proxies
@@ -906,24 +906,47 @@ def connect(self) -> None:
906906
# Set `self.proxy_is_verified` unless it's already set while
907907
# establishing a tunnel.
908908
if self._has_connected_to_proxy and self.proxy_is_verified is None:
909-
self.proxy_is_verified = sock_and_verified.is_verified
909+
self.proxy_is_verified = is_verified
910910

911911
def _connect_tls_proxy(self, hostname: str, sock: socket.socket) -> ssl.SSLSocket:
912912
"""
913-
Establish a TLS connection to the proxy using the provided SSL context.
913+
Establish a TLS connection to the proxy using proxy-specific policy.
914914
"""
915-
# `_connect_tls_proxy` is called when self._tunnel_host is truthy.
916915
proxy_config = typing.cast(ProxyConfig, self.proxy_config)
917-
ssl_context = proxy_config.ssl_context
916+
proxy_ssl_context = proxy_config.ssl_context
917+
918+
ssl_context: ssl.SSLContext | None
919+
cert_reqs: int | str | None
920+
if proxy_ssl_context is not None:
921+
# Prefer the proxy's cert policy for the proxy connection
922+
ssl_context = proxy_ssl_context
923+
cert_reqs = proxy_ssl_context.verify_mode
924+
ca_certs = None
925+
ca_cert_dir = None
926+
ca_cert_data = None
927+
ssl_version = None
928+
ssl_minimum_version = None
929+
ssl_maximum_version = None
930+
else:
931+
# Otherwise we inherit the pool's cert policies
932+
ssl_context = self.ssl_context if self.proxy_is_forwarding else None
933+
cert_reqs = self.cert_reqs
934+
ca_certs = self.ca_certs
935+
ca_cert_dir = self.ca_cert_dir
936+
ca_cert_data = self.ca_cert_data
937+
ssl_version = self.ssl_version
938+
ssl_minimum_version = self.ssl_minimum_version
939+
ssl_maximum_version = self.ssl_maximum_version
940+
918941
sock_and_verified = _ssl_wrap_socket_and_match_hostname(
919942
sock,
920-
cert_reqs=self.cert_reqs,
921-
ssl_version=self.ssl_version,
922-
ssl_minimum_version=self.ssl_minimum_version,
923-
ssl_maximum_version=self.ssl_maximum_version,
924-
ca_certs=self.ca_certs,
925-
ca_cert_dir=self.ca_cert_dir,
926-
ca_cert_data=self.ca_cert_data,
943+
cert_reqs=cert_reqs,
944+
ssl_version=ssl_version,
945+
ssl_minimum_version=ssl_minimum_version,
946+
ssl_maximum_version=ssl_maximum_version,
947+
ca_certs=ca_certs,
948+
ca_cert_dir=ca_cert_dir,
949+
ca_cert_data=ca_cert_data,
927950
server_hostname=hostname,
928951
ssl_context=ssl_context,
929952
assert_hostname=proxy_config.assert_hostname,

‎src/urllib3/poolmanager.py‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,9 @@ def __init__(
583583
if proxy.scheme not in ("http", "https"):
584584
raise ProxySchemeUnknown(proxy.scheme)
585585

586+
# Keep the deprecated ssl_context fallback on the manager for
587+
# compatibility, while passing only explicit proxy policy to connections.
588+
self.proxy_ssl_context = proxy_ssl_context
586589
if (
587590
use_forwarding_for_https
588591
and proxy.scheme == "https"
@@ -595,16 +598,15 @@ def __init__(
595598
FutureWarning,
596599
stacklevel=2,
597600
)
598-
if proxy_ssl_context is None:
599-
proxy_ssl_context = connection_pool_kw.get("ssl_context")
601+
if self.proxy_ssl_context is None:
602+
self.proxy_ssl_context = connection_pool_kw.get("ssl_context")
600603

601604
if proxy.port is None:
602605
port = port_by_scheme.get(proxy.scheme, 80)
603606
proxy = proxy._replace(port=port)
604607

605608
self.proxy = proxy
606609
self.proxy_headers = proxy_headers or {}
607-
self.proxy_ssl_context = proxy_ssl_context
608610
self.proxy_config = ProxyConfig(
609611
proxy_ssl_context,
610612
use_forwarding_for_https,

0 commit comments

Comments
 (0)