Repository: pika/pika

Issue:
Update fallback SSL/TLS protocol version
In the connection module, the method `URLParameters._set_url_ssl_options()` tries to set the secure protocol version to `ssl.PROTOCOL_TLS` and falls back to `ssl.PROTOCOL_TLSv1` if the first is not available (since it was introduced in 3.6 and this client supports 3.4+).

https://github.com/pika/pika/blob/c34c411874f049f2048aa5133ea20f87c95fa223/pika/connection.py#L905-L912

This has the following issues:
- The usage of `ssl.PROTOCOL_TLS` is [deprecated in 3.10](https://docs.python.org/3/library/ssl.html#ssl.PROTOCOL_TLS) in favour of ` ssl.PROTOCOL_TLS_CLIENT`
- The fallback sets an unsecure protocol

I propose the following changes:
1. Try to set [ssl.PROTOCOL_TLS_CLIENT](https://docs.python.org/3/library/ssl.html#ssl.PROTOCOL_TLS_CLIENT)
2. Fall back to [ssl.PROTOCOL_TLSv1_2](https://docs.python.org/3/library/ssl.html#ssl.PROTOCOL_TLSv1_2)

Those changes would address the usage of deprecated `ssl.PROTOCOL_TLS`, and would fall back to a secure protocol by default. The constant `ssl.PROTOCOL_TLSv1_2` was introduced in Python 3.4, therefore it is safe to assume this constant will be there for supported Python versions.

Discussion:

Patch:
diff --git a/pika/connection.py b/pika/connection.py
index b1278f3..c2f169f 100644
--- a/pika/connection.py
+++ b/pika/connection.py
@@ -903,9 +903,9 @@ class URLParameters(Parameters):
             #
             # SSLContext.load_verify_locations(cafile=None, capath=None, cadata=None)
             try:
-                opt_protocol = ssl.PROTOCOL_TLS
+                opt_protocol = ssl.PROTOCOL_TLS_CLIENT
             except AttributeError:
-                opt_protocol = ssl.PROTOCOL_TLSv1
+                opt_protocol = ssl.PROTOCOL_TLSv1_2
             if 'protocol' in opts:
                 opt_protocol = opts['protocol']
 


Test patch:
diff --git a/tests/unit/connection_parameters_tests.py b/tests/unit/connection_parameters_tests.py
index 75d8a8a..ff8dc04 100644
--- a/tests/unit/connection_parameters_tests.py
+++ b/tests/unit/connection_parameters_tests.py
@@ -12,18 +12,16 @@ import pika
 from pika.compat import urlencode, url_quote, dict_iteritems
 from pika import channel, connection, credentials, spec
 
-
 # disable missing-docstring
 # pylint: disable=C0111
 
 # disable invalid-name
 # pylint: disable=C0103
 
-
 # Unordered sequence of connection.Parameters's property getters
 _ALL_PUBLIC_PARAMETERS_PROPERTIES = tuple(
-    attr for attr in vars(connection.Parameters) if not attr.startswith('_')
-    and issubclass(type(getattr(connection.Parameters, attr)), property))
+    attr for attr in vars(connection.Parameters) if not attr.startswith('_') and
+    issubclass(type(getattr(connection.Parameters, attr)), property))
 
 
 class ChildParameters(connection.Parameters):
@@ -642,6 +640,20 @@ class URLParametersTests(ParametersTestsBase):
         params.port = params.DEFAULT_PORT
         self.assert_default_parameter_values(params)
 
+    def test_ssl_default_protocol_version_fallback(self):
+        """
+        This test does not set protocol_version option in ssl_options. Instead,
+        it relies on connection.URLParameters class to setup the default, and
+        then it asserts the protocol is what we expected (auto or TLSv1_2)
+        """
+        params = connection.URLParameters(
+            'amqps://foo.bar/some-vhost?ssl_options=%7B%27ca_certs%27%3A%27testdata%2Fcerts%2Fca_certificate.pem%27%7D'
+        )
+        self.assertTrue(
+            params.ssl_options.context.protocol == ssl.PROTOCOL_TLS_CLIENT or
+            params.ssl_options.context.protocol == ssl.PROTOCOL_TLSv1_2,
+            msg='Expected to fallback to a secure protocol')
+
     def test_no_url_scheme_defaults_to_plaintext(self):
         params = connection.URLParameters('//')
         self.assertIsNone(params.ssl_options)