Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1aa9a75

Browse files
committed
Issue #19509: Add SSLContext.check_hostname to match the peer's certificate
with server_hostname on handshake.
1 parent 6e6429a commit 1aa9a75

5 files changed

Lines changed: 162 additions & 6 deletions

File tree

Doc/library/ssl.rst

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,11 @@ SSL sockets also have the following additional methods and attributes:
773773

774774
Perform the SSL setup handshake.
775775

776+
.. versionchanged:: 3.4
777+
The handshake method also performce :func:`match_hostname` when the
778+
:attr:`~SSLContext.check_hostname` attribute of the socket's
779+
:attr:`~SSLSocket.context` is true.
780+
776781
.. method:: SSLSocket.getpeercert(binary_form=False)
777782

778783
If there is no certificate for the peer on the other end of the connection,
@@ -1182,6 +1187,33 @@ to speed up repeated connections from the same clients.
11821187

11831188
.. versionadded:: 3.4
11841189

1190+
.. attribute:: SSLContext.check_hostname
1191+
1192+
Wether to match the peer cert's hostname with :func:`match_hostname` in
1193+
:meth:`SSLSocket.do_handshake`. The context's
1194+
:attr:`~SSLContext.verify_mode` must be set to :data:`CERT_OPTIONAL` or
1195+
:data:`CERT_REQUIRED`, and you must pass *server_hostname* to
1196+
:meth:`~SSLContext.wrap_socket` in order to match the hostname.
1197+
1198+
Example::
1199+
1200+
import socket, ssl
1201+
1202+
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
1203+
context.verify_mode = ssl.CERT_REQUIRED
1204+
context.check_hostname = True
1205+
context.load_default_certs()
1206+
1207+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1208+
ssl_sock = context.wrap_socket(s, server_hostname='www.verisign.com'):
1209+
ssl_sock.connect(('www.verisign.com', 443))
1210+
1211+
.. versionadded:: 3.4
1212+
1213+
.. note::
1214+
1215+
This features requires OpenSSL 0.9.8f or newer.
1216+
11851217
.. attribute:: SSLContext.options
11861218

11871219
An integer representing the set of SSL options enabled on this context.
@@ -1596,7 +1628,9 @@ Therefore, when in client mode, it is highly recommended to use
15961628
have to check that the server certificate, which can be obtained by calling
15971629
:meth:`SSLSocket.getpeercert`, matches the desired service. For many
15981630
protocols and applications, the service can be identified by the hostname;
1599-
in this case, the :func:`match_hostname` function can be used.
1631+
in this case, the :func:`match_hostname` function can be used. This common
1632+
check is automatically performed when :attr:`SSLContext.check_hostname` is
1633+
enabled.
16001634

16011635
In server mode, if you want to authenticate your clients using the SSL layer
16021636
(rather than using a higher-level authentication mechanism), you'll also have

Lib/ssl.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ def _import_symbols(prefix):
148148
from _ssl import enum_certificates, enum_crls
149149

150150
from socket import getnameinfo as _getnameinfo
151+
from socket import SHUT_RDWR as _SHUT_RDWR
151152
from socket import socket, AF_INET, SOCK_STREAM, create_connection
152153
import base64 # for DER-to-PEM translation
153154
import traceback
@@ -235,7 +236,9 @@ def match_hostname(cert, hostname):
235236
returns nothing.
236237
"""
237238
if not cert:
238-
raise ValueError("empty or no certificate")
239+
raise ValueError("empty or no certificate, match_hostname needs a "
240+
"SSL socket or SSL context with either "
241+
"CERT_OPTIONAL or CERT_REQUIRED")
239242
dnsnames = []
240243
san = cert.get('subjectAltName', ())
241244
for key, value in san:
@@ -387,9 +390,10 @@ def create_default_context(purpose=Purpose.SERVER_AUTH, *, cafile=None,
387390
context.options |= getattr(_ssl, "OP_NO_COMPRESSION", 0)
388391
# disallow ciphers with known vulnerabilities
389392
context.set_ciphers(_RESTRICTED_CIPHERS)
390-
# verify certs in client mode
393+
# verify certs and host name in client mode
391394
if purpose == Purpose.SERVER_AUTH:
392395
context.verify_mode = CERT_REQUIRED
396+
context.check_hostname = True
393397
if cafile or capath or cadata:
394398
context.load_verify_locations(cafile, capath, cadata)
395399
elif context.verify_mode != CERT_NONE:
@@ -480,6 +484,13 @@ def __init__(self, sock=None, keyfile=None, certfile=None,
480484
if server_side and server_hostname:
481485
raise ValueError("server_hostname can only be specified "
482486
"in client mode")
487+
if self._context.check_hostname and not server_hostname:
488+
if HAS_SNI:
489+
raise ValueError("check_hostname requires server_hostname")
490+
else:
491+
raise ValueError("check_hostname requires server_hostname, "
492+
"but it's not supported by your OpenSSL "
493+
"library")
483494
self.server_side = server_side
484495
self.server_hostname = server_hostname
485496
self.do_handshake_on_connect = do_handshake_on_connect
@@ -522,9 +533,9 @@ def __init__(self, sock=None, keyfile=None, certfile=None,
522533
raise ValueError("do_handshake_on_connect should not be specified for non-blocking sockets")
523534
self.do_handshake()
524535

525-
except OSError as x:
536+
except (OSError, ValueError):
526537
self.close()
527-
raise x
538+
raise
528539

529540
@property
530541
def context(self):
@@ -751,6 +762,17 @@ def do_handshake(self, block=False):
751762
finally:
752763
self.settimeout(timeout)
753764

765+
if self.context.check_hostname:
766+
try:
767+
if not self.server_hostname:
768+
raise ValueError("check_hostname needs server_hostname "
769+
"argument")
770+
match_hostname(self.getpeercert(), self.server_hostname)
771+
except Exception:
772+
self.shutdown(_SHUT_RDWR)
773+
self.close()
774+
raise
775+
754776
def _real_connect(self, addr, connect_ex):
755777
if self.server_side:
756778
raise ValueError("can't connect in server-side mode")
@@ -770,7 +792,7 @@ def _real_connect(self, addr, connect_ex):
770792
if self.do_handshake_on_connect:
771793
self.do_handshake()
772794
return rc
773-
except OSError:
795+
except (OSError, ValueError):
774796
self._sslobj = None
775797
raise
776798

Lib/test/test_ssl.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1003,6 +1003,7 @@ def test_create_default_context(self):
10031003
ctx = ssl.create_default_context()
10041004
self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1)
10051005
self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED)
1006+
self.assertTrue(ctx.check_hostname)
10061007
self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
10071008

10081009
with open(SIGNING_CA) as f:
@@ -1022,6 +1023,7 @@ def test__create_stdlib_context(self):
10221023
ctx = ssl._create_stdlib_context()
10231024
self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23)
10241025
self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)
1026+
self.assertFalse(ctx.check_hostname)
10251027
self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
10261028

10271029
ctx = ssl._create_stdlib_context(ssl.PROTOCOL_TLSv1)
@@ -1040,6 +1042,28 @@ def test__create_stdlib_context(self):
10401042
self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)
10411043
self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
10421044

1045+
def test_check_hostname(self):
1046+
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
1047+
self.assertFalse(ctx.check_hostname)
1048+
1049+
# Requires CERT_REQUIRED or CERT_OPTIONAL
1050+
with self.assertRaises(ValueError):
1051+
ctx.check_hostname = True
1052+
ctx.verify_mode = ssl.CERT_REQUIRED
1053+
self.assertFalse(ctx.check_hostname)
1054+
ctx.check_hostname = True
1055+
self.assertTrue(ctx.check_hostname)
1056+
1057+
ctx.verify_mode = ssl.CERT_OPTIONAL
1058+
ctx.check_hostname = True
1059+
self.assertTrue(ctx.check_hostname)
1060+
1061+
# Cannot set CERT_NONE with check_hostname enabled
1062+
with self.assertRaises(ValueError):
1063+
ctx.verify_mode = ssl.CERT_NONE
1064+
ctx.check_hostname = False
1065+
self.assertFalse(ctx.check_hostname)
1066+
10431067

10441068
class SSLErrorTests(unittest.TestCase):
10451069

@@ -1930,6 +1954,44 @@ def test_crl_check(self):
19301954
cert = s.getpeercert()
19311955
self.assertTrue(cert, "Can't get peer certificate.")
19321956

1957+
def test_check_hostname(self):
1958+
if support.verbose:
1959+
sys.stdout.write("\n")
1960+
1961+
server_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
1962+
server_context.load_cert_chain(SIGNED_CERTFILE)
1963+
1964+
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
1965+
context.verify_mode = ssl.CERT_REQUIRED
1966+
context.check_hostname = True
1967+
context.load_verify_locations(SIGNING_CA)
1968+
1969+
# correct hostname should verify
1970+
server = ThreadedEchoServer(context=server_context, chatty=True)
1971+
with server:
1972+
with context.wrap_socket(socket.socket(),
1973+
server_hostname="localhost") as s:
1974+
s.connect((HOST, server.port))
1975+
cert = s.getpeercert()
1976+
self.assertTrue(cert, "Can't get peer certificate.")
1977+
1978+
# incorrect hostname should raise an exception
1979+
server = ThreadedEchoServer(context=server_context, chatty=True)
1980+
with server:
1981+
with context.wrap_socket(socket.socket(),
1982+
server_hostname="invalid") as s:
1983+
with self.assertRaisesRegex(ssl.CertificateError,
1984+
"hostname 'invalid' doesn't match 'localhost'"):
1985+
s.connect((HOST, server.port))
1986+
1987+
# missing server_hostname arg should cause an exception, too
1988+
server = ThreadedEchoServer(context=server_context, chatty=True)
1989+
with server:
1990+
with socket.socket() as s:
1991+
with self.assertRaisesRegex(ValueError,
1992+
"check_hostname requires server_hostname"):
1993+
context.wrap_socket(s)
1994+
19331995
def test_empty_cert(self):
19341996
"""Connecting with an empty cert file"""
19351997
bad_cert_test(os.path.join(os.path.dirname(__file__) or os.curdir,

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ Core and Builtins
1818
Library
1919
-------
2020

21+
- Issue #19509: Add SSLContext.check_hostname to match the peer's certificate
22+
with server_hostname on handshake.
23+
2124
- Issue #15798: Fixed subprocess.Popen() to no longer fail if file
2225
descriptor 0, 1 or 2 is closed.
2326

Modules/_ssl.c

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ typedef struct {
214214
#ifndef OPENSSL_NO_TLSEXT
215215
PyObject *set_hostname;
216216
#endif
217+
int check_hostname;
217218
} PySSLContext;
218219

219220
typedef struct {
@@ -2050,6 +2051,8 @@ context_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
20502051
#ifndef OPENSSL_NO_TLSEXT
20512052
self->set_hostname = NULL;
20522053
#endif
2054+
/* Don't check host name by default */
2055+
self->check_hostname = 0;
20532056
/* Defaults */
20542057
SSL_CTX_set_verify(self->ctx, SSL_VERIFY_NONE, NULL);
20552058
SSL_CTX_set_options(self->ctx,
@@ -2231,6 +2234,12 @@ set_verify_mode(PySSLContext *self, PyObject *arg, void *c)
22312234
"invalid value for verify_mode");
22322235
return -1;
22332236
}
2237+
if (mode == SSL_VERIFY_NONE && self->check_hostname) {
2238+
PyErr_SetString(PyExc_ValueError,
2239+
"Cannot set verify_mode to CERT_NONE when "
2240+
"check_hostname is enabled.");
2241+
return -1;
2242+
}
22342243
SSL_CTX_set_verify(self->ctx, mode, NULL);
22352244
return 0;
22362245
}
@@ -2304,6 +2313,30 @@ set_options(PySSLContext *self, PyObject *arg, void *c)
23042313
return 0;
23052314
}
23062315

2316+
static PyObject *
2317+
get_check_hostname(PySSLContext *self, void *c)
2318+
{
2319+
return PyBool_FromLong(self->check_hostname);
2320+
}
2321+
2322+
static int
2323+
set_check_hostname(PySSLContext *self, PyObject *arg, void *c)
2324+
{
2325+
int check_hostname;
2326+
if (!PyArg_Parse(arg, "p", &check_hostname))
2327+
return -1;
2328+
if (check_hostname &&
2329+
SSL_CTX_get_verify_mode(self->ctx) == SSL_VERIFY_NONE) {
2330+
PyErr_SetString(PyExc_ValueError,
2331+
"check_hostname needs a SSL context with either "
2332+
"CERT_OPTIONAL or CERT_REQUIRED");
2333+
return -1;
2334+
}
2335+
self->check_hostname = check_hostname;
2336+
return 0;
2337+
}
2338+
2339+
23072340
typedef struct {
23082341
PyThreadState *thread_state;
23092342
PyObject *callable;
@@ -3093,6 +3126,8 @@ get_ca_certs(PySSLContext *self, PyObject *args, PyObject *kwds)
30933126

30943127

30953128
static PyGetSetDef context_getsetlist[] = {
3129+
{"check_hostname", (getter) get_check_hostname,
3130+
(setter) set_check_hostname, NULL},
30963131
{"options", (getter) get_options,
30973132
(setter) set_options, NULL},
30983133
#ifdef HAVE_OPENSSL_VERIFY_PARAM

0 commit comments

Comments
 (0)