diff -r a812de69b493 Lib/test/keycert2.pem --- a/Lib/test/keycert2.pem Mon Aug 20 23:02:28 2012 +1000 +++ b/Lib/test/keycert2.pem Wed Aug 22 02:50:27 2012 +1000 @@ -1,31 +1,54 @@ -----BEGIN PRIVATE KEY----- -MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAJnsJZVrppL+W5I9 -zGQrrawWwE5QJpBK9nWw17mXrZ03R1cD9BamLGivVISbPlRlAVnZBEyh1ATpsB7d -CUQ+WHEvALquvx4+Yw5l+fXeiYRjrLRBYZuVy8yNtXzU3iWcGObcYRkUdiXdOyP7 -sLF2YZHRvQZpzgDBKkrraeQ81w21AgMBAAECgYBEm7n07FMHWlE+0kT0sXNsLYfy -YE+QKZnJw9WkaDN+zFEEPELkhZVt5BjsMraJr6v2fIEqF0gGGJPkbenffVq2B5dC -lWUOxvJHufMK4sM3Cp6s/gOp3LP+QkzVnvJSfAyZU6l+4PGX5pLdUsXYjPxgzjzL -S36tF7/2Uv1WePyLUQJBAMsPhYzUXOPRgmbhcJiqi9A9c3GO8kvSDYTCKt3VMnqz -HBn6MQ4VQasCD1F+7jWTI0FU/3vdw8non/Fj8hhYqZcCQQDCDRdvmZqDiZnpMqDq -L6ZSrLTVtMvZXZbgwForaAD9uHj51TME7+eYT7EG2YCgJTXJ4YvRJEnPNyskwdKt -vTSTAkEAtaaN/vyemEJ82BIGStwONNw0ILsSr5cZ9tBHzqiA/tipY+e36HRFiXhP -QcU9zXlxyWkDH8iz9DSAmE2jbfoqwwJANlMJ65E543cjIlitGcKLMnvtCCLcKpb7 -xSG0XJB6Lo11OKPJ66jp0gcFTSCY1Lx2CXVd+gfJrfwI1Pp562+bhwJBAJ9IfDPU -R8OpO9v1SGd8x33Owm7uXOpB9d63/T70AD1QOXjKUC4eXYbt0WWfWuny/RNPRuyh -w7DXSfUF+kPKolU= +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDTlUHjWEKXNxPq +xjExLeEEPvn7FT2MWX3LMrjcGLKVifNn90uEdYezBOGQCtrmg1wX+tA8jZ85PScM +3asSvRTlJWtWtvU3WTbz4c34MBsTZDUG/IGDIfVyql7ihseyRkk2XnRCc6uyA6ZC +CUaGBiHspD+oRHcc71Ezg3WpERFkQ7J/o//VoaddshSc9AV/VD5WCv75L/H9lD7x +NJZaBa/Tzq7tow53CwYuLlGX8ZzCNAo3zObWEtbMWavRJiyCKv5go/okcT7SZuYB +BTM4Uuw6ZYQ9UElkvo+qIZuRQw2x1JpkJTzXYcOerktcGRnZPHbIEilQREGuRyHO +GIPdRAYJAgMBAAECggEBAMnJtMAFAeVDoxwqjunPTqYFBdZfqIZ9JwqvQjyu1Urm ++RwzbnNSv+uNAS6mG6TyrNc13nmRu8QUPXT4x9okOJRd/qQB85Yo4M7xkMVd48S3 +shcKpN4Segs0zy+NyVCN1e8jYA+sT5iEAIFEZV0W5i3Ra+CoA022NCREAOgQHh0W +8w6BxRTZo+Gd8SA/LvT6nTNIHFp5XluRL7n0A4f0zZECXevkpI13bj/nuZ/P6sdP +4oL0h2ugf9V3Tybd10WrtreMleWYvTAXwal8Lkljsg1IHP6W9l6y+Fr67nbVQLer +T8Nk7ybdqTX6RIPzHaYr7PZxL+aw8ENcC8AoV5cTuwECgYEA8nRSRttYdoitrVNS +da5oNkFnQxsTFV9YD0GB++iWcZFPOC9xCGNj3+vmxhuIsYry2jOYMrDc6w1x5rHr +RJe5W+jTt+UEck/kW5j2nL748Y8Y11rn+xDJ2VC0vzorib8fxNPs6JoA3YIJL48x +l4B9mYTrKBGhUwVx8VIwAFny9iECgYEA32dm280/5PXbquu81bwiKkGlqAI/Pyrg +8FLb8cDjPg5EvAGKVvfrmYR8/SRvEXtjw9roYHnDxNokUNRBprEvQrP1ioJfVS7w +ezCH49mAI1Hw0U9B+CuGW91yvIIyfw7ZOUKxGpeW34C0FspUX4f53BXXAiE+4VKF +SK5icmL9wukCgYBeJCp6Vtsfk2yUe2DuTt0qC/UzikS7rLy9l7sH/ETGLhzrfQhP +sH/sR2XhEHngLkqjzRrQu/nbzummIPlwNdWESYBDzEoQtNK/ekJvlHuHGAAWRrcC +tn8ZwYudINIc9zg3re40Vu/1EnPMorYn1H1wabsBYO8aFGgswrPevw5m4QKBgDJ/ +jxKUPKMmp/bkvpSZqCdhG270wxMmQB5SvIDTFwrJ8uOEfZ7W/5x+BDq8Z5bOC2kU +LH7SR1xR6u+Saxyg5IvxXclnWuFIcPeHqs/oNDzHouXNQp40SHSvg+X0kDGES7nT +x3C5X1M6JnsYVSYa+HG1RhqnJS2Nl68sK/irQ60xAoGBAOFQyLye8qRye90oQu+a +SYqXBH5mPrhtFl0PtdYTGoFeL427uY7lxNAAKjoDAKHkWnUEOUzaBGlB21ixVbe2 +xBX5ad9Tk+1mN9d6m5ZZmT/Uznyn3NLI3WXVoW8TNuV0W4hZg8CjpQBv3oNVHG8l +M5mEeS1rwjQcJRZtY46LtxDh -----END PRIVATE KEY----- + -----BEGIN CERTIFICATE----- -MIICXTCCAcagAwIBAgIJAIO3upAG445fMA0GCSqGSIb3DQEBBQUAMGIxCzAJBgNV -BAYTAlhZMRcwFQYDVQQHEw5DYXN0bGUgQW50aHJheDEjMCEGA1UEChMaUHl0aG9u -IFNvZnR3YXJlIEZvdW5kYXRpb24xFTATBgNVBAMTDGZha2Vob3N0bmFtZTAeFw0x -MDEwMDkxNTAxMDBaFw0yMDEwMDYxNTAxMDBaMGIxCzAJBgNVBAYTAlhZMRcwFQYD -VQQHEw5DYXN0bGUgQW50aHJheDEjMCEGA1UEChMaUHl0aG9uIFNvZnR3YXJlIEZv -dW5kYXRpb24xFTATBgNVBAMTDGZha2Vob3N0bmFtZTCBnzANBgkqhkiG9w0BAQEF -AAOBjQAwgYkCgYEAmewllWumkv5bkj3MZCutrBbATlAmkEr2dbDXuZetnTdHVwP0 -FqYsaK9UhJs+VGUBWdkETKHUBOmwHt0JRD5YcS8Auq6/Hj5jDmX59d6JhGOstEFh -m5XLzI21fNTeJZwY5txhGRR2Jd07I/uwsXZhkdG9BmnOAMEqSutp5DzXDbUCAwEA -AaMbMBkwFwYDVR0RBBAwDoIMZmFrZWhvc3RuYW1lMA0GCSqGSIb3DQEBBQUAA4GB -AH+iMClLLGSaKWgwXsmdVo4FhTZZHo8Uprrtg3N9FxEeE50btpDVQysgRt5ias3K -m+bME9zbKwvbVWD5zZdjus4pDgzwF/iHyccL8JyYhxOvS/9zmvAtFXj/APIIbZFp -IT75d9f88ScIGEtknZQejnrdhB64tYki/EqluiuKBqKD +MIIESzCCAzOgAwIBAgIJALHfIBkMb9VvMA0GCSqGSIb3DQEBBQUAMIG7MQswCQYD +VQQGEwItLTESMBAGA1UECAwJU29tZVN0YXRlMREwDwYDVQQHDAhTb21lQ2l0eTEZ +MBcGA1UECgwQU29tZU9yZ2FuaXphdGlvbjEfMB0GA1UECwwWU29tZU9yZ2FuaXph +dGlvbmFsVW5pdDEeMBwGA1UEAwwVbG9jYWxob3N0LmxvY2FsZG9tYWluMSkwJwYJ +KoZIhvcNAQkBFhpyb290QGxvY2FsaG9zdC5sb2NhbGRvbWFpbjAeFw0xMjA4MjEw +NjQwMDdaFw0xMzA4MjEwNjQwMDdaMIG7MQswCQYDVQQGEwItLTESMBAGA1UECAwJ +U29tZVN0YXRlMREwDwYDVQQHDAhTb21lQ2l0eTEZMBcGA1UECgwQU29tZU9yZ2Fu +aXphdGlvbjEfMB0GA1UECwwWU29tZU9yZ2FuaXphdGlvbmFsVW5pdDEeMBwGA1UE +AwwVbG9jYWxob3N0LmxvY2FsZG9tYWluMSkwJwYJKoZIhvcNAQkBFhpyb290QGxv +Y2FsaG9zdC5sb2NhbGRvbWFpbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBANOVQeNYQpc3E+rGMTEt4QQ++fsVPYxZfcsyuNwYspWJ82f3S4R1h7ME4ZAK +2uaDXBf60DyNnzk9JwzdqxK9FOUla1a29TdZNvPhzfgwGxNkNQb8gYMh9XKqXuKG +x7JGSTZedEJzq7IDpkIJRoYGIeykP6hEdxzvUTODdakREWRDsn+j/9Whp12yFJz0 +BX9UPlYK/vkv8f2UPvE0lloFr9POru2jDncLBi4uUZfxnMI0CjfM5tYS1sxZq9Em +LIIq/mCj+iRxPtJm5gEFMzhS7DplhD1QSWS+j6ohm5FDDbHUmmQlPNdhw56uS1wZ +Gdk8dsgSKVBEQa5HIc4Yg91EBgkCAwEAAaNQME4wHQYDVR0OBBYEFFEfUT0f5jRv +q4PY1LbLpQzi0K+KMB8GA1UdIwQYMBaAFFEfUT0f5jRvq4PY1LbLpQzi0K+KMAwG +A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADgeB+3mmyPPlIteN+92mRqc +xDaGa5+W5PgWdo8jk9cnMmn287XrvBSxCBMm8xw2oqPO+h1Q8tyUC8CWI9fwdlcf +zOrAfOE+fY0VyB4gu5A3vleNotOwgBpez2aMO5quiQA4qXQCg1JLDJPdY1WG94Le +OJN+bD2KlDH2MTU68LbZ65twQDo5nzCSqmLYe10Wc761X2Buv34ozBUzDZRUHgZ0 +iV5lUAvnber6XNcyvqa9DPrclWqwsP7W6gurytaLrs5T2b1NblqoXzwFuSEV/Pzt +l1NKi/s6MY+sYrA4ccbGMAqCCD+UJIfzQ5S47soY6BqJH4FrkuzZh7WAAxJX4Ag= -----END CERTIFICATE----- diff -r a812de69b493 Lib/test/test_ssl.py --- a/Lib/test/test_ssl.py Mon Aug 20 23:02:28 2012 +1000 +++ b/Lib/test/test_ssl.py Wed Aug 22 02:50:27 2012 +1000 @@ -37,6 +37,7 @@ # are meant to authenticate. CERTFILE = data_file("keycert.pem") +CERTFILE2 = data_file("keycert2.pem") BYTES_CERTFILE = os.fsencode(CERTFILE) ONLYCERT = data_file("ssl_cert.pem") ONLYKEY = data_file("ssl_key.pem") @@ -1242,7 +1243,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. @@ -1252,7 +1253,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: @@ -1276,6 +1278,7 @@ stats.update({ 'compression': s.compression(), 'cipher': s.cipher(), + 'peercert': s.getpeercert(), 'client_npn_protocol': s.selected_npn_protocol() }) s.close() @@ -1945,6 +1948,52 @@ 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(ctx,name): + nonlocal hostname + ctx.load_cert_chain(CERTFILE2) + hostname = name + + server_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1) + server_context.set_servername_callback(servername) + server_context.load_cert_chain(CERTFILE) + + client_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1) + #client_context.verify_mode = ssl.CERT_NONE + #client_context.load_verify_locations(CERTFILE2) + 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) + + try: + self.assertEqual(stats['peercert']['serialNumber'], 'b1:df:20:19:0c:6f:d5:6f', + "Callback didn't change certificate got %s" % pprint.pformat(stats['peercert'])) + except KeyError: + raise AssertionError('[peercert][serialNumber] missing %s' % pprint.pformat(stats)) + + hostname = "funny" + server_context.set_servername_callback(None) + + server_context.load_cert_chain(CERTFILE) + stats = server_params_test(client_context, server_context, + chatty=True, + connectionchatty=True, + sni_name='notfunny') + self.assertEqual(hostname, "funny", + "sni server callback disabled. hostname changed") + try: + self.assertNotEqual(stats['peercert']['serialNumber'], + 'b1:df:20:19:0c:6f:d5:6f', + "sni server callback disabled. certs changed. got %s" + % pprint.pformat(stats['peercert'])) + except KeyError: + raise AssertionError('[peercert][serialNumber] missing %s' % pprint.pformat(stats)) def test_main(verbose=False): if support.verbose: @@ -1966,7 +2015,7 @@ print(" HAS_SNI = %r" % ssl.HAS_SNI) for filename in [ - CERTFILE, SVN_PYTHON_ORG_ROOT_CERT, BYTES_CERTFILE, + CERTFILE, CERTFILE2, SVN_PYTHON_ORG_ROOT_CERT, BYTES_CERTFILE, ONLYCERT, ONLYKEY, BYTES_ONLYCERT, BYTES_ONLYKEY, BADCERT, BADKEY, EMPTYCERT]: if not os.path.exists(filename): diff -r a812de69b493 Modules/_ssl.c --- a/Modules/_ssl.c Mon Aug 20 23:02:28 2012 +1000 +++ b/Modules/_ssl.c Wed Aug 22 02:50:27 2012 +1000 @@ -181,6 +181,9 @@ char *npn_protocols; int npn_protocols_len; #endif +#ifndef OPENSSL_NO_TLSEXT + PyObject *set_hostname; +#endif } PySSLContext; typedef struct { @@ -1714,6 +1717,9 @@ } self->ctx = ctx; /* Defaults */ +#ifndef OPENSSL_NO_TLSEXT + self->set_hostname == NULL; +#endif SSL_CTX_set_verify(self->ctx, SSL_VERIFY_NONE, NULL); SSL_CTX_set_options(self->ctx, SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS); @@ -2301,6 +2307,75 @@ } #endif +#ifndef OPENSSL_NO_TLSEXT +static int _servername_callback(SSL *s, int *al, void *args) { + int ret = SSL_TLSEXT_ERR_OK; + PyObject *arglist; + PyObject *result; + PySSLContext *ssl_ctx = (PySSLContext *) args; + + const char * servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name); + + PyGILState_STATE gstate; + gstate = PyGILState_Ensure(); + + arglist = Py_BuildValue("(Os)", ssl_ctx, servername); + + if (arglist) { + result = PyObject_CallObject(ssl_ctx->set_hostname, arglist); + Py_DECREF(arglist); + + if (result == NULL) { + *al = SSL_AD_HANDSHAKE_FAILURE; + ret = SSL_TLSEXT_ERR_ALERT_FATAL; + } else { + Py_DECREF(result); + } + } else { + *al = SSL_AD_HANDSHAKE_FAILURE; + ret = SSL_TLSEXT_ERR_ALERT_FATAL; + } + + PyGILState_Release(gstate); + 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 the\n\ +SSL/TLS clientn in the SNI extension.\n\ +\ +If the argument is method then the callback is disabled. The method is called\n\ +with the SSLContext object and the string of the server name. See RFC 4366"); +#endif + +static PyObject * +set_servername_callback(PySSLContext *self, PyObject *args) +{ +#ifndef OPENSSL_NO_TLSEXT + if (!PyArg_ParseTuple(args, "O", &self->set_hostname)) { + return NULL; + } + + if (self->set_hostname == Py_None) { + if (self->set_hostname != NULL) { + Py_DECREF(self->set_hostname); + } + SSL_CTX_set_tlsext_servername_callback(self->ctx, NULL); + self->set_hostname = NULL; + } else { + 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, + "This TLS extension servername callback is not in the current OpenSSL library."); +#endif +} + static PyGetSetDef context_getsetlist[] = { {"options", (getter) get_options, (setter) set_options, NULL}, @@ -2330,6 +2405,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 */ };