Issue #28085: Add PROTOCOL_TLS_CLIENT and PROTOCOL_TLS_SERVER for SSLContext

This commit is contained in:
Christian Heimes 2016-09-12 00:01:11 +02:00
parent 722898065c
commit 5fe668c672
4 changed files with 123 additions and 32 deletions

View File

@ -610,6 +610,22 @@ Constants
.. 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 @@ Protocol versions
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::
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
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
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 @@ enabled when negotiating a SSL session is possible through the
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 <https://www.openssl.org/docs/apps/ciphers.html#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
^^^^^^^^^^^^^^^^

View File

@ -52,6 +52,8 @@ PROTOCOL_SSLv2
PROTOCOL_SSLv3
PROTOCOL_SSLv23
PROTOCOL_TLS
PROTOCOL_TLS_CLIENT
PROTOCOL_TLS_SERVER
PROTOCOL_TLSv1
PROTOCOL_TLSv1_1
PROTOCOL_TLSv1_2

View File

@ -1342,6 +1342,17 @@ class ContextTests(unittest.TestCase):
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 _have_threads:
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")

View File

@ -140,6 +140,8 @@ struct py_ssl_library_code {
#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_cert_requirements {
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 @@ static PyTypeObject PySSLSocket_Type = {
* _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 @@ _ssl__SSLContext_impl(PyTypeObject *type, int proto_version)
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 @@ _ssl__SSLContext_impl(PyTypeObject *type, int proto_version)
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 @@ get_verify_mode(PySSLContext *self, void *c)
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 @@ PyInit__ssl(void)
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