Skip to content

bpo-31431: SSLContext.check_hostname auto-sets CERT_REQUIRED #3531

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 15, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion Doc/library/ssl.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1648,7 +1648,10 @@ to speed up repeated connections from the same clients.
:meth:`SSLSocket.do_handshake`. The context's
:attr:`~SSLContext.verify_mode` must be set to :data:`CERT_OPTIONAL` or
:data:`CERT_REQUIRED`, and you must pass *server_hostname* to
:meth:`~SSLContext.wrap_socket` in order to match the hostname.
:meth:`~SSLContext.wrap_socket` in order to match the hostname. Enabling
hostname checking automatically sets :attr:`~SSLContext.verify_mode` from
:data:`CERT_NONE` to :data:`CERT_REQUIRED`. It cannot be set back to
:data:`CERT_NONE` as long as hostname checking is enabled.

Example::

Expand All @@ -1665,6 +1668,13 @@ to speed up repeated connections from the same clients.

.. versionadded:: 3.4

.. versionchanged:: 3.7

:attr:`~SSLContext.verify_mode` is now automatically changed
to :data:`CERT_REQUIRED` when hostname checking is enabled and
:attr:`~SSLContext.verify_mode` is :data:`CERT_NONE`. Previously
the same operation would have failed with a :exc:`ValueError`.

.. note::

This features requires OpenSSL 0.9.8f or newer.
Expand Down
27 changes: 24 additions & 3 deletions Lib/test/test_ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -1329,24 +1329,45 @@ def test__create_stdlib_context(self):
def test_check_hostname(self):
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
self.assertFalse(ctx.check_hostname)
self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)

# Requires CERT_REQUIRED or CERT_OPTIONAL
with self.assertRaises(ValueError):
ctx.check_hostname = True
# Auto set CERT_REQUIRED
ctx.check_hostname = True
self.assertTrue(ctx.check_hostname)
self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED)
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_REQUIRED
self.assertFalse(ctx.check_hostname)
self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED)

# Changing verify_mode does not affect check_hostname
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
ctx.check_hostname = False
self.assertFalse(ctx.check_hostname)
self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)
# Auto set
ctx.check_hostname = True
self.assertTrue(ctx.check_hostname)
self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED)

ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_OPTIONAL
ctx.check_hostname = False
self.assertFalse(ctx.check_hostname)
self.assertEqual(ctx.verify_mode, ssl.CERT_OPTIONAL)
# keep CERT_OPTIONAL
ctx.check_hostname = True
self.assertTrue(ctx.check_hostname)
self.assertEqual(ctx.verify_mode, ssl.CERT_OPTIONAL)

# Cannot set CERT_NONE with check_hostname enabled
with self.assertRaises(ValueError):
ctx.verify_mode = ssl.CERT_NONE
ctx.check_hostname = False
self.assertFalse(ctx.check_hostname)
ctx.verify_mode = ssl.CERT_NONE
self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)

def test_context_client_server(self):
# PROTOCOL_TLS_CLIENT has sane defaults
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SSLContext.check_hostname now automatically sets SSLContext.verify_mode to
ssl.CERT_REQUIRED instead of failing with a ValueError.
8 changes: 4 additions & 4 deletions Modules/_ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -3227,10 +3227,10 @@ set_check_hostname(PySSLContext *self, PyObject *arg, void *c)
return -1;
if (check_hostname &&
SSL_CTX_get_verify_mode(self->ctx) == SSL_VERIFY_NONE) {
PyErr_SetString(PyExc_ValueError,
"check_hostname needs a SSL context with either "
"CERT_OPTIONAL or CERT_REQUIRED");
return -1;
/* check_hostname = True sets verify_mode = CERT_REQUIRED */
if (_set_verify_mode(self->ctx, PY_SSL_CERT_REQUIRED) == -1) {
return -1;
}
}
self->check_hostname = check_hostname;
return 0;
Expand Down