Skip to content

Commit

Permalink
Fix issue 6652: Raise aiohttp.ServerFingerprintMismatch exception o… (
Browse files Browse the repository at this point in the history
#9363)

…… (#6653)

(cherry picked from commit e3b1011)

Co-authored-by: Gang Ji <62988402+gangj@users.noreply.github.com>
  • Loading branch information
Dreamsorcerer and gangj authored Oct 1, 2024
1 parent 354153e commit 22a12cc
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGES/6652.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Raise `aiohttp.ServerFingerprintMismatch` exception on client-side if request through http proxy with mismatching server fingerprint digest: `aiohttp.ClientSession(headers=headers, connector=TCPConnector(ssl=aiohttp.Fingerprint(mismatch_digest), trust_env=True).request(...)`.
1 change: 1 addition & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ Franek Magiera
Frederik Gladhorn
Frederik Peter Aalund
Gabriel Tremblay
Gang Ji
Gary Wilson Jr.
Gennady Andreyev
Georges Dubus
Expand Down
10 changes: 10 additions & 0 deletions aiohttp/connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -1217,6 +1217,16 @@ async def _start_tls_connection(
# chance to do this:
underlying_transport.close()
raise
if isinstance(tls_transport, asyncio.Transport):
fingerprint = self._get_fingerprint(req)
if fingerprint:
try:
fingerprint.check(tls_transport)
except ServerFingerprintMismatch:
tls_transport.close()
if not self._cleanup_closed_disabled:
self._cleanup_closed_transports.append(tls_transport)
raise
except cert_errors as exc:
raise ClientConnectorCertificateError(req.connection_key, exc) from exc
except ssl_errors as exc:
Expand Down
106 changes: 104 additions & 2 deletions tests/test_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from yarl import URL

import aiohttp
from aiohttp.client_reqrep import ClientRequest, ClientResponse
from aiohttp.client_reqrep import ClientRequest, ClientResponse, Fingerprint
from aiohttp.connector import _SSL_CONTEXT_VERIFIED
from aiohttp.helpers import TimerNoop
from aiohttp.test_utils import make_mocked_coro
Expand Down Expand Up @@ -384,7 +384,109 @@ async def make_conn():
autospec=True,
spec_set=True,
)
def test_https_connect(self, start_connection: Any, ClientRequestMock: Any) -> None:
def test_https_connect_fingerprint_mismatch(
self, start_connection: mock.Mock, ClientRequestMock: mock.Mock
) -> None:
async def make_conn() -> aiohttp.TCPConnector:
return aiohttp.TCPConnector(enable_cleanup_closed=cleanup)

for cleanup in (True, False):
with self.subTest(cleanup=cleanup):
proxy_req = ClientRequest(
"GET", URL("http://proxy.example.com"), loop=self.loop
)
ClientRequestMock.return_value = proxy_req

class TransportMock(asyncio.Transport):
def close(self) -> None:
pass

proxy_resp = ClientResponse(
"get",
URL("http://proxy.example.com"),
request_info=mock.Mock(),
writer=mock.Mock(),
continue100=None,
timer=TimerNoop(),
traces=[],
loop=self.loop,
session=mock.Mock(),
)
fingerprint_mock = mock.Mock(spec=Fingerprint, auto_spec=True)
fingerprint_mock.check.side_effect = aiohttp.ServerFingerprintMismatch(
b"exp", b"got", "example.com", 8080
)
with mock.patch.object(
proxy_req,
"send",
autospec=True,
spec_set=True,
return_value=proxy_resp,
), mock.patch.object(
proxy_resp,
"start",
autospec=True,
spec_set=True,
return_value=mock.Mock(status=200),
):
connector = self.loop.run_until_complete(make_conn())
host = [
{
"hostname": "hostname",
"host": "127.0.0.1",
"port": 80,
"family": socket.AF_INET,
"proto": 0,
"flags": 0,
}
]
with mock.patch.object(
connector,
"_resolve_host",
autospec=True,
spec_set=True,
return_value=host,
), mock.patch.object(
connector,
"_get_fingerprint",
autospec=True,
spec_set=True,
return_value=fingerprint_mock,
), mock.patch.object( # Called on connection to http://proxy.example.com
self.loop,
"create_connection",
autospec=True,
spec_set=True,
return_value=(mock.Mock(), mock.Mock()),
), mock.patch.object( # Called on connection to https://www.python.org
self.loop,
"start_tls",
autospec=True,
spec_set=True,
return_value=TransportMock(),
):
req = ClientRequest(
"GET",
URL("https://www.python.org"),
proxy=URL("http://proxy.example.com"),
loop=self.loop,
)
with self.assertRaises(aiohttp.ServerFingerprintMismatch):
self.loop.run_until_complete(
connector._create_connection(
req, [], aiohttp.ClientTimeout()
)
)

@mock.patch("aiohttp.connector.ClientRequest")
@mock.patch(
"aiohttp.connector.aiohappyeyeballs.start_connection",
autospec=True,
spec_set=True,
)
def test_https_connect(
self, start_connection: mock.Mock, ClientRequestMock: mock.Mock
) -> None:
proxy_req = ClientRequest(
"GET", URL("http://proxy.example.com"), loop=self.loop
)
Expand Down

0 comments on commit 22a12cc

Please sign in to comment.