如何在Python 3.6 的ssl 模块中实现FIPS_mode() 和FIPS_mode_set()?

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

我正在尝试在 Python 的 ssl 模块中实现 FIPS_mode 和 FIPS_mode_set 函数,因为它们在默认情况下不存在。 A patch for Python 3.4已经提交,由于各种使用原因被拒。

以那个补丁为灵感,我做了一些修改并在 ssl.py 中添加了以下代码:

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

以及_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 */
};

然而,这会引发以下错误:

./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) {  ^

我很确定我在这里遗漏了一些非常微不足道的东西,但我似乎无法弄清楚到底是什么。任何帮助,将不胜感激!谢谢!

更新:

感谢 @CristiFati,他指出我缺少需要定义的宏,我能够解决这个问题。如果其他人需要在 Python 3.6 中实现 FIPS 模式,请添加以下代码:

_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)   

查看源代码,我注意到由于 Python 3.5${与 相比,PYTHON_SRC_DIR}/Modules/_ssl.c 结构发生了变化(“结构化了一点点”并且还包含生成的代码)我提交补丁的3.4版本([Python.Bugs]: FIPS_mode() and FIPS_mode_set() functions in Python (ssl))。
基本上,PySSL_methods 数组中的每个条目不再当场定义,而是通过预处理器宏间接定义。
您按照新的标准操作,但忘记定义宏。

为了解决问题:

  1. PyMethodDef 条目定义 2 个宏(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. "保护" #ifdef 的新宏定义 PySSL_methods 时的子句(类似于定义它们的位置)(这样如果 EXPORT_FIPSMODE_FUNCS 宏不是已定义,你不会得到编译错误):

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

备注:

  • 因为你没有定义docstrings,我用NULLs (PyMethodDef 的最后一个成员)。这将导致语法正确的代码,但我不确定它在 运行 时的行为如何,当您对 2 个函数中的任何一个执行 运行 help() 时。如果出现段错误,请从补丁中复制定义或使用 PyDoc_STRVAR 定义一些虚拟字符串(当然还要重新编译)
  • 虽然这比原始补丁更接近 标准,但它并不完全存在,如 OP (@HussainAliAkbar) 所示(2nd) 编辑。事情有点复杂(还涉及${PYTHON_SRC_DIR}/Modules/clinic/_ssl.c.h)。请参阅下面的下一个更新部分


更新#0

  • 我花了一些时间检查 clinic 文件夹是怎么回事。是 [Python.Docs]: Argument Clinic How-To。我认为这是一个 重大改进 因为它使开发人员无需编写参数解析代码(涉及 PyArg_*Py*_Check funcs) 在每个函数中 ("monkey work")
  • 我花了几个小时,但我设法“按规矩”做事(至少,我是这么认为的)

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']), )

备注:

  • 那是一个diff(注意在外面${PYTHON_SRC_DIR})。请参阅 修补 ut运行ner 部分)了解如何在 Win(基本上,以 一个“+” 符号开头的每一行,以及以 一个“-” 开头的每一行标志熄灭)。我正在使用 Cygwinbtw

  • 源基线是 v3.6.4(如文件名所指)

  • 4个文件被修改:

    • ${PYTHON_SRC_DIR}/Modules/_ssl.c - 包含手动和生成的(如您所猜,由 Argument Clinic ) 更改(生成的片段很容易从评论中发现)
    • ${PYTHON_SRC_DIR}/Modules/clinic/_ssl.c.h - 仅包含生成的代码
    • 其他 2 个包含手动更改

我还测试了更改(有一次我使用了我为 构建的 OpenSSL 版本)

输出:

[cfati@cfati-ubtu16x64-0:~/Work/Dev/Whosebug/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/Whosebug/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/Whosebug/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

可能还会看一下