From 0fa89f1bae4b3163aa20b0bda56d5214c038bca4 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Fri, 16 Sep 2011 14:55:28 -0400 Subject: [PATCH 3/4] Add optional "usedforsecurity" argument in various places to _hashlib Improve _hashlib's support for OpenSSL FIPS mode, by initializing each twice, once in a liberal more, and once in a strict mode. Add a usedforsecurity=True default argument when working with hashes, so that in a FIPS-compliant environment usage of MD5 can fail (without segfaulting), and be marked as usedforsecurity=False on a callsite by callsite basis to use the non-strict mode. --- Modules/_hashopenssl.c | 125 ++++++++++++++++++++++++++++++++++------------- 1 files changed, 90 insertions(+), 35 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index 0ee98cf..d9a4c57 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -37,6 +37,7 @@ #endif /* EVP is the preferred interface to hashing in OpenSSL */ +#include #include #include /* We use the object interface to discover what hashes OpenSSL supports. */ @@ -75,15 +76,26 @@ static PyTypeObject EVPtype; typedef struct { PyObject *name_obj; - EVP_MD_CTX ctx; + /* We initialize each hash twice, giving a pair of values for each of the + following fields. + + Index 0 in each array (usedforsecurity=0) reflects initialization with + the EVP_MD_CTX_FLAG_NON_FIPS_ALLOW flag, giving a liberal approach in + which non-secure hashes such as MD5 are still allowed. + + Index 1 in each array (usedforsecurity=1) reflects the default option, + which in a FIPS-compliant environment may disallow non-secure hashes + such as MD5. + */ + EVP_MD_CTX ctxs[2]; /* ctx_ptr will point to ctx above, unless an initialization error occurred, when it will be NULL: */ - EVP_MD_CTX *ctx_ptr; + EVP_MD_CTX *ctx_ptrs[2]; /* If there was an error, we store the error message here (or NULL if the error string cannot be allocated): */ - PyObject *error_msg; + PyObject *error_msgs[2]; } EVPConstructorCache; #define DEFINE_CONSTS_FOR_NEW(Name) \ @@ -152,6 +164,24 @@ set_exception_for_evp_error(void) } +static void +mc_ctx_init(EVP_MD_CTX *ctx, int usedforsecurity) +{ + EVP_MD_CTX_init(ctx); + + /* + If the user has declared that this digest is being used in a + non-security role (e.g. indexing into a data structure), set + the exception flag for openssl to allow it + */ + if (!usedforsecurity) { +#ifdef EVP_MD_CTX_FLAG_NON_FIPS_ALLOW + EVP_MD_CTX_set_flags(ctx, + EVP_MD_CTX_FLAG_NON_FIPS_ALLOW); +#endif + } +} + /* Internal methods for a hash object */ static void @@ -338,15 +368,16 @@ EVP_repr(EVPobject *self) static int EVP_tp_init(EVPobject *self, PyObject *args, PyObject *kwds) { - static char *kwlist[] = {"name", "string", NULL}; + static char *kwlist[] = {"name", "string", "usedforsecurity", NULL}; PyObject *name_obj = NULL; PyObject *data_obj = NULL; + int usedforsecurity = 1; Py_buffer view; char *nameStr; const EVP_MD *digest; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O:HASH", kwlist, - &name_obj, &data_obj)) { + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|Oi:HASH", kwlist, + &name_obj, &data_obj, &usedforsecurity)) { return -1; } @@ -367,7 +398,7 @@ EVP_tp_init(EVPobject *self, PyObject *args, PyObject *kwds) PyBuffer_Release(&view); return -1; } - EVP_MD_CTX_init(&self->ctx); + mc_ctx_init(&self->ctx, usedforsecurity); if (!EVP_DigestInit_ex(&self->ctx, digest, NULL)) { set_exception_for_evp_error(); PyBuffer_Release(&view); @@ -456,7 +487,8 @@ static PyTypeObject EVPtype = { static PyObject * EVPnew(PyObject *name_obj, const EVP_MD *digest, const EVP_MD_CTX *initial_ctx, - const unsigned char *cp, Py_ssize_t len) + const unsigned char *cp, Py_ssize_t len, + int usedforsecurity) { EVPobject *self; @@ -471,7 +503,7 @@ EVPnew(PyObject *name_obj, if (initial_ctx) { EVP_MD_CTX_copy(&self->ctx, initial_ctx); } else { - EVP_MD_CTX_init(&self->ctx); + mc_ctx_init(&self->ctx, usedforsecurity); if (!EVP_DigestInit_ex(&self->ctx, digest, NULL)) { set_exception_for_evp_error(); Py_DECREF(self); @@ -500,21 +532,29 @@ PyDoc_STRVAR(EVP_new__doc__, An optional string argument may be provided and will be\n\ automatically hashed.\n\ \n\ -The MD5 and SHA1 algorithms are always supported.\n"); +The MD5 and SHA1 algorithms are always supported.\n\ +\n\ +An optional \"usedforsecurity=True\" keyword argument is provided for use in\n\ +environments that enforce FIPS-based restrictions. Some implementations of\n\ +OpenSSL can be configured to prevent the usage of non-secure algorithms (such\n\ +as MD5). If you have a non-security use for these algorithms (e.g. a hash\n\ +table), you can override this argument by marking the callsite as\n\ +\"usedforsecurity=False\"."); static PyObject * EVP_new(PyObject *self, PyObject *args, PyObject *kwdict) { - static char *kwlist[] = {"name", "string", NULL}; + static char *kwlist[] = {"name", "string", "usedforsecurity", NULL}; PyObject *name_obj = NULL; PyObject *data_obj = NULL; + int usedforsecurity = 1; Py_buffer view = { 0 }; PyObject *ret_obj; char *name; const EVP_MD *digest; - if (!PyArg_ParseTupleAndKeywords(args, kwdict, "O|O:new", kwlist, - &name_obj, &data_obj)) { + if (!PyArg_ParseTupleAndKeywords(args, kwdict, "O|Oi:new", kwlist, + &name_obj, &data_obj, &usedforsecurity)) { return NULL; } @@ -528,7 +568,8 @@ EVP_new(PyObject *self, PyObject *args, PyObject *kwdict) digest = EVP_get_digestbyname(name); - ret_obj = EVPnew(name_obj, digest, NULL, (unsigned char*)view.buf, view.len); + ret_obj = EVPnew(name_obj, digest, NULL, (unsigned char*)view.buf, view.len, + usedforsecurity); if (data_obj) PyBuffer_Release(&view); @@ -600,49 +641,56 @@ generate_hash_name_list(void) */ #define GEN_CONSTRUCTOR(NAME) \ static PyObject * \ - EVP_new_ ## NAME (PyObject *self, PyObject *args) \ + EVP_new_ ## NAME (PyObject *self, PyObject *args, PyObject *kwdict) \ { \ - return implement_specific_EVP_new(self, args, \ - "|O:" #NAME, \ + return implement_specific_EVP_new(self, args, kwdict, \ + "|Oi:" #NAME, \ &cached_info_ ## NAME ); \ } static PyObject * -implement_specific_EVP_new(PyObject *self, PyObject *args, +implement_specific_EVP_new(PyObject *self, PyObject *args, PyObject *kwdict, const char *format, EVPConstructorCache *cached_info) { + static char *kwlist[] = {"data", "usedforsecurity", NULL}; PyObject *data_obj = NULL; Py_buffer view = { 0 }; + int usedforsecurity = 1; + int idx; PyObject *ret_obj = NULL; assert(cached_info); - if (!PyArg_ParseTuple(args, format, &data_obj)) { + if (!PyArg_ParseTupleAndKeywords(args, kwdict, format, kwlist, + &data_obj, &usedforsecurity)) { return NULL; } if (data_obj) GET_BUFFER_VIEW_OR_ERROUT(data_obj, &view); + idx = usedforsecurity ? 1 : 0; + /* * If an error occurred during creation of the global content, the ctx_ptr * will be NULL, and the error_msg will, if non-NULL, contain the error * message: */ - if (cached_info->ctx_ptr) { + if (cached_info->ctx_ptrs[idx]) { /* This context was successfully initialized when _hashlib was initialized; copy it: */ ret_obj = EVPnew(cached_info->name_obj, NULL, - cached_info->ctx_ptr, - (unsigned char*)view.buf, view.len); + cached_info->ctx_ptrs[idx], + (unsigned char*)view.buf, view.len, + usedforsecurity); } else { /* Some kind of error happened initializing the cached context for this digest when _hashlib was initialized. Raise an exception with the saved error message: */ - if (cached_info->error_msg) { - PyErr_SetObject(PyExc_ValueError, cached_info->error_msg); + if (cached_info->error_msgs[idx]) { + PyErr_SetObject(PyExc_ValueError, cached_info->error_msgs[idx]); } else { PyErr_SetString(PyExc_ValueError, "Error initializing hash"); } @@ -656,7 +704,8 @@ implement_specific_EVP_new(PyObject *self, PyObject *args, /* a PyMethodDef structure for the constructor */ #define CONSTRUCTOR_METH_DEF(NAME) \ - {"openssl_" #NAME, (PyCFunction)EVP_new_ ## NAME, METH_VARARGS, \ + {"openssl_" #NAME, (PyCFunction)EVP_new_ ## NAME, \ + METH_VARARGS|METH_KEYWORDS, \ PyDoc_STR("Returns a " #NAME \ " hash object; optionally initialized with a string") \ } @@ -676,16 +725,19 @@ init_constructor_constant(EVPConstructorCache *cached_info, const char *name) assert(cached_info); cached_info->name_obj = PyUnicode_FromString(name); if (EVP_get_digestbyname(name)) { - EVP_MD_CTX_init(&cached_info->ctx); - if (EVP_DigestInit_ex(&cached_info->ctx, - EVP_get_digestbyname(name), NULL)) { - /* Success: */ - cached_info->ctx_ptr = &cached_info->ctx; - } else { - /* Failure: */ - cached_info->ctx_ptr = NULL; - cached_info->error_msg = PyUnicode_FromString(errstr_for_last_error()); - /* this could fail, e.g. low on memory, or encodings */ + int i; + for (i=0; i<2; i++) { + mc_ctx_init(&cached_info->ctxs[i], i); + if (EVP_DigestInit_ex(&cached_info->ctxs[i], + EVP_get_digestbyname(name), NULL)) { + /* Success: */ + cached_info->ctx_ptrs[i] = &cached_info->ctxs[i]; + } else { + /* Failure: */ + cached_info->ctx_ptrs[i] = NULL; + cached_info->error_msgs[i] = PyUnicode_FromString(errstr_for_last_error()); + /* this could fail, e.g. low on memory, or encodings */ + } } } } @@ -735,6 +787,9 @@ PyInit__hashlib(void) { PyObject *m, *openssl_md_meth_names; + SSL_load_error_strings(); + SSL_library_init(); + OpenSSL_add_all_digests(); /* TODO build EVP_functions openssl_* entries dynamically based -- 1.7.6