diff -r 7c9327ff5de6 Doc/library/ssl.rst --- a/Doc/library/ssl.rst Sat Dec 15 22:36:49 2012 +0100 +++ b/Doc/library/ssl.rst Sun Dec 16 14:02:06 2012 +1100 @@ -533,6 +533,42 @@ .. versionadded:: 3.2 +.. data:: ALERT_DESCRIPTION_CLOSE_NOTIFY + ALERT_DESCRIPTION_UNEXPECTED_MESSAGE + ALERT_DESCRIPTION_BAD_RECORD_MAC + ALERT_DESCRIPTION_RECORD_OVERFLOW + ALERT_DESCRIPTION_DECOMPRESSION_FAILURE + ALERT_DESCRIPTION_HANDSHAKE_FAILURE + ALERT_DESCRIPTION_BAD_CERTIFICATE + ALERT_DESCRIPTION_UNSUPPORTED_CERTIFICATE + ALERT_DESCRIPTION_CERTIFICATE_REVOKED + ALERT_DESCRIPTION_CERTIFICATE_EXPIRED + ALERT_DESCRIPTION_CERTIFICATE_UNKNOWN + ALERT_DESCRIPTION_ILLEGAL_PARAMETER + ALERT_DESCRIPTION_UNKNOWN_CA + ALERT_DESCRIPTION_ACCESS_DENIED + ALERT_DESCRIPTION_DECODE_ERROR + ALERT_DESCRIPTION_DECRYPT_ERROR + ALERT_DESCRIPTION_PROTOCOL_VERSION + ALERT_DESCRIPTION_INSUFFICIENT_SECURITY + ALERT_DESCRIPTION_INTERNAL_ERROR + ALERT_DESCRIPTION_USER_CANCELLED + ALERT_DESCRIPTION_NO_RENEGOTIATION + ALERT_DESCRIPTION_UNSUPPORTED_EXTENSION + ALERT_DESCRIPTION_CERTIFICATE_UNOBTAINABLE + ALERT_DESCRIPTION_UNRECOGNIZED_NAME + ALERT_DESCRIPTION_BAD_CERTIFICATE_STATUS_RESPONSE + ALERT_DESCRIPTION_BAD_CERTIFICATE_HASH_VALUE + ALERT_DESCRIPTION_UNKNOWN_PSK_IDENTITY + + Alert Descriptions from :rfc:`5246` and others. The `IANA TLS Alert Registry + `_ + contains this list and references to the RFCs where their meaning is defined. + + Used as the return value of the callback function in + :meth:`SSLContext.set_servername_callback`. + + .. versionadded:: 3.4 SSL Sockets ----------- @@ -780,6 +816,55 @@ .. versionadded:: 3.3 +.. method:: SSLContext.set_servername_callback(server_name_callback) + + Register a callback function that will be called after the TLS Client Hello + handshake message has been received by the SSL/TLS server when the TLS client + specifies a server name indication. The server name indication mechanism + is specified in :rfc:`6066` section 3 - Server Name Indication. + + Only one callback can be set per ``SSLContext``. If *server_name_callback* + is ``None`` then the callback is disabled. Calling this function a + subsequent time will disable the previously registered callback. + + The callback function, *server_name_callback*, will be called with three + arguments; the first being the :class:`ssl.SSLSocket`, the second is a string + that represents the server name that the client is intending to communicate + and the third argument is the original :class:`SSLContext`. The server name + argument is the IDNA decoded server name. + + A typical use of this callback is to change the :class:`ssl.SSLSocket`'s + :attr:`SSLSocket.context` attribute to a new object of type + :class:`SSLContext` representing a certificate chain that matches the server + name. + + Due to the early negotation phase of the TLS connection only limited + methods and attributes are usable like + :meth:`SSLSocket.selected_npn_protocol` and :attr:`SSLSocket.context`. + :meth:`SSLSocket.getpeercert`, :meth:`SSLSocket.getpeercert`, + :meth:`SSLSocket.cipher` and :meth:`SSLSocket.compress` methods require that + the TLS connection has progressed beyond the TLS Client Hello and therefore + will not contain return meaning values nor can they be called safely. + + The *server_name_callback* function must return ``None`` to allow the + the TLS negiotation to continue. If a TLS failure is required a constant + :const:`ALERT_DESCRIPTION_* ` can be + returned. Other return values will result in a TLS fatal error + :const:`ALERT_DESCRIPTION_INTERNAL_ERROR`. + + If there is a IDNA decoding error on the server name, the TLS connection + will terminate with an :const:`ALERT_DESCRIPTION_INTERNAL_ERROR` fatal TLS + alert message to the client. + + If an exception is raised from the *server_name_callback* function the TLS + connection will terminate with a fatal TLS alert message + :const:`ALERT_DESCRIPTION_HANDSHAKE_FAILURE`. + + This method will raise :exc:`NotImplementedError` if the OpenSSL library has + OPENSSL_NO_TLSEXT defined when it was built. + + .. versionadded:: 3.4 + .. method:: SSLContext.load_dh_params(dhfile) Load the key generation parameters for Diffie-Helman (DH) key exchange. @@ -1313,3 +1398,12 @@ `RFC 4366: Transport Layer Security (TLS) Extensions `_ Blake-Wilson et. al. + + `RFC 5246: The Transport Layer Security (TLS) Protocol Version 1.2 `_ + T. Dierks et. al. + + `RFC 6066: Transport Layer Security (TLS) Extensions `_ + D. Eastlake + + `IANA TLS: Transport Layer Security (TLS) Parameters `_ + IANA diff -r 7c9327ff5de6 Lib/ssl.py --- a/Lib/ssl.py Sat Dec 15 22:36:49 2012 +0100 +++ b/Lib/ssl.py Sun Dec 16 14:02:06 2012 +1100 @@ -52,6 +52,37 @@ PROTOCOL_SSLv3 PROTOCOL_SSLv23 PROTOCOL_TLSv1 + +The following constants identify various SSL alert message descriptions as per +http://www.iana.org/assignments/tls-parameters/tls-parameters.xml#tls-parameters-6 + +ALERT_DESCRIPTION_CLOSE_NOTIFY +ALERT_DESCRIPTION_UNEXPECTED_MESSAGE +ALERT_DESCRIPTION_BAD_RECORD_MAC +ALERT_DESCRIPTION_RECORD_OVERFLOW +ALERT_DESCRIPTION_DECOMPRESSION_FAILURE +ALERT_DESCRIPTION_HANDSHAKE_FAILURE +ALERT_DESCRIPTION_BAD_CERTIFICATE +ALERT_DESCRIPTION_UNSUPPORTED_CERTIFICATE +ALERT_DESCRIPTION_CERTIFICATE_REVOKED +ALERT_DESCRIPTION_CERTIFICATE_EXPIRED +ALERT_DESCRIPTION_CERTIFICATE_UNKNOWN +ALERT_DESCRIPTION_ILLEGAL_PARAMETER +ALERT_DESCRIPTION_UNKNOWN_CA +ALERT_DESCRIPTION_ACCESS_DENIED +ALERT_DESCRIPTION_DECODE_ERROR +ALERT_DESCRIPTION_DECRYPT_ERROR +ALERT_DESCRIPTION_PROTOCOL_VERSION +ALERT_DESCRIPTION_INSUFFICIENT_SECURITY +ALERT_DESCRIPTION_INTERNAL_ERROR +ALERT_DESCRIPTION_USER_CANCELLED +ALERT_DESCRIPTION_NO_RENEGOTIATION +ALERT_DESCRIPTION_UNSUPPORTED_EXTENSION +ALERT_DESCRIPTION_CERTIFICATE_UNOBTAINABLE +ALERT_DESCRIPTION_UNRECOGNIZED_NAME +ALERT_DESCRIPTION_BAD_CERTIFICATE_STATUS_RESPONSE +ALERT_DESCRIPTION_BAD_CERTIFICATE_HASH_VALUE +ALERT_DESCRIPTION_UNKNOWN_PSK_IDENTITY """ import textwrap @@ -91,6 +122,37 @@ SSL_ERROR_INVALID_ERROR_CODE, ) from _ssl import HAS_SNI, HAS_ECDH, HAS_NPN + +from _ssl import ( + ALERT_DESCRIPTION_CLOSE_NOTIFY, + ALERT_DESCRIPTION_UNEXPECTED_MESSAGE, + ALERT_DESCRIPTION_BAD_RECORD_MAC, + ALERT_DESCRIPTION_RECORD_OVERFLOW, + ALERT_DESCRIPTION_DECOMPRESSION_FAILURE, + ALERT_DESCRIPTION_HANDSHAKE_FAILURE, + ALERT_DESCRIPTION_BAD_CERTIFICATE, + ALERT_DESCRIPTION_UNSUPPORTED_CERTIFICATE, + ALERT_DESCRIPTION_CERTIFICATE_REVOKED, + ALERT_DESCRIPTION_CERTIFICATE_EXPIRED, + ALERT_DESCRIPTION_CERTIFICATE_UNKNOWN, + ALERT_DESCRIPTION_ILLEGAL_PARAMETER, + ALERT_DESCRIPTION_UNKNOWN_CA, + ALERT_DESCRIPTION_ACCESS_DENIED, + ALERT_DESCRIPTION_DECODE_ERROR, + ALERT_DESCRIPTION_DECRYPT_ERROR, + ALERT_DESCRIPTION_PROTOCOL_VERSION, + ALERT_DESCRIPTION_INSUFFICIENT_SECURITY, + ALERT_DESCRIPTION_INTERNAL_ERROR, + ALERT_DESCRIPTION_USER_CANCELLED, + ALERT_DESCRIPTION_NO_RENEGOTIATION, + ALERT_DESCRIPTION_UNSUPPORTED_EXTENSION, + ALERT_DESCRIPTION_CERTIFICATE_UNOBTAINABLE, + ALERT_DESCRIPTION_UNRECOGNIZED_NAME, + ALERT_DESCRIPTION_BAD_CERTIFICATE_STATUS_RESPONSE, + ALERT_DESCRIPTION_BAD_CERTIFICATE_HASH_VALUE, + ALERT_DESCRIPTION_UNKNOWN_PSK_IDENTITY, +) + from _ssl import (PROTOCOL_SSLv3, PROTOCOL_SSLv23, PROTOCOL_TLSv1) from _ssl import _OPENSSL_API_VERSION @@ -236,7 +298,7 @@ _context=None): if _context: - self.context = _context + self._context = _context else: if server_side and not certfile: raise ValueError("certfile must be specified for server-side " @@ -245,16 +307,16 @@ raise ValueError("certfile must be specified") if certfile and not keyfile: keyfile = certfile - self.context = SSLContext(ssl_version) - self.context.verify_mode = cert_reqs + self._context = SSLContext(ssl_version) + self._context.verify_mode = cert_reqs if ca_certs: - self.context.load_verify_locations(ca_certs) + self._context.load_verify_locations(ca_certs) if certfile: - self.context.load_cert_chain(certfile, keyfile) + self._context.load_cert_chain(certfile, keyfile) if npn_protocols: - self.context.set_npn_protocols(npn_protocols) + self._context.set_npn_protocols(npn_protocols) if ciphers: - self.context.set_ciphers(ciphers) + self._context.set_ciphers(ciphers) self.keyfile = keyfile self.certfile = certfile self.cert_reqs = cert_reqs @@ -296,7 +358,7 @@ if connected: # create the SSL object try: - self._sslobj = self.context._wrap_socket(self, server_side, + self._sslobj = self._context._wrap_socket(self, server_side, server_hostname) if do_handshake_on_connect: timeout = self.gettimeout() @@ -308,6 +370,14 @@ except socket_error as x: self.close() raise x + @property + def context(self): + return self._context + + @context.setter + def context(self, ctx): + self._context = ctx + self._sslobj.context = ctx def dup(self): raise NotImplemented("Can't dup() %s instances" % diff -r 7c9327ff5de6 Lib/test/make_ssl_certs.py --- a/Lib/test/make_ssl_certs.py Sat Dec 15 22:36:49 2012 +0100 +++ b/Lib/test/make_ssl_certs.py Sun Dec 16 14:02:06 2012 +1100 @@ -20,11 +20,52 @@ [req_x509_extensions] subjectAltName = DNS:{hostname} + + [ ca ] + default_ca = CA_default + + [ CA_default ] + dir = cadir + database = $dir/index.txt + default_md = sha1 + default_days = 3600 + certificate = pycacert.pem + private_key = pycakey.pem + serial = $dir/serial + RANDFILE = $dir/.rand + + policy = policy_match + + [ policy_match ] + countryName = match + stateOrProvinceName = optional + organizationName = match + organizationalUnitName = optional + commonName = supplied + emailAddress = optional + + [ policy_anything ] + countryName = optional + stateOrProvinceName = optional + localityName = optional + organizationName = optional + organizationalUnitName = optional + commonName = supplied + emailAddress = optional + + + [ v3_ca ] + + subjectKeyIdentifier=hash + authorityKeyIdentifier=keyid:always,issuer + basicConstraints = CA:true + """ here = os.path.abspath(os.path.dirname(__file__)) -def make_cert_key(hostname): +def make_cert_key(hostname, sign=False): + print("creating cert for " + hostname) tempnames = [] for i in range(3): with tempfile.NamedTemporaryFile(delete=False) as f: @@ -33,10 +74,25 @@ try: with open(req_file, 'w') as f: f.write(req_template.format(hostname=hostname)) - args = ['req', '-new', '-days', '3650', '-nodes', '-x509', + args = ['req', '-new', '-days', '3650', '-nodes', '-newkey', 'rsa:1024', '-keyout', key_file, - '-out', cert_file, '-config', req_file] + '-config', req_file] + if sign: + with tempfile.NamedTemporaryFile(delete=False) as f: + tempnames.append(f.name) + reqfile = f.name + args += ['-out', reqfile ] + + else: + args += ['-x509', '-out', cert_file ] check_call(['openssl'] + args) + + if sign: + args = ['ca', '-config', req_file, '-out', cert_file, '-outdir', 'cadir', + '-policy', 'policy_anything', '-batch', '-infiles', reqfile ] + check_call(['openssl'] + args) + + with open(cert_file, 'r') as f: cert = f.read() with open(key_file, 'r') as f: @@ -46,6 +102,29 @@ for name in tempnames: os.remove(name) +def unmake_ca(): + import shutil + shutil.rmtree('cadir') + +def make_ca(): + os.mkdir('cadir') + with open(os.path.join('cadir','index.txt'),'a+') as f: + pass # empty file + with open(os.path.join('cadir','index.txt.attr'),'w+') as f: + f.write('unique_subject = no') + + with tempfile.NamedTemporaryFile() as t: + t.write(bytes(req_template.format(hostname='the_ca_server_of.python.org'),'UTF-8')) + t.flush() + with tempfile.NamedTemporaryFile() as f: + args = ['req', '-new', '-days', '3650', '-extensions', 'v3_ca', '-nodes', + '-newkey', 'rsa:2048', '-keyout', 'pycakey.pem', + '-out', f.name, '-subj', '/C=XY/L=Castle Anthrax/O=Python Software Foundation CA/CN=the_ca_server_of.python.org'] + check_call(['openssl'] + args) + args = ['ca', '-config', t.name, '-create_serial', '-out', 'pycacert.pem', '-batch', '-outdir', 'cadir', + '-keyfile', 'pycakey.pem', '-days', '3650', + '-selfsign', '-extensions', 'v3_ca', '-infiles', f.name ] + check_call(['openssl'] + args) if __name__ == '__main__': os.chdir(here) @@ -54,11 +133,34 @@ f.write(cert) with open('ssl_key.pem', 'w') as f: f.write(key) + print("password protecting ssl_key.pem in ssl_key.passwd.pem") + check_call(['openssl','rsa','-in','ssl_key.pem','-out','ssl_key.passwd.pem','-des3','-passout','pass:somepass']) + check_call(['openssl','rsa','-in','ssl_key.pem','-out','keycert.passwd.pem','-des3','-passout','pass:somepass']) + with open('keycert.pem', 'w') as f: f.write(key) f.write(cert) + + with open('keycert.passwd.pem', 'a+') as f: + f.write(cert) + # For certificate matching tests + make_ca() cert, key = make_cert_key('fakehostname') with open('keycert2.pem', 'w') as f: f.write(key) f.write(cert) + + cert, key = make_cert_key('localhost',True) + with open('keycert3.pem', 'w') as f: + f.write(key) + f.write(cert) + + cert, key = make_cert_key('fakehostname',True) + with open('keycert4.pem', 'w') as f: + f.write(key) + f.write(cert) + + unmake_ca() + print("\n\nPlease change the values in test_ssl.py, test_parse_cert function related to notAfter,notBefore and serialNumber") + check_call(['openssl','x509','-in','keycert.pem','-dates','-serial','-noout']) diff -r 7c9327ff5de6 Lib/test/test_ssl.py --- a/Lib/test/test_ssl.py Sat Dec 15 22:36:49 2012 +0100 +++ b/Lib/test/test_ssl.py Sun Dec 16 14:02:06 2012 +1100 @@ -37,6 +37,9 @@ # are meant to authenticate. CERTFILE = data_file("keycert.pem") +CERTFILE3 = data_file("keycert3.pem") +CERTFILE4 = data_file("keycert4.pem") +CA = data_file("pycacert.pem") BYTES_CERTFILE = os.fsencode(CERTFILE) ONLYCERT = data_file("ssl_cert.pem") ONLYKEY = data_file("ssl_key.pem") @@ -142,6 +145,7 @@ (('organizationName', 'Python Software Foundation'),), (('commonName', 'localhost'),)) ) + # Note the next three asserts will fail if the keys are regenerated self.assertEqual(p['notAfter'], 'Oct 5 23:01:56 2020 GMT') self.assertEqual(p['notBefore'], 'Oct 8 23:01:56 2010 GMT') self.assertEqual(p['serialNumber'], 'D7C7381919AFC24E') @@ -1238,7 +1242,7 @@ raise AssertionError("Use of invalid cert should have failed!") def server_params_test(client_context, server_context, indata=b"FOO\n", - chatty=True, connectionchatty=False): + chatty=True, connectionchatty=False, sni_name=None): """ Launch a server, connect a client to it and try various reads and writes. @@ -1248,7 +1252,8 @@ chatty=chatty, connectionchatty=False) with server: - with client_context.wrap_socket(socket.socket()) as s: + with client_context.wrap_socket(socket.socket(), + server_hostname=sni_name) as s: s.connect((HOST, server.port)) for arg in [indata, bytearray(indata), memoryview(indata)]: if connectionchatty: @@ -1272,6 +1277,7 @@ stats.update({ 'compression': s.compression(), 'cipher': s.cipher(), + 'peercert': s.getpeercert(), 'client_npn_protocol': s.selected_npn_protocol() }) s.close() @@ -1977,6 +1983,64 @@ if len(stats['server_npn_protocols']) else 'nothing' self.assertEqual(server_result, expected, msg % (server_result, "server")) + @unittest.skipUnless(ssl.HAS_SNI, "SNI support needed for this test") + def test_sni(self): + hostname = "funny" + def servername(s,servername,oldctx): + nonlocal hostname + newctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1) + newctx.load_cert_chain(CERTFILE4) + s.context = newctx + hostname = servername + + server_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1) + + self.assertRaises(TypeError, server_context.set_servername_callback, 4) + self.assertRaises(TypeError, server_context.set_servername_callback, "This definately isn't a function") + self.assertRaises(TypeError, server_context.set_servername_callback, server_context) + + server_context.set_servername_callback(servername) + server_context.load_cert_chain(CERTFILE3) + + client_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1) + client_context.verify_mode = ssl.CERT_REQUIRED + client_context.load_verify_locations(CA) + stats = server_params_test(client_context, server_context, + chatty=True, + connectionchatty=True, + sni_name='supermessage') + + self.assertEqual(hostname, "supermessage", + "sni server didn't get hostname 'supermessage' got %s" % hostname) + + def certreceived(stats, name): + try: + cert = stats['peercert'] + self.assertTrue( (('commonName',name),) in cert['subject'], + "Callback didn't change certificate got %s" % pprint.pformat(cert)) + except KeyError: + raise AssertionError('[peercert][subject] missing %s' % pprint.pformat('stat')) + + certreceived(stats, 'fakehostname') + + hostname = "funny" + server_context.set_servername_callback(None) + + server_context.load_cert_chain(CERTFILE3) + stats = server_params_test(client_context, server_context, + chatty=True, + connectionchatty=True, + sni_name='notfunny') + self.assertEqual(hostname, "funny", + "sni server callback was disabled. hostname changed and it shouldn't have") + + certreceived(stats, 'localhost') + + def dummycallback(s, name, ctx): + pass + + # Check our object reference counting + server_context.set_servername_callback(dummycallback) def test_main(verbose=False): if support.verbose: @@ -1999,6 +2063,7 @@ for filename in [ CERTFILE, SVN_PYTHON_ORG_ROOT_CERT, BYTES_CERTFILE, + CA, CERTFILE3, CERTFILE4, ONLYCERT, ONLYKEY, BYTES_ONLYCERT, BYTES_ONLYKEY, BADCERT, BADKEY, EMPTYCERT]: if not os.path.exists(filename): diff -r 7c9327ff5de6 Modules/_ssl.c --- a/Modules/_ssl.c Sat Dec 15 22:36:49 2012 +0100 +++ b/Modules/_ssl.c Sun Dec 16 14:02:06 2012 +1100 @@ -181,12 +181,16 @@ char *npn_protocols; int npn_protocols_len; #endif +#ifndef OPENSSL_NO_TLSEXT + PyObject *set_hostname; +#endif } PySSLContext; typedef struct { PyObject_HEAD PyObject *Socket; /* weakref to socket on which we're layered */ SSL *ssl; + PySSLContext *ctx; /* weakref to SSL context */ X509 *peer_cert; int shutdown_seen_zero; enum py_ssl_server_or_client socket_type; @@ -437,11 +441,12 @@ */ static PySSLSocket * -newPySSLSocket(SSL_CTX *ctx, PySocketSockObject *sock, +newPySSLSocket(PySSLContext *sslctx, PySocketSockObject *sock, enum py_ssl_server_or_client socket_type, char *server_hostname) { PySSLSocket *self; + SSL_CTX *ctx = sslctx->ctx; self = PyObject_New(PySSLSocket, &PySSLSocket_Type); if (self == NULL) @@ -450,6 +455,8 @@ self->peer_cert = NULL; self->ssl = NULL; self->Socket = NULL; + self->ctx = sslctx; + Py_INCREF(sslctx); /* Make sure the SSL error state is initialized */ (void) ERR_get_state(); @@ -458,6 +465,7 @@ PySSL_BEGIN_ALLOW_THREADS self->ssl = SSL_new(ctx); PySSL_END_ALLOW_THREADS + SSL_set_app_data(self->ssl,self); SSL_set_fd(self->ssl, sock->sock_fd); #ifdef SSL_MODE_AUTO_RETRY SSL_set_mode(self->ssl, SSL_MODE_AUTO_RETRY); @@ -1164,6 +1172,38 @@ #endif } +static PySSLContext *PySSL_get_context(PySSLSocket *self, void *closure) { + Py_INCREF(self->ctx); + return self->ctx; +} + +static int PySSL_set_context(PySSLSocket *self, PyObject *value, + void *closure) { + + if (PyObject_TypeCheck(value, &PySSLContext_Type)) { + + Py_INCREF(value); + Py_DECREF(self->ctx); + self->ctx = (PySSLContext *) value; + SSL_set_SSL_CTX(self->ssl, self->ctx->ctx); + } else { + PyErr_SetString(PyExc_TypeError, "The value must be a SSLContext"); + return -1; + } + + return 0; +} + +PyDoc_STRVAR(PySSL_set_context_doc, +"_setter_context(ctx)\n\ +\ +This changes the context associated with the SSLSocket. This is typically\n\ +used from within a callback function set by the set_servername_callback\n\ +on the SSLContext to change the certificate information associated with the\n\ +SSLSocket before the cryptographic exchange handshake messages\n"); + + + static void PySSL_dealloc(PySSLSocket *self) { if (self->peer_cert) /* Possible not to have one? */ @@ -1171,6 +1211,7 @@ if (self->ssl) SSL_free(self->ssl); Py_XDECREF(self->Socket); + Py_XDECREF(self->ctx); PyObject_Del(self); } @@ -1606,6 +1647,12 @@ #endif /* HAVE_OPENSSL_FINISHED */ +static PyGetSetDef ssl_getsetlist[] = { + {"context", (getter) PySSL_get_context, + (setter) PySSL_set_context, PySSL_set_context_doc}, + {NULL}, /* sentinel */ +}; + static PyMethodDef PySSLMethods[] = { {"do_handshake", (PyCFunction)PySSL_SSLdo_handshake, METH_NOARGS}, {"write", (PyCFunction)PySSL_SSLwrite, METH_VARARGS, @@ -1660,6 +1707,8 @@ 0, /*tp_iter*/ 0, /*tp_iternext*/ PySSLMethods, /*tp_methods*/ + 0, /*tp_members*/ + ssl_getsetlist, /*tp_getset*/ }; @@ -1716,6 +1765,9 @@ #ifdef OPENSSL_NPN_NEGOTIATED self->npn_protocols = NULL; #endif +#ifndef OPENSSL_NO_TLSEXT + self->set_hostname = NULL; +#endif /* Defaults */ SSL_CTX_set_verify(self->ctx, SSL_VERIFY_NONE, NULL); SSL_CTX_set_options(self->ctx, @@ -1736,6 +1788,9 @@ #ifdef OPENSSL_NPN_NEGOTIATED PyMem_Free(self->npn_protocols); #endif +#ifndef OPENSSL_NO_TLSEXT + Py_XDECREF(self->set_hostname); +#endif Py_TYPE(self)->tp_free(self); } @@ -2223,7 +2278,7 @@ #endif } - res = (PyObject *) newPySSLSocket(self->ctx, sock, server_side, + res = (PyObject *) newPySSLSocket(self, sock, server_side, hostname); if (hostname != NULL) PyMem_Free(hostname); @@ -2308,6 +2363,131 @@ } #endif +#ifndef OPENSSL_NO_TLSEXT +static int _servername_callback(SSL *s, int *al, void *args) { + int ret = SSL_TLSEXT_ERR_OK; + PySSLContext *ssl_ctx = (PySSLContext *) args; + PySSLSocket *ssl; + PyObject *servername_o; + PyObject *servername_idna; + PyObject *result; + PyObject *error; + PyObject *sslSocket; + PyGILState_STATE gstate; + const char * servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name); + + ssl = SSL_get_app_data(s); + + if (ssl_ctx->set_hostname == NULL) { + /* remove race condition in this the call back while if removing the + * callback is in progress */ + return ret; + } + + gstate = PyGILState_Ensure(); + sslSocket = PyWeakref_GetObject(ssl->Socket); + Py_INCREF(sslSocket); + if (sslSocket == Py_None) { + goto error; + } + + servername_o = PyBytes_FromString(servername); + if (servername_o == NULL) { + PyErr_WriteUnraisable((PyObject *) ssl_ctx); + goto error; + } + servername_idna = PyUnicode_FromEncodedObject(servername_o,"idna",NULL); + if (servername_idna == NULL) { + PyErr_WriteUnraisable(servername_o); + Py_DECREF(servername_o); + goto error; + } + Py_DECREF(servername_o); + result = PyObject_CallFunctionObjArgs(ssl_ctx->set_hostname, sslSocket, + servername_idna, ssl_ctx, NULL); + Py_DECREF(sslSocket); + Py_DECREF(servername_idna); + + if (result == NULL) { + PyErr_WriteUnraisable(ssl_ctx->set_hostname); + *al = SSL_AD_HANDSHAKE_FAILURE; + ret = SSL_TLSEXT_ERR_ALERT_FATAL; + } else { + if (result != Py_None) { + if (PyLong_Check(result)) { + *al = (int) PyLong_AsLong(result); + error = PyErr_Occurred(); + if (error != NULL) { + PyErr_WriteUnraisable(error); + *al = SSL_AD_INTERNAL_ERROR; + } + } else { + PyErr_SetString(PyExc_TypeError, + "not a long object - expecting SSL.ALERT_DESCRIPTION_*"); + PyErr_WriteUnraisable(result); + *al = SSL_AD_INTERNAL_ERROR; + } + ret = SSL_TLSEXT_ERR_ALERT_FATAL; + } + Py_DECREF(result); + } + + PyGILState_Release(gstate); + return ret; + +error: + Py_DECREF(sslSocket); + PyGILState_Release(gstate); + *al = SSL_AD_INTERNAL_ERROR; + ret = SSL_TLSEXT_ERR_ALERT_FATAL; + return ret; +} + +PyDoc_STRVAR(PySSL_set_servername_callback_doc, +"set_servername(method)\n\ +\ +This sets a callback that will be called when a server name is provided by\n\ +the SSL/TLS client in the SNI extension.\n\ +\ +If the argument is None then the callback is disabled. The method is called\n\ +with the SSLSocket, the server name as a string, and the SSLContext object.\n\ +See RFC 6066 for details of the SNI"); +#endif + +static PyObject * +set_servername_callback(PySSLContext *self, PyObject *args) +{ +#ifndef OPENSSL_NO_TLSEXT + PyObject *o; + + if (!PyArg_ParseTuple(args, "O", &o)) { + return NULL; + } else { + Py_XDECREF(self->set_hostname); + self->set_hostname = o; + if (self->set_hostname == Py_None) { + SSL_CTX_set_tlsext_servername_callback(self->ctx, NULL); + self->set_hostname = NULL; + } else { + if (!PyCallable_Check(self->set_hostname)) { + SSL_CTX_set_tlsext_servername_callback(self->ctx, NULL); + self->set_hostname = NULL; + PyErr_SetString(PyExc_TypeError, + "not a callable object"); + return NULL; + } + Py_INCREF(self->set_hostname); + SSL_CTX_set_tlsext_servername_callback(self->ctx, _servername_callback); + SSL_CTX_set_tlsext_servername_arg(self->ctx, self); + } + Py_RETURN_NONE; + } +#else + PyErr_SetString(PyExc_NotImplementedError, + "The TLS extension servername callback, SSL_CTX_set_tlsext_servername_callback, is not in the current OpenSSL library."); +#endif +} + static PyGetSetDef context_getsetlist[] = { {"options", (getter) get_options, (setter) set_options, NULL}, @@ -2337,6 +2517,8 @@ {"set_ecdh_curve", (PyCFunction) set_ecdh_curve, METH_O, NULL}, #endif + {"set_servername_callback", (PyCFunction) set_servername_callback, + METH_VARARGS, PySSL_set_servername_callback_doc}, {NULL, NULL} /* sentinel */ }; @@ -2743,6 +2925,65 @@ PyModule_AddIntConstant(m, "CERT_REQUIRED", PY_SSL_CERT_REQUIRED); + /* Alert Descriptions from ssl.h */ + /* note RESERVED constants no longer intended for use have been removed */ + /* http://www.iana.org/assignments/tls-parameters/tls-parameters.xml#tls-parameters-6 */ + + PyModule_AddIntConstant(m, "ALERT_DESCRIPTION_CLOSE_NOTIFY", + SSL_AD_CLOSE_NOTIFY); + PyModule_AddIntConstant(m, "ALERT_DESCRIPTION_UNEXPECTED_MESSAGE", + SSL_AD_UNEXPECTED_MESSAGE); + PyModule_AddIntConstant(m, "ALERT_DESCRIPTION_BAD_RECORD_MAC", + SSL_AD_BAD_RECORD_MAC); + PyModule_AddIntConstant(m, "ALERT_DESCRIPTION_RECORD_OVERFLOW", + SSL_AD_RECORD_OVERFLOW); + PyModule_AddIntConstant(m, "ALERT_DESCRIPTION_DECOMPRESSION_FAILURE", + SSL_AD_DECOMPRESSION_FAILURE); + PyModule_AddIntConstant(m, "ALERT_DESCRIPTION_HANDSHAKE_FAILURE", + SSL_AD_HANDSHAKE_FAILURE); + PyModule_AddIntConstant(m, "ALERT_DESCRIPTION_BAD_CERTIFICATE", + SSL_AD_BAD_CERTIFICATE); + PyModule_AddIntConstant(m, "ALERT_DESCRIPTION_UNSUPPORTED_CERTIFICATE", + SSL_AD_UNSUPPORTED_CERTIFICATE); + PyModule_AddIntConstant(m, "ALERT_DESCRIPTION_CERTIFICATE_REVOKED", + SSL_AD_CERTIFICATE_REVOKED); + PyModule_AddIntConstant(m, "ALERT_DESCRIPTION_CERTIFICATE_EXPIRED", + SSL_AD_CERTIFICATE_EXPIRED); + PyModule_AddIntConstant(m, "ALERT_DESCRIPTION_CERTIFICATE_UNKNOWN", + SSL_AD_CERTIFICATE_UNKNOWN); + PyModule_AddIntConstant(m, "ALERT_DESCRIPTION_ILLEGAL_PARAMETER", + SSL_AD_ILLEGAL_PARAMETER); + PyModule_AddIntConstant(m, "ALERT_DESCRIPTION_UNKNOWN_CA", + SSL_AD_UNKNOWN_CA); + PyModule_AddIntConstant(m, "ALERT_DESCRIPTION_ACCESS_DENIED", + SSL_AD_ACCESS_DENIED); + PyModule_AddIntConstant(m, "ALERT_DESCRIPTION_DECODE_ERROR", + SSL_AD_DECODE_ERROR); + PyModule_AddIntConstant(m, "ALERT_DESCRIPTION_DECRYPT_ERROR", + SSL_AD_DECRYPT_ERROR); + PyModule_AddIntConstant(m, "ALERT_DESCRIPTION_PROTOCOL_VERSION", + SSL_AD_PROTOCOL_VERSION); + PyModule_AddIntConstant(m, "ALERT_DESCRIPTION_INSUFFICIENT_SECURITY", + SSL_AD_INSUFFICIENT_SECURITY); + PyModule_AddIntConstant(m, "ALERT_DESCRIPTION_INTERNAL_ERROR", + SSL_AD_INTERNAL_ERROR); + PyModule_AddIntConstant(m, "ALERT_DESCRIPTION_USER_CANCELLED", + SSL_AD_USER_CANCELLED); + PyModule_AddIntConstant(m, "ALERT_DESCRIPTION_NO_RENEGOTIATION", + SSL_AD_NO_RENEGOTIATION); + PyModule_AddIntConstant(m, "ALERT_DESCRIPTION_UNSUPPORTED_EXTENSION", + SSL_AD_UNSUPPORTED_EXTENSION); + PyModule_AddIntConstant(m, "ALERT_DESCRIPTION_CERTIFICATE_UNOBTAINABLE", + SSL_AD_CERTIFICATE_UNOBTAINABLE); + PyModule_AddIntConstant(m, "ALERT_DESCRIPTION_UNRECOGNIZED_NAME", + SSL_AD_UNRECOGNIZED_NAME); + PyModule_AddIntConstant(m, "ALERT_DESCRIPTION_BAD_CERTIFICATE_STATUS_RESPONSE", + SSL_AD_BAD_CERTIFICATE_STATUS_RESPONSE); + PyModule_AddIntConstant(m, "ALERT_DESCRIPTION_BAD_CERTIFICATE_HASH_VALUE", + SSL_AD_BAD_CERTIFICATE_HASH_VALUE); + PyModule_AddIntConstant(m, "ALERT_DESCRIPTION_UNKNOWN_PSK_IDENTITY", + SSL_AD_UNKNOWN_PSK_IDENTITY); + /* protocol versions */ #ifndef OPENSSL_NO_SSL2 PyModule_AddIntConstant(m, "PROTOCOL_SSLv2",