diff options
author | Christian Heimes <christian@python.org> | 2018-01-27 14:51:38 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-01-27 14:51:38 (GMT) |
commit | 61d478c71c5341cdc54e6bfb4ace4252852fd972 (patch) | |
tree | 5ad17242b4c341df03664ee5cde87cdb80b0ee50 /Modules | |
parent | 746cc75541f31278864a10b995e7d009bd2ff053 (diff) | |
download | cpython-61d478c71c5341cdc54e6bfb4ace4252852fd972.zip cpython-61d478c71c5341cdc54e6bfb4ace4252852fd972.tar.gz cpython-61d478c71c5341cdc54e6bfb4ace4252852fd972.tar.bz2 |
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>
Diffstat (limited to 'Modules')
-rw-r--r-- | Modules/_ssl.c | 172 |
1 files changed, 140 insertions, 32 deletions
diff --git a/Modules/_ssl.c b/Modules/_ssl.c index c5eec7e..ec8c8af 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -64,10 +64,13 @@ static PySocketModule_APIObject PySocketModule; #include "openssl/rand.h" #include "openssl/bio.h" -/* Set HAVE_X509_VERIFY_PARAM_SET1_HOST for non-autoconf builds */ #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 +# else +# error "libssl is too old and does not support X509_VERIFY_PARAM_set1_host()" # endif #endif @@ -217,11 +220,6 @@ static STACK_OF(X509_OBJECT) *X509_STORE_get0_objects(X509_STORE *store) { return store->objs; } -static X509_VERIFY_PARAM *X509_STORE_get0_param(X509_STORE *store) -{ - return store->param; -} - static int SSL_SESSION_has_ticket(const SSL_SESSION *s) { @@ -317,6 +315,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 { @@ -701,6 +703,74 @@ _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; + 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 * newPySSLSocket(PySSLContext *sslctx, PySocketSockObject *sock, enum py_ssl_server_or_client socket_type, @@ -722,15 +792,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 @@ -761,10 +822,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) */ @@ -2711,6 +2774,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; @@ -2760,6 +2824,7 @@ _ssl__SSLContext_impl(PyTypeObject *type, int proto_version) return NULL; } self->ctx = ctx; + self->hostflags = X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS; #if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG) self->npn_protocols = NULL; #endif @@ -2858,14 +2923,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; } @@ -3152,12 +3216,10 @@ 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); } @@ -3165,14 +3227,12 @@ get_verify_flags(PySSLContext *self, void *c) 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; @@ -3221,6 +3281,27 @@ set_options(PySSLContext *self, PyObject *arg, void *c) } 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) { return PyBool_FromLong(self->check_hostname); @@ -4104,6 +4185,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, @@ -5491,6 +5574,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 |