diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -421,6 +421,14 @@ Constants .. versionadded:: 3.2 +.. data:: OP_SINGLE_ECDH_USE + + Prevents re-use of the same ECDH key for several SSL sessions. This + improves forward secrecy but requires more computational resources. + This option only applies to server sockets. + + .. versionadded:: 3.3 + .. data:: HAS_SNI Whether the OpenSSL library has built-in support for the *Server Name @@ -665,6 +673,24 @@ to speed up repeated connections from th when connected, the :meth:`SSLSocket.cipher` method of SSL sockets will give the currently selected cipher. +.. method:: SSLContext.set_ecdh_curve(curve_name) + + Set the curve name for Elliptic Curve-based Diffie-Hellman (abbreviated + ECDH) key exchange. Using Diffie-Hellman key exchange improves forward + secrecy at the expense of computational resources (both on the server and + on the client). The *curve_name* parameter should be a string describing + a well-known elliptic curve, for example ``prime256v1`` for a widely + supported curve. + + This setting doesn't apply to client sockets. You can also use the + :data:`OP_SINGLE_ECDH_USE` option to further improve security. + + .. versionadded:: 3.3 + + .. seealso:: + `SSL/TLS & Perfect Forward Secrecy `_ + Vincent Bernat. + .. method:: SSLContext.wrap_socket(sock, server_side=False, \ do_handshake_on_connect=True, suppress_ragged_eofs=True, \ server_hostname=None) diff --git a/Lib/ssl.py b/Lib/ssl.py --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -66,7 +66,10 @@ from _ssl import ( SSLSyscallError, SSLEOFError, ) from _ssl import CERT_NONE, CERT_OPTIONAL, CERT_REQUIRED -from _ssl import OP_ALL, OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_TLSv1 +from _ssl import ( + OP_ALL, OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_TLSv1, + OP_SINGLE_ECDH_USE, + ) from _ssl import RAND_status, RAND_egd, RAND_add, RAND_bytes, RAND_pseudo_bytes from _ssl import ( SSL_ERROR_ZERO_RETURN, diff --git a/Lib/test/ssl_servers.py b/Lib/test/ssl_servers.py --- a/Lib/test/ssl_servers.py +++ b/Lib/test/ssl_servers.py @@ -176,6 +176,9 @@ if __name__ == "__main__": action='store_false', help='be less verbose') parser.add_argument('-s', '--stats', dest='use_stats_handler', default=False, action='store_true', help='always return stats page') + parser.add_argument('--curve-name', dest='curve_name', type=str, + action='store', + help='curve name for EC-based Diffie-Hellman') args = parser.parse_args() support.verbose = args.verbose @@ -186,6 +189,9 @@ if __name__ == "__main__": handler_class.root = os.getcwd() context = ssl.SSLContext(ssl.PROTOCOL_TLSv1) context.load_cert_chain(CERTFILE) + if args.curve_name: + context.set_ecdh_curve(args.curve_name) + context.options |= ssl.OP_SINGLE_ECDH_USE server = HTTPSServer(("", args.port), handler_class, context) if args.verbose: diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -98,6 +98,7 @@ class BasicSocketTests(unittest.TestCase ssl.CERT_NONE ssl.CERT_OPTIONAL ssl.CERT_REQUIRED + ssl.OP_SINGLE_ECDH_USE self.assertIn(ssl.HAS_SNI, {True, False}) def test_random(self): @@ -557,6 +558,15 @@ class ContextTests(unittest.TestCase): ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1) ctx.set_default_verify_paths() + def test_set_ecdh_curve(self): + ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1) + ctx.set_ecdh_curve("prime256v1") + ctx.set_ecdh_curve(b"prime256v1") + self.assertRaises(TypeError, ctx.set_ecdh_curve) + self.assertRaises(TypeError, ctx.set_ecdh_curve, None) + self.assertRaises(ValueError, ctx.set_ecdh_curve, "foo") + self.assertRaises(ValueError, ctx.set_ecdh_curve, b"foo") + class NetworkedTests(unittest.TestCase): diff --git a/Modules/_ssl.c b/Modules/_ssl.c --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -1986,6 +1986,33 @@ set_default_verify_paths(PySSLContext *s Py_RETURN_NONE; } +static PyObject * +set_ecdh_curve(PySSLContext *self, PyObject *name) +{ + PyObject *name_bytes; + int nid; + EC_KEY *key; + + if (!PyUnicode_FSConverter(name, &name_bytes)) + return NULL; + assert(PyBytes_Check(name_bytes)); + nid = OBJ_sn2nid(PyBytes_AS_STRING(name_bytes)); + Py_DECREF(name_bytes); + if (nid == 0) { + PyErr_Format(PyExc_ValueError, + "unknown elliptic curve name %R", name); + return NULL; + } + key = EC_KEY_new_by_curve_name(nid); + if (key == NULL) { + _setSSLError(NULL, 0, __FILE__, __LINE__); + return NULL; + } + SSL_CTX_set_tmp_ecdh(self->ctx, key); + EC_KEY_free(key); + Py_RETURN_NONE; +} + static PyGetSetDef context_getsetlist[] = { {"options", (getter) get_options, (setter) set_options, NULL}, @@ -2007,6 +2034,8 @@ static struct PyMethodDef context_method METH_NOARGS, NULL}, {"set_default_verify_paths", (PyCFunction) set_default_verify_paths, METH_NOARGS, NULL}, + {"set_ecdh_curve", (PyCFunction) set_ecdh_curve, + METH_O, NULL}, {NULL, NULL} /* sentinel */ }; @@ -2450,6 +2479,7 @@ PyInit__ssl(void) PyModule_AddIntConstant(m, "OP_NO_SSLv2", SSL_OP_NO_SSLv2); PyModule_AddIntConstant(m, "OP_NO_SSLv3", SSL_OP_NO_SSLv3); PyModule_AddIntConstant(m, "OP_NO_TLSv1", SSL_OP_NO_TLSv1); + PyModule_AddIntConstant(m, "OP_SINGLE_ECDH_USE", SSL_OP_SINGLE_ECDH_USE); #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME r = Py_True;