[Python-checkins] [3.6] bpo-35925: Skip SSL tests that fail due to weak external certs or old TLS (GH-13124) (GH-13252)

Ned Deily webhook-mailer at python.org
Tue May 28 22:08:32 EDT 2019


https://github.com/python/cpython/commit/8ab624b17ba656e9af5a79be6af0cf2911a111ba
commit: 8ab624b17ba656e9af5a79be6af0cf2911a111ba
branch: 3.6
author: Gregory P. Smith <greg at krypto.org>
committer: Ned Deily <nad at python.org>
date: 2019-05-28T22:08:27-04:00
summary:

[3.6] bpo-35925: Skip SSL tests that fail due to weak external certs or old TLS (GH-13124) (GH-13252)

* [3.6] bpo-35925: Skip SSL tests that fail due to weak external certs. (GH-13124)

Modern Linux distros such as Debian Buster have default OpenSSL system
configurations that reject connections to servers with weak certificates
by default.  This causes our test suite run with external networking
resources enabled to skip these tests when they encounter such a failure.

Fixing the network servers is a separate issue..
(cherry picked from commit 2cc0223f43a1ffd59c887a73e2b0ce5202f3be90)

Co-authored-by: Gregory P. Smith <greg at krypto.org>

* Also skip ssl tests that fail when the system rejects TLSv1.

* Remove the test_httplib change; server was updated.

self-signed.pythontest.net was updated so the test_httplib change is
no longer necessary.

files:
A Misc/NEWS.d/next/Tests/2019-05-06-18-29-54.bpo-35925.gwQPuC.rst
M Lib/test/test_nntplib.py
M Lib/test/test_ssl.py

diff --git a/Lib/test/test_nntplib.py b/Lib/test/test_nntplib.py
index d7642bc66ab3..1d1750a5be22 100644
--- a/Lib/test/test_nntplib.py
+++ b/Lib/test/test_nntplib.py
@@ -6,6 +6,8 @@
 import functools
 import contextlib
 import os.path
+import re
+
 from test import support
 from nntplib import NNTP, GroupInfo
 import nntplib
@@ -22,6 +24,13 @@
 TIMEOUT = 30
 certfile = os.path.join(os.path.dirname(__file__), 'keycert3.pem')
 
+if ssl is not None:
+    SSLError = ssl.SSLError
+else:
+    class SSLError(Exception):
+        """Non-existent exception class when we lack SSL support."""
+        reason = "This will never be raised."
+
 # TODO:
 # - test the `file` arg to more commands
 # - test error conditions
@@ -262,14 +271,21 @@ def is_connected():
                 return False
             return True
 
-        with self.NNTP_CLASS(self.NNTP_HOST, timeout=TIMEOUT, usenetrc=False) as server:
-            self.assertTrue(is_connected())
-            self.assertTrue(server.help())
-        self.assertFalse(is_connected())
-
-        with self.NNTP_CLASS(self.NNTP_HOST, timeout=TIMEOUT, usenetrc=False) as server:
-            server.quit()
-        self.assertFalse(is_connected())
+        try:
+            with self.NNTP_CLASS(self.NNTP_HOST, timeout=TIMEOUT, usenetrc=False) as server:
+                self.assertTrue(is_connected())
+                self.assertTrue(server.help())
+            self.assertFalse(is_connected())
+
+            with self.NNTP_CLASS(self.NNTP_HOST, timeout=TIMEOUT, usenetrc=False) as server:
+                server.quit()
+            self.assertFalse(is_connected())
+        except SSLError as ssl_err:
+            # matches "[SSL: DH_KEY_TOO_SMALL] dh key too small"
+            if re.search(r'(?i)KEY.TOO.SMALL', ssl_err.reason):
+                raise unittest.SkipTest(f"Got {ssl_err} connecting "
+                                        f"to {self.NNTP_HOST!r}")
+            raise
 
 
 NetworkedNNTPTestsMixin.wrap_methods()
@@ -290,6 +306,12 @@ def setUpClass(cls):
             try:
                 cls.server = cls.NNTP_CLASS(cls.NNTP_HOST, timeout=TIMEOUT,
                                             usenetrc=False)
+            except SSLError as ssl_err:
+                # matches "[SSL: DH_KEY_TOO_SMALL] dh key too small"
+                if re.search(r'(?i)KEY.TOO.SMALL', ssl_err.reason):
+                    raise unittest.SkipTest(f"{cls} got {ssl_err} connecting "
+                                            f"to {cls.NNTP_HOST!r}")
+                raise
             except EOFError:
                 raise unittest.SkipTest(f"{cls} got EOF error on connecting "
                                         f"to {cls.NNTP_HOST!r}")
diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py
index 2cabfe5d0b08..74adebc0fb99 100644
--- a/Lib/test/test_ssl.py
+++ b/Lib/test/test_ssl.py
@@ -17,6 +17,7 @@
 import asyncore
 import weakref
 import platform
+import re
 import functools
 try:
     import ctypes
@@ -145,6 +146,38 @@ def f(*args, **kwargs):
     else:
         return func
 
+def skip_if_openssl_cnf_minprotocol_gt_tls1(func):
+    """Skip a test if the OpenSSL config MinProtocol is > TLSv1.
+
+    OS distros with an /etc/ssl/openssl.cnf and MinProtocol set often do so to
+    require TLSv1.2 or higher (Debian Buster).  Some of our tests for older
+    protocol versions will fail under such a config.
+
+    Alternative workaround: Run this test in a process with
+    OPENSSL_CONF=/dev/null in the environment.
+    """
+    @functools.wraps(func)
+    def f(*args, **kwargs):
+        openssl_cnf = os.environ.get("OPENSSL_CONF", "/etc/ssl/openssl.cnf")
+        try:
+            with open(openssl_cnf, "r") as config:
+                for line in config:
+                    match = re.match(r"MinProtocol\s*=\s*(TLSv\d+\S*)", line)
+                    if match:
+                        tls_ver = match.group(1)
+                        if tls_ver > "TLSv1":
+                            raise unittest.SkipTest(
+                                "%s has MinProtocol = %s which is > TLSv1." %
+                                (openssl_cnf, tls_ver))
+        except (EnvironmentError, UnicodeDecodeError) as err:
+            # no config file found, etc.
+            if support.verbose:
+                sys.stdout.write("\n Could not scan %s for MinProtocol: %s\n"
+                                 % (openssl_cnf, err))
+        return func(*args, **kwargs)
+    return f
+
+
 needs_sni = unittest.skipUnless(ssl.HAS_SNI, "SNI support needed for this test")
 
 
@@ -2629,6 +2662,7 @@ def test_protocol_sslv2(self):
                                client_options=ssl.OP_NO_TLSv1)
 
         @skip_if_broken_ubuntu_ssl
+        @skip_if_openssl_cnf_minprotocol_gt_tls1
         def test_protocol_sslv23(self):
             """Connecting to an SSLv23 server with various client options"""
             if support.verbose:
@@ -2706,6 +2740,7 @@ def test_protocol_tlsv1(self):
         @skip_if_broken_ubuntu_ssl
         @unittest.skipUnless(hasattr(ssl, "PROTOCOL_TLSv1_1"),
                              "TLS version 1.1 not supported.")
+        @skip_if_openssl_cnf_minprotocol_gt_tls1
         def test_protocol_tlsv1_1(self):
             """Connecting to a TLSv1.1 server with various client options.
                Testing against older TLS versions."""
diff --git a/Misc/NEWS.d/next/Tests/2019-05-06-18-29-54.bpo-35925.gwQPuC.rst b/Misc/NEWS.d/next/Tests/2019-05-06-18-29-54.bpo-35925.gwQPuC.rst
new file mode 100644
index 000000000000..428326cdfe5b
--- /dev/null
+++ b/Misc/NEWS.d/next/Tests/2019-05-06-18-29-54.bpo-35925.gwQPuC.rst
@@ -0,0 +1 @@
+Skip specific nntplib and ssl networking tests when they would otherwise fail due to a modern OS or distro with a default OpenSSL policy of rejecting connections to servers with weak certificates or disabling TLS below TLSv1.2.



More information about the Python-checkins mailing list