(original) (raw)
changeset: 103661:3ea641343244 user: Christian Heimes christian@python.org date: Mon Sep 12 00:01:11 2016 +0200 files: Doc/library/ssl.rst Lib/ssl.py Lib/test/test_ssl.py Modules/_ssl.c description: Issue #28085: Add PROTOCOL_TLS_CLIENT and PROTOCOL_TLS_SERVER for SSLContext diff -r 7369ec91d0f7 -r 3ea641343244 Doc/library/ssl.rst --- a/Doc/library/ssl.rst Sun Sep 11 14:54:27 2016 -0700 +++ b/Doc/library/ssl.rst Mon Sep 12 00:01:11 2016 +0200 @@ -610,6 +610,22 @@ .. versionadded:: 3.6 +.. data:: PROTOCOL_TLS_CLIENT + + Auto-negotiate the the highest protocol version like :data:`PROTOCOL_SSLv23`, + but only support client-side :class:`SSLSocket` connections. The protocol + enables :data:`CERT_REQUIRED` and :attr:`~SSLContext.check_hostname` by + default. + + .. versionadded:: 3.6 + +.. data:: PROTOCOL_TLS_SERVER + + Auto-negotiate the the highest protocol version like :data:`PROTOCOL_SSLv23`, + but only support server-side :class:`SSLSocket` connections. + + .. versionadded:: 3.6 + .. data:: PROTOCOL_SSLv23 Alias for data:`PROTOCOL_TLS`. @@ -2235,18 +2251,20 @@ SSL versions 2 and 3 are considered insecure and are therefore dangerous to use. If you want maximum compatibility between clients and servers, it is -recommended to use :const:`PROTOCOL_TLS` as the protocol version and then -disable SSLv2 and SSLv3 explicitly using the :data:`SSLContext.options` -attribute:: - - context = ssl.SSLContext(ssl.PROTOCOL_TLS) - context.options |= ssl.OP_NO_SSLv2 - context.options |= ssl.OP_NO_SSLv3 - context.options |= ssl.OP_NO_TLSv1 - context.options |= ssl.OP_NO_TLSv1_1 +recommended to use :const:`PROTOCOL_TLS_CLIENT` or +:const:`PROTOCOL_TLS_SERVER` as the protocol version. SSLv2 and SSLv3 are +disabled by default. + + client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + client_context.options |= ssl.OP_NO_TLSv1 + client_context.options |= ssl.OP_NO_TLSv1_1 + The SSL context created above will only allow TLSv1.2 and later (if -supported by your system) connections. +supported by your system) connections to a server. :const:`PROTOCOL_TLS_CLIENT` +implies certificate validation and hostname checks by default. You have to +load certificates into the context. + Cipher selection '''''''''''''''' @@ -2257,8 +2275,9 @@ ssl module disables certain weak ciphers by default, but you may want to further restrict the cipher choice. Be sure to read OpenSSL's documentation about the `cipher list format `_. -If you want to check which ciphers are enabled by a given cipher list, use the -``openssl ciphers`` command on your system. +If you want to check which ciphers are enabled by a given cipher list, use +:meth:`SSLContext.get_ciphers` or the ``openssl ciphers`` command on your +system. Multi-processing ^^^^^^^^^^^^^^^^ diff -r 7369ec91d0f7 -r 3ea641343244 Lib/ssl.py --- a/Lib/ssl.py Sun Sep 11 14:54:27 2016 -0700 +++ b/Lib/ssl.py Mon Sep 12 00:01:11 2016 +0200 @@ -52,6 +52,8 @@ PROTOCOL_SSLv3 PROTOCOL_SSLv23 PROTOCOL_TLS +PROTOCOL_TLS_CLIENT +PROTOCOL_TLS_SERVER PROTOCOL_TLSv1 PROTOCOL_TLSv1_1 PROTOCOL_TLSv1_2 diff -r 7369ec91d0f7 -r 3ea641343244 Lib/test/test_ssl.py --- a/Lib/test/test_ssl.py Sun Sep 11 14:54:27 2016 -0700 +++ b/Lib/test/test_ssl.py Mon Sep 12 00:01:11 2016 +0200 @@ -1342,6 +1342,17 @@ ctx.check_hostname = False self.assertFalse(ctx.check_hostname) + def test_context_client_server(self): + # PROTOCOL_TLS_CLIENT has sane defaults + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + self.assertTrue(ctx.check_hostname) + self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) + + # PROTOCOL_TLS_SERVER has different but also sane defaults + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + self.assertFalse(ctx.check_hostname) + self.assertEqual(ctx.verify_mode, ssl.CERT_NONE) + class SSLErrorTests(unittest.TestCase): @@ -2280,12 +2291,33 @@ if support.verbose: sys.stdout.write("\n") for protocol in PROTOCOLS: + if protocol in {ssl.PROTOCOL_TLS_CLIENT, ssl.PROTOCOL_TLS_SERVER}: + continue with self.subTest(protocol=ssl._PROTOCOL_NAMES[protocol]): context = ssl.SSLContext(protocol) context.load_cert_chain(CERTFILE) server_params_test(context, context, chatty=True, connectionchatty=True) + client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + client_context.load_verify_locations(SIGNING_CA) + server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + # server_context.load_verify_locations(SIGNING_CA) + server_context.load_cert_chain(SIGNED_CERTFILE2) + + with self.subTest(client='PROTOCOL_TLS_CLIENT', server='PROTOCOL_TLS_SERVER'): + server_params_test(client_context=client_context, + server_context=server_context, + chatty=True, connectionchatty=True, + sni_name='fakehostname') + + with self.subTest(client='PROTOCOL_TLS_SERVER', server='PROTOCOL_TLS_CLIENT'): + with self.assertRaises(ssl.SSLError): + server_params_test(client_context=server_context, + server_context=client_context, + chatty=True, connectionchatty=True, + sni_name='fakehostname') + def test_getpeercert(self): if support.verbose: sys.stdout.write("\n") diff -r 7369ec91d0f7 -r 3ea641343244 Modules/_ssl.c --- a/Modules/_ssl.c Sun Sep 11 14:54:27 2016 -0700 +++ b/Modules/_ssl.c Mon Sep 12 00:01:11 2016 +0200 @@ -140,6 +140,8 @@ #endif #define TLS_method SSLv23_method +#define TLS_client_method SSLv23_client_method +#define TLS_server_method SSLv23_server_method static int X509_NAME_ENTRY_set(const X509_NAME_ENTRY *ne) { @@ -233,14 +235,16 @@ enum py_ssl_version { PY_SSL_VERSION_SSL2, PY_SSL_VERSION_SSL3=1, - PY_SSL_VERSION_TLS, + PY_SSL_VERSION_TLS, /* SSLv23 */ #if HAVE_TLSv1_2 PY_SSL_VERSION_TLS1, PY_SSL_VERSION_TLS1_1, - PY_SSL_VERSION_TLS1_2 + PY_SSL_VERSION_TLS1_2, #else - PY_SSL_VERSION_TLS1 + PY_SSL_VERSION_TLS1, #endif + PY_SSL_VERSION_TLS_CLIENT=0x10, + PY_SSL_VERSION_TLS_SERVER, }; #ifdef WITH_THREAD @@ -2566,6 +2570,33 @@ * _SSLContext objects */ +static int +_set_verify_mode(SSL_CTX *ctx, enum py_ssl_cert_requirements n) +{ + int mode; + int (*verify_cb)(int, X509_STORE_CTX *) = NULL; + + switch(n) { + case PY_SSL_CERT_NONE: + mode = SSL_VERIFY_NONE; + break; + case PY_SSL_CERT_OPTIONAL: + mode = SSL_VERIFY_PEER; + break; + case PY_SSL_CERT_REQUIRED: + mode = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + break; + default: + PyErr_SetString(PyExc_ValueError, + "invalid value for verify_mode"); + return -1; + } + /* keep current verify cb */ + verify_cb = SSL_CTX_get_verify_callback(ctx); + SSL_CTX_set_verify(ctx, mode, verify_cb); + return 0; +} + /*[clinic input] @classmethod _ssl._SSLContext.__new__ @@ -2602,8 +2633,12 @@ else if (proto_version == PY_SSL_VERSION_SSL2) ctx = SSL_CTX_new(SSLv2_method()); #endif - else if (proto_version == PY_SSL_VERSION_TLS) + else if (proto_version == PY_SSL_VERSION_TLS) /* SSLv23 */ ctx = SSL_CTX_new(TLS_method()); + else if (proto_version == PY_SSL_VERSION_TLS_CLIENT) + ctx = SSL_CTX_new(TLS_client_method()); + else if (proto_version == PY_SSL_VERSION_TLS_SERVER) + ctx = SSL_CTX_new(TLS_server_method()); else proto_version = -1; PySSL_END_ALLOW_THREADS @@ -2636,9 +2671,20 @@ self->set_hostname = NULL; #endif /* Don't check host name by default */ - self->check_hostname = 0; + if (proto_version == PY_SSL_VERSION_TLS_CLIENT) { + self->check_hostname = 1; + if (_set_verify_mode(self->ctx, PY_SSL_CERT_REQUIRED) == -1) { + Py_DECREF(self); + return NULL; + } + } else { + self->check_hostname = 0; + if (_set_verify_mode(self->ctx, PY_SSL_CERT_NONE) == -1) { + Py_DECREF(self); + return NULL; + } + } /* Defaults */ - SSL_CTX_set_verify(self->ctx, SSL_VERIFY_NONE, NULL); options = SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; if (proto_version != PY_SSL_VERSION_SSL2) options |= SSL_OP_NO_SSLv2; @@ -2982,28 +3028,16 @@ static int set_verify_mode(PySSLContext *self, PyObject *arg, void *c) { - int n, mode; + int n; if (!PyArg_Parse(arg, "i", &n)) return -1; - if (n == PY_SSL_CERT_NONE) - mode = SSL_VERIFY_NONE; - else if (n == PY_SSL_CERT_OPTIONAL) - mode = SSL_VERIFY_PEER; - else if (n == PY_SSL_CERT_REQUIRED) - mode = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT; - else { - PyErr_SetString(PyExc_ValueError, - "invalid value for verify_mode"); - return -1; - } - if (mode == SSL_VERIFY_NONE && self->check_hostname) { + if (n == PY_SSL_CERT_NONE && self->check_hostname) { PyErr_SetString(PyExc_ValueError, "Cannot set verify_mode to CERT_NONE when " "check_hostname is enabled."); return -1; } - SSL_CTX_set_verify(self->ctx, mode, NULL); - return 0; + return _set_verify_mode(self->ctx, n); } static PyObject * @@ -5313,6 +5347,10 @@ PY_SSL_VERSION_TLS); PyModule_AddIntConstant(m, "PROTOCOL_TLS", PY_SSL_VERSION_TLS); + PyModule_AddIntConstant(m, "PROTOCOL_TLS_CLIENT", + PY_SSL_VERSION_TLS_CLIENT); + PyModule_AddIntConstant(m, "PROTOCOL_TLS_SERVER", + PY_SSL_VERSION_TLS_SERVER); PyModule_AddIntConstant(m, "PROTOCOL_TLSv1", PY_SSL_VERSION_TLS1); #if HAVE_TLSv1_2/christian@python.org