Skip to content

Commit 23af110

Browse files
committed
Use trustme for test TLS certificate and key
With the current approach TLS certificate and key are hardcoded in separate files and distributed as part of wheels. This is unnecessary for end users of the package, also this brings difficulties while supporting Python 3.13, see: aio-libs#473 Use trustme for generating TLS certificate and key on the fly, as it is done in other aio-libs packages. Initially proposed in: aio-libs#473 (comment)
1 parent 54b9643 commit 23af110

File tree

9 files changed

+55
-133
lines changed

9 files changed

+55
-133
lines changed

aiosmtpd/tests/certs/__init__.py

Whitespace-only changes.

aiosmtpd/tests/certs/server.crt

Lines changed: 0 additions & 23 deletions
This file was deleted.

aiosmtpd/tests/certs/server.key

Lines changed: 0 additions & 28 deletions
This file was deleted.

aiosmtpd/tests/certs/server_alt.crt

Lines changed: 0 additions & 23 deletions
This file was deleted.

aiosmtpd/tests/certs/server_alt.key

Lines changed: 0 additions & 28 deletions
This file was deleted.

aiosmtpd/tests/conftest.py

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
from contextlib import suppress
1010
from functools import wraps
1111
from smtplib import SMTP as SMTPClient
12-
from typing import Any, Callable, Generator, NamedTuple, Optional, Type, TypeVar
12+
from typing import Any, Callable, Generator, Iterator, NamedTuple, Optional, Type, TypeVar
1313

1414
import pytest
15-
from pkg_resources import resource_filename
15+
import trustme
1616
from pytest_mock import MockFixture
1717

1818
from aiosmtpd.controller import Controller
@@ -32,8 +32,6 @@
3232
"handler_data",
3333
"Global",
3434
"AUTOSTOP_DELAY",
35-
"SERVER_CRT",
36-
"SERVER_KEY",
3735
]
3836

3937

@@ -73,8 +71,6 @@ def set_addr_from(cls, contr: Controller):
7371
# If less than 1.0, might cause intermittent error if test system
7472
# is too busy/overloaded.
7573
AUTOSTOP_DELAY = 1.5
76-
SERVER_CRT = resource_filename("aiosmtpd.tests.certs", "server.crt")
77-
SERVER_KEY = resource_filename("aiosmtpd.tests.certs", "server.key")
7874

7975
# endregion
8076

@@ -315,27 +311,50 @@ def client(request: pytest.FixtureRequest) -> Generator[SMTPClient, None, None]:
315311

316312

317313
@pytest.fixture
318-
def ssl_context_server() -> ssl.SSLContext:
314+
def tls_certificate_authority() -> trustme.CA:
315+
return trustme.CA()
316+
317+
318+
@pytest.fixture
319+
def tls_certificate(tls_certificate_authority: trustme.CA) -> trustme.LeafCert:
320+
return tls_certificate_authority.issue_cert(
321+
"localhost",
322+
"xn--prklad-4va.localhost", # príklad.localhost
323+
"127.0.0.1",
324+
"::1",
325+
)
326+
327+
328+
@pytest.fixture
329+
def ssl_context_server(tls_certificate: trustme.LeafCert) -> ssl.SSLContext:
319330
"""
320331
Provides a server-side SSL Context
321332
"""
322-
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
323-
context.check_hostname = False
324-
context.load_cert_chain(SERVER_CRT, SERVER_KEY)
325-
#
326-
return context
333+
ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
334+
tls_certificate.configure_cert(ssl_ctx)
335+
return ssl_ctx
327336

328337

329338
@pytest.fixture
330-
def ssl_context_client() -> ssl.SSLContext:
339+
def ssl_context_client(tls_certificate_authority: trustme.CA) -> ssl.SSLContext:
331340
"""
332341
Provides a client-side SSL Context
333342
"""
334-
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
335-
context.check_hostname = False
336-
context.load_verify_locations(SERVER_CRT)
337-
#
338-
return context
343+
ssl_ctx = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH)
344+
tls_certificate_authority.configure_trust(ssl_ctx)
345+
return ssl_ctx
346+
347+
348+
@pytest.fixture
349+
def tls_cert_pem_path(tls_certificate: trustme.LeafCert) -> Iterator[str]:
350+
with tls_certificate.cert_chain_pems[0].tempfile() as cert_pem:
351+
yield cert_pem
352+
353+
354+
@pytest.fixture
355+
def tls_key_pem_path(tls_certificate: trustme.LeafCert) -> Iterator[str]:
356+
with tls_certificate.private_key_pem.tempfile() as key_pem:
357+
yield key_pem
339358

340359

341360
# Please keep the scope as "module"; setting it as "function" (the default) somehow

aiosmtpd/tests/test_main.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from aiosmtpd.main import main, parseargs
2222
from aiosmtpd.testing.helpers import catchup_delay
2323
from aiosmtpd.testing.statuscodes import SMTP_STATUS_CODES as S
24-
from aiosmtpd.tests.conftest import AUTOSTOP_DELAY, SERVER_CRT, SERVER_KEY
24+
from aiosmtpd.tests.conftest import AUTOSTOP_DELAY
2525

2626
try:
2727
import pwd
@@ -199,24 +199,24 @@ def test_debug_3(self):
199199

200200
@pytest.mark.skipif(sys.platform == "darwin", reason="No idea why these are failing")
201201
class TestMainByWatcher:
202-
def test_tls(self, temp_event_loop):
202+
def test_tls(self, temp_event_loop, tls_cert_pem_path, tls_key_pem_path):
203203
with watcher_process(watch_for_tls) as retq:
204204
temp_event_loop.call_later(AUTOSTOP_DELAY, temp_event_loop.stop)
205-
main_n("--tlscert", str(SERVER_CRT), "--tlskey", str(SERVER_KEY))
205+
main_n("--tlscert", tls_cert_pem_path, "--tlskey", tls_key_pem_path)
206206
catchup_delay()
207207
has_starttls = retq.get()
208208
assert has_starttls is True
209209
require_tls = retq.get()
210210
assert require_tls is True
211211

212-
def test_tls_noreq(self, temp_event_loop):
212+
def test_tls_noreq(self, temp_event_loop, tls_cert_pem_path, tls_key_pem_path):
213213
with watcher_process(watch_for_tls) as retq:
214214
temp_event_loop.call_later(AUTOSTOP_DELAY, temp_event_loop.stop)
215215
main_n(
216216
"--tlscert",
217-
str(SERVER_CRT),
217+
tls_cert_pem_path,
218218
"--tlskey",
219-
str(SERVER_KEY),
219+
tls_key_pem_path,
220220
"--no-requiretls",
221221
)
222222
catchup_delay()
@@ -225,10 +225,10 @@ def test_tls_noreq(self, temp_event_loop):
225225
require_tls = retq.get()
226226
assert require_tls is False
227227

228-
def test_smtps(self, temp_event_loop):
228+
def test_smtps(self, temp_event_loop, tls_cert_pem_path, tls_key_pem_path):
229229
with watcher_process(watch_for_smtps) as retq:
230230
temp_event_loop.call_later(AUTOSTOP_DELAY, temp_event_loop.stop)
231-
main_n("--smtpscert", str(SERVER_CRT), "--smtpskey", str(SERVER_KEY))
231+
main_n("--smtpscert", tls_cert_pem_path, "--smtpskey", tls_key_pem_path)
232232
catchup_delay()
233233
has_smtps = retq.get()
234234
assert has_smtps is True
@@ -335,16 +335,18 @@ def test_norequiretls(self, capsys, mocker):
335335
assert args.requiretls is False
336336

337337
@pytest.mark.parametrize(
338-
("certfile", "keyfile", "expect"),
338+
("certfile_present", "keyfile_present", "expect"),
339339
[
340-
("x", "x", "Cert file x not found"),
341-
(SERVER_CRT, "x", "Key file x not found"),
342-
("x", SERVER_KEY, "Cert file x not found"),
340+
(False, False, "Cert file x not found"),
341+
(True, False, "Key file x not found"),
342+
(False, True, "Cert file x not found"),
343343
],
344344
ids=["x-x", "cert-x", "x-key"],
345345
)
346346
@pytest.mark.parametrize("meth", ["smtps", "tls"])
347-
def test_ssl_files_err(self, capsys, mocker, meth, certfile, keyfile, expect):
347+
def test_ssl_files_err(self, capsys, mocker, meth, certfile_present, keyfile_present, expect, request):
348+
certfile = request.getfixturevalue("tls_cert_pem_path") if certfile_present else "x"
349+
keyfile = request.getfixturevalue("tls_key_pem_path") if keyfile_present else "x"
348350
mocker.patch("aiosmtpd.main.PROGRAM", "smtpd")
349351
with pytest.raises(SystemExit) as exc:
350352
parseargs((f"--{meth}cert", certfile, f"--{meth}key", keyfile))

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ pytest==8.3.4
55
pytest-asyncio==0.24.0
66
pytest-cov==5.0.0
77
pytest-mock==3.14.0
8+
trustme==1.1.0; python_version == '3.8'
9+
trustme==1.2.1; python_version > '3.8'

tox.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ deps =
2828
pytest-profiling
2929
pytest-sugar
3030
py # needed for pytest-sugar as it doesn't declare dependency on it.
31+
trustme
3132
!nocov: coverage>=7.0.1
3233
!nocov: coverage[toml]
3334
!nocov: coverage-conditional-plugin

0 commit comments

Comments
 (0)