bpo-31399: Let OpenSSL verify hostname and IP address (#3462)
bpo-31399: Let OpenSSL verify hostname and IP The ssl module now uses OpenSSL's X509_VERIFY_PARAM_set1_host() and X509_VERIFY_PARAM_set1_ip() API to verify hostname and IP addresses. * Remove match_hostname calls * Check for libssl with set1_host, libssl must provide X509_VERIFY_PARAM_set1_host() * Add documentation for OpenSSL 1.0.2 requirement * Don't support OpenSSL special mode with a leading dot, e.g. ".example.org" matches "www.example.org". It's not standard conform. * Add hostname_checks_common_name Signed-off-by: Christian Heimes <christian@python.org>
This commit is contained in:
parent
746cc75541
commit
61d478c71c
|
@ -146,9 +146,10 @@ Functions, Constants, and Exceptions
|
||||||
|
|
||||||
.. exception:: CertificateError
|
.. exception:: CertificateError
|
||||||
|
|
||||||
Raised to signal an error with a certificate (such as mismatching
|
An alias for :exc:`SSLCertVerificationError`.
|
||||||
hostname). Certificate errors detected by OpenSSL, though, raise
|
|
||||||
an :exc:`SSLCertVerificationError`.
|
.. versionchanged:: 3.7
|
||||||
|
The exception is now an alias for :exc:`SSLCertVerificationError`.
|
||||||
|
|
||||||
|
|
||||||
Socket creation
|
Socket creation
|
||||||
|
@ -430,8 +431,14 @@ Certificate handling
|
||||||
of the certificate, is now supported.
|
of the certificate, is now supported.
|
||||||
|
|
||||||
.. versionchanged:: 3.7
|
.. versionchanged:: 3.7
|
||||||
|
The function is no longer used to TLS connections. Hostname matching
|
||||||
|
is now performed by OpenSSL.
|
||||||
|
|
||||||
Allow wildcard when it is the leftmost and the only character
|
Allow wildcard when it is the leftmost and the only character
|
||||||
in that segment.
|
in that segment. Partial wildcards like ``www*.example.com`` are no
|
||||||
|
longer supported.
|
||||||
|
|
||||||
|
.. deprecated:: 3.7
|
||||||
|
|
||||||
.. function:: cert_time_to_seconds(cert_time)
|
.. function:: cert_time_to_seconds(cert_time)
|
||||||
|
|
||||||
|
@ -850,6 +857,14 @@ Constants
|
||||||
|
|
||||||
.. versionadded:: 3.5
|
.. versionadded:: 3.5
|
||||||
|
|
||||||
|
.. data:: HAS_NEVER_CHECK_COMMON_NAME
|
||||||
|
|
||||||
|
Whether the OpenSSL library has built-in support not checking subject
|
||||||
|
common name and :attr:`SSLContext.hostname_checks_common_name` is
|
||||||
|
writeable.
|
||||||
|
|
||||||
|
.. versionadded:: 3.7
|
||||||
|
|
||||||
.. data:: HAS_ECDH
|
.. data:: HAS_ECDH
|
||||||
|
|
||||||
Whether the OpenSSL library has built-in support for Elliptic Curve-based
|
Whether the OpenSSL library has built-in support for Elliptic Curve-based
|
||||||
|
@ -1075,6 +1090,12 @@ SSL sockets also have the following additional methods and attributes:
|
||||||
The socket timeout is no more reset each time bytes are received or sent.
|
The socket timeout is no more reset each time bytes are received or sent.
|
||||||
The socket timeout is now to maximum total duration of the handshake.
|
The socket timeout is now to maximum total duration of the handshake.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.7
|
||||||
|
Hostname or IP address is matched by OpenSSL during handshake. The
|
||||||
|
function :func:`match_hostname` is no longer used. In case OpenSSL
|
||||||
|
refuses a hostname or IP address, the handshake is aborted early and
|
||||||
|
a TLS alert message is send to the peer.
|
||||||
|
|
||||||
.. method:: SSLSocket.getpeercert(binary_form=False)
|
.. method:: SSLSocket.getpeercert(binary_form=False)
|
||||||
|
|
||||||
If there is no certificate for the peer on the other end of the connection,
|
If there is no certificate for the peer on the other end of the connection,
|
||||||
|
@ -1730,6 +1751,17 @@ to speed up repeated connections from the same clients.
|
||||||
The protocol version chosen when constructing the context. This attribute
|
The protocol version chosen when constructing the context. This attribute
|
||||||
is read-only.
|
is read-only.
|
||||||
|
|
||||||
|
.. attribute:: SSLContext.hostname_checks_common_name
|
||||||
|
|
||||||
|
Whether :attr:`~SSLContext.check_hostname` falls back to verify the cert's
|
||||||
|
subject common name in the absence of a subject alternative name
|
||||||
|
extension (default: true).
|
||||||
|
|
||||||
|
.. versionadded:: 3.7
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Only writeable with OpenSSL 1.1.0 or higher.
|
||||||
|
|
||||||
.. attribute:: SSLContext.verify_flags
|
.. attribute:: SSLContext.verify_flags
|
||||||
|
|
||||||
The flags for certificate verification operations. You can set flags like
|
The flags for certificate verification operations. You can set flags like
|
||||||
|
@ -2324,6 +2356,10 @@ in this case, the :func:`match_hostname` function can be used. This common
|
||||||
check is automatically performed when :attr:`SSLContext.check_hostname` is
|
check is automatically performed when :attr:`SSLContext.check_hostname` is
|
||||||
enabled.
|
enabled.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.7
|
||||||
|
Hostname matchings is now performed by OpenSSL. Python no longer uses
|
||||||
|
:func:`match_hostname`.
|
||||||
|
|
||||||
In server mode, if you want to authenticate your clients using the SSL layer
|
In server mode, if you want to authenticate your clients using the SSL layer
|
||||||
(rather than using a higher-level authentication mechanism), you'll also have
|
(rather than using a higher-level authentication mechanism), you'll also have
|
||||||
to specify :const:`CERT_REQUIRED` and similarly check the client certificate.
|
to specify :const:`CERT_REQUIRED` and similarly check the client certificate.
|
||||||
|
|
|
@ -568,6 +568,32 @@ can be set within the scope of a group.
|
||||||
``'^$'`` or ``(?=-)`` that matches an empty string.
|
``'^$'`` or ``(?=-)`` that matches an empty string.
|
||||||
(Contributed by Serhiy Storchaka in :issue:`25054`.)
|
(Contributed by Serhiy Storchaka in :issue:`25054`.)
|
||||||
|
|
||||||
|
ssl
|
||||||
|
---
|
||||||
|
|
||||||
|
The ssl module now uses OpenSSL's builtin API instead of
|
||||||
|
:func:`~ssl.match_hostname` to check host name or IP address. Values
|
||||||
|
are validated during TLS handshake. Any cert validation error including
|
||||||
|
a failing host name match now raises :exc:`~ssl.SSLCertVerificationError` and
|
||||||
|
aborts the handshake with a proper TLS Alert message. The new exception
|
||||||
|
contains additional information. Host name validation can be customized
|
||||||
|
with :attr:`~ssl.SSLContext.host_flags`.
|
||||||
|
(Contributed by Christian Heimes in :issue:`31399`.)
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
The improved host name check requires an OpenSSL 1.0.2 or 1.1 compatible
|
||||||
|
libssl. OpenSSL 0.9.8 and 1.0.1 are no longer supported. LibreSSL is
|
||||||
|
temporarily not supported until it gains the necessary OpenSSL 1.0.2 APIs.
|
||||||
|
|
||||||
|
The ssl module no longer sends IP addresses in SNI TLS extension.
|
||||||
|
(Contributed by Christian Heimes in :issue:`32185`.)
|
||||||
|
|
||||||
|
:func:`~ssl.match_hostname` no longer supports partial wildcards like
|
||||||
|
``www*.example.org``. :attr:`~ssl.SSLContext.host_flags` has partial
|
||||||
|
wildcard matching disabled by default.
|
||||||
|
(Contributed by Mandeep Singh in :issue:`23033` and Christian Heimes in
|
||||||
|
:issue:`31399`.)
|
||||||
|
|
||||||
string
|
string
|
||||||
------
|
------
|
||||||
|
|
||||||
|
@ -1120,6 +1146,12 @@ Other CPython implementation changes
|
||||||
emitted in the first place), and an explicit ``error::BytesWarning``
|
emitted in the first place), and an explicit ``error::BytesWarning``
|
||||||
warnings filter added to convert them to exceptions.
|
warnings filter added to convert them to exceptions.
|
||||||
|
|
||||||
|
* CPython' :mod:`ssl` module requires OpenSSL 1.0.2 or 1.1 compatible libssl.
|
||||||
|
OpenSSL 1.0.1 has reached end of lifetime on 2016-12-31 and is no longer
|
||||||
|
supported. LibreSSL is temporarily not supported as well. LibreSSL releases
|
||||||
|
up to version 2.6.4 are missing required OpenSSL 1.0.2 APIs.
|
||||||
|
|
||||||
|
|
||||||
Documentation
|
Documentation
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
|
|
@ -590,12 +590,6 @@ class SSLProtocol(protocols.Protocol):
|
||||||
raise handshake_exc
|
raise handshake_exc
|
||||||
|
|
||||||
peercert = sslobj.getpeercert()
|
peercert = sslobj.getpeercert()
|
||||||
if not hasattr(self._sslcontext, 'check_hostname'):
|
|
||||||
# Verify hostname if requested, Python 3.4+ uses check_hostname
|
|
||||||
# and checks the hostname in do_handshake()
|
|
||||||
if (self._server_hostname and
|
|
||||||
self._sslcontext.verify_mode != ssl.CERT_NONE):
|
|
||||||
ssl.match_hostname(peercert, self._server_hostname)
|
|
||||||
except BaseException as exc:
|
except BaseException as exc:
|
||||||
if self._loop.get_debug():
|
if self._loop.get_debug():
|
||||||
if isinstance(exc, ssl.CertificateError):
|
if isinstance(exc, ssl.CertificateError):
|
||||||
|
|
|
@ -1375,7 +1375,8 @@ else:
|
||||||
if key_file or cert_file:
|
if key_file or cert_file:
|
||||||
context.load_cert_chain(cert_file, key_file)
|
context.load_cert_chain(cert_file, key_file)
|
||||||
self._context = context
|
self._context = context
|
||||||
self._check_hostname = check_hostname
|
if check_hostname is not None:
|
||||||
|
self._context.check_hostname = check_hostname
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
"Connect to a host on a given (SSL) port."
|
"Connect to a host on a given (SSL) port."
|
||||||
|
@ -1389,13 +1390,6 @@ else:
|
||||||
|
|
||||||
self.sock = self._context.wrap_socket(self.sock,
|
self.sock = self._context.wrap_socket(self.sock,
|
||||||
server_hostname=server_hostname)
|
server_hostname=server_hostname)
|
||||||
if not self._context.check_hostname and self._check_hostname:
|
|
||||||
try:
|
|
||||||
ssl.match_hostname(self.sock.getpeercert(), server_hostname)
|
|
||||||
except Exception:
|
|
||||||
self.sock.shutdown(socket.SHUT_RDWR)
|
|
||||||
self.sock.close()
|
|
||||||
raise
|
|
||||||
|
|
||||||
__all__.append("HTTPSConnection")
|
__all__.append("HTTPSConnection")
|
||||||
|
|
||||||
|
|
29
Lib/ssl.py
29
Lib/ssl.py
|
@ -148,7 +148,6 @@ _IntEnum._convert(
|
||||||
lambda name: name.startswith('CERT_'),
|
lambda name: name.startswith('CERT_'),
|
||||||
source=_ssl)
|
source=_ssl)
|
||||||
|
|
||||||
|
|
||||||
PROTOCOL_SSLv23 = _SSLMethod.PROTOCOL_SSLv23 = _SSLMethod.PROTOCOL_TLS
|
PROTOCOL_SSLv23 = _SSLMethod.PROTOCOL_SSLv23 = _SSLMethod.PROTOCOL_TLS
|
||||||
_PROTOCOL_NAMES = {value: name for name, value in _SSLMethod.__members__.items()}
|
_PROTOCOL_NAMES = {value: name for name, value in _SSLMethod.__members__.items()}
|
||||||
|
|
||||||
|
@ -172,6 +171,8 @@ if _ssl.HAS_TLS_UNIQUE:
|
||||||
else:
|
else:
|
||||||
CHANNEL_BINDING_TYPES = []
|
CHANNEL_BINDING_TYPES = []
|
||||||
|
|
||||||
|
HAS_NEVER_CHECK_COMMON_NAME = hasattr(_ssl, 'HOSTFLAG_NEVER_CHECK_SUBJECT')
|
||||||
|
|
||||||
|
|
||||||
# Disable weak or insecure ciphers by default
|
# Disable weak or insecure ciphers by default
|
||||||
# (OpenSSL's default setting is 'DEFAULT:!aNULL:!eNULL')
|
# (OpenSSL's default setting is 'DEFAULT:!aNULL:!eNULL')
|
||||||
|
@ -216,9 +217,7 @@ _RESTRICTED_SERVER_CIPHERS = (
|
||||||
'!aNULL:!eNULL:!MD5:!DSS:!RC4:!3DES'
|
'!aNULL:!eNULL:!MD5:!DSS:!RC4:!3DES'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
CertificateError = SSLCertVerificationError
|
||||||
class CertificateError(ValueError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def _dnsname_match(dn, hostname):
|
def _dnsname_match(dn, hostname):
|
||||||
|
@ -473,6 +472,23 @@ class SSLContext(_SSLContext):
|
||||||
def options(self, value):
|
def options(self, value):
|
||||||
super(SSLContext, SSLContext).options.__set__(self, value)
|
super(SSLContext, SSLContext).options.__set__(self, value)
|
||||||
|
|
||||||
|
if hasattr(_ssl, 'HOSTFLAG_NEVER_CHECK_SUBJECT'):
|
||||||
|
@property
|
||||||
|
def hostname_checks_common_name(self):
|
||||||
|
ncs = self._host_flags & _ssl.HOSTFLAG_NEVER_CHECK_SUBJECT
|
||||||
|
return ncs != _ssl.HOSTFLAG_NEVER_CHECK_SUBJECT
|
||||||
|
|
||||||
|
@hostname_checks_common_name.setter
|
||||||
|
def hostname_checks_common_name(self, value):
|
||||||
|
if value:
|
||||||
|
self._host_flags &= ~_ssl.HOSTFLAG_NEVER_CHECK_SUBJECT
|
||||||
|
else:
|
||||||
|
self._host_flags |= _ssl.HOSTFLAG_NEVER_CHECK_SUBJECT
|
||||||
|
else:
|
||||||
|
@property
|
||||||
|
def hostname_checks_common_name(self):
|
||||||
|
return True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def verify_flags(self):
|
def verify_flags(self):
|
||||||
return VerifyFlags(super().verify_flags)
|
return VerifyFlags(super().verify_flags)
|
||||||
|
@ -699,11 +715,6 @@ class SSLObject:
|
||||||
def do_handshake(self):
|
def do_handshake(self):
|
||||||
"""Start the SSL/TLS handshake."""
|
"""Start the SSL/TLS handshake."""
|
||||||
self._sslobj.do_handshake()
|
self._sslobj.do_handshake()
|
||||||
if self.context.check_hostname:
|
|
||||||
if not self.server_hostname:
|
|
||||||
raise ValueError("check_hostname needs server_hostname "
|
|
||||||
"argument")
|
|
||||||
match_hostname(self.getpeercert(), self.server_hostname)
|
|
||||||
|
|
||||||
def unwrap(self):
|
def unwrap(self):
|
||||||
"""Start the SSL shutdown handshake."""
|
"""Start the SSL shutdown handshake."""
|
||||||
|
|
|
@ -1148,11 +1148,13 @@ class EventLoopTestsMixin:
|
||||||
with test_utils.disable_logger():
|
with test_utils.disable_logger():
|
||||||
with self.assertRaisesRegex(
|
with self.assertRaisesRegex(
|
||||||
ssl.CertificateError,
|
ssl.CertificateError,
|
||||||
"hostname '127.0.0.1' doesn't match 'localhost'"):
|
"IP address mismatch, certificate is not valid for "
|
||||||
|
"'127.0.0.1'"):
|
||||||
self.loop.run_until_complete(f_c)
|
self.loop.run_until_complete(f_c)
|
||||||
|
|
||||||
# close connection
|
# close connection
|
||||||
proto.transport.close()
|
# transport is None because TLS ALERT aborted the handshake
|
||||||
|
self.assertIsNone(proto.transport)
|
||||||
server.close()
|
server.close()
|
||||||
|
|
||||||
@support.skip_unless_bind_unix_socket
|
@support.skip_unless_bind_unix_socket
|
||||||
|
|
|
@ -330,6 +330,9 @@ if ssl is not None:
|
||||||
return
|
return
|
||||||
elif err.args[0] == ssl.SSL_ERROR_EOF:
|
elif err.args[0] == ssl.SSL_ERROR_EOF:
|
||||||
return self.handle_close()
|
return self.handle_close()
|
||||||
|
# TODO: SSLError does not expose alert information
|
||||||
|
elif "SSLV3_ALERT_BAD_CERTIFICATE" in err.args[1]:
|
||||||
|
return self.handle_close()
|
||||||
raise
|
raise
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
if err.args[0] == errno.ECONNABORTED:
|
if err.args[0] == errno.ECONNABORTED:
|
||||||
|
|
|
@ -485,7 +485,8 @@ class NewIMAPSSLTests(NewIMAPTestsMixin, unittest.TestCase):
|
||||||
ssl_context.load_verify_locations(CAFILE)
|
ssl_context.load_verify_locations(CAFILE)
|
||||||
|
|
||||||
with self.assertRaisesRegex(ssl.CertificateError,
|
with self.assertRaisesRegex(ssl.CertificateError,
|
||||||
"hostname '127.0.0.1' doesn't match 'localhost'"):
|
"IP address mismatch, certificate is not valid for "
|
||||||
|
"'127.0.0.1'"):
|
||||||
_, server = self._setup(SimpleIMAPHandler)
|
_, server = self._setup(SimpleIMAPHandler)
|
||||||
client = self.imap_class(*server.server_address,
|
client = self.imap_class(*server.server_address,
|
||||||
ssl_context=ssl_context)
|
ssl_context=ssl_context)
|
||||||
|
@ -874,7 +875,8 @@ class ThreadedNetworkedTestsSSL(ThreadedNetworkedTests):
|
||||||
|
|
||||||
with self.assertRaisesRegex(
|
with self.assertRaisesRegex(
|
||||||
ssl.CertificateError,
|
ssl.CertificateError,
|
||||||
"hostname '127.0.0.1' doesn't match 'localhost'"):
|
"IP address mismatch, certificate is not valid for "
|
||||||
|
"'127.0.0.1'"):
|
||||||
with self.reaped_server(SimpleIMAPHandler) as server:
|
with self.reaped_server(SimpleIMAPHandler) as server:
|
||||||
client = self.imap_class(*server.server_address,
|
client = self.imap_class(*server.server_address,
|
||||||
ssl_context=ssl_context)
|
ssl_context=ssl_context)
|
||||||
|
|
|
@ -176,6 +176,9 @@ class DummyPOP3Handler(asynchat.async_chat):
|
||||||
return
|
return
|
||||||
elif err.args[0] == ssl.SSL_ERROR_EOF:
|
elif err.args[0] == ssl.SSL_ERROR_EOF:
|
||||||
return self.handle_close()
|
return self.handle_close()
|
||||||
|
# TODO: SSLError does not expose alert information
|
||||||
|
elif "SSLV3_ALERT_BAD_CERTIFICATE" in err.args[1]:
|
||||||
|
return self.handle_close()
|
||||||
raise
|
raise
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
if err.args[0] == errno.ECONNABORTED:
|
if err.args[0] == errno.ECONNABORTED:
|
||||||
|
|
|
@ -988,6 +988,19 @@ class ContextTests(unittest.TestCase):
|
||||||
self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED)
|
self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED)
|
||||||
self.assertTrue(ctx.check_hostname)
|
self.assertTrue(ctx.check_hostname)
|
||||||
|
|
||||||
|
def test_hostname_checks_common_name(self):
|
||||||
|
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
||||||
|
self.assertTrue(ctx.hostname_checks_common_name)
|
||||||
|
if ssl.HAS_NEVER_CHECK_COMMON_NAME:
|
||||||
|
ctx.hostname_checks_common_name = True
|
||||||
|
self.assertTrue(ctx.hostname_checks_common_name)
|
||||||
|
ctx.hostname_checks_common_name = False
|
||||||
|
self.assertFalse(ctx.hostname_checks_common_name)
|
||||||
|
ctx.hostname_checks_common_name = True
|
||||||
|
self.assertTrue(ctx.hostname_checks_common_name)
|
||||||
|
else:
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
ctx.hostname_checks_common_name = True
|
||||||
|
|
||||||
@unittest.skipUnless(have_verify_flags(),
|
@unittest.skipUnless(have_verify_flags(),
|
||||||
"verify_flags need OpenSSL > 0.9.8")
|
"verify_flags need OpenSSL > 0.9.8")
|
||||||
|
@ -1511,6 +1524,16 @@ class SSLErrorTests(unittest.TestCase):
|
||||||
ctx.wrap_bio(ssl.MemoryBIO(), ssl.MemoryBIO(),
|
ctx.wrap_bio(ssl.MemoryBIO(), ssl.MemoryBIO(),
|
||||||
server_hostname="xn--.com")
|
server_hostname="xn--.com")
|
||||||
|
|
||||||
|
def test_bad_server_hostname(self):
|
||||||
|
ctx = ssl.create_default_context()
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
ctx.wrap_bio(ssl.MemoryBIO(), ssl.MemoryBIO(),
|
||||||
|
server_hostname="")
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
ctx.wrap_bio(ssl.MemoryBIO(), ssl.MemoryBIO(),
|
||||||
|
server_hostname=".example.org")
|
||||||
|
|
||||||
|
|
||||||
class MemoryBIOTests(unittest.TestCase):
|
class MemoryBIOTests(unittest.TestCase):
|
||||||
|
|
||||||
def test_read_write(self):
|
def test_read_write(self):
|
||||||
|
@ -2536,8 +2559,9 @@ class ThreadedTests(unittest.TestCase):
|
||||||
with server:
|
with server:
|
||||||
with client_context.wrap_socket(socket.socket(),
|
with client_context.wrap_socket(socket.socket(),
|
||||||
server_hostname="invalid") as s:
|
server_hostname="invalid") as s:
|
||||||
with self.assertRaisesRegex(ssl.CertificateError,
|
with self.assertRaisesRegex(
|
||||||
"hostname 'invalid' doesn't match 'localhost'"):
|
ssl.CertificateError,
|
||||||
|
"Hostname mismatch, certificate is not valid for 'invalid'."):
|
||||||
s.connect((HOST, server.port))
|
s.connect((HOST, server.port))
|
||||||
|
|
||||||
# missing server_hostname arg should cause an exception, too
|
# missing server_hostname arg should cause an exception, too
|
||||||
|
|
|
@ -573,7 +573,7 @@ class TestUrlopen(unittest.TestCase):
|
||||||
cafile=CERT_fakehostname)
|
cafile=CERT_fakehostname)
|
||||||
# Good cert, but mismatching hostname
|
# Good cert, but mismatching hostname
|
||||||
handler = self.start_https_server(certfile=CERT_fakehostname)
|
handler = self.start_https_server(certfile=CERT_fakehostname)
|
||||||
with self.assertRaises(ssl.CertificateError) as cm:
|
with self.assertRaises(urllib.error.URLError) as cm:
|
||||||
self.urlopen("https://localhost:%s/bizarre" % handler.port,
|
self.urlopen("https://localhost:%s/bizarre" % handler.port,
|
||||||
cafile=CERT_fakehostname)
|
cafile=CERT_fakehostname)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
The ssl module now uses OpenSSL's X509_VERIFY_PARAM_set1_host() and
|
||||||
|
X509_VERIFY_PARAM_set1_ip() API to verify hostname and IP addresses. Subject
|
||||||
|
common name fallback can be disabled with
|
||||||
|
SSLContext.hostname_checks_common_name.
|
172
Modules/_ssl.c
172
Modules/_ssl.c
|
@ -64,10 +64,13 @@ static PySocketModule_APIObject PySocketModule;
|
||||||
#include "openssl/rand.h"
|
#include "openssl/rand.h"
|
||||||
#include "openssl/bio.h"
|
#include "openssl/bio.h"
|
||||||
|
|
||||||
/* Set HAVE_X509_VERIFY_PARAM_SET1_HOST for non-autoconf builds */
|
|
||||||
#ifndef HAVE_X509_VERIFY_PARAM_SET1_HOST
|
#ifndef HAVE_X509_VERIFY_PARAM_SET1_HOST
|
||||||
# if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER > 0x1000200fL
|
# ifdef LIBRESSL_VERSION_NUMBER
|
||||||
|
# error "LibreSSL is missing X509_VERIFY_PARAM_set1_host(), see https://github.com/libressl-portable/portable/issues/381"
|
||||||
|
# elif OPENSSL_VERSION_NUMBER > 0x1000200fL
|
||||||
# define HAVE_X509_VERIFY_PARAM_SET1_HOST
|
# define HAVE_X509_VERIFY_PARAM_SET1_HOST
|
||||||
|
# else
|
||||||
|
# error "libssl is too old and does not support X509_VERIFY_PARAM_set1_host()"
|
||||||
# endif
|
# endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -217,11 +220,6 @@ static STACK_OF(X509_OBJECT) *X509_STORE_get0_objects(X509_STORE *store) {
|
||||||
return store->objs;
|
return store->objs;
|
||||||
}
|
}
|
||||||
|
|
||||||
static X509_VERIFY_PARAM *X509_STORE_get0_param(X509_STORE *store)
|
|
||||||
{
|
|
||||||
return store->param;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
SSL_SESSION_has_ticket(const SSL_SESSION *s)
|
SSL_SESSION_has_ticket(const SSL_SESSION *s)
|
||||||
{
|
{
|
||||||
|
@ -317,6 +315,10 @@ typedef struct {
|
||||||
PyObject *set_hostname;
|
PyObject *set_hostname;
|
||||||
#endif
|
#endif
|
||||||
int check_hostname;
|
int check_hostname;
|
||||||
|
/* OpenSSL has no API to get hostflags from X509_VERIFY_PARAM* struct.
|
||||||
|
* We have to maintain our own copy. OpenSSL's hostflags default to 0.
|
||||||
|
*/
|
||||||
|
unsigned int hostflags;
|
||||||
} PySSLContext;
|
} PySSLContext;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -701,6 +703,74 @@ _setSSLError (const char *errstr, int errcode, const char *filename, int lineno)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SSL objects
|
||||||
|
*/
|
||||||
|
|
||||||
|
static int
|
||||||
|
_ssl_configure_hostname(PySSLSocket *self, const char* server_hostname)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
ASN1_OCTET_STRING *ip;
|
||||||
|
PyObject *hostname;
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
assert(server_hostname);
|
||||||
|
|
||||||
|
/* Disable OpenSSL's special mode with leading dot in hostname:
|
||||||
|
* When name starts with a dot (e.g ".example.com"), it will be
|
||||||
|
* matched by a certificate valid for any sub-domain of name.
|
||||||
|
*/
|
||||||
|
len = strlen(server_hostname);
|
||||||
|
if (len == 0 || *server_hostname == '.') {
|
||||||
|
PyErr_SetString(
|
||||||
|
PyExc_ValueError,
|
||||||
|
"server_hostname cannot be an empty string or start with a "
|
||||||
|
"leading dot.");
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* inet_pton is not available on all platforms. */
|
||||||
|
ip = a2i_IPADDRESS(server_hostname);
|
||||||
|
if (ip == NULL) {
|
||||||
|
ERR_clear_error();
|
||||||
|
}
|
||||||
|
|
||||||
|
hostname = PyUnicode_Decode(server_hostname, len, "idna", "strict");
|
||||||
|
if (hostname == NULL) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
self->server_hostname = hostname;
|
||||||
|
|
||||||
|
/* Only send SNI extension for non-IP hostnames */
|
||||||
|
if (ip == NULL) {
|
||||||
|
if (!SSL_set_tlsext_host_name(self->ssl, server_hostname)) {
|
||||||
|
_setSSLError(NULL, 0, __FILE__, __LINE__);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (self->ctx->check_hostname) {
|
||||||
|
X509_VERIFY_PARAM *param = SSL_get0_param(self->ssl);
|
||||||
|
if (ip == NULL) {
|
||||||
|
if (!X509_VERIFY_PARAM_set1_host(param, server_hostname, 0)) {
|
||||||
|
_setSSLError(NULL, 0, __FILE__, __LINE__);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!X509_VERIFY_PARAM_set1_ip(param, ASN1_STRING_data(ip),
|
||||||
|
ASN1_STRING_length(ip))) {
|
||||||
|
_setSSLError(NULL, 0, __FILE__, __LINE__);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
retval = 0;
|
||||||
|
error:
|
||||||
|
if (ip != NULL) {
|
||||||
|
ASN1_OCTET_STRING_free(ip);
|
||||||
|
}
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
static PySSLSocket *
|
static PySSLSocket *
|
||||||
newPySSLSocket(PySSLContext *sslctx, PySocketSockObject *sock,
|
newPySSLSocket(PySSLContext *sslctx, PySocketSockObject *sock,
|
||||||
enum py_ssl_server_or_client socket_type,
|
enum py_ssl_server_or_client socket_type,
|
||||||
|
@ -722,15 +792,6 @@ newPySSLSocket(PySSLContext *sslctx, PySocketSockObject *sock,
|
||||||
self->shutdown_seen_zero = 0;
|
self->shutdown_seen_zero = 0;
|
||||||
self->owner = NULL;
|
self->owner = NULL;
|
||||||
self->server_hostname = NULL;
|
self->server_hostname = NULL;
|
||||||
if (server_hostname != NULL) {
|
|
||||||
PyObject *hostname = PyUnicode_Decode(server_hostname, strlen(server_hostname),
|
|
||||||
"idna", "strict");
|
|
||||||
if (hostname == NULL) {
|
|
||||||
Py_DECREF(self);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
self->server_hostname = hostname;
|
|
||||||
}
|
|
||||||
self->ssl_errno = 0;
|
self->ssl_errno = 0;
|
||||||
self->c_errno = 0;
|
self->c_errno = 0;
|
||||||
#ifdef MS_WINDOWS
|
#ifdef MS_WINDOWS
|
||||||
|
@ -761,10 +822,12 @@ newPySSLSocket(PySSLContext *sslctx, PySocketSockObject *sock,
|
||||||
#endif
|
#endif
|
||||||
SSL_set_mode(self->ssl, mode);
|
SSL_set_mode(self->ssl, mode);
|
||||||
|
|
||||||
#if HAVE_SNI
|
if (server_hostname != NULL) {
|
||||||
if (server_hostname != NULL)
|
if (_ssl_configure_hostname(self, server_hostname) < 0) {
|
||||||
SSL_set_tlsext_host_name(self->ssl, server_hostname);
|
Py_DECREF(self);
|
||||||
#endif
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
/* If the socket is in non-blocking mode or timeout mode, set the BIO
|
/* If the socket is in non-blocking mode or timeout mode, set the BIO
|
||||||
* to non-blocking mode (blocking is the default)
|
* to non-blocking mode (blocking is the default)
|
||||||
*/
|
*/
|
||||||
|
@ -2711,6 +2774,7 @@ _ssl__SSLContext_impl(PyTypeObject *type, int proto_version)
|
||||||
PySSLContext *self;
|
PySSLContext *self;
|
||||||
long options;
|
long options;
|
||||||
SSL_CTX *ctx = NULL;
|
SSL_CTX *ctx = NULL;
|
||||||
|
X509_VERIFY_PARAM *params;
|
||||||
int result;
|
int result;
|
||||||
#if defined(SSL_MODE_RELEASE_BUFFERS)
|
#if defined(SSL_MODE_RELEASE_BUFFERS)
|
||||||
unsigned long libver;
|
unsigned long libver;
|
||||||
|
@ -2760,6 +2824,7 @@ _ssl__SSLContext_impl(PyTypeObject *type, int proto_version)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
self->ctx = ctx;
|
self->ctx = ctx;
|
||||||
|
self->hostflags = X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS;
|
||||||
#if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG)
|
#if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG)
|
||||||
self->npn_protocols = NULL;
|
self->npn_protocols = NULL;
|
||||||
#endif
|
#endif
|
||||||
|
@ -2858,14 +2923,13 @@ _ssl__SSLContext_impl(PyTypeObject *type, int proto_version)
|
||||||
sizeof(SID_CTX));
|
sizeof(SID_CTX));
|
||||||
#undef SID_CTX
|
#undef SID_CTX
|
||||||
|
|
||||||
|
params = SSL_CTX_get0_param(self->ctx);
|
||||||
#ifdef X509_V_FLAG_TRUSTED_FIRST
|
#ifdef X509_V_FLAG_TRUSTED_FIRST
|
||||||
{
|
/* Improve trust chain building when cross-signed intermediate
|
||||||
/* Improve trust chain building when cross-signed intermediate
|
certificates are present. See https://bugs.python.org/issue23476. */
|
||||||
certificates are present. See https://bugs.python.org/issue23476. */
|
X509_VERIFY_PARAM_set_flags(params, X509_V_FLAG_TRUSTED_FIRST);
|
||||||
X509_STORE *store = SSL_CTX_get_cert_store(self->ctx);
|
|
||||||
X509_STORE_set_flags(store, X509_V_FLAG_TRUSTED_FIRST);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
X509_VERIFY_PARAM_set_hostflags(params, self->hostflags);
|
||||||
|
|
||||||
return (PyObject *)self;
|
return (PyObject *)self;
|
||||||
}
|
}
|
||||||
|
@ -3152,12 +3216,10 @@ set_verify_mode(PySSLContext *self, PyObject *arg, void *c)
|
||||||
static PyObject *
|
static PyObject *
|
||||||
get_verify_flags(PySSLContext *self, void *c)
|
get_verify_flags(PySSLContext *self, void *c)
|
||||||
{
|
{
|
||||||
X509_STORE *store;
|
|
||||||
X509_VERIFY_PARAM *param;
|
X509_VERIFY_PARAM *param;
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
|
|
||||||
store = SSL_CTX_get_cert_store(self->ctx);
|
param = SSL_CTX_get0_param(self->ctx);
|
||||||
param = X509_STORE_get0_param(store);
|
|
||||||
flags = X509_VERIFY_PARAM_get_flags(param);
|
flags = X509_VERIFY_PARAM_get_flags(param);
|
||||||
return PyLong_FromUnsignedLong(flags);
|
return PyLong_FromUnsignedLong(flags);
|
||||||
}
|
}
|
||||||
|
@ -3165,14 +3227,12 @@ get_verify_flags(PySSLContext *self, void *c)
|
||||||
static int
|
static int
|
||||||
set_verify_flags(PySSLContext *self, PyObject *arg, void *c)
|
set_verify_flags(PySSLContext *self, PyObject *arg, void *c)
|
||||||
{
|
{
|
||||||
X509_STORE *store;
|
|
||||||
X509_VERIFY_PARAM *param;
|
X509_VERIFY_PARAM *param;
|
||||||
unsigned long new_flags, flags, set, clear;
|
unsigned long new_flags, flags, set, clear;
|
||||||
|
|
||||||
if (!PyArg_Parse(arg, "k", &new_flags))
|
if (!PyArg_Parse(arg, "k", &new_flags))
|
||||||
return -1;
|
return -1;
|
||||||
store = SSL_CTX_get_cert_store(self->ctx);
|
param = SSL_CTX_get0_param(self->ctx);
|
||||||
param = X509_STORE_get0_param(store);
|
|
||||||
flags = X509_VERIFY_PARAM_get_flags(param);
|
flags = X509_VERIFY_PARAM_get_flags(param);
|
||||||
clear = flags & ~new_flags;
|
clear = flags & ~new_flags;
|
||||||
set = ~flags & new_flags;
|
set = ~flags & new_flags;
|
||||||
|
@ -3220,6 +3280,27 @@ set_options(PySSLContext *self, PyObject *arg, void *c)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
get_host_flags(PySSLContext *self, void *c)
|
||||||
|
{
|
||||||
|
return PyLong_FromUnsignedLong(self->hostflags);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
set_host_flags(PySSLContext *self, PyObject *arg, void *c)
|
||||||
|
{
|
||||||
|
X509_VERIFY_PARAM *param;
|
||||||
|
unsigned int new_flags = 0;
|
||||||
|
|
||||||
|
if (!PyArg_Parse(arg, "I", &new_flags))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
param = SSL_CTX_get0_param(self->ctx);
|
||||||
|
self->hostflags = new_flags;
|
||||||
|
X509_VERIFY_PARAM_set_hostflags(param, new_flags);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
get_check_hostname(PySSLContext *self, void *c)
|
get_check_hostname(PySSLContext *self, void *c)
|
||||||
{
|
{
|
||||||
|
@ -4104,6 +4185,8 @@ _ssl__SSLContext_get_ca_certs_impl(PySSLContext *self, int binary_form)
|
||||||
static PyGetSetDef context_getsetlist[] = {
|
static PyGetSetDef context_getsetlist[] = {
|
||||||
{"check_hostname", (getter) get_check_hostname,
|
{"check_hostname", (getter) get_check_hostname,
|
||||||
(setter) set_check_hostname, NULL},
|
(setter) set_check_hostname, NULL},
|
||||||
|
{"_host_flags", (getter) get_host_flags,
|
||||||
|
(setter) set_host_flags, NULL},
|
||||||
{"options", (getter) get_options,
|
{"options", (getter) get_options,
|
||||||
(setter) set_options, NULL},
|
(setter) set_options, NULL},
|
||||||
{"verify_flags", (getter) get_verify_flags,
|
{"verify_flags", (getter) get_verify_flags,
|
||||||
|
@ -5491,6 +5574,31 @@ PyInit__ssl(void)
|
||||||
SSL_OP_NO_COMPRESSION);
|
SSL_OP_NO_COMPRESSION);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT
|
||||||
|
PyModule_AddIntConstant(m, "HOSTFLAG_ALWAYS_CHECK_SUBJECT",
|
||||||
|
X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT);
|
||||||
|
#endif
|
||||||
|
#ifdef X509_CHECK_FLAG_NEVER_CHECK_SUBJECT
|
||||||
|
PyModule_AddIntConstant(m, "HOSTFLAG_NEVER_CHECK_SUBJECT",
|
||||||
|
X509_CHECK_FLAG_NEVER_CHECK_SUBJECT);
|
||||||
|
#endif
|
||||||
|
#ifdef X509_CHECK_FLAG_NO_WILDCARDS
|
||||||
|
PyModule_AddIntConstant(m, "HOSTFLAG_NO_WILDCARDS",
|
||||||
|
X509_CHECK_FLAG_NO_WILDCARDS);
|
||||||
|
#endif
|
||||||
|
#ifdef X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS
|
||||||
|
PyModule_AddIntConstant(m, "HOSTFLAG_NO_PARTIAL_WILDCARDS",
|
||||||
|
X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
|
||||||
|
#endif
|
||||||
|
#ifdef X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS
|
||||||
|
PyModule_AddIntConstant(m, "HOSTFLAG_MULTI_LABEL_WILDCARDS",
|
||||||
|
X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS);
|
||||||
|
#endif
|
||||||
|
#ifdef X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS
|
||||||
|
PyModule_AddIntConstant(m, "HOSTFLAG_SINGLE_LABEL_SUBDOMAINS",
|
||||||
|
X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS);
|
||||||
|
#endif
|
||||||
|
|
||||||
#if HAVE_SNI
|
#if HAVE_SNI
|
||||||
r = Py_True;
|
r = Py_True;
|
||||||
#else
|
#else
|
||||||
|
|
|
@ -687,4 +687,7 @@ Py_NO_ENABLE_SHARED to find out. Also support MS_NO_COREDLL for b/w compat */
|
||||||
/* framework name */
|
/* framework name */
|
||||||
#define _PYTHONFRAMEWORK ""
|
#define _PYTHONFRAMEWORK ""
|
||||||
|
|
||||||
|
/* Define if libssl has X509_VERIFY_PARAM_set1_host and related function */
|
||||||
|
#define HAVE_X509_VERIFY_PARAM_SET1_HOST 1
|
||||||
|
|
||||||
#endif /* !Py_CONFIG_H */
|
#endif /* !Py_CONFIG_H */
|
||||||
|
|
27
setup.py
27
setup.py
|
@ -363,6 +363,16 @@ class PyBuildExt(build_ext):
|
||||||
print_three_column(failed)
|
print_three_column(failed)
|
||||||
print()
|
print()
|
||||||
|
|
||||||
|
if any('_ssl' in l
|
||||||
|
for l in (missing, self.failed, self.failed_on_import)):
|
||||||
|
print()
|
||||||
|
print("Could not build the ssl module!")
|
||||||
|
print("Python requires an OpenSSL 1.0.2 or 1.1 compatible "
|
||||||
|
"libssl with X509_VERIFY_PARAM_set1_host().")
|
||||||
|
print("LibreSSL 2.6.4 and earlier do not provide the necessary "
|
||||||
|
"APIs, https://github.com/libressl-portable/portable/issues/381")
|
||||||
|
print()
|
||||||
|
|
||||||
def build_extension(self, ext):
|
def build_extension(self, ext):
|
||||||
|
|
||||||
if ext.name == '_ctypes':
|
if ext.name == '_ctypes':
|
||||||
|
@ -2144,13 +2154,16 @@ class PyBuildExt(build_ext):
|
||||||
if krb5_h:
|
if krb5_h:
|
||||||
ssl_incs.extend(krb5_h)
|
ssl_incs.extend(krb5_h)
|
||||||
|
|
||||||
ssl_ext = Extension(
|
if config_vars.get("HAVE_X509_VERIFY_PARAM_SET1_HOST"):
|
||||||
'_ssl', ['_ssl.c'],
|
ssl_ext = Extension(
|
||||||
include_dirs=openssl_includes,
|
'_ssl', ['_ssl.c'],
|
||||||
library_dirs=openssl_libdirs,
|
include_dirs=openssl_includes,
|
||||||
libraries=openssl_libs,
|
library_dirs=openssl_libdirs,
|
||||||
depends=['socketmodule.h']
|
libraries=openssl_libs,
|
||||||
)
|
depends=['socketmodule.h']
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
ssl_ext = None
|
||||||
|
|
||||||
hashlib_ext = Extension(
|
hashlib_ext = Extension(
|
||||||
'_hashlib', ['_hashopenssl.c'],
|
'_hashlib', ['_hashopenssl.c'],
|
||||||
|
|
Loading…
Reference in New Issue