From 30f00d002ae14f23b267607b3850bee316b843bd Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Wed, 4 May 2016 21:12:56 +0200 Subject: [PATCH] Add new CPRNG ENGINE The implementation is based on python-cryptography's Python code. --- Lib/test/test_ssl.py | 28 +++- Modules/_ssl.c | 375 ++++++++++++++++++++++++++++++++++++++++++++++++ Modules/clinic/_ssl.c.h | 111 +++++++++++++- 3 files changed, 512 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 00d437a951c5e44bc4d44d52fd0808a65cfb4fea..b461cb724653eb7abcec1079efab263779004cab 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -20,6 +20,7 @@ import platform import functools ssl = support.import_module("ssl") +_ssl = support.import_module("_ssl") try: import threading @@ -3322,6 +3323,31 @@ if _have_threads: self.assertEqual(s.recv(1024), TEST_DATA) +class EngineTests(unittest.TestCase): + def test_osrandom_engine(self): + engine = _ssl._get_rand_engine() + self.assertEqual(engine.id, 'osrandom') + self.assertEqual(engine.name, 'CPython_osrandom_engine') + _ssl._set_rand_engine('osrandom') + engine = _ssl._get_rand_engine() + self.assertEqual(engine.id, 'osrandom') + + def test_disable_osrandom_engine(self): + try: + _ssl._set_rand_engine(None) + engine = _ssl._get_rand_engine() + # engine is either None or 'openssl' + if engine is not None: + self.assertEqual(engine.id, 'openssl') + self.assertEqual(len(ssl.RAND_bytes(8)), 8) + finally: + _ssl._set_rand_engine('osrandom') + + def test_list_engines(self): + _ssl._load_engines() + _ssl._get_engines() + + def test_main(verbose=False): if support.verbose: import warnings @@ -3364,7 +3390,7 @@ def test_main(verbose=False): tests = [ ContextTests, BasicSocketTests, SSLErrorTests, MemoryBIOTests, - SimpleBackgroundTests, + SimpleBackgroundTests, EngineTests ] if support.is_resource_enabled('network'): diff --git a/Modules/_ssl.c b/Modules/_ssl.c index ead5b0ef759320e49a4dc5d74cee7dd11c514194..d1b6b38c40cb13aa534d6850098ebd387e75b126 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -65,6 +65,9 @@ static PySocketModule_APIObject PySocketModule; #include "openssl/err.h" #include "openssl/rand.h" #include "openssl/bio.h" +#ifndef OPENSSL_NO_ENGINE +#include +#endif /* SSL error object */ static PyObject *PySSLErrorObject; @@ -4347,6 +4350,357 @@ _ssl_enum_crls_impl(PyModuleDef *module, const char *store_name) #endif /* _MSC_VER */ +#ifndef OPENSSL_NO_ENGINE +/* OpenSSL RAND engine + * + * The osrandom engine provides an alternative CPRNG for OpenSSL. Instead + * of a userspace implementation, it uses _PyOS_URandom() to get random data + * from the operating system's entropy source (getentropy(), /dev/urandom, + * CryptGenRandom()). The engine is automatically added to OpenSSL's engine + * table and installed as default RAND engine. + * + * The engine has the same ID as PyCA's cryptography so it can pick up the + * engine automatically. A couple of private helper functions are available + * to query the default RAND engine or to set a different one. + */ + +/* Same as PyCA/cryptography's RAND engine */ +static const char osrandom_engine_id[] = "osrandom"; +static const char osrandom_engine_name[] = "CPython_osrandom_engine"; +static int osrandom_initialized = 0; + +static int +_ssl_osrandom_bytes(unsigned char *buf, int num) +{ + int rc; +#ifdef WITH_THREAD + /* Need to acquire the GIL, _PyOS_URandom calls Python APIs */ + PyGILState_STATE gstate = PyGILState_Ensure(); +#endif + rc = _PyOS_URandom((char *)buf, num); +#ifdef WITH_THREAD + PyGILState_Release(gstate); +#endif + return rc == 0 ? 1 : 0; +} + +static int +_ssl_osrandom_status(void) +{ + return 1; +} + +RAND_METHOD _ssl_osrandom_meth = { + NULL, /* seed */ + _ssl_osrandom_bytes, /* bytes */ + NULL, /* cleanup */ + NULL, /* add */ + _ssl_osrandom_bytes, /* pseudobytes */ + _ssl_osrandom_status /* status */ +}; + +/* Register osrandom engine + * Return codes: + * 1: succcessfully registered + * 0: an engine by that id is already registered (not an error) + * -1: error + */ + +static int +_ssl_osrandom_engine_register(void) +{ + ENGINE *e = NULL; + int rc = -1; + + if ((e = ENGINE_new()) == NULL) { + _setSSLError(NULL, 0, __FILE__, __LINE__); + goto exit; + } + if (!ENGINE_set_id(e, osrandom_engine_id)) { + _setSSLError(NULL, 0, __FILE__, __LINE__); + goto exit; + } + if (!ENGINE_set_name(e, osrandom_engine_name)) { + _setSSLError(NULL, 0, __FILE__, __LINE__); + goto exit; + } + + if (!ENGINE_set_RAND(e, &_ssl_osrandom_meth)) { + _setSSLError(NULL, 0, __FILE__, __LINE__); + goto exit; + } + assert(ENGINE_get_RAND(e) == &_ssl_osrandom_meth); + + /* Add engine to OpenSSL + * ENGINE_add() obtains a new structural reference + */ + if (!ENGINE_add(e)) { + /* The conflict error is somewhere down the error stack + * XXX is this really necssary? + */ + unsigned long err, first_error; + first_error = ERR_peek_error(); + do { + err = ERR_get_error(); + if (ERR_GET_REASON(err) == ENGINE_R_CONFLICTING_ENGINE_ID) { + /* Ignore: + * Somebody else has registered an engine by the same name. + * It's either cryptography or another subinterpreter. + */ + ERR_clear_error(); + rc = 0; + goto exit; + } + } while (err != 0); + + _setSSLError("ENGINE_add(osrandom) failed", first_error, + __FILE__, __LINE__); + goto exit; + + } + + /* OK */ + rc = 1; + + exit: + /* Clear structural reference from ENGINE_new() */ + if (e != NULL) { + ENGINE_free(e); + } + return rc; +} + +/* Initial and activiate an engine as RAND engine + */ + +static int +_ssl_rand_engine_activate(const char *name) +{ + ENGINE *e = NULL; + int funcref = 0; + int structref = 0; + + if ((e = ENGINE_by_id(name)) == NULL) { + _setSSLError(NULL, 0, __FILE__, __LINE__); + goto error; + } + structref++; + + if (!ENGINE_init(e)) { + _setSSLError("ENGINE_init() failed", 0, __FILE__, __LINE__); + goto error; + } + funcref++; + + if (ENGINE_get_RAND(e) == NULL) { + PyErr_Format(PyExc_ValueError, + "Engine %.100s does not implement rand_method.", + name); + goto error; + } + /* Initialize engine as default RAND engine */ + if (!ENGINE_set_default_RAND(e)) { + _setSSLError(NULL, 0, __FILE__, __LINE__); + goto error; + } + /* Reset RAND to use the other engine */ + RAND_cleanup(); + + /* Release the functional reference from ENGINE_init() */ + if (!ENGINE_finish(e)) { + _setSSLError(NULL, 0, __FILE__, __LINE__); + goto error; + } + funcref--; + /* Release the structural reference from ENGINE_by_id() */ + if (!ENGINE_free(e)) { + _setSSLError(NULL, 0, __FILE__, __LINE__); + goto error; + } + structref--; + + return 0; + + error: + if (funcref) { + ENGINE_finish(e); + } + if (structref) { + ENGINE_free(e); + } + return -1; +} + +/* Deactivate default RAND engine + * This forces OpenSSL to use its internal RAND engine. This may install + * the 'openssl' engine, which is basically the same. + */ +static int +_ssl_rand_engine_deactivate(void) +{ + ENGINE *e = NULL; + + if ((e = ENGINE_get_default_RAND()) == NULL) { + return 0; + } + ENGINE_unregister_RAND(e); + /* Reset RAND to use the other engine */ + RAND_cleanup(); + + if (!ENGINE_finish(e)) { + _setSSLError(NULL, 0, __FILE__, __LINE__); + return -1; + } + return 1; +} + +static PyTypeObject StructEngineType; + +PyDoc_STRVAR(struct_engine__doc__, +"struct_engine: OpenSSL engine."); + +static PyStructSequence_Field struct_engine_fields[] = { + {"id", "engine id"}, + {"name", "engine name"}, +}; + +static PyStructSequence_Desc struct_engine_desc = { + "_ssl.engine", /* name */ + struct_engine__doc__, /* doc */ + struct_engine_fields, /* fields */ + 2 /* n_in_sequence */ +}; + +static PyObject * +_ssl_engine_struct(ENGINE *e) +{ + PyObject *result; + PyObject *obj = NULL; + const char *c = NULL; + + result = PyStructSequence_New(&StructEngineType); + if (result == NULL) { + return NULL; + } + + c = ENGINE_get_id(e); + if ((obj = PyUnicode_FromString(c)) == NULL) { + goto error; + } + PyStructSequence_SET_ITEM(result, 0, obj); + + c = ENGINE_get_name(e); + if (c != NULL) { + if ((obj = PyUnicode_FromString(c)) == NULL) { + goto error; + } + } else { + obj = Py_None; + Py_INCREF(obj); + } + PyStructSequence_SET_ITEM(result, 1, obj); + + return result; + + error: + Py_DECREF(result); + return NULL; +} + +/*[clinic input] +_ssl._set_rand_engine + name: str(accept={str, NoneType}) + / +[clinic start generated code]*/ + +static PyObject * +_ssl__set_rand_engine_impl(PyModuleDef *module, const char *name) +/*[clinic end generated code: output=d40d36d80f36df7a input=8cac7a8576db477e]*/ +{ + int rc; + + if (name == NULL) { + rc = _ssl_rand_engine_deactivate(); + } else { + rc = _ssl_rand_engine_activate(name); + } + if (rc == -1) { + return NULL; + } + Py_RETURN_NONE; +} + +/*[clinic input] +_ssl._get_rand_engine + +Get OpenSSL RAND engine +[clinic start generated code]*/ + +static PyObject * +_ssl__get_rand_engine_impl(PyModuleDef *module) +/*[clinic end generated code: output=c408514b3ae178b3 input=65786cdf8dc9b497]*/ +{ + ENGINE *e = NULL; + e = ENGINE_get_default_RAND(); + if (e != NULL) { + PyObject *result = _ssl_engine_struct(e); + ENGINE_finish(e); + return result; + } else { + Py_RETURN_NONE; + } +} + +/*[clinic input] +_ssl._get_engines + +[clinic start generated code]*/ + +static PyObject * +_ssl__get_engines_impl(PyModuleDef *module) +/*[clinic end generated code: output=cf78a0aefea9eb40 input=b8d8bac3ad161cde]*/ +{ + ENGINE *e; + PyObject *lst; + + if ((lst = PyList_New(0)) == NULL) { + return NULL; + } + + /* get_next consumes the previous structural reference */ + for (e = ENGINE_get_first(); e != NULL; e = ENGINE_get_next(e)) { + PyObject *result; + result = _ssl_engine_struct(e); + + if (PyList_Append(lst, result) < 0) { + Py_DECREF(result); + Py_DECREF(lst); + return NULL; + } + Py_DECREF(result); + } + return lst; +} + + +/*[clinic input] +_ssl._load_engines + +Load all builtin engines and the OpenSSL engine. +[clinic start generated code]*/ + +static PyObject * +_ssl__load_engines_impl(PyModuleDef *module) +/*[clinic end generated code: output=941bea0daf3c9e15 input=98aa0dbdca14b72d]*/ +{ + ENGINE_load_builtin_engines(); + ENGINE_load_openssl(); + Py_RETURN_NONE; +} + +#endif + + /* List of functions exported by this module. */ static PyMethodDef PySSL_methods[] = { _SSL__TEST_DECODE_CERT_METHODDEF @@ -4360,6 +4714,10 @@ static PyMethodDef PySSL_methods[] = { _SSL_ENUM_CRLS_METHODDEF _SSL_TXT2OBJ_METHODDEF _SSL_NID2OBJ_METHODDEF + _SSL__SET_RAND_ENGINE_METHODDEF + _SSL__GET_RAND_ENGINE_METHODDEF + _SSL__GET_ENGINES_METHODDEF + _SSL__LOAD_ENGINES_METHODDEF {NULL, NULL} /* Sentinel */ }; @@ -4450,6 +4808,7 @@ static int _setup_ssl_threads(void) { #endif /* def HAVE_THREAD */ + PyDoc_STRVAR(module_doc, "Implementation module for SSL socket operations. See the socket module\n\ for documentation."); @@ -4524,6 +4883,22 @@ PyInit__ssl(void) #endif OpenSSL_add_all_algorithms(); +#ifndef OPENSSL_NO_ENGINE + if (!osrandom_initialized) { + if (PyStructSequence_InitType2(&StructEngineType, + &struct_engine_desc) < 0) + return NULL; + + if (_ssl_osrandom_engine_register() < 0) { + return NULL; + } + if (_ssl_rand_engine_activate(osrandom_engine_id) < 0) { + return NULL; + } + osrandom_initialized = 1; + } +#endif + /* Add symbols to module dict */ sslerror_type_slots[0].pfunc = PyExc_OSError; PySSLErrorObject = PyType_FromSpec(&sslerror_type_spec); diff --git a/Modules/clinic/_ssl.c.h b/Modules/clinic/_ssl.c.h index 4dbc5d0010790bc81afc561ee726c175d259ac95..0a7b4f33f3495939c51af9698bf371ad1b7284ff 100644 --- a/Modules/clinic/_ssl.c.h +++ b/Modules/clinic/_ssl.c.h @@ -1079,6 +1079,99 @@ exit: #endif /* defined(_MSC_VER) */ +#if !defined(OPENSSL_NO_ENGINE) + +PyDoc_STRVAR(_ssl__set_rand_engine__doc__, +"_set_rand_engine($module, name, /)\n" +"--\n" +"\n"); + +#define _SSL__SET_RAND_ENGINE_METHODDEF \ + {"_set_rand_engine", (PyCFunction)_ssl__set_rand_engine, METH_O, _ssl__set_rand_engine__doc__}, + +static PyObject * +_ssl__set_rand_engine_impl(PyModuleDef *module, const char *name); + +static PyObject * +_ssl__set_rand_engine(PyModuleDef *module, PyObject *arg) +{ + PyObject *return_value = NULL; + const char *name; + + if (!PyArg_Parse(arg, "z:_set_rand_engine", &name)) + goto exit; + return_value = _ssl__set_rand_engine_impl(module, name); + +exit: + return return_value; +} + +#endif /* !defined(OPENSSL_NO_ENGINE) */ + +#if !defined(OPENSSL_NO_ENGINE) + +PyDoc_STRVAR(_ssl__get_rand_engine__doc__, +"_get_rand_engine($module, /)\n" +"--\n" +"\n" +"Get engine id and name of the current RAND engine"); + +#define _SSL__GET_RAND_ENGINE_METHODDEF \ + {"_get_rand_engine", (PyCFunction)_ssl__get_rand_engine, METH_NOARGS, _ssl__get_rand_engine__doc__}, + +static PyObject * +_ssl__get_rand_engine_impl(PyModuleDef *module); + +static PyObject * +_ssl__get_rand_engine(PyModuleDef *module, PyObject *Py_UNUSED(ignored)) +{ + return _ssl__get_rand_engine_impl(module); +} + +#endif /* !defined(OPENSSL_NO_ENGINE) */ + +#if !defined(OPENSSL_NO_ENGINE) + +PyDoc_STRVAR(_ssl__get_engines__doc__, +"_get_engines($module, /)\n" +"--\n" +"\n"); + +#define _SSL__GET_ENGINES_METHODDEF \ + {"_get_engines", (PyCFunction)_ssl__get_engines, METH_NOARGS, _ssl__get_engines__doc__}, + +static PyObject * +_ssl__get_engines_impl(PyModuleDef *module); + +static PyObject * +_ssl__get_engines(PyModuleDef *module, PyObject *Py_UNUSED(ignored)) +{ + return _ssl__get_engines_impl(module); +} + +#endif /* !defined(OPENSSL_NO_ENGINE) */ + +#if !defined(OPENSSL_NO_ENGINE) + +PyDoc_STRVAR(_ssl__load_engines__doc__, +"_load_engines($module, /)\n" +"--\n" +"\n"); + +#define _SSL__LOAD_ENGINES_METHODDEF \ + {"_load_engines", (PyCFunction)_ssl__load_engines, METH_NOARGS, _ssl__load_engines__doc__}, + +static PyObject * +_ssl__load_engines_impl(PyModuleDef *module); + +static PyObject * +_ssl__load_engines(PyModuleDef *module, PyObject *Py_UNUSED(ignored)) +{ + return _ssl__load_engines_impl(module); +} + +#endif /* !defined(OPENSSL_NO_ENGINE) */ + #ifndef _SSL__SSLSOCKET_SELECTED_NPN_PROTOCOL_METHODDEF #define _SSL__SSLSOCKET_SELECTED_NPN_PROTOCOL_METHODDEF #endif /* !defined(_SSL__SSLSOCKET_SELECTED_NPN_PROTOCOL_METHODDEF) */ @@ -1102,4 +1195,20 @@ exit: #ifndef _SSL_ENUM_CRLS_METHODDEF #define _SSL_ENUM_CRLS_METHODDEF #endif /* !defined(_SSL_ENUM_CRLS_METHODDEF) */ -/*[clinic end generated code: output=a14999cb565a69a2 input=a9049054013a1b77]*/ + +#ifndef _SSL__SET_RAND_ENGINE_METHODDEF + #define _SSL__SET_RAND_ENGINE_METHODDEF +#endif /* !defined(_SSL__SET_RAND_ENGINE_METHODDEF) */ + +#ifndef _SSL__GET_RAND_ENGINE_METHODDEF + #define _SSL__GET_RAND_ENGINE_METHODDEF +#endif /* !defined(_SSL__GET_RAND_ENGINE_METHODDEF) */ + +#ifndef _SSL__GET_ENGINES_METHODDEF + #define _SSL__GET_ENGINES_METHODDEF +#endif /* !defined(_SSL__GET_ENGINES_METHODDEF) */ + +#ifndef _SSL__LOAD_ENGINES_METHODDEF + #define _SSL__LOAD_ENGINES_METHODDEF +#endif /* !defined(_SSL__LOAD_ENGINES_METHODDEF) */ +/*[clinic end generated code: output=295b7db3eaccc364 input=a9049054013a1b77]*/ -- 2.5.5