FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

bpo-31399: Let OpenSSL verify hostname and IP address by tiran · Pull Request #3462 · python/cpython · GitHub

Repository navigation

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension .c  (1) .py  (7) .rst  (1) All 3 file types selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
Next Next commit
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.

Signed-off-by: Christian Heimes <christian@python.org>
  • Loading branch information
tiran committed Jan 26, 2018
commit f289cab3e70595d6e79fb797069e029403d991d6
16 changes: 13 additions & 3 deletions Lib/ssl.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@
lambda name: name.startswith('CERT_'),
source=_ssl)

_IntFlag._convert(
'HostFlags', __name__,
lambda name: name.startswith('HOSTFLAG_'),
source=_ssl)

PROTOCOL_SSLv23 = _SSLMethod.PROTOCOL_SSLv23 = _SSLMethod.PROTOCOL_TLS
_PROTOCOL_NAMES = {value: name for name, value in _SSLMethod.__members__.items()}
Expand Down Expand Up @@ -216,9 +220,7 @@
'!aNULL:!eNULL:!MD5:!DSS:!RC4:!3DES'
)


class CertificateError(ValueError):
pass
CertificateError = SSLCertVerificationError


def _dnsname_match(dn, hostname):
Expand Down Expand Up @@ -473,6 +475,14 @@ def options(self):
def options(self, value):
super(SSLContext, SSLContext).options.__set__(self, value)

@property
def host_flags(self):
return HostFlags(super().host_flags)

@host_flags.setter
def host_flags(self, value):
super(SSLContext, SSLContext).host_flags.__set__(self, value)

@property
def verify_flags(self):
return VerifyFlags(super().verify_flags)
Expand Down
6 changes: 4 additions & 2 deletions Lib/test/test_asyncio/test_events.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -1148,11 +1148,13 @@ def test_create_server_ssl_match_failed(self):
with test_utils.disable_logger():
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'"):
self.loop.run_until_complete(f_c)

# close connection
proto.transport.close()
# transport is None because TLS ALERT aborted the handshake
self.assertIsNone(proto.transport)
server.close()

@support.skip_unless_bind_unix_socket
Expand Down
3 changes: 3 additions & 0 deletions Lib/test/test_ftplib.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,9 @@ def _do_ssl_handshake(self):
return
elif err.args[0] == ssl.SSL_ERROR_EOF:
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
except OSError as err:
if err.args[0] == errno.ECONNABORTED:
Expand Down
6 changes: 4 additions & 2 deletions Lib/test/test_imaplib.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,8 @@ def test_ssl_raises(self):
ssl_context.load_verify_locations(CAFILE)

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)
client = self.imap_class(*server.server_address,
ssl_context=ssl_context)
Expand Down Expand Up @@ -874,7 +875,8 @@ def test_ssl_verified(self):

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'"):
with self.reaped_server(SimpleIMAPHandler) as server:
client = self.imap_class(*server.server_address,
ssl_context=ssl_context)
Expand Down
3 changes: 3 additions & 0 deletions Lib/test/test_poplib.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,9 @@ def _do_tls_handshake(self):
return
elif err.args[0] == ssl.SSL_ERROR_EOF:
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
except OSError as err:
if err.args[0] == errno.ECONNABORTED:
Expand Down
5 changes: 3 additions & 2 deletions Lib/test/test_ssl.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -2536,8 +2536,9 @@ def test_check_hostname(self):
with server:
with client_context.wrap_socket(socket.socket(),
server_hostname="invalid") as s:
with self.assertRaisesRegex(ssl.CertificateError,
"hostname 'invalid' doesn't match 'localhost'"):
with self.assertRaisesRegex(
ssl.CertificateError,
"Hostname mismatch, certificate is not valid for 'invalid'."):
s.connect((HOST, server.port))

# missing server_hostname arg should cause an exception, too
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_urllib2_localnet.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,7 @@ def test_https_with_cafile(self):
cafile=CERT_fakehostname)
# Good cert, but mismatching hostname
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,
cafile=CERT_fakehostname)

Expand Down
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Let OpenSSL verify hostname and IP addressw
150 changes: 125 additions & 25 deletions Modules/_ssl.c
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,10 @@ typedef struct {
PyObject *set_hostname;
#endif
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;

typedef struct {
Expand Down Expand Up @@ -701,6 +705,61 @@ _setSSLError (const char *errstr, int errcode, const char *filename, int lineno)
return NULL;
}

/*
* SSL objects
*/

static int
_ssl_configure_hostname(PySSLSocket *self, const char* server_hostname)
{
int retval = -1;
ASN1_OCTET_STRING *ip;
PyObject *hostname;

assert(server_hostname);

/* inet_pton is not available on all platforms. */
ip = a2i_IPADDRESS(server_hostname);
if (ip == NULL) {
ERR_clear_error();
}

hostname = PyUnicode_Decode(server_hostname, strlen(server_hostname),
"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 *
newPySSLSocket(PySSLContext *sslctx, PySocketSockObject *sock,
enum py_ssl_server_or_client socket_type,
Expand All @@ -722,15 +781,6 @@ newPySSLSocket(PySSLContext *sslctx, PySocketSockObject *sock,
self->shutdown_seen_zero = 0;
self->owner = 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->c_errno = 0;
#ifdef MS_WINDOWS
Expand Down Expand Up @@ -761,10 +811,12 @@ newPySSLSocket(PySSLContext *sslctx, PySocketSockObject *sock,
#endif
SSL_set_mode(self->ssl, mode);

#if HAVE_SNI
if (server_hostname != NULL)
SSL_set_tlsext_host_name(self->ssl, server_hostname);
#endif
if (server_hostname != NULL) {
if (_ssl_configure_hostname(self, server_hostname) < 0) {
Py_DECREF(self);
return NULL;
}
}
/* If the socket is in non-blocking mode or timeout mode, set the BIO
* to non-blocking mode (blocking is the default)
*/
Expand Down Expand Up @@ -2711,6 +2763,7 @@ _ssl__SSLContext_impl(PyTypeObject *type, int proto_version)
PySSLContext *self;
long options;
SSL_CTX *ctx = NULL;
X509_VERIFY_PARAM *params;
int result;
#if defined(SSL_MODE_RELEASE_BUFFERS)
unsigned long libver;
Expand Down Expand Up @@ -2760,6 +2813,10 @@ _ssl__SSLContext_impl(PyTypeObject *type, int proto_version)
return NULL;
}
self->ctx = ctx;
self->hostflags = (
X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS |
X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS
);
#if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG)
self->npn_protocols = NULL;
#endif
Expand Down Expand Up @@ -2858,14 +2915,13 @@ _ssl__SSLContext_impl(PyTypeObject *type, int proto_version)
sizeof(SID_CTX));
#undef SID_CTX

params = SSL_CTX_get0_param(self->ctx);
#ifdef X509_V_FLAG_TRUSTED_FIRST
{
/* Improve trust chain building when cross-signed intermediate
certificates are present. See https://bugs.python.org/issue23476. */
X509_STORE *store = SSL_CTX_get_cert_store(self->ctx);
X509_STORE_set_flags(store, X509_V_FLAG_TRUSTED_FIRST);
}
/* Improve trust chain building when cross-signed intermediate
certificates are present. See https://bugs.python.org/issue23476. */
X509_VERIFY_PARAM_set_flags(params, X509_V_FLAG_TRUSTED_FIRST);
#endif
X509_VERIFY_PARAM_set_hostflags(params, self->hostflags);

return (PyObject *)self;
}
Expand Down Expand Up @@ -3152,27 +3208,23 @@ set_verify_mode(PySSLContext *self, PyObject *arg, void *c)
static PyObject *
get_verify_flags(PySSLContext *self, void *c)
{
X509_STORE *store;
X509_VERIFY_PARAM *param;
unsigned long flags;

store = SSL_CTX_get_cert_store(self->ctx);
param = X509_STORE_get0_param(store);
param = SSL_CTX_get0_param(self->ctx);
flags = X509_VERIFY_PARAM_get_flags(param);
return PyLong_FromUnsignedLong(flags);
}

static int
set_verify_flags(PySSLContext *self, PyObject *arg, void *c)
{
X509_STORE *store;
X509_VERIFY_PARAM *param;
unsigned long new_flags, flags, set, clear;

if (!PyArg_Parse(arg, "k", &new_flags))
return -1;
store = SSL_CTX_get_cert_store(self->ctx);
param = X509_STORE_get0_param(store);
param = SSL_CTX_get0_param(self->ctx);
flags = X509_VERIFY_PARAM_get_flags(param);
clear = flags & ~new_flags;
set = ~flags & new_flags;
Expand Down Expand Up @@ -3220,6 +3272,27 @@ set_options(PySSLContext *self, PyObject *arg, void *c)
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 *
get_check_hostname(PySSLContext *self, void *c)
{
Expand Down Expand Up @@ -4104,6 +4177,8 @@ _ssl__SSLContext_get_ca_certs_impl(PySSLContext *self, int binary_form)
static PyGetSetDef context_getsetlist[] = {
{"check_hostname", (getter) get_check_hostname,
(setter) set_check_hostname, NULL},
{"host_flags", (getter) get_host_flags,
(setter) set_host_flags, NULL},
{"options", (getter) get_options,
(setter) set_options, NULL},
{"verify_flags", (getter) get_verify_flags,
Expand Down Expand Up @@ -5491,6 +5566,31 @@ PyInit__ssl(void)
SSL_OP_NO_COMPRESSION);
#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
r = Py_True;
#else
Expand Down

Back | FazBrowse Home | New Git URL