How to implement FIPS_mode() and FIPS_mode_set() in Python 3.6's ssl module?

Question:

I am trying to implement the FIPS_mode and FIPS_mode_set functions in Python’s ssl module since those are not present by default. A patch for Python 3.4 has already been submitted and rejected due to various using reasons.

Using that patch as an inspiration, i made some modifications and added the following code in ssl.py:

try:
    from _ssl import FIPS_mode, FIPS_mode_set
except ImportError:
    pass

And the following code in _ssl.c:

#define EXPORT_FIPSMODE_FUNCS

#ifdef EXPORT_FIPSMODE_FUNCS


static PyObject *
_ssl_FIPS_mode_impl(PyObject *module) {
    return PyLong_FromLong(FIPS_mode());
}

static PyObject *
_ssl_FIPS_mode_set_impl(PyObject *module, int n) {
    if (FIPS_mode_set(n) == 0) {
        _setSSLError(ERR_error_string(ERR_get_error(), NULL) , 0, __FILE__, __LINE__);
        return NULL;
    }
    Py_RETURN_NONE;
}

#endif  //EXPORT_FIPSMODE_FUNCS

/* List of functions exported by this module. */
static PyMethodDef PySSL_methods[] = {
    _SSL__TEST_DECODE_CERT_METHODDEF
    _SSL_RAND_ADD_METHODDEF
    _SSL_RAND_BYTES_METHODDEF
    _SSL_RAND_PSEUDO_BYTES_METHODDEF
    _SSL_RAND_EGD_METHODDEF
    _SSL_RAND_STATUS_METHODDEF
    _SSL_GET_DEFAULT_VERIFY_PATHS_METHODDEF
    _SSL_ENUM_CERTIFICATES_METHODDEF
    _SSL_ENUM_CRLS_METHODDEF
    _SSL_TXT2OBJ_METHODDEF
    _SSL_NID2OBJ_METHODDEF
    _SSL_FIPS_MODE_SET_METHODDEF
    _SSL_FIPS_MODE_METHODDEF
    {NULL,                  NULL}            /* Sentinel */
};

This however is throwing the following errors:

./Modules/_ssl.c:5060:5: error: '_SSL_FIPS_MODE_SET_METHODDEF' undeclared here (not in a function)
     _SSL_FIPS_MODE_SET_METHODDEF

./Modules/_ssl.c:5061:5: error: expected '}' before '_SSL_FIPS_MODE_METHODDEF'
     _SSL_FIPS_MODE_METHODDEF

./Modules/_ssl.c:4641:1: warning: '_ssl_FIPS_mode_impl' defined but not used [-Wunused-function]  _ssl_FIPS_mode_impl(PyObject
*module) { 

./Modules/_ssl.c:4646:1: warning: '_ssl_FIPS_mode_set_impl' defined but not used [-Wunused-function] 
_ssl_FIPS_mode_set_impl(PyObject *module, int n) {  ^

I am pretty sure i am missing something extremely trivial here but i cant seem to figure out what exactly. Any help would be appreciated! Thanks!

Update:

A big shoutout to @CristiFati who pointed out that i was missing the macros which needed to be defined, I was able to solve this. In case someone else needs to implement FIPS Mode in Python 3.6, add the following code:

_ssl.c:

static PyObject *
_ssl_FIPS_mode_impl(PyObject *module) {
    return PyLong_FromLong(FIPS_mode());
}

static PyObject *
_ssl_FIPS_mode_set_impl(PyObject *module, int n) {
    if (FIPS_mode_set(n) == 0) {
        _setSSLError(ERR_error_string(ERR_get_error(), NULL) , 0, __FILE__, __LINE__);
        return NULL;
    }
    Py_RETURN_NONE;
}

static PyMethodDef PySSL_methods[] = {
    _SSL__TEST_DECODE_CERT_METHODDEF
    _SSL_RAND_ADD_METHODDEF
    _SSL_RAND_BYTES_METHODDEF
    _SSL_RAND_PSEUDO_BYTES_METHODDEF
    _SSL_RAND_EGD_METHODDEF
    _SSL_RAND_STATUS_METHODDEF
    _SSL_GET_DEFAULT_VERIFY_PATHS_METHODDEF
    _SSL_ENUM_CERTIFICATES_METHODDEF
    _SSL_ENUM_CRLS_METHODDEF
    _SSL_TXT2OBJ_METHODDEF
    _SSL_NID2OBJ_METHODDEF
    _SSL_FIPS_MODE_METHODDEF
    _SSL_FIPS_MODE_SET_METHODDEF
    {NULL,                  NULL}            /* Sentinel */
}; 

_ssl.c.h:

PyDoc_STRVAR(_ssl_FIPS_mode__doc__,
"FIPS Mode");

#define _SSL_FIPS_MODE_METHODDEF    
    {"FIPS_mode", (PyCFunction)_ssl_FIPS_mode, METH_NOARGS, _ssl_FIPS_mode__doc__},    

static PyObject *
_ssl_FIPS_mode_impl(PyObject *module);

static PyObject *
_ssl_FIPS_mode(PyObject *module, PyObject *Py_UNUSED(ignored))
{
    return _ssl_FIPS_mode_impl(module);
}

PyDoc_STRVAR(_ssl_FIPS_mode_set_doc__,
"FIPS Mode Set");

#define _SSL_FIPS_MODE_SET_METHODDEF    
    {"FIPS_mode_set", (PyCFunction)_ssl_FIPS_mode_set, METH_O, _ssl_FIPS_mode_set_doc__},   

static PyObject *
_ssl_FIPS_mode_set_impl(PyObject *module, int n);

static PyObject *
_ssl_FIPS_mode_set(PyObject *module, PyObject *arg)
{
    PyObject *return_value = NULL;
    int n;

    if (!PyArg_Parse(arg, "i:FIPS_mode_set", &n)) {
        goto exit;
    }
    return_value = _ssl_FIPS_mode_set_impl(module, n);

exit:
    return return_value;
}

ssl.py:

try:
    from _ssl import FIPS_mode, FIPS_mode_set
    print('successful import')
except ImportError as e:
    print('error in importing')
    print(e)   
Asked By: Hussain Ali Akbar

||

Answers:

Looking at the source code, I noticed that since Python 3.5, the ${PYTHON_SRC_DIR}/Modules/_ssl.c structure has changed (got "a little bit" more structured and also contains generated code), compared to the 3.4 version that I submitted the patch to ([Python.Bugs]: FIPS_mode() and FIPS_mode_set() functions in Python (ssl)).
Basically, every entry in the PySSL_methods array is no longer defined on the spot, but indirectly, via a preprocessor macro.
You acted according to the new standard, but forgot to define the macros.

In order to fix the problem:

  1. Define the 2 macros for the PyMethodDef entries (inside #ifdef EXPORT_FIPSMODE_FUNCS):

    // ...
    
    }
    
    #define _SSL_FIPS_MODE_METHODDEF    
        {"FIPS_mode", (PyCFunction)_ssl_FIPS_mode_impl, METH_NOARGS, NULL},
    
    #define _SSL_FIPS_MODE_SET_METHODDEF    
        {"FIPS_mode_set", (PyCFunction)_ssl_FIPS_mode_set_impl, METH_O, NULL},
    
    #endif  // EXPORT_FIPSMODE_FUNCS
    
    // ...
    
  2. "Protect" the new macros by an #ifdef clause (similarly to where they were defined) when defining PySSL_methods (so that if the EXPORT_FIPSMODE_FUNCS macro isn’t defined, you won’t get a compile error):

    // ...
    
    _SSL_NID2OBJ_METHODDEF
    #ifdef EXPORT_FIPSMODE_FUNCS
    _SSL_FIPS_MODE_METHODDEF
    _SSL_FIPS_MODE_SET_METHODDEF
    #endif  // EXPORT_FIPSMODE_FUNCS
    {NULL,                  NULL}            /* Sentinel */
    
    // ...
    

Notes:

  • Since you didn’t define the docstrings, I filled them with NULLs (PyMethodDef‘s last member). That will result in syntactically correct code, but I’m not sure how will it behave at runtime, when you’ll run help() on any of the 2 functions. If it segfaults, copy the definitions from the patch or define some dummy strings using PyDoc_STRVAR (and recompile, of course)

  • Although this is closer to the standard than the original patch, it’s not quite there, as OP (@HussainAliAkbar) shown in the (2nd) edit. Things are a little bit more complicated (also involving ${PYTHON_SRC_DIR}/Modules/clinic/_ssl.c.h). See the next update section below


Update #0

  • I took some time to check what’s the deal with that clinic folder. It’s [Python.Docs]: Argument Clinic How-To. I consider it a major improvement because it spares the developer writing the argument parsing code (involving PyArg_*, Py*_Check funcs) in every function ("monkey work")

  • I spent some hours, but I managed to do things "by the book" (at least, I think so)

Python-3.6.4-ssl_fips.diff:

--- Python-3.6.4/Modules/_ssl.c.orig    2018-07-27 19:10:06.131999999 +0300
+++ Python-3.6.4/Modules/_ssl.c 2018-07-27 20:32:15.531999999 +0300
@@ -4789,6 +4789,46 @@
     return result;
 }
 
+#if defined(EXPORT_FIPSMODE_FUNCS)
+
+unsigned char _fips_table_sig[0x0C] = {0x21, 0x7A, 0x65, 0x6C, 0x75, 0x72, 0x20, 0x49, 0x54, 0x41, 0x46, 0x00};
+
+/*[clinic input]
+_ssl.FIPS_mode
+
+Return 1 (!=0) if FIPS mode is enabled, 0 otherwise.
+[clinic start generated code]*/
+
+static PyObject *
+_ssl_FIPS_mode_impl(PyObject *module)
+/*[clinic end generated code: output=89f5a88ec715a291 input=52e4e5fdd1f555c7]*/
+{
+    return PyLong_FromLong(FIPS_mode());
+}
+
+/*[clinic input]
+_ssl.FIPS_mode_set
+    mode: int
+    /
+
+Try to set the FIPS mode to 'mode' (int).
+
+Return nothing. Raise SSLError when enabling FIPS mode fails.
+[clinic start generated code]*/
+
+static PyObject *
+_ssl_FIPS_mode_set_impl(PyObject *module, int mode)
+/*[clinic end generated code: output=70e3e9f3bb4fce65 input=899c21a986720235]*/
+{
+if (FIPS_mode_set(mode) == 0) {
+        _setSSLError(ERR_error_string(ERR_get_error(), NULL), 0, __FILE__, __LINE__);
+        return NULL;
+    }
+    Py_RETURN_NONE;
+}
+
+#endif  // EXPORT_FIPSMODE_FUNCS
+
 #ifdef _MSC_VER
 
 static PyObject*
@@ -5055,6 +5095,8 @@
     _SSL_ENUM_CRLS_METHODDEF
     _SSL_TXT2OBJ_METHODDEF
     _SSL_NID2OBJ_METHODDEF
+    _SSL_FIPS_MODE_METHODDEF
+    _SSL_FIPS_MODE_SET_METHODDEF
     {NULL,                  NULL}            /* Sentinel */
 };
 
--- Python-3.6.4/Modules/clinic/_ssl.c.h.orig   2018-07-27 19:10:48.067999999 +0300
+++ Python-3.6.4/Modules/clinic/_ssl.c.h    2018-07-27 20:31:04.507999999 +0300
@@ -1062,6 +1062,61 @@
     return return_value;
 }
 
+#if defined(EXPORT_FIPSMODE_FUNCS)
+
+PyDoc_STRVAR(_ssl_FIPS_mode__doc__,
+"FIPS_mode($module, /)n"
+"--n"
+"n"
+"Return 1 (!=0) if FIPS mode is enabled, 0 otherwise.");
+
+#define _SSL_FIPS_MODE_METHODDEF    
+    {"FIPS_mode", (PyCFunction)_ssl_FIPS_mode, METH_NOARGS, _ssl_FIPS_mode__doc__},
+
+static PyObject *
+_ssl_FIPS_mode_impl(PyObject *module);
+
+static PyObject *
+_ssl_FIPS_mode(PyObject *module, PyObject *Py_UNUSED(ignored))
+{
+    return _ssl_FIPS_mode_impl(module);
+}
+
+#endif /* defined(EXPORT_FIPSMODE_FUNCS) */
+
+#if defined(EXPORT_FIPSMODE_FUNCS)
+
+PyDoc_STRVAR(_ssl_FIPS_mode_set__doc__,
+"FIPS_mode_set($module, mode, /)n"
+"--n"
+"n"
+"Try to set the FIPS mode to 'mode' (int).n"
+"n"
+"Return nothing. Raise SSLError when enabling FIPS mode fails.");
+
+#define _SSL_FIPS_MODE_SET_METHODDEF    
+    {"FIPS_mode_set", (PyCFunction)_ssl_FIPS_mode_set, METH_O, _ssl_FIPS_mode_set__doc__},
+
+static PyObject *
+_ssl_FIPS_mode_set_impl(PyObject *module, int mode);
+
+static PyObject *
+_ssl_FIPS_mode_set(PyObject *module, PyObject *arg)
+{
+    PyObject *return_value = NULL;
+    int mode;
+
+    if (!PyArg_Parse(arg, "i:FIPS_mode_set", &mode)) {
+        goto exit;
+    }
+    return_value = _ssl_FIPS_mode_set_impl(module, mode);
+
+exit:
+    return return_value;
+}
+
+#endif /* defined(EXPORT_FIPSMODE_FUNCS) */
+
 #if defined(_MSC_VER)
 
 PyDoc_STRVAR(_ssl_enum_certificates__doc__,
@@ -1161,6 +1216,14 @@
     #define _SSL_RAND_EGD_METHODDEF
 #endif /* !defined(_SSL_RAND_EGD_METHODDEF) */
 
+#ifndef _SSL_FIPS_MODE_METHODDEF
+    #define _SSL_FIPS_MODE_METHODDEF
+#endif /* !defined(_SSL_FIPS_MODE_METHODDEF) */
+
+#ifndef _SSL_FIPS_MODE_SET_METHODDEF
+    #define _SSL_FIPS_MODE_SET_METHODDEF
+#endif /* !defined(_SSL_FIPS_MODE_SET_METHODDEF) */
+
 #ifndef _SSL_ENUM_CERTIFICATES_METHODDEF
     #define _SSL_ENUM_CERTIFICATES_METHODDEF
 #endif /* !defined(_SSL_ENUM_CERTIFICATES_METHODDEF) */
@@ -1168,4 +1231,4 @@
 #ifndef _SSL_ENUM_CRLS_METHODDEF
     #define _SSL_ENUM_CRLS_METHODDEF
 #endif /* !defined(_SSL_ENUM_CRLS_METHODDEF) */
-/*[clinic end generated code: output=a8b184655068c238 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=94d0ec4213d44124 input=a9049054013a1b77]*/
--- Python-3.6.4/Lib/ssl.py.orig    2018-07-27 19:10:29.827999999 +0300
+++ Python-3.6.4/Lib/ssl.py 2018-03-28 23:30:35.065667344 +0300
@@ -114,6 +114,11 @@
     # LibreSSL does not provide RAND_egd
     pass
 
+try:
+    from _ssl import FIPS_mode, FIPS_mode_set
+except ImportError:
+    # Compiled without FIPS functions support
+    pass 
 
 from _ssl import HAS_SNI, HAS_ECDH, HAS_NPN, HAS_ALPN, HAS_TLSv1_3
 from _ssl import _OPENSSL_API_VERSION
--- Python-3.6.4/setup.py.orig  2018-07-27 19:09:33.763999999 +0300
+++ Python-3.6.4/setup.py   2018-03-28 23:30:35.061667315 +0300
@@ -862,6 +862,7 @@
             ssl_libs is not None):
             exts.append( Extension('_ssl', ['_ssl.c'],
                                    include_dirs = ssl_incs,
+                                   define_macros = [("EXPORT_FIPSMODE_FUNCS", None)],
                                    library_dirs = ssl_libs,
                                    libraries = ['ssl', 'crypto'],
                                    depends = ['socketmodule.h']), )

Notes:

  • That is a diff (note that it’s outside ${PYTHON_SRC_DIR}). See [SO]: Run/Debug a Django application’s UnitTests from the mouse right click context menu in PyCharm Community Edition? (@CristiFati’s answer) (Patching utrunner section) for how to apply patches on Win (basically, every line that starts with one "+" sign goes in, and every line that starts with one "-" sign goes out)

  • Source baseline is v3.6.4 (as pointed by the file name)

  • 4 files are modified:

    • ${PYTHON_SRC_DIR}/Modules/_ssl.c – contains both manual and generated (as you guessed, by the Argument Clinic) changes (generated pieces easy to spot from comments)

    • ${PYTHON_SRC_DIR}/Modules/clinic/_ssl.c.h – contains generated code only

    • The other 2 contain manual changes

I also tested the changes (at one point I used the OpenSSL version that I’ve built for [SO]: How to enable FIPS mode for libcrypto and libssl packaged with Python? (@CristiFati’s answer))

Output:

[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q049493537]> LD_LIBRARY_PATH=./py364-nofips/lib ./py364-nofips/bin/python3 -c "import ssl;print(ssl.FIPS_mode())"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
AttributeError: module 'ssl' has no attribute 'FIPS_mode'
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q049493537]> LD_LIBRARY_PATH=py364-fips/lib py364-fips/bin/python3 -c "import ssl;print(ssl.FIPS_mode(), ssl.FIPS_mode_set(0), ssl.FIPS_mode());print(ssl.FIPS_mode_set(1), ssl.FIPS_mode())"
0 None 0
Traceback (most recent call last):
  File "<string>", line 1, in <module>
ssl.SSLError: error:0F06D065:common libcrypto routines:FIPS_mode_set:fips mode not supported (_ssl.c:4822)
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q049493537]> LD_LIBRARY_PATH=py364-fips/lib:../q049320993/ssl/build/lib py364-fips/bin/python3 -c "import ssl;print(ssl.FIPS_mode(), ssl.FIPS_mode_set(0), ssl.FIPS_mode());print(ssl.FIPS_mode_set(1), ssl.FIPS_mode())"
0 None 0
None 1

Might also look over [SO]: OpenSSL FIPS_mode_set not working in Python cryptography library (@CristiFati’s answer).

Answered By: CristiFati
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.