diff --git a/aiosmtpd/tests/certs/__init__.py b/aiosmtpd/tests/certs/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/aiosmtpd/tests/certs/server.crt b/aiosmtpd/tests/certs/server.crt deleted file mode 100644 index 04f19cf4c..000000000 --- a/aiosmtpd/tests/certs/server.crt +++ /dev/null @@ -1,23 +0,0 @@ ------BEGIN CERTIFICATE----- -MIID6DCCAtCgAwIBAgIJAOT/DNOqIMqmMA0GCSqGSIb3DQEBCwUAMIGHMQswCQYD -VQQGEwJVUzELMAkGA1UECAwCQVIxEjAQBgNVBAcMCUdyZWVud29vZDERMA8GA1UE -CgwIQWlvc210cGQxEDAOBgNVBAsMB0RldlRlYW0xDDAKBgNVBAMMA2FlczEkMCIG -CSqGSIb3DQEJARYVd2F5bmVAd2F5bmV3ZXJuZXIuY29tMCAXDTE5MDYwMTEzNTUy -NloYDzIxMTkwNTA4MTM1NTI2WjCBhzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkFS -MRIwEAYDVQQHDAlHcmVlbndvb2QxETAPBgNVBAoMCEFpb3NtdHBkMRAwDgYDVQQL -DAdEZXZUZWFtMQwwCgYDVQQDDANhZXMxJDAiBgkqhkiG9w0BCQEWFXdheW5lQHdh -eW5ld2VybmVyLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMXp -glx/G19+jt/m/AQAy4+85ng3L1/PsXri91SpvPz1YD7Z3/0Yz3SFyuP1mkRCTplO -H3Ok1BVbnycHxBDLBkbEjoJOfzMWxsV1Xp1vE4XEVQaq111pjgxQoFD1qU9vOs4c -0g54PSTtGio0WCOcJq1fWXz9T1QqM5n4MAL2KzFNkqfyyhCesoja4qnPn9n8MCjk -TFslwX/2xJVXrsZyGH0IwiGmJDzkW3/FgXj0brcRZe4BYx/BM7ka1LDNnrdUQ7Wj -GuYbC7mQLWzOUJBF+UQUWHbPadCEPYpAgd4J4seME2XUW/ygi95oY6mJcZGOyz0c -c8D/Dja8Elt5DeTYypsCAwEAAaNTMFEwHQYDVR0OBBYEFDofuwFE+DEx8uQisFlQ -Dfn4LIqdMB8GA1UdIwQYMBaAFDofuwFE+DEx8uQisFlQDfn4LIqdMA8GA1UdEwEB -/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAKdtV5GiiE66bZyqh7aKAOJb6dAe -qAD8LH9u2hqili7fHNzRQLppSiNGGxy/yZoqh7+I3Z64km5jEiSiw2bY7vqbhReC -qU57Dlf5Q4PQVSe36d+2T/g0oGq1pzJkfY27Pse+e9c/m4FkKeEltdqS8Tl2WJFI -Qfux88wBnfrZWCgCvZFQGD6RaqEx9Z2//cUOmU+FcM+JHfbfnQy2QEY13CKQfniP -YBprCg866+ecVC+J+Aeu9ubZgv557SJwJ/0b4rsQ/ETUw95g6AxqdHntDTrWamxQ -iKiGHt3N9iEdnnjXsKYNMsOFXSHHG10PtBQUpNOSUrp5HMb1Kd0oTdNz/kU= ------END CERTIFICATE----- diff --git a/aiosmtpd/tests/certs/server.key b/aiosmtpd/tests/certs/server.key deleted file mode 100644 index f22e0f59e..000000000 --- a/aiosmtpd/tests/certs/server.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDF6YJcfxtffo7f -5vwEAMuPvOZ4Ny9fz7F64vdUqbz89WA+2d/9GM90hcrj9ZpEQk6ZTh9zpNQVW58n -B8QQywZGxI6CTn8zFsbFdV6dbxOFxFUGqtddaY4MUKBQ9alPbzrOHNIOeD0k7Roq -NFgjnCatX1l8/U9UKjOZ+DAC9isxTZKn8soQnrKI2uKpz5/Z/DAo5ExbJcF/9sSV -V67Gchh9CMIhpiQ85Ft/xYF49G63EWXuAWMfwTO5GtSwzZ63VEO1oxrmGwu5kC1s -zlCQRflEFFh2z2nQhD2KQIHeCeLHjBNl1Fv8oIveaGOpiXGRjss9HHPA/w42vBJb -eQ3k2MqbAgMBAAECggEBAMLwy8giJys7tK2Ujn2+7sMpNPYKnW5JXK8HasmI269q -Xp/p6XgafRVwR7WckJRVn6ffzJkTLRfQhIZkXtqPsCH6r8hoW2BOOgH7JvP5mggz -p/CGTNYlB5bXv+Ge6GNm00x8FOfNxaReq1wQ7RQ+VdaFydaUiBQF8YficCAqq8bF -v2q/5XBydcJ4N2fyI4Mm3g03NIiXjeCNbxU5MBTwx2W6xN+PitN/KPGrC/KQoQEt -PibnBkojBFfty8FTjISX/7ZKaQBp3UEktccru0k1V2LpOJ+aQMfPLGSgMdyx8hei -oQbwv97dQfTjMaC3z53ae2sbfOyFmurWcV8Yhlo1uQECgYEA528s2Gsybcg1NQYo -63Z7UYNd4PWOIHVuaf6XX06e0zUncaY0yf5sb0W1VTrXJ6VdgRT1Et9kkgYKZ2I7 -zsLnJQQiB0Xk7K+kha9WRgmtsxqsks9E61S/K+Ao0hNBeegwn8G6tu8KQlFDKVF0 -LN/SU/Q9J2/CVeaMDK8fugs3yoECgYEA2utto2CJ3TALc451yhqYiVHMq2WLW3M0 -+ctrRqTQJ/vgTgCXNDNZPPY+xY83Hbcaw4XxBUI9idNyorqUP3Z05kKEyZUSe4YK -SnRi5+m/YLghNCX/MDAQasmCvV8I3ZXvzqP0TuAQW3XETqs3Cwy5wJfGXYxkVf41 -4fe0ypvo7xsCgYA+C744HzUb3Yr2NjqONeuFxPRMNUjvRsxdOlYWxRsrgJqci3Sn -msAzbLraqLW5+UmCK74wWxe5Vkk/wkRKgFI7yEnfLUvccJJpDMLScBHTbJlLmqnd -dZDzEFuhRmxNZIR0sBmApcFYWjTpRN8ikLbwrxAeHIY7RV3SoLiexhclAQKBgAD4 -2KTIEfSkePiLYmSCV7kMXu9H5SWDznFpgNFwe+ghiy5tfD5kF/pYUZEJAMKmBH1n -w9k1IRbSlIi6cVwSx5QaKYLHoaxgvPz1pVbIR+xDBQq5PHfXTstal7UFjgGF3+m3 -+qa7AfeV/0gmJHltFgoP4naZ3/wtw8l8ExZvOMqPAoGBAMG9pMCCY653KfRQ237I -m/ds9b3VmaLZ5wAwoAEeU/kfwVa27GlP6uDwz6xJmn3XLQ29YwWx8hgqIBm5TfBU -EYQE2RNEcTOY5fnT7QK+xrKyvr3o8fTxCV15EzX/Nwc723QCO8mXN/8ekAhBRHQ7 -6QRoqSt0NeWpSSVRTNwYwFrL ------END PRIVATE KEY----- diff --git a/aiosmtpd/tests/certs/server_alt.crt b/aiosmtpd/tests/certs/server_alt.crt deleted file mode 100644 index ded67db07..000000000 --- a/aiosmtpd/tests/certs/server_alt.crt +++ /dev/null @@ -1,23 +0,0 @@ ------BEGIN CERTIFICATE----- -MIID5jCCAs6gAwIBAgIJAMEFH58o28ymMA0GCSqGSIb3DQEBCwUAMIGGMQswCQYD -VQQGEwJVUzELMAkGA1UECAwCQVIxETAPBgNVBAcMCE1hdW1lbGxlMREwDwYDVQQK -DAhBaW9zbXRwZDEQMA4GA1UECwwHRGV2VGVhbTEMMAoGA1UEAwwDYWVzMSQwIgYJ -KoZIhvcNAQkBFhV3YXluZUB3YXluZXdlcm5lci5jb20wIBcNMjEwMjIzMTczMTAx -WhgPMjEyMTAxMzAxNzMxMDFaMIGGMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQVIx -ETAPBgNVBAcMCE1hdW1lbGxlMREwDwYDVQQKDAhBaW9zbXRwZDEQMA4GA1UECwwH -RGV2VGVhbTEMMAoGA1UEAwwDYWVzMSQwIgYJKoZIhvcNAQkBFhV3YXluZUB3YXlu -ZXdlcm5lci5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDDaZsR -N0vuvXI3nJc2faKMcCMIZ92k60yzNITrzJauuVNqZI31rxU2lEjBXWF+yd1Ag3JC -JDzUYNlyrvSo6ij93g18+YIEfmlYcyawLvKEeV1nA3vC0/9uK4ruhcdRAPhkVi6Z -/GGvjMj05ILFtX6cW3XPHyKJYVFj82muxmXqSjs8kncqlU/ByRb295X80LMwR3bH -Tr5BOez2jCWPOK38OqE/mhL7kt/Xd/c8csCO+H3Ep1lGFb9gCHi0/B06I6lJ490x -PRYfYhcObpfxgtJ6EB17ZAnKySc46pRhzgWPry2G2J/B8q0J+ySOjQ/+ciIQP1Hf -17K5/teUZs3AuvlTAgMBAAGjUzBRMB0GA1UdDgQWBBS1ZwfD6bcyw1WMEP+2ol3R -aiIwFDAfBgNVHSMEGDAWgBS1ZwfD6bcyw1WMEP+2ol3RaiIwFDAPBgNVHRMBAf8E -BTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBXogsxOG74QJWfqEvTfJtL2Zf1+Krg -Da0JhUcFhewjvkfjBS/pMWRQGx35IRZKztbq3PBhgIS7oCnpqlHWwXqID3Ygypee -C3ZNoedWvgI9HAPZCL/Se2Dv+fh2WrARECMPxEgIJ53vCjmAAO/nt7gKHZHW5KgW -DcsdcLE5nfVwUEyS+gJvMEx56hUdYldBN2plXqumMsaMXyTPYCzqaNxHTBcTJogd -tNzUk0M0+I1PRS3/47pZOex8fbbok0nkdGoHT02URv/+7MV7dwzOmuG0qdtDP0Yo -rZjNk76yt3/azUuaSc9LqXN+BOeHCBz69xqwZEeV/jGQ+bl2XS/OgMKw ------END CERTIFICATE----- diff --git a/aiosmtpd/tests/certs/server_alt.key b/aiosmtpd/tests/certs/server_alt.key deleted file mode 100644 index 1b0f762b4..000000000 --- a/aiosmtpd/tests/certs/server_alt.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDDaZsRN0vuvXI3 -nJc2faKMcCMIZ92k60yzNITrzJauuVNqZI31rxU2lEjBXWF+yd1Ag3JCJDzUYNly -rvSo6ij93g18+YIEfmlYcyawLvKEeV1nA3vC0/9uK4ruhcdRAPhkVi6Z/GGvjMj0 -5ILFtX6cW3XPHyKJYVFj82muxmXqSjs8kncqlU/ByRb295X80LMwR3bHTr5BOez2 -jCWPOK38OqE/mhL7kt/Xd/c8csCO+H3Ep1lGFb9gCHi0/B06I6lJ490xPRYfYhcO -bpfxgtJ6EB17ZAnKySc46pRhzgWPry2G2J/B8q0J+ySOjQ/+ciIQP1Hf17K5/teU -Zs3AuvlTAgMBAAECggEBAKukbjWACa1pMBMS82tEjWun782pVfFPUO6zufFYoh5U -4KU6L8tWf16SfxmBiWqRq0rIaqsYI2o0q6tla7eW/myHB/f3PTHvS18KvjfQ9OpR -pC4gzCuEhP9jNcRvM3S+Um/nl0Vgfvlwwu8AbLF2ywBSAbftVCuxlIkjvHdtAwX3 -pmfPCn40jcFHIM8eRu2+gsUccY9Lmed2Ct/C1SdA4kzDLrYfVw4U/aGOtf4p7YEa -k89/SRPR29AMlDwOn38qHm9o08P08E+6XO9bmrrdnKlbUtx8BhEL1FtsN5ErZk+B -dI/kYpVec3cTEC1D/qz+vNFmeb3aBtoJmwHXC5LaxwkCgYEA7Jlf6xWMKY+8VmvH -C2NF+EQ7YrrrMkpJY+XXdzDjGYDN6pKF6MBuyCpKt+5DWc9ftqriPKbj+mvTdsLW -lGnqGRfJyzqy5eM7jSQFRpbWjd3g/hsShduYdNj2G1lJbKUOz+6lHg7xWo0sVHaS -+RpI2Uou5pVwG6Ifc7ID1u0qLf8CgYEA02+nLT3A7FtOd5wCIp3Sk3Nr/KhWE1Aw -iq0OMHFVFFf3JY4xb2+GEsOupwEpMvYVNrb+dIhXP0FdAigRo876F0aDWMDxsKuH -4R76jWSdfycIb5TV1VSE2Ald0RybGioTLWLLr5YDmZKlPEF1ibwCtebPxlXrWgHI -F2Qz+CXOHK0CgYEAgVOo8Yg6sDEoQpjxGRFvmrA5QdNoYnnmudtVtoobaMJWTEVB -OyOMqo8rfnSXjgzjhabMuViEP0sXMNB5mHB4jLTLEfKI/U4DLDgnRhmHN86zgFJu -AosxP5WkmPhqQR/MA+6vhMmoNdX7CEQ0PEOY1GVPU60VtZUd2hDRNnc22iMCgYAK -xbaDxJCuuhwuVeF5+AaBgrDux3jTNRO0DQsbBrsp9S1fWXZFUi5HiHa+hX2e3hDI -n9wo/cVMML1XXclASkxNoUcR34qw0Jx0qMplJ8oqb0erv66BVvQJubhw7f8s/xXJ -Cy6LfJ4kVedbQY6GfPC6ac8OMNRz2oFiR7WqH+r3ZQKBgQDnCC7nEP/KLQqsP7E3 -EmtMtiXdpKBQvukezF2Zv8xNMeouLfMJdf1XMMTL06Qpj1OCAb76SIqFJOtp8+aR -P3w2jZQK1A62eBvpjjlZCauaA4MZaVlvaKsExkM0XDho4Xt6Q0xkqjUme4hLcdr/ -xXvhZaWMLnl57GOQL6GBba3LXQ== ------END PRIVATE KEY----- diff --git a/aiosmtpd/tests/conftest.py b/aiosmtpd/tests/conftest.py index 0c6910317..49d847d21 100644 --- a/aiosmtpd/tests/conftest.py +++ b/aiosmtpd/tests/conftest.py @@ -9,10 +9,10 @@ from contextlib import suppress from functools import wraps from smtplib import SMTP as SMTPClient -from typing import Any, Callable, Generator, NamedTuple, Optional, Type, TypeVar +from typing import Any, Callable, Generator, Iterator, NamedTuple, Optional, Type, TypeVar import pytest -from pkg_resources import resource_filename +import trustme from pytest_mock import MockFixture from aiosmtpd.controller import Controller @@ -32,8 +32,6 @@ "handler_data", "Global", "AUTOSTOP_DELAY", - "SERVER_CRT", - "SERVER_KEY", ] @@ -73,8 +71,6 @@ def set_addr_from(cls, contr: Controller): # If less than 1.0, might cause intermittent error if test system # is too busy/overloaded. AUTOSTOP_DELAY = 1.5 -SERVER_CRT = resource_filename("aiosmtpd.tests.certs", "server.crt") -SERVER_KEY = resource_filename("aiosmtpd.tests.certs", "server.key") # endregion @@ -315,27 +311,50 @@ def client(request: pytest.FixtureRequest) -> Generator[SMTPClient, None, None]: @pytest.fixture -def ssl_context_server() -> ssl.SSLContext: +def tls_certificate_authority() -> trustme.CA: + return trustme.CA() + + +@pytest.fixture +def tls_certificate(tls_certificate_authority: trustme.CA) -> trustme.LeafCert: + return tls_certificate_authority.issue_cert( + "localhost", + "xn--prklad-4va.localhost", # prĂ­klad.localhost + "127.0.0.1", + "::1", + ) + + +@pytest.fixture +def ssl_context_server(tls_certificate: trustme.LeafCert) -> ssl.SSLContext: """ Provides a server-side SSL Context """ - context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) - context.check_hostname = False - context.load_cert_chain(SERVER_CRT, SERVER_KEY) - # - return context + ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + tls_certificate.configure_cert(ssl_ctx) + return ssl_ctx @pytest.fixture -def ssl_context_client() -> ssl.SSLContext: +def ssl_context_client(tls_certificate_authority: trustme.CA) -> ssl.SSLContext: """ Provides a client-side SSL Context """ - context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH) - context.check_hostname = False - context.load_verify_locations(SERVER_CRT) - # - return context + ssl_ctx = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH) + tls_certificate_authority.configure_trust(ssl_ctx) + return ssl_ctx + + +@pytest.fixture +def tls_cert_pem_path(tls_certificate: trustme.LeafCert) -> Iterator[str]: + with tls_certificate.cert_chain_pems[0].tempfile() as cert_pem: + yield cert_pem + + +@pytest.fixture +def tls_key_pem_path(tls_certificate: trustme.LeafCert) -> Iterator[str]: + with tls_certificate.private_key_pem.tempfile() as key_pem: + yield key_pem # Please keep the scope as "module"; setting it as "function" (the default) somehow diff --git a/aiosmtpd/tests/test_main.py b/aiosmtpd/tests/test_main.py index b53cd8566..b64031f2f 100644 --- a/aiosmtpd/tests/test_main.py +++ b/aiosmtpd/tests/test_main.py @@ -21,7 +21,7 @@ from aiosmtpd.main import main, parseargs from aiosmtpd.testing.helpers import catchup_delay from aiosmtpd.testing.statuscodes import SMTP_STATUS_CODES as S -from aiosmtpd.tests.conftest import AUTOSTOP_DELAY, SERVER_CRT, SERVER_KEY +from aiosmtpd.tests.conftest import AUTOSTOP_DELAY try: import pwd @@ -199,24 +199,24 @@ def test_debug_3(self): @pytest.mark.skipif(sys.platform == "darwin", reason="No idea why these are failing") class TestMainByWatcher: - def test_tls(self, temp_event_loop): + def test_tls(self, temp_event_loop, tls_cert_pem_path, tls_key_pem_path): with watcher_process(watch_for_tls) as retq: temp_event_loop.call_later(AUTOSTOP_DELAY, temp_event_loop.stop) - main_n("--tlscert", str(SERVER_CRT), "--tlskey", str(SERVER_KEY)) + main_n("--tlscert", tls_cert_pem_path, "--tlskey", tls_key_pem_path) catchup_delay() has_starttls = retq.get() assert has_starttls is True require_tls = retq.get() assert require_tls is True - def test_tls_noreq(self, temp_event_loop): + def test_tls_noreq(self, temp_event_loop, tls_cert_pem_path, tls_key_pem_path): with watcher_process(watch_for_tls) as retq: temp_event_loop.call_later(AUTOSTOP_DELAY, temp_event_loop.stop) main_n( "--tlscert", - str(SERVER_CRT), + tls_cert_pem_path, "--tlskey", - str(SERVER_KEY), + tls_key_pem_path, "--no-requiretls", ) catchup_delay() @@ -225,10 +225,10 @@ def test_tls_noreq(self, temp_event_loop): require_tls = retq.get() assert require_tls is False - def test_smtps(self, temp_event_loop): + def test_smtps(self, temp_event_loop, tls_cert_pem_path, tls_key_pem_path): with watcher_process(watch_for_smtps) as retq: temp_event_loop.call_later(AUTOSTOP_DELAY, temp_event_loop.stop) - main_n("--smtpscert", str(SERVER_CRT), "--smtpskey", str(SERVER_KEY)) + main_n("--smtpscert", tls_cert_pem_path, "--smtpskey", tls_key_pem_path) catchup_delay() has_smtps = retq.get() assert has_smtps is True @@ -335,16 +335,18 @@ def test_norequiretls(self, capsys, mocker): assert args.requiretls is False @pytest.mark.parametrize( - ("certfile", "keyfile", "expect"), + ("certfile_present", "keyfile_present", "expect"), [ - ("x", "x", "Cert file x not found"), - (SERVER_CRT, "x", "Key file x not found"), - ("x", SERVER_KEY, "Cert file x not found"), + (False, False, "Cert file x not found"), + (True, False, "Key file x not found"), + (False, True, "Cert file x not found"), ], ids=["x-x", "cert-x", "x-key"], ) @pytest.mark.parametrize("meth", ["smtps", "tls"]) - def test_ssl_files_err(self, capsys, mocker, meth, certfile, keyfile, expect): + def test_ssl_files_err(self, capsys, mocker, meth, certfile_present, keyfile_present, expect, request): + certfile = request.getfixturevalue("tls_cert_pem_path") if certfile_present else "x" + keyfile = request.getfixturevalue("tls_key_pem_path") if keyfile_present else "x" mocker.patch("aiosmtpd.main.PROGRAM", "smtpd") with pytest.raises(SystemExit) as exc: parseargs((f"--{meth}cert", certfile, f"--{meth}key", keyfile)) diff --git a/requirements.txt b/requirements.txt index bbc5921fa..7ebd9342d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,10 @@ +# Python 3.8 is EOL, so latest versions of some packages do not support it, use newest compatible versions atpublic==5.0 attrs==24.2.0 coverage==7.6.1 +cryptography==41.0.7; python_version == '3.8' and platform_python_implementation == "PyPy" pytest==8.3.4 pytest-cov==5.0.0 pytest-mock==3.14.0 +trustme==1.1.0; python_version == '3.8' +trustme==1.2.1; python_version > '3.8' diff --git a/tox.ini b/tox.ini index 8282343de..91144d9af 100644 --- a/tox.ini +++ b/tox.ini @@ -28,6 +28,7 @@ deps = pytest-profiling pytest-sugar py # needed for pytest-sugar as it doesn't declare dependency on it. + trustme !nocov: coverage>=7.0.1 !nocov: coverage[toml] !nocov: coverage-conditional-plugin