-
-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
support new TCPConnector param fingerprint
#366
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ | |
'ClientError', 'ClientHttpProcessingError', 'ClientConnectionError', | ||
'ClientOSError', 'ClientTimeoutError', 'ProxyConnectionError', | ||
'ClientRequestError', 'ClientResponseError', | ||
'FingerprintMismatch', | ||
|
||
'WSServerHandshakeError', 'WSClientDisconnectedError') | ||
|
||
|
@@ -170,3 +171,18 @@ class LineLimitExceededParserError(ParserError): | |
def __init__(self, msg, limit): | ||
super().__init__(msg) | ||
self.limit = limit | ||
|
||
|
||
class FingerprintMismatch(ClientConnectionError): | ||
"""SSL certificate does not match expected fingerprint.""" | ||
|
||
def __init__(self, expected, got, host, port): | ||
self.expected = expected | ||
self.got = got | ||
self.host = host | ||
self.port = port | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add |
||
|
||
def __repr__(self): | ||
return '<{} expected={} got={} host={} port={}>'.format( | ||
self.__class__.__name__, self.expected, self.got, | ||
self.host, self.port) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ | |
import aiohttp | ||
from aiohttp import client | ||
from aiohttp import test_utils | ||
from aiohttp.errors import FingerprintMismatch | ||
from aiohttp.client import ClientResponse, ClientRequest | ||
from aiohttp.connector import Connection | ||
|
||
|
@@ -452,10 +453,57 @@ def test_cleanup3(self): | |
def test_tcp_connector_ctor(self): | ||
conn = aiohttp.TCPConnector(loop=self.loop) | ||
self.assertTrue(conn.verify_ssl) | ||
self.assertIs(conn.fingerprint, None) | ||
self.assertFalse(conn.resolve) | ||
self.assertEqual(conn.family, socket.AF_INET) | ||
self.assertEqual(conn.resolved_hosts, {}) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need for test for |
||
def test_tcp_connector_ctor_fingerprint_valid(self): | ||
valid = b'\xa2\x06G\xad\xaa\xf5\xd8\\J\x99^by;\x06=' | ||
conn = aiohttp.TCPConnector(loop=self.loop, fingerprint=valid) | ||
self.assertEqual(conn.fingerprint, valid) | ||
|
||
def test_tcp_connector_fingerprint_invalid(self): | ||
invalid = b'\x00' | ||
with self.assertRaises(ValueError): | ||
aiohttp.TCPConnector(loop=self.loop, fingerprint=invalid) | ||
|
||
def test_tcp_connector_fingerprint(self): | ||
# The even-index fingerprints below are "expect success" cases | ||
# for ./sample.crt.der, the cert presented by test_utils.run_server. | ||
# The odd-index fingerprints are "expect fail" cases. | ||
testcases = ( | ||
# md5 | ||
b'\xa2\x06G\xad\xaa\xf5\xd8\\J\x99^by;\x06=', | ||
b'\x00' * 16, | ||
|
||
# sha1 | ||
b's\x93\xfd:\xed\x08\x1do\xa9\xaeq9\x1a\xe3\xc5\x7f\x89\xe7l\xf9', | ||
b'\x00' * 20, | ||
|
||
# sha256 | ||
b'0\x9a\xc9D\x83\xdc\x91\'\x88\x91\x11\xa1d\x97\xfd\xcb~7U\x14D@L' | ||
b'\x11\xab\x99\xa8\xae\xb7\x14\xee\x8b', | ||
b'\x00' * 32, | ||
) | ||
for i, fingerprint in enumerate(testcases): | ||
expect_fail = i % 2 | ||
conn = aiohttp.TCPConnector(loop=self.loop, verify_ssl=False, | ||
fingerprint=fingerprint) | ||
with test_utils.run_server(self.loop, use_ssl=True) as httpd: | ||
coro = client.request('get', httpd.url('method', 'get'), | ||
connector=conn, loop=self.loop) | ||
if expect_fail: | ||
with self.assertRaises(FingerprintMismatch) as cm: | ||
self.loop.run_until_complete(coro) | ||
exc = cm.exception | ||
self.assertEqual(exc.expected, fingerprint) | ||
# the previous test case should be what we actually got | ||
self.assertEqual(exc.got, testcases[i-1]) | ||
else: | ||
# should not raise | ||
self.loop.run_until_complete(coro) | ||
|
||
def test_tcp_connector_clear_resolved_hosts(self): | ||
conn = aiohttp.TCPConnector(loop=self.loop) | ||
info = object() | ||
|
@@ -741,7 +789,7 @@ def test_connect(self, ClientRequestMock): | |
self.assertIs(conn._protocol, proto) | ||
|
||
# resolve_host.assert_called_once_with('proxy.example.com', 80) | ||
self.assertEqual(tr.mock_calls, []) | ||
tr.get_extra_info.assert_called_once_with('sslcontext') | ||
|
||
ClientRequestMock.assert_called_with( | ||
'GET', 'http://proxy.example.com', | ||
|
@@ -897,7 +945,7 @@ def test_https_connect(self, ClientRequestMock): | |
self.assertEqual(proxy_req.method, 'CONNECT') | ||
self.assertEqual(proxy_req.path, 'www.python.org:443') | ||
tr.pause_reading.assert_called_once_with() | ||
tr.get_extra_info.assert_called_once_with('socket', default=None) | ||
tr.get_extra_info.assert_called_with('socket', default=None) | ||
|
||
@unittest.mock.patch('aiohttp.connector.ClientRequest') | ||
def test_https_connect_runtime_error(self, ClientRequestMock): | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we have a way to pass
bytes
fingerprint?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something like 8661202?