fbo / rpms / python-gear

Forked from rpms/python-gear 4 years ago
Clone
Blob Blame History Raw
From 66ba8442dcaba24de0322793876bd241858b49a3 Mon Sep 17 00:00:00 2001
From: Guillaume Chauvel <guillaume.chauvel@gmail.com>
Date: Wed, 15 Jul 2020 19:34:49 +0200
Subject: [PATCH 8/9] Create SSL context using PROTOCOL_TLS, fallback to
 highest supported version

Zuul test: tests.unit.test_scheduler.TestSchedulerSSL.test_jobs_executed
fails on ubuntu focal with the following exception:

Traceback (most recent call last):
  File "/home/gchauvel/zuul/zuul/.tox/py38/lib/python3.8/site-packages/gear/__init__.py", line 2835, in _doConnectLoop
    self.connectLoop()
  File "/home/gchauvel/zuul/zuul/.tox/py38/lib/python3.8/site-packages/gear/__init__.py", line 2865, in connectLoop
    c = context.wrap_socket(c, server_side=True)
  File "/usr/lib/python3.8/ssl.py", line 500, in wrap_socket
    return self.sslsocket_class._create(
  File "/usr/lib/python3.8/ssl.py", line 1040, in _create
    self.do_handshake()
  File "/usr/lib/python3.8/ssl.py", line 1309, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLError: [SSL] internal error (_ssl.c:1108)

This is due to libssl1.1 being compiled with
"-DOPENSSL_TLS_SECURITY_LEVEL=2" and gear forcing TLSv1.0

Extracted from ubuntu source package:

  The default security level for TLS connections was increased from
  level 1 to level 2. This moves from the 80 bit security level to the
  112 bit security level and will require 2048 bit or larger RSA and
  DHE keys, 224 bit or larger ECC keys, SHA-2, TLSv1.2 or DTLSv1.2.

Allowing to negotiate TLS to the highest available version between
server and client solves the issue, provided that TLSv1.2 is useable.
The option is supported by in the latest version of all pythons >=3.5
[1][2][3]. Unfortunately Xenial doesn't have latest 3.5 and lacks the
ssl.PROTOCOL_TLS definition. We provide a fallback to select the highest
version of TLS supported in that case.

There is some risk using the fallback beacuse both the client and server
need to agree on the version supported in this case. Xenial python 3.5
does support TLSv1_2 which means that for all practical purposes TLS
v1.2 should be available on all platforms that gear runs avoiding this
problem.

Disable TLSv1.3:
According to https://bugs.python.org/issue43622#msg389497, an event on
ssl socket can happen without data being available at application level.
As gear is using a polling loop with multiple file descriptors and ssl
socket used as a blocking one, a blocked state could happen.
This is highlighted by Zuul SSL test: TestSchedulerSSL, where such
blocked state appears consistently.
note: gear tests and zuul tests are ok when using TLSv1.2 but the
previous behavior could also happen

[1] https://docs.python.org/2.7/library/ssl.html?highlight=protocol_tls#ssl.PROTOCOL_TLS
[2] https://docs.python.org/3.5/library/ssl.html?highlight=protocol_tls#ssl.PROTOCOL_TLS
[3] https://docs.python.org/3/library/ssl.html?highlight=protocol_tls#ssl.PROTOCOL_TLS

Change-Id: I5efb6c0576987815c5b93f8bc4020cdee2898d04
Signed-off-by: Matthieu Huin <mhuin@redhat.com>
---
 gear/__init__.py | 42 ++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 40 insertions(+), 2 deletions(-)

diff --git a/gear/__init__.py b/gear/__init__.py
index 6287281..6e0f1c8 100644
--- a/gear/__init__.py
+++ b/gear/__init__.py
@@ -91,6 +91,44 @@ def convert_to_bytes(data):
     return data
 
 
+def best_tls_version():
+    if hasattr(ssl, 'PROTOCOL_TLS'):
+        return ssl.PROTOCOL_TLS
+    # Note there is some risk in selecting tls 1.2 if available
+    # as both the client and server may not support it and need 1.1
+    # or 1.0. However, a xenial installation with python 3.5 does
+    # support 1.2 which is probably as old a setup as we need to worry
+    # about.
+    elif hasattr(ssl, 'PROTOCOL_TLSv1_2'):
+        return ssl.PROTOCOL_TLSv1_2
+    elif hasattr(ssl, 'PROTOCOL_TLSv1_1'):
+        return ssl.PROTOCOL_TLSv1_1
+    elif hasattr(ssl, 'PROTOCOL_TLSv1'):
+        return ssl.PROTOCOL_TLSv1
+    else:
+        raise ConnectionError('No supported TLS version available.')
+
+
+def create_ssl_context():
+    tls_version = best_tls_version()
+    context = ssl.SSLContext(tls_version)
+
+    # Disable TLSv1.3
+    # According to https://bugs.python.org/issue43622#msg389497, an event on
+    # ssl socket can happen without data being available at application level.
+    # As gear is using a polling loop with multiple file descriptors and ssl
+    # socket used as a blocking one, a blocked state could happen.
+    # This is highlighted by Zuul SSL test: TestSchedulerSSL, where such
+    # blocked state appears consistently.
+    # note: gear tests and zuul tests are ok for TLSv1.2 but this behavior
+    # could also happen
+    if (hasattr(ssl, 'PROTOCOL_TLS') and
+            tls_version == ssl.PROTOCOL_TLS):
+        context.options |= ssl.OP_NO_TLSv1_3
+
+    return context
+
+
 class Task(object):
     def __init__(self):
         self._wait_event = threading.Event()
@@ -209,7 +247,7 @@ class Connection(object):
 
             if self.use_ssl:
                 self.log.debug("Using SSL")
-                context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+                context = create_ssl_context()
                 context.verify_mode = ssl.CERT_REQUIRED
                 context.check_hostname = False
                 context.load_cert_chain(self.ssl_cert, self.ssl_key)
@@ -2862,7 +2900,7 @@ class Server(BaseClientServer):
                     self.log.debug("Accepting new connection")
                     c, addr = self.socket.accept()
                     if self.use_ssl:
-                        context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+                        context = create_ssl_context()
                         context.verify_mode = ssl.CERT_REQUIRED
                         context.load_cert_chain(self.ssl_cert, self.ssl_key)
                         context.load_verify_locations(self.ssl_ca)
-- 
2.31.1