From c2f446ec7e2aece7c5e2d07fb183ed3d694d0e40 2020-09-23 19:48:30 From: Branko Majic Date: 2020-09-23 19:48:30 Subject: [PATCH] MAR-158: Update default TLS ciphers configuration in the mail_server role: - Updated the default value for parameter mail_server_tls_ciphers. - Updated tests, making them explicitly test for enabled and disabled ciphers. - Refactored tests for TLS to use nmap ssl-enum-ciphers script for listing available TLS versions and ciphers. - Install nmap as part of preparation step. - Updated role reference documentation. --- diff --git a/docs/rolereference.rst b/docs/rolereference.rst index 19680e306d9cb0683b0f2c845762e7a00a5b1bcc..c614e686157d7a6b62785ed9e140008c57a82d85 100644 --- a/docs/rolereference.rst +++ b/docs/rolereference.rst @@ -1118,7 +1118,7 @@ Parameters ``smtpd_tls_mandatory_protocols`` and Dovecot configuration option ``ssl_protocols``. -**mail_server_tls_ciphers** (string, optional ``DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:!aNULL:!MD5:!EXPORT``) +**mail_server_tls_ciphers** (string, optional ``DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305:!aNULL:!MD5:!EXPORT``) TLS ciphers to enable on the mail server (for IMAP and SMTP submission). This should be an OpenSSL-compatible cipher specification. Value should be compatible with Postfix configuration option ``tls_high_cipherlist`` and diff --git a/roles/mail_server/defaults/main.yml b/roles/mail_server/defaults/main.yml index 7c198d9772e9a5944bef9987fd449670b8580633..7eb533e1130ab5cf3c08d87b63c7ac41fa255a64 100644 --- a/roles/mail_server/defaults/main.yml +++ b/roles/mail_server/defaults/main.yml @@ -10,7 +10,12 @@ local_mail_aliases: {} imap_max_user_connections_per_ip: 10 mail_server_tls_protocols: - "TLSv1.2" -mail_server_tls_ciphers: "DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-GCM-SHA384:\ -DHE-RSA-AES256-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-GCM-SHA384:\ -ECDHE-RSA-AES256-SHA384:!aNULL:!MD5:!EXPORT" +mail_server_tls_ciphers: "\ +DHE-RSA-AES128-GCM-SHA256:\ +DHE-RSA-AES256-GCM-SHA384:\ +DHE-RSA-CHACHA20-POLY1305:\ +ECDHE-RSA-AES128-GCM-SHA256:\ +ECDHE-RSA-AES256-GCM-SHA384:\ +ECDHE-RSA-CHACHA20-POLY1305:\ +!aNULL:!MD5:!EXPORT" mail_message_size_limit: 10240000 diff --git a/roles/mail_server/molecule/default/prepare.yml b/roles/mail_server/molecule/default/prepare.yml index 2eadc76d339b918800e4a647315086a3c8150979..c84fb778856ad4b0ae36f012761da8de1d73571d 100644 --- a/roles/mail_server/molecule/default/prepare.yml +++ b/roles/mail_server/molecule/default/prepare.yml @@ -61,7 +61,9 @@ - name: Install tools for testing apt: - name: gnutls-bin + name: + - gnutls-bin + - nmap state: present - hosts: stretch diff --git a/roles/mail_server/molecule/default/tests/test_mandatory.py b/roles/mail_server/molecule/default/tests/test_mandatory.py index 0937e7de8afb93f56aa52f7b0f43e096116b5b43..5f93602e03456878893608d414cf053f557aeb78 100644 --- a/roles/mail_server/molecule/default/tests/test_mandatory.py +++ b/roles/mail_server/molecule/default/tests/test_mandatory.py @@ -1,5 +1,9 @@ import os +import defusedxml.ElementTree as ElementTree + +import pytest + import testinfra.utils.ansible_runner @@ -106,12 +110,11 @@ def test_mail_owner(host): assert user.groups == ["vmail"] -def test_imap_tls_configuration(host): +def test_imap_tls_connectivity(host): """ - Tests TLS configuration for IMAP in Dovecot. + Tests connectivity over STARTTLS/TLS towards IMAP server. """ - # Test plain connectivity first. starttls = host.run('echo "a0001 LOGOUT" | openssl s_client -quiet -starttls imap -connect parameters-mandatory:143') assert starttls.rc == 0 assert '* BYE Logging out' in starttls.stdout @@ -120,34 +123,50 @@ def test_imap_tls_configuration(host): assert tls.rc == 0 assert '* BYE Logging out' in starttls.stdout - # Test TLS protocol versions. - starttls_old_tls_versions_disabled = host.run("echo 'a0001 LOGOUT' | openssl s_client -quiet -starttls imap -no_tls1_2 -connect parameters-mandatory:143") - assert starttls_old_tls_versions_disabled.rc != 0 - assert "write:errno=104" in starttls_old_tls_versions_disabled.stderr or 'SSL alert number 70' in starttls_old_tls_versions_disabled.stderr - tls_old_tls_versions_disabled = host.run("echo 'a0001 LOGOUT' | openssl s_client -quiet -no_tls1_2 -connect parameters-mandatory:993") - assert tls_old_tls_versions_disabled.rc != 0 - assert "write:errno=104" in tls_old_tls_versions_disabled.stderr or 'SSL alert number 70' in tls_old_tls_versions_disabled.stderr +@pytest.mark.parametrize("port", [ + 143, + 993, + 587, +]) +def test_imap_and_smtp_submission_tls_version_and_ciphers(host, port): + """ + Tests if the correct TLS version and ciphers have been enabled for + IMAP and SMTP submission. + """ + + expected_tls_versions = ["TLSv1.2"] + + expected_tls_ciphers = [ + "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + ] - # Test at least one strong TLS cipher. - starttls_cipher = host.run("echo 'a0001 LOGOUT' | openssl s_client -starttls imap -cipher ECDHE-RSA-AES128-SHA256 -connect parameters-mandatory:143") - assert starttls_cipher.rc == 0 - assert "ECDHE-RSA-AES128-SHA256" in starttls_cipher.stdout + # Run the nmap scanner against the server, and fetch the results. + nmap = host.run("nmap -sV --script ssl-enum-ciphers -p %s localhost -oX /tmp/report.xml", str(port)) + assert nmap.rc == 0 + report_content = host.file('/tmp/report.xml').content_string - tls_cipher = host.run("echo 'a0001 LOGOUT' | openssl s_client -cipher ECDHE-RSA-AES128-SHA256 -connect parameters-mandatory:993") - assert tls_cipher.rc == 0 - assert "ECDHE-RSA-AES128-SHA256" in tls_cipher.stdout + report_root = ElementTree.fromstring(report_content) - # Test weaker TLS cipher are disabled. - starttls_cipher = host.run("echo 'a0001 LOGOUT' | openssl s_client -starttls imap -cipher ECDHE-RSA-AES128-SHA -connect parameters-mandatory:143") - assert starttls_cipher.rc != 0 - assert "CONNECTED" in starttls_cipher.stdout - assert "ECDHE-RSA-AES128-SHA" not in starttls_cipher.stdout + tls_versions = [] + tls_ciphers = set() - tls_cipher = host.run("echo 'a0001 LOGOUT' | openssl s_client -cipher ECDHE-RSA-AES128-SHA -connect parameters-mandatory:993") - assert tls_cipher.rc != 0 - assert "CONNECTED" in tls_cipher.stdout - assert "ECDHE-RSA-AES128-SHA" not in tls_cipher.stdout + for child in report_root.findall("./host/ports/port/script/table"): + tls_versions.append(child.attrib['key']) + + for child in report_root.findall(".//table[@key='ciphers']/table/elem[@key='name']"): + tls_ciphers.add(child.text) + + tls_versions.sort() + tls_ciphers = sorted(list(tls_ciphers)) + + assert tls_versions == expected_tls_versions + assert tls_ciphers == expected_tls_ciphers def test_dovecot_postmaster(host): @@ -175,51 +194,106 @@ def test_imap_max_user_connections_per_ip(host): assert " mail_max_userip_connections = 10" in config.stdout -def test_postfix_tls_configuration(host): +def test_smtp_tls_connectivity(host): """ - Tests TLS configuration for SMTP in Postfix. + Tests connectivity over default/submission port towards SMTP + server. """ - # Test TLS protocol versions for default port (all should be enabled). - starttls = host.run("echo 'QUIT' | openssl s_client -quiet -starttls smtp -no_tls1 -no_tls1_1 -connect parameters-mandatory:25") + starttls = host.run('echo "QUIT" | openssl s_client -quiet -starttls smtp -connect parameters-mandatory:25') assert starttls.rc == 0 assert '221 2.0.0 Bye' in starttls.stdout - starttls = host.run("echo 'QUIT' | openssl s_client -quiet -starttls smtp -no_tls1_2 -connect parameters-mandatory:25") - assert starttls.rc == 0 - assert '221 2.0.0 Bye' in starttls.stdout - - starttls = host.run("echo 'QUIT' | openssl s_client -quiet -starttls smtp -no_tls1_2 -no_tls1_1 -connect parameters-mandatory:25") - assert starttls.rc == 0 - assert '221 2.0.0 Bye' in starttls.stdout - - # Test TLS protocol versions for submission port (only TLS 1.2 should be enabled). - starttls = host.run("echo 'QUIT' | openssl s_client -quiet -starttls smtp -connect parameters-mandatory:587") - assert starttls.rc == 0 + tls = host.run('echo "QUIT" | openssl s_client -quiet -starttls smtp -connect parameters-mandatory:587') + assert tls.rc == 0 assert '221 2.0.0 Bye' in starttls.stdout - starttls = host.run("echo 'QUIT' | openssl s_client -quiet -starttls smtp -no_tls1_2 -connect parameters-mandatory:587") - assert starttls.rc != 0 - assert 'write:errno=104' in starttls.stderr or 'SSL alert number 70' in starttls.stderr - - # Test ciphers for default port (less restrictive). - starttls_cipher = host.run("echo 'QUIT' | openssl s_client -starttls smtp -cipher ECDHE-RSA-AES128-SHA256 -connect parameters-mandatory:25") - assert starttls_cipher.rc == 0 - assert "ECDHE-RSA-AES128-SHA256" in starttls_cipher.stdout - - starttls_cipher = host.run("echo 'QUIT' | openssl s_client -starttls smtp -cipher ECDHE-RSA-AES128-SHA -connect parameters-mandatory:25") - assert starttls_cipher.rc == 0 - assert "ECDHE-RSA-AES128-SHA" in starttls_cipher.stdout - # Test ciphers for submission port (weak ciphers not available). - starttls_cipher = host.run("echo 'QUIT' | openssl s_client -starttls smtp -cipher ECDHE-RSA-AES128-SHA256 -connect parameters-mandatory:587") - assert starttls_cipher.rc == 0 - assert "ECDHE-RSA-AES128-SHA256" in starttls_cipher.stdout +def test_smtp_default_port_tls_version_and_ciphers(host): + """ + Tests TLS configuration for SMTP default port (needs to be less + restrictive for interoperability purposes). + """ - starttls_cipher = host.run("echo 'QUIT' | openssl s_client -starttls smtp -cipher ECDHE-RSA-AES128-SHA -connect parameters-mandatory:587") - assert starttls_cipher.rc != 0 - assert "CONNECTED" in starttls_cipher.stdout - assert "ECDHE-RSA-AES128-SHA" not in starttls_cipher.stdout + expected_tls_versions = ["TLSv1.0", "TLSv1.1", "TLSv1.2"] + + expected_tls_ciphers = [ + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", + "TLS_DHE_RSA_WITH_AES_128_CCM", + "TLS_DHE_RSA_WITH_AES_128_CCM_8", + "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", + "TLS_DHE_RSA_WITH_AES_256_CCM", + "TLS_DHE_RSA_WITH_AES_256_CCM_8", + "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", + "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", + "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", + "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256", + "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_DHE_RSA_WITH_SEED_CBC_SHA", + "TLS_DH_anon_WITH_AES_128_CBC_SHA", + "TLS_DH_anon_WITH_AES_128_CBC_SHA256", + "TLS_DH_anon_WITH_AES_128_GCM_SHA256", + "TLS_DH_anon_WITH_AES_256_CBC_SHA", + "TLS_DH_anon_WITH_AES_256_CBC_SHA256", + "TLS_DH_anon_WITH_AES_256_GCM_SHA384", + "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", + "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256", + "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", + "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256", + "TLS_DH_anon_WITH_SEED_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", + "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDH_anon_WITH_AES_128_CBC_SHA", + "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", + "TLS_RSA_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_AES_128_CBC_SHA256", + "TLS_RSA_WITH_AES_128_CCM", + "TLS_RSA_WITH_AES_128_CCM_8", + "TLS_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_CBC_SHA", + "TLS_RSA_WITH_AES_256_CBC_SHA256", + "TLS_RSA_WITH_AES_256_CCM", + "TLS_RSA_WITH_AES_256_CCM_8", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", + "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256", + "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", + "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256", + "TLS_RSA_WITH_SEED_CBC_SHA", + ] + + # Run the nmap scanner against the server, and fetch the results. + nmap = host.run("nmap -sV --script ssl-enum-ciphers -p 25 localhost -oX /tmp/report.xml") + assert nmap.rc == 0 + report_content = host.file('/tmp/report.xml').content_string + + report_root = ElementTree.fromstring(report_content) + + tls_versions = [] + tls_ciphers = set() + + for child in report_root.findall("./host/ports/port/script/table"): + tls_versions.append(child.attrib['key']) + + for child in report_root.findall(".//table[@key='ciphers']/table/elem[@key='name']"): + tls_ciphers.add(child.text) + + tls_versions.sort() + tls_ciphers = sorted(list(tls_ciphers)) + + assert tls_versions == expected_tls_versions + assert tls_ciphers == expected_tls_ciphers def test_sieve_tls_configuration(host): diff --git a/roles/mail_server/molecule/default/tests/test_optional.py b/roles/mail_server/molecule/default/tests/test_optional.py index ba5f621400cdf1f0246d4e8c03498b0146b2b0d3..e562d7fcee637adc541956c4c0a4a160c54f25a0 100644 --- a/roles/mail_server/molecule/default/tests/test_optional.py +++ b/roles/mail_server/molecule/default/tests/test_optional.py @@ -3,6 +3,10 @@ import re import time import uuid +import defusedxml.ElementTree as ElementTree + +import pytest + import testinfra.utils.ansible_runner @@ -129,12 +133,11 @@ def test_mail_owner(host): assert user.groups == ["virtmail"] -def test_imap_tls_configuration(host): +def test_imap_tls_connectivity(host): """ - Tests TLS configuration for IMAP in Dovecot. + Tests connectivity over STARTTLS/TLS towards IMAP server. """ - # Test plain connectivity first. starttls = host.run('echo "a0001 LOGOUT" | openssl s_client -quiet -starttls imap -connect parameters-optional:143') assert starttls.rc == 0 assert '* BYE Logging out' in starttls.stdout @@ -143,40 +146,53 @@ def test_imap_tls_configuration(host): assert tls.rc == 0 assert '* BYE Logging out' in starttls.stdout - # Test TLS protocol versions. - starttls = host.run('echo "a0001 LOGOUT" | openssl s_client -quiet -starttls imap -no_tls1_2 -connect parameters-optional:143') - assert starttls.rc == 0 - assert '* BYE Logging out' in starttls.stdout - tls = host.run('echo "a0001 LOGOUT" | openssl s_client -quiet -no_tls1_2 -connect parameters-optional:993') - assert tls.rc == 0 - assert '* BYE Logging out' in starttls.stdout +@pytest.mark.parametrize("port", [ + 143, + 993, + 587, +]) +def test_imap_and_smtp_submission_tls_version_and_ciphers(host, port): + """ + Tests if the correct TLS version and ciphers have been enabled for + IMAP and SMTP submission. + """ + + expected_tls_versions = ["TLSv1.1", "TLSv1.2"] + + expected_tls_ciphers = [ + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", + "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", + "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + ] - starttls = host.run("echo 'a0001 LOGOUT' | openssl s_client -quiet -starttls imap -no_tls1_1 -no_tls1_2 -connect parameters-optional:143") - assert starttls.rc != 0 - assert 'SSL alert number 70' in starttls.stderr + # Run the nmap scanner against the server, and fetch the results. + nmap = host.run("nmap -sV --script ssl-enum-ciphers -p %s localhost -oX /tmp/report.xml", str(port)) + assert nmap.rc == 0 + report_content = host.file('/tmp/report.xml').content_string - tls = host.run("echo 'a0001 LOGOUT' | openssl s_client -quiet -no_tls1_1 -no_tls1_2 -connect parameters-optional:993") - assert tls.rc != 0 - assert 'SSL alert number 70' in tls.stderr + report_root = ElementTree.fromstring(report_content) - # Test at least one strong TLS cipher. - starttls_cipher = host.run("echo 'a0001 LOGOUT' | openssl s_client -starttls imap -cipher ECDHE-RSA-AES128-SHA256 -connect parameters-optional:143") - assert starttls_cipher.rc == 0 - assert "ECDHE-RSA-AES128-SHA256" in starttls_cipher.stdout + tls_versions = [] + tls_ciphers = set() - tls_cipher = host.run("echo 'a0001 LOGOUT' | openssl s_client -cipher ECDHE-RSA-AES128-SHA256 -connect parameters-optional:993") - assert tls_cipher.rc == 0 - assert "ECDHE-RSA-AES128-SHA256" in tls_cipher.stdout + for child in report_root.findall("./host/ports/port/script/table"): + tls_versions.append(child.attrib['key']) - # Test weaker TLS cipher that was explicitly configured - starttls_cipher = host.run("echo 'a0001 LOGOUT' | openssl s_client -starttls imap -cipher ECDHE-RSA-AES128-SHA -connect parameters-optional:143") - assert starttls_cipher.rc == 0 - assert "ECDHE-RSA-AES128-SHA" in starttls_cipher.stdout + for child in report_root.findall(".//table[@key='ciphers']/table/elem[@key='name']"): + tls_ciphers.add(child.text) - tls_cipher = host.run("echo 'a0001 LOGOUT' | openssl s_client -cipher ECDHE-RSA-AES128-SHA -connect parameters-optional:993") - assert tls_cipher.rc == 0 - assert "ECDHE-RSA-AES128-SHA" in tls_cipher.stdout + tls_versions.sort() + tls_ciphers = sorted(list(tls_ciphers)) + + assert tls_versions == expected_tls_versions + assert tls_ciphers == expected_tls_ciphers def test_dovecot_postmaster(host): @@ -205,54 +221,106 @@ def test_imap_max_user_connections_per_ip(host): assert " mail_max_userip_connections = 2" in config.stdout -def test_postfix_tls_configuration(host): +def test_smtp_tls_connectivity(host): """ - Tests TLS configuration for SMTP in Postfix. + Tests connectivity over default/submission port towards SMTP + server. """ - # Test TLS protocol versions for default port (all should be enabled). - starttls = host.run("echo 'QUIT' | openssl s_client -quiet -starttls smtp -no_tls1 -no_tls1_1 -connect parameters-optional:25") - assert starttls.rc == 0 - assert '221 2.0.0 Bye' in starttls.stdout - - starttls = host.run("echo 'QUIT' | openssl s_client -quiet -starttls smtp -no_tls1_2 -connect parameters-optional:25") - assert starttls.rc == 0 - assert '221 2.0.0 Bye' in starttls.stdout - - starttls = host.run("echo 'QUIT' | openssl s_client -quiet -starttls smtp -no_tls1_2 -no_tls1_1 -connect parameters-optional:25") - assert starttls.rc == 0 - assert '221 2.0.0 Bye' in starttls.stdout - - # Test TLS protocol versions for submission port (only TLS 1.1 and TLS 1.2 should be enabled). - starttls = host.run("echo 'QUIT' | openssl s_client -quiet -starttls smtp -connect parameters-optional:587") + starttls = host.run('echo "QUIT" | openssl s_client -quiet -starttls smtp -connect parameters-optional:25') assert starttls.rc == 0 assert '221 2.0.0 Bye' in starttls.stdout - starttls = host.run("echo 'QUIT' | openssl s_client -quiet -starttls smtp -no_tls1_2 -connect parameters-optional:587") - assert starttls.rc == 0 + tls = host.run('echo "QUIT" | openssl s_client -quiet -starttls smtp -connect parameters-optional:587') + assert tls.rc == 0 assert '221 2.0.0 Bye' in starttls.stdout - starttls = host.run("echo 'QUIT' | openssl s_client -quiet -starttls smtp -no_tls1_1 -no_tls1_2 -connect parameters-optional:587") - assert starttls.rc != 0 - assert 'SSL alert number 70' in starttls.stderr - - # Test ciphers for default port (less restrictive). - starttls_cipher = host.run("echo 'QUIT' | openssl s_client -starttls smtp -cipher ECDHE-RSA-AES128-SHA256 -connect parameters-optional:25") - assert starttls_cipher.rc == 0 - assert "ECDHE-RSA-AES128-SHA256" in starttls_cipher.stdout - starttls_cipher = host.run("echo 'QUIT' | openssl s_client -starttls smtp -cipher ECDHE-RSA-AES128-SHA -connect parameters-optional:25") - assert starttls_cipher.rc == 0 - assert "ECDHE-RSA-AES128-SHA" in starttls_cipher.stdout - - # Test ciphers for submission port (at least one weak cipher was configured). - starttls_cipher = host.run("echo 'QUIT' | openssl s_client -starttls smtp -cipher ECDHE-RSA-AES128-SHA256 -connect parameters-optional:587") - assert starttls_cipher.rc == 0 - assert "ECDHE-RSA-AES128-SHA256" in starttls_cipher.stdout +def test_smtp_default_port_tls_version_and_ciphers(host): + """ + Tests TLS configuration for SMTP default port (needs to be less + restrictive for interoperability purposes). + """ - starttls_cipher = host.run("echo 'QUIT' | openssl s_client -starttls smtp -cipher ECDHE-RSA-AES128-SHA -connect parameters-optional:587") - assert starttls_cipher.rc == 0 - assert "ECDHE-RSA-AES128-SHA" in starttls_cipher.stdout + expected_tls_versions = ["TLSv1.0", "TLSv1.1", "TLSv1.2"] + + expected_tls_ciphers = [ + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", + "TLS_DHE_RSA_WITH_AES_128_CCM", + "TLS_DHE_RSA_WITH_AES_128_CCM_8", + "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", + "TLS_DHE_RSA_WITH_AES_256_CCM", + "TLS_DHE_RSA_WITH_AES_256_CCM_8", + "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", + "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", + "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", + "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256", + "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_DHE_RSA_WITH_SEED_CBC_SHA", + "TLS_DH_anon_WITH_AES_128_CBC_SHA", + "TLS_DH_anon_WITH_AES_128_CBC_SHA256", + "TLS_DH_anon_WITH_AES_128_GCM_SHA256", + "TLS_DH_anon_WITH_AES_256_CBC_SHA", + "TLS_DH_anon_WITH_AES_256_CBC_SHA256", + "TLS_DH_anon_WITH_AES_256_GCM_SHA384", + "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", + "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256", + "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", + "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256", + "TLS_DH_anon_WITH_SEED_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", + "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDH_anon_WITH_AES_128_CBC_SHA", + "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", + "TLS_RSA_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_AES_128_CBC_SHA256", + "TLS_RSA_WITH_AES_128_CCM", + "TLS_RSA_WITH_AES_128_CCM_8", + "TLS_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_CBC_SHA", + "TLS_RSA_WITH_AES_256_CBC_SHA256", + "TLS_RSA_WITH_AES_256_CCM", + "TLS_RSA_WITH_AES_256_CCM_8", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", + "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256", + "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", + "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256", + "TLS_RSA_WITH_SEED_CBC_SHA", + ] + + # Run the nmap scanner against the server, and fetch the results. + nmap = host.run("nmap -sV --script ssl-enum-ciphers -p 25 localhost -oX /tmp/report.xml") + assert nmap.rc == 0 + report_content = host.file('/tmp/report.xml').content_string + + report_root = ElementTree.fromstring(report_content) + + tls_versions = [] + tls_ciphers = set() + + for child in report_root.findall("./host/ports/port/script/table"): + tls_versions.append(child.attrib['key']) + + for child in report_root.findall(".//table[@key='ciphers']/table/elem[@key='name']"): + tls_ciphers.add(child.text) + + tls_versions.sort() + tls_ciphers = sorted(list(tls_ciphers)) + + assert tls_versions == expected_tls_versions + assert tls_ciphers == expected_tls_ciphers def test_sieve_tls_configuration(host):