diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -26,7 +26,8 @@ probably additional platforms, as long a Some behavior may be platform dependent, since calls are made to the operating system socket APIs. The installed version of OpenSSL may also - cause variations in behavior. + cause variations in behavior. For example, TLSv1.1 and TLSv1.2 come with + openssl version 1.0.1. This section documents the objects and functions in the ``ssl`` module; for more general information about TLS, SSL, and certificates, the reader is referred to @@ -177,14 +178,16 @@ instead. .. table:: - ======================== ========= ========= ========== ========= - *client* / **server** **SSLv2** **SSLv3** **SSLv23** **TLSv1** - ------------------------ --------- --------- ---------- --------- - *SSLv2* yes no yes no - *SSLv3* no yes yes no - *SSLv23* yes no yes no - *TLSv1* no no yes yes - ======================== ========= ========= ========== ========= + ======================== ========= ========= ========== ========= =========== =========== + *client* / **server** **SSLv2** **SSLv3** **SSLv23** **TLSv1** **TLSv1.1** **TLSv1.2** + ------------------------ --------- --------- ---------- --------- ----------- ----------- + *SSLv2* yes no yes no no no + *SSLv3* no yes yes no no no + *SSLv23* yes no yes no no no + *TLSv1* no no yes yes no no + *TLSv1.1* no no yes no yes no + *TLSv1.2* no no yes no no yes + ======================== ========= ========= ========== ========= =========== =========== .. note:: @@ -401,9 +404,25 @@ Constants .. data:: PROTOCOL_TLSv1 - Selects TLS version 1 as the channel encryption protocol. This is the most + Selects TLS version 1.0 as the channel encryption protocol. + +.. data:: PROTOCOL_TLSv1_1 + + + Selects TLS version 1.1 as the channel encryption protocol. + Available only with openssl version 1.0.1+. + + .. versionadded:: 3.4 + +.. data:: PROTOCOL_TLSv1_2 + + + Selects TLS version 1.2 as the channel encryption protocol. This is the most modern version, and probably the best choice for maximum protection, if both sides can speak it. + Available only with openssl version 1.0.1+. + + .. versionadded:: 3.4 .. data:: OP_ALL @@ -437,6 +456,22 @@ Constants .. versionadded:: 3.2 +.. data:: OP_NO_TLSv1_1 + + Prevents a TLSv1.1 connection. This option is only applicable in conjunction + with :const:`PROTOCOL_SSLv23`. It prevents the peers from choosing TLSv1.1 as + the protocol version. Available only with openssl version 1.0.1+. + + .. versionadded:: 3.4 + +.. data:: OP_NO_TLSv1_2 + + Prevents a TLSv1.2 connection. This option is only applicable in conjunction + with :const:`PROTOCOL_SSLv23`. It prevents the peers from choosing TLSv1.2 as + the protocol version. Available only with openssl version 1.0.1+. + + .. versionadded:: 3.4 + .. data:: OP_CIPHER_SERVER_PREFERENCE Use the server's cipher ordering preference, rather than the client's. diff --git a/Doc/whatsnew/3.4.rst b/Doc/whatsnew/3.4.rst --- a/Doc/whatsnew/3.4.rst +++ b/Doc/whatsnew/3.4.rst @@ -103,6 +103,7 @@ Implementation improvements: Significantly Improved Library Modules: * SHA-3 (Keccak) support for :mod:`hashlib`. +* TLSv1.1 and TLSv1.2 support for :mod:`ssl`. Security improvements: diff --git a/Lib/ssl.py b/Lib/ssl.py --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -52,6 +52,8 @@ PROTOCOL_SSLv2 PROTOCOL_SSLv3 PROTOCOL_SSLv23 PROTOCOL_TLSv1 +PROTOCOL_TLSv1_1 +PROTOCOL_TLSv1_2 The following constants identify various SSL alert message descriptions as per http://www.iana.org/assignments/tls-parameters/tls-parameters.xml#tls-parameters-6 @@ -110,8 +112,7 @@ def _import_symbols(prefix): from _ssl import HAS_SNI, HAS_ECDH, HAS_NPN -from _ssl import (PROTOCOL_SSLv3, PROTOCOL_SSLv23, - PROTOCOL_TLSv1) +from _ssl import PROTOCOL_SSLv3, PROTOCOL_SSLv23, PROTOCOL_TLSv1 from _ssl import _OPENSSL_API_VERSION @@ -128,6 +129,14 @@ except ImportError: else: _PROTOCOL_NAMES[PROTOCOL_SSLv2] = "SSLv2" +try: + from _ssl import PROTOCOL_TLSv1_1, PROTOCOL_TLSv1_2 +except ImportError: + pass +else: + _PROTOCOL_NAMES[PROTOCOL_TLSv1_1] = "TLSv1.1" + _PROTOCOL_NAMES[PROTOCOL_TLSv1_2] = "TLSv1.2" + from socket import getnameinfo as _getnameinfo from socket import socket, AF_INET, SOCK_STREAM, create_connection import base64 # for DER-to-PEM translation diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -20,13 +20,7 @@ import functools ssl = support.import_module("ssl") -PROTOCOLS = [ - ssl.PROTOCOL_SSLv3, - ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_TLSv1 -] -if hasattr(ssl, 'PROTOCOL_SSLv2'): - PROTOCOLS.append(ssl.PROTOCOL_SSLv2) - +PROTOCOLS = sorted(ssl._PROTOCOL_NAMES) HOST = support.HOST data_file = lambda name: os.path.join(os.path.dirname(__file__), name) @@ -101,10 +95,6 @@ needs_sni = unittest.skipUnless(ssl.HAS_ class BasicSocketTests(unittest.TestCase): def test_constants(self): - #ssl.PROTOCOL_SSLv2 - ssl.PROTOCOL_SSLv23 - ssl.PROTOCOL_SSLv3 - ssl.PROTOCOL_TLSv1 ssl.CERT_NONE ssl.CERT_OPTIONAL ssl.CERT_REQUIRED @@ -396,11 +386,8 @@ class ContextTests(unittest.TestCase): @skip_if_broken_ubuntu_ssl def test_constructor(self): - if hasattr(ssl, 'PROTOCOL_SSLv2'): - ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv2) - ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23) - ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv3) - ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1) + for protocol in PROTOCOLS: + ssl.SSLContext(protocol) self.assertRaises(TypeError, ssl.SSLContext) self.assertRaises(ValueError, ssl.SSLContext, -1) self.assertRaises(ValueError, ssl.SSLContext, 42) @@ -1360,12 +1347,15 @@ else: client_context.options = ssl.OP_ALL | client_options server_context = ssl.SSLContext(server_protocol) server_context.options = ssl.OP_ALL | server_options + + # NOTE: we must enable "ALL" ciphers on the client, otherwise an + # SSLv23 client will send an SSLv3 hello (rather than SSLv2) + # starting from OpenSSL 1.0.0 (see issue #8322). + if client_context.protocol == ssl.PROTOCOL_SSLv23: + client_context.set_ciphers("ALL") + for ctx in (client_context, server_context): ctx.verify_mode = certsreqs - # NOTE: we must enable "ALL" ciphers, otherwise an SSLv23 client - # will send an SSLv3 hello (rather than SSLv2) starting from - # OpenSSL 1.0.0 (see issue #8322). - ctx.set_ciphers("ALL") ctx.load_cert_chain(CERTFILE) ctx.load_verify_locations(CERTFILE) try: @@ -1581,6 +1571,49 @@ else: try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_SSLv23, False, client_options=ssl.OP_NO_TLSv1) + @skip_if_broken_ubuntu_ssl + @unittest.skipUnless("PROTOCOL_TLSv1_1" in PROTOCOLS, + "TLS version 1.1 not supported.") + def test_protocol_tlsv1_1(self): + """Connecting to a TLSv1.1 server with various client options. + Testing against older TLS versions.""" + if support.verbose: + sys.stdout.write("\n") + try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_TLSv1_1, True) + if hasattr(ssl, 'PROTOCOL_SSLv2'): + try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_SSLv2, False) + try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_SSLv3, False) + try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_SSLv23, False, + client_options=ssl.OP_NO_TLSv1_1) + + try_protocol_combo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_TLSv1_1, True) + try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_TLSv1, False) + try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_TLSv1_1, False) + + + @skip_if_broken_ubuntu_ssl + @unittest.skipUnless("PROTOCOL_TLSv1_2" in PROTOCOLS, + "TLS version 1.2 not supported.") + def test_protocol_tlsv1_2(self): + """Connecting to a TLSv1.2 server with various client options. + Testing against older TLS versions.""" + if support.verbose: + sys.stdout.write("\n") + try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_TLSv1_2, True, + server_options=ssl.OP_NO_SSLv3|ssl.OP_NO_SSLv2, + client_options=ssl.OP_NO_SSLv3|ssl.OP_NO_SSLv2,) + if hasattr(ssl, 'PROTOCOL_SSLv2'): + try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_SSLv2, False) + try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_SSLv3, False) + try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_SSLv23, False, + client_options=ssl.OP_NO_TLSv1_2) + + try_protocol_combo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_TLSv1_2, True) + try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_TLSv1, False) + try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_TLSv1_2, False) + try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_TLSv1_1, False) + try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_TLSv1_2, False) + def test_starttls(self): """Switching from clear text to encrypted and back again.""" msgs = (b"msg 1", b"MSG 2", b"STARTTLS", b"MSG 3", b"msg 4", b"ENDTLS", b"msg 5", b"msg 6") diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -1005,6 +1005,8 @@ Library - ctypes.call_commethod was removed, since its only usage was in the defunct samples directory. +- Issue #16692: Added TLSv1.1 and TLSv1.2 support for the ssl modules. + Extension Modules ----------------- diff --git a/Modules/_ssl.c b/Modules/_ssl.c --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -40,6 +40,11 @@ #endif +/* openssl comes with TLSv1.1 and TLSv1.2 between 1.0.0h version 1.0.1 + http://www.openssl.org/news/changelog.html + */ +#define SSL_SUPPORT_TLSv1_2 (OPENSSL_VERSION_NUMBER > 0x1000007fL) + enum py_ssl_error { /* these mirror ssl.h */ PY_SSL_ERROR_NONE, @@ -73,7 +78,13 @@ enum py_ssl_version { #endif PY_SSL_VERSION_SSL3=1, PY_SSL_VERSION_SSL23, +#ifdef SSL_SUPPORT_TLSv1_2 + PY_SSL_VERSION_TLS1, + PY_SSL_VERSION_TLS1_1, + PY_SSL_VERSION_TLS1_2 +#else PY_SSL_VERSION_TLS1 +#endif }; struct py_ssl_error_code { @@ -1732,6 +1743,12 @@ context_new(PyTypeObject *type, PyObject PySSL_BEGIN_ALLOW_THREADS if (proto_version == PY_SSL_VERSION_TLS1) ctx = SSL_CTX_new(TLSv1_method()); +#if SSL_SUPPORT_TLSv1_2 + else if (proto_version == PY_SSL_VERSION_TLS1_1) + ctx = SSL_CTX_new(TLSv1_1_method()); + else if (proto_version == PY_SSL_VERSION_TLS1_2) + ctx = SSL_CTX_new(TLSv1_2_method()); +#endif else if (proto_version == PY_SSL_VERSION_SSL3) ctx = SSL_CTX_new(SSLv3_method()); #ifndef OPENSSL_NO_SSL2 @@ -3004,6 +3021,12 @@ PyInit__ssl(void) PY_SSL_VERSION_SSL23); PyModule_AddIntConstant(m, "PROTOCOL_TLSv1", PY_SSL_VERSION_TLS1); +#if SSL_SUPPORT_TLSv1_2 + PyModule_AddIntConstant(m, "PROTOCOL_TLSv1_1", + PY_SSL_VERSION_TLS1_1); + PyModule_AddIntConstant(m, "PROTOCOL_TLSv1_2", + PY_SSL_VERSION_TLS1_2); +#endif /* protocol options */ PyModule_AddIntConstant(m, "OP_ALL", @@ -3011,6 +3034,10 @@ PyInit__ssl(void) PyModule_AddIntConstant(m, "OP_NO_SSLv2", SSL_OP_NO_SSLv2); PyModule_AddIntConstant(m, "OP_NO_SSLv3", SSL_OP_NO_SSLv3); PyModule_AddIntConstant(m, "OP_NO_TLSv1", SSL_OP_NO_TLSv1); +#if SSL_SUPPORT_TLSv1_2 + PyModule_AddIntConstant(m, "OP_NO_TLSv1_1", SSL_OP_NO_TLSv1_1); + PyModule_AddIntConstant(m, "OP_NO_TLSv1_2", SSL_OP_NO_TLSv1_2); +#endif PyModule_AddIntConstant(m, "OP_CIPHER_SERVER_PREFERENCE", SSL_OP_CIPHER_SERVER_PREFERENCE); PyModule_AddIntConstant(m, "OP_SINGLE_DH_USE", SSL_OP_SINGLE_DH_USE); diff --git a/setup.py b/setup.py --- a/setup.py +++ b/setup.py @@ -786,10 +786,10 @@ class PyBuildExt(build_ext): for line in incfile: m = openssl_ver_re.match(line) if m: - openssl_ver = eval(m.group(1)) + openssl_ver = int(m.group(1), 16) + break except IOError as msg: print("IOError while reading opensshv.h:", msg) - pass #print('openssl_ver = 0x%08x' % openssl_ver) min_openssl_ver = 0x00907000