Skip to content

Commit c180800

Browse files
committed
Integrate trustme fake CA lib for testing TLS
Also: * Use real TLS context where it's disabled in tests * Add a change note about trustme integration Closes #3487
1 parent a929c06 commit c180800

13 files changed

+138
-119
lines changed

Diff for: CHANGES/3487.misc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Integrate [`trustme`](https://trustme.readthedocs.io/en/latest/) to better test TLS support.

Diff for: requirements/ci-wheel.txt

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pytest==4.0.2
1313
pytest-cov==2.6.0
1414
pytest-mock==1.10.0
1515
tox==3.6.1
16+
trustme==0.4.0
1617
twine==1.12.1
1718
yarl==1.3.0
1819

Diff for: tests/conftest.py

+54
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import hashlib
12
import pathlib
23
import shutil
4+
import ssl
35
import tempfile
46

57
import pytest
8+
import trustme
69

710
pytest_plugins = ['aiohttp.pytest_plugin', 'pytester']
811

@@ -15,3 +18,54 @@ def shorttmpdir():
1518
tmpdir = pathlib.Path(tempfile.mkdtemp())
1619
yield tmpdir
1720
shutil.rmtree(tmpdir, ignore_errors=True)
21+
22+
23+
@pytest.fixture
24+
def tls_certificate_authority():
25+
return trustme.CA()
26+
27+
28+
@pytest.fixture
29+
def tls_certificate(tls_certificate_authority):
30+
return tls_certificate_authority.issue_server_cert(
31+
'localhost',
32+
'127.0.0.1',
33+
'::1',
34+
)
35+
36+
37+
@pytest.fixture
38+
def ssl_ctx(tls_certificate):
39+
ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
40+
tls_certificate.configure_cert(ssl_ctx)
41+
return ssl_ctx
42+
43+
44+
@pytest.fixture
45+
def client_ssl_ctx(tls_certificate_authority):
46+
ssl_ctx = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH)
47+
tls_certificate_authority.configure_trust(ssl_ctx)
48+
return ssl_ctx
49+
50+
51+
@pytest.fixture
52+
def tls_ca_certificate_pem_path(tls_certificate_authority):
53+
with tls_certificate_authority.cert_pem.tempfile() as ca_cert_pem:
54+
yield ca_cert_pem
55+
56+
57+
@pytest.fixture
58+
def tls_certificate_pem_path(tls_certificate):
59+
with tls_certificate.private_key_and_cert_chain_pem.tempfile() as cert_pem:
60+
yield cert_pem
61+
62+
63+
@pytest.fixture
64+
def tls_certificate_pem_bytes(tls_certificate):
65+
return tls_certificate.cert_chain_pems[0].bytes()
66+
67+
68+
@pytest.fixture
69+
def tls_certificate_fingerprint_sha256(tls_certificate_pem_bytes):
70+
tls_cert_der = ssl.PEM_cert_to_DER_cert(tls_certificate_pem_bytes.decode())
71+
return hashlib.sha256(tls_cert_der).digest()

Diff for: tests/sample.crt

-14
This file was deleted.

Diff for: tests/sample.crt.der

-567 Bytes
Binary file not shown.

Diff for: tests/sample.key

-15
This file was deleted.

Diff for: tests/test_client_functional.py

+20-30
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import json
77
import pathlib
88
import socket
9-
import ssl
109
from unittest import mock
1110

1211
import pytest
@@ -25,18 +24,9 @@ def here():
2524
return pathlib.Path(__file__).parent
2625

2726

28-
@pytest.fixture
29-
def ssl_ctx(here):
30-
ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
31-
ssl_ctx.load_cert_chain(
32-
str(here / 'sample.crt'),
33-
str(here / 'sample.key'))
34-
return ssl_ctx
35-
36-
3727
@pytest.fixture
3828
def fname(here):
39-
return here / 'sample.key'
29+
return here / 'conftest.py'
4030

4131

4232
def ceil(val):
@@ -274,8 +264,11 @@ async def handler(request):
274264
assert 200 == resp.status
275265

276266

277-
async def test_ssl_client(ssl_ctx, aiohttp_server, aiohttp_client) -> None:
278-
connector = aiohttp.TCPConnector(ssl=False)
267+
async def test_ssl_client(
268+
aiohttp_server, ssl_ctx,
269+
aiohttp_client, client_ssl_ctx,
270+
) -> None:
271+
connector = aiohttp.TCPConnector(ssl=client_ssl_ctx)
279272

280273
async def handler(request):
281274
return web.Response(text='Test message')
@@ -291,17 +284,16 @@ async def handler(request):
291284
assert txt == 'Test message'
292285

293286

294-
async def test_tcp_connector_fingerprint_ok(aiohttp_server, aiohttp_client,
295-
ssl_ctx):
296-
297-
fingerprint = (b'0\x9a\xc9D\x83\xdc\x91\'\x88\x91\x11\xa1d\x97\xfd'
298-
b'\xcb~7U\x14D@L'
299-
b'\x11\xab\x99\xa8\xae\xb7\x14\xee\x8b')
287+
async def test_tcp_connector_fingerprint_ok(
288+
aiohttp_server, aiohttp_client,
289+
ssl_ctx, tls_certificate_fingerprint_sha256,
290+
):
291+
tls_fingerprint = Fingerprint(tls_certificate_fingerprint_sha256)
300292

301293
async def handler(request):
302294
return web.Response(text='Test message')
303295

304-
connector = aiohttp.TCPConnector(ssl=Fingerprint(fingerprint))
296+
connector = aiohttp.TCPConnector(ssl=tls_fingerprint)
305297
app = web.Application()
306298
app.router.add_route('GET', '/', handler)
307299
server = await aiohttp_server(app, ssl=ssl_ctx)
@@ -312,17 +304,14 @@ async def handler(request):
312304
resp.close()
313305

314306

315-
async def test_tcp_connector_fingerprint_fail(aiohttp_server, aiohttp_client,
316-
ssl_ctx):
317-
318-
fingerprint = (b'0\x9a\xc9D\x83\xdc\x91\'\x88\x91\x11\xa1d\x97\xfd'
319-
b'\xcb~7U\x14D@L'
320-
b'\x11\xab\x99\xa8\xae\xb7\x14\xee\x8b')
321-
307+
async def test_tcp_connector_fingerprint_fail(
308+
aiohttp_server, aiohttp_client,
309+
ssl_ctx, tls_certificate_fingerprint_sha256,
310+
):
322311
async def handler(request):
323312
return web.Response(text='Test message')
324313

325-
bad_fingerprint = b'\x00' * len(fingerprint)
314+
bad_fingerprint = b'\x00' * len(tls_certificate_fingerprint_sha256)
326315

327316
connector = aiohttp.TCPConnector(ssl=Fingerprint(bad_fingerprint))
328317

@@ -335,7 +324,7 @@ async def handler(request):
335324
await client.get('/')
336325
exc = cm.value
337326
assert exc.expected == bad_fingerprint
338-
assert exc.got == fingerprint
327+
assert exc.got == tls_certificate_fingerprint_sha256
339328

340329

341330
async def test_format_task_get(aiohttp_server) -> None:
@@ -1402,7 +1391,7 @@ async def handler(request):
14021391
'text/plain',
14031392
'application/octet-stream']
14041393
assert request.headers['content-disposition'] == (
1405-
"inline; filename=\"sample.key\"; filename*=utf-8''sample.key")
1394+
"inline; filename=\"conftest.py\"; filename*=utf-8''conftest.py")
14061395

14071396
return web.Response()
14081397

@@ -1428,6 +1417,7 @@ async def handler(request):
14281417
# then use 'application/octet-stream' default
14291418
assert request.content_type in ['application/pgp-keys',
14301419
'text/plain',
1420+
'text/x-python',
14311421
'application/octet-stream']
14321422
return web.Response()
14331423

Diff for: tests/test_client_request.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -801,7 +801,7 @@ async def test_chunked_transfer_encoding(loop, conn) -> None:
801801

802802
async def test_file_upload_not_chunked(loop) -> None:
803803
here = os.path.dirname(__file__)
804-
fname = os.path.join(here, 'sample.key')
804+
fname = os.path.join(here, 'aiohttp.png')
805805
with open(fname, 'rb') as f:
806806
req = ClientRequest(
807807
'post', URL('http://python.org/'),
@@ -828,7 +828,7 @@ async def test_precompressed_data_stays_intact(loop) -> None:
828828

829829
async def test_file_upload_not_chunked_seek(loop) -> None:
830830
here = os.path.dirname(__file__)
831-
fname = os.path.join(here, 'sample.key')
831+
fname = os.path.join(here, 'aiohttp.png')
832832
with open(fname, 'rb') as f:
833833
f.seek(100)
834834
req = ClientRequest(
@@ -842,7 +842,7 @@ async def test_file_upload_not_chunked_seek(loop) -> None:
842842

843843
async def test_file_upload_force_chunked(loop) -> None:
844844
here = os.path.dirname(__file__)
845-
fname = os.path.join(here, 'sample.key')
845+
fname = os.path.join(here, 'aiohttp.png')
846846
with open(fname, 'rb') as f:
847847
req = ClientRequest(
848848
'post', URL('http://python.org/'),
@@ -1270,11 +1270,11 @@ async def create_connection(req, traces, timeout):
12701270
conn.close()
12711271

12721272

1273-
def test_verify_ssl_false_with_ssl_context(loop) -> None:
1273+
def test_verify_ssl_false_with_ssl_context(loop, ssl_ctx) -> None:
12741274
with pytest.warns(DeprecationWarning):
12751275
with pytest.raises(ValueError):
12761276
_merge_ssl_params(None, verify_ssl=False,
1277-
ssl_context=mock.Mock(), fingerprint=None)
1277+
ssl_context=ssl_ctx, fingerprint=None)
12781278

12791279

12801280
def test_bad_fingerprint(loop) -> None:

Diff for: tests/test_connector.py

+9-19
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import asyncio
44
import gc
55
import hashlib
6-
import os.path
76
import platform
87
import socket
98
import ssl
@@ -1999,20 +1998,16 @@ async def test_resolver_not_called_with_address_is_ip(loop) -> None:
19991998
resolver.resolve.assert_not_called()
20001999

20012000

2002-
async def test_tcp_connector_raise_connector_ssl_error(aiohttp_server) -> None:
2001+
async def test_tcp_connector_raise_connector_ssl_error(
2002+
aiohttp_server, ssl_ctx,
2003+
) -> None:
20032004
async def handler(request):
20042005
return web.Response()
20052006

20062007
app = web.Application()
20072008
app.router.add_get('/', handler)
20082009

2009-
here = os.path.join(os.path.dirname(__file__), '..', 'tests')
2010-
keyfile = os.path.join(here, 'sample.key')
2011-
certfile = os.path.join(here, 'sample.crt')
2012-
sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
2013-
sslcontext.load_cert_chain(certfile, keyfile)
2014-
2015-
srv = await aiohttp_server(app, ssl=sslcontext)
2010+
srv = await aiohttp_server(app, ssl=ssl_ctx)
20162011

20172012
port = unused_port()
20182013
conn = aiohttp.TCPConnector(local_addr=('127.0.0.1', port))
@@ -2038,27 +2033,22 @@ async def handler(request):
20382033

20392034

20402035
async def test_tcp_connector_do_not_raise_connector_ssl_error(
2041-
aiohttp_server) -> None:
2036+
aiohttp_server, ssl_ctx, client_ssl_ctx,
2037+
) -> None:
20422038
async def handler(request):
20432039
return web.Response()
20442040

20452041
app = web.Application()
20462042
app.router.add_get('/', handler)
20472043

2048-
here = os.path.join(os.path.dirname(__file__), '..', 'tests')
2049-
keyfile = os.path.join(here, 'sample.key')
2050-
certfile = os.path.join(here, 'sample.crt')
2051-
sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
2052-
sslcontext.load_cert_chain(certfile, keyfile)
2053-
2054-
srv = await aiohttp_server(app, ssl=sslcontext)
2044+
srv = await aiohttp_server(app, ssl=ssl_ctx)
20552045
port = unused_port()
20562046
conn = aiohttp.TCPConnector(local_addr=('127.0.0.1', port))
20572047

20582048
session = aiohttp.ClientSession(connector=conn)
20592049
url = srv.make_url('/')
20602050

2061-
r = await session.get(url, ssl=sslcontext)
2051+
r = await session.get(url, ssl=client_ssl_ctx)
20622052

20632053
r.release()
20642054
first_conn = next(iter(conn._conns.values()))[0][0]
@@ -2068,7 +2058,7 @@ async def handler(request):
20682058
except AttributeError:
20692059
_sslcontext = first_conn.transport._sslcontext
20702060

2071-
assert _sslcontext is sslcontext
2061+
assert _sslcontext is client_ssl_ctx
20722062
r.close()
20732063

20742064
await session.close()

Diff for: tests/test_route_def.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,8 @@ def test_static(router) -> None:
128128
info = resource.get_info()
129129
assert info['prefix'] == '/prefix'
130130
assert info['directory'] == folder
131-
url = resource.url_for(filename='sample.key')
132-
assert url == URL('/prefix/sample.key')
131+
url = resource.url_for(filename='aiohttp.png')
132+
assert url == URL('/prefix/aiohttp.png')
133133

134134

135135
def test_head_deco(router) -> None:

0 commit comments

Comments
 (0)