OpenSSL

密码学和 SSL/TLS 工具包

fips_module

名称

fips_module - OpenSSL FIPS 模块指南

概要

有关详细信息,请参阅各个手册页。

描述

本指南详细介绍了 OpenSSL 与 FIPS 模块结合使用的不同方法。使用哪种正确的方法取决于您自己的具体情况以及您想要达成的目标。

有关安装 FIPS 模块的信息,请参见 https://github.com/openssl/openssl/blob/master/README-FIPS.md

请注意,旧函数 FIPS_mode() 和 FIPS_mode_set() 不再存在,因此如果您使用它们,则必须从应用程序中删除它们。

为使用 OpenSSL 3.0 FIPS 模块编写的应用程序不应该使用任何遗留 API 或功能来避免 FIPS 模块。具体来说,这包括

  • 低级密码学 API(改用高级 API,例如 EVP)

  • 引擎

  • 任何创建或修改自定义“方法”的函数(例如 EVP_MD_meth_new()、EVP_CIPHER_meth_new()、EVP_PKEY_meth_new()、RSA_meth_new()、EC_KEY_METHOD_new() 等)

所有上述 API 在 OpenSSL 3.0 中已被弃用 - 因此一个简单的规则是避免使用所有弃用函数。有关弃用函数的列表,请参见 ossl-guide-migration(7)

默认情况下使所有应用程序都使用 FIPS 模块

一种简单的方法是默认情况下让所有使用 OpenSSL 的应用程序仅使用 FIPS 模块进行密码学算法。

这种方法可以通过配置来完成。只要应用程序是在 OpenSSL 3.0 上构建和链接的,并且不覆盖默认配置文件的加载或其设置,那么它们就可以自动开始使用 FIPS 模块,而无需进行任何进一步的代码更改。

为此,必须修改默认的 OpenSSL 配置文件。此配置文件的位置将取决于平台以及在构建过程中提供的任何选项。您可以通过运行以下命令来检查配置文件的位置

$ openssl version -d
OPENSSLDIR: "/usr/local/ssl"

注意:许多操作系统默认情况下安装 OpenSSL。$PATH 中没有正确版本的 OpenSSL 是一个常见错误。检查您是否正在运行类似这样的 OpenSSL 3.0 版本

$ openssl version -v
OpenSSL 3.0.0-dev xx XXX xxxx (Library: OpenSSL 3.0.0-dev xx XXX xxxx)

上面的 OPENSSLDIR 值给出默认配置文件存储位置的目录名。因此,在本例中,默认配置文件将被命名为 /usr/local/ssl/openssl.cnf

编辑配置文件以在开头附近添加以下行

config_diagnostics = 1
openssl_conf = openssl_init

.include /usr/local/ssl/fipsmodule.cnf

[openssl_init]
providers = provider_sect
alg_section = algorithm_sect

[provider_sect]
fips = fips_sect
base = base_sect

[base_sect]
activate = 1

[algorithm_sect]
default_properties = fips=yes

显然,上面的包含文件位置应该与您之前安装的 FIPS 模块配置文件的路径和名称匹配。请参见 https://github.com/openssl/openssl/blob/master/README-FIPS.md

对于 FIPS 使用,建议启用 config_diagnostics 选项,以防止通过损坏或错误的配置意外使用未经 FIPS 验证的算法。请参见 config(5)

任何使用 OpenSSL 3.0 并在进行这些更改后启动的应用程序都将开始仅使用 FIPS 模块,除非这些应用程序采取明确的步骤来避免这种默认行为。请注意,此配置还激活了“base”提供程序。基本提供程序不包含任何密码学算法(因此不影响任何密码学操作的验证状态),但包含可能需要的其他支持算法。它旨在与 FIPS 模块结合使用。

这种方法的主要优点是它很简单,并且应用程序不需要进行任何代码更改就可以从 FIPS 模块中受益。这种方法有一些缺点

  • 您可能不希望所有应用程序都使用 FIPS 模块。

    有些应用程序应该使用 FIPS 模块,而另一些应用程序不应该使用。

  • 如果应用程序采取明确的步骤来不加载默认配置文件或设置不同的设置。

    此方法不适用于这些情况。

  • FIPS 模块中可用的算法是默认 OpenSSL 提供程序中可用的算法的子集。

    如果任何应用程序尝试使用任何不存在的算法,那么它们将失败。

  • 使用某些弃用 API 将避免使用 FIPS 模块。

    如果任何应用程序使用这些 API,则不会使用 FIPS 模块。

选择性地使应用程序默认使用 FIPS 模块

上述方法的一个变体是在单个应用程序的基础上做同样的事情。默认的 OpenSSL 配置文件取决于编译的 OPENSSLDIR 值,如上面的部分所述。但是,也可以通过 OPENSSL_CONF 环境变量覆盖要使用的配置文件。例如,以下内容(在 Unix 上)将导致应用程序使用非标准配置文件位置执行

$ OPENSSL_CONF=/my/nondefault/openssl.cnf myapplication

使用这种机制,您可以控制加载哪个配置文件(以及是否加载 FIPS 模块)在应用程序的基础上。

这消除了上面列出的缺点,即您可能不希望所有应用程序都使用 FIPS 模块。所有其他优点和缺点仍然适用。

以编程方式加载 FIPS 模块(默认库上下文)

应用程序可以选择显式加载 FIPS 提供程序,而不是依赖配置来执行此操作。配置文件仍然是必要的,以便保存 FIPS 模块配置数据(例如其自测状态和完整性数据)。但在这种情况下,我们不会通过该配置文件自动激活 FIPS 提供程序。

要以这种方式执行操作,请按照上面的 "使所有应用程序都使用 FIPS 模块默认情况下" 中的配置进行操作,但编辑 fipsmodule.cnf 文件以删除或注释掉表示 activate = 1 的行(请注意,将此值设置为 0 不足以)。这意味着所有必需的配置信息都将可用以加载 FIPS 模块,但它在应用程序启动时不会自动加载。然后可以像这样以编程方式加载 FIPS 提供程序

#include <openssl/provider.h>

int main(void)
{
    OSSL_PROVIDER *fips;
    OSSL_PROVIDER *base;

    fips = OSSL_PROVIDER_load(NULL, "fips");
    if (fips == NULL) {
        printf("Failed to load FIPS provider\n");
        exit(EXIT_FAILURE);
    }
    base = OSSL_PROVIDER_load(NULL, "base");
    if (base == NULL) {
        OSSL_PROVIDER_unload(fips);
        printf("Failed to load base provider\n");
        exit(EXIT_FAILURE);
    }

    /* Rest of application */

    OSSL_PROVIDER_unload(base);
    OSSL_PROVIDER_unload(fips);
    exit(EXIT_SUCCESS);
}

请注意,这应该是您在应用程序中执行的第一件事之一。如果在发生这种情况之前调用了任何需要使用密码学函数的 OpenSSL 函数,那么,如果还没有加载任何提供程序,则将自动加载默认提供程序。如果您随后显式加载 FIPS 提供程序,那么您将同时加载 FIPS 和默认提供程序。如果有多个实现可用,并且您尚未通过属性查询(见下文)显式指定应该使用哪一个,则使用哪个算法的实现是未定义的。

还要注意,在本例中,我们还加载了“base”提供程序。这将加载默认提供程序中也可用的一组算法 - 特别是非密码学算法,这些算法可以与 FIPS 提供程序一起使用。例如,这包含用于编码和解码密钥的算法。如果您决定不加载默认提供程序,那么您通常希望改为加载基本提供程序。

在本例中,我们使用的是“默认”库上下文。OpenSSL 函数在库上下文的作用域内运行。如果没有显式指定库上下文,则使用默认库上下文。有关库上下文的更多详细信息,请参见 OSSL_LIB_CTX(3) 手册页。

同时加载 FIPS 模块和其他提供程序

可以将 FIPS 提供程序和其他提供程序(如默认提供程序)同时加载到同一个库上下文中。您可以在算法获取期间使用属性查询字符串来指定要使用的实现。

例如,要获取符合 FIPS 标准的 SHA256 实现,您可以指定属性查询 fips=yes,如下所示

EVP_MD *sha256;

sha256 = EVP_MD_fetch(NULL, "SHA2-256", "fips=yes");

如果没有指定属性查询,或者多个实现与属性查询匹配,则返回哪个特定算法的实现是未定义的。

此示例显示了对来自默认提供程序的 SHA256 实现的显式请求

EVP_MD *sha256;

sha256 = EVP_MD_fetch(NULL, "SHA2-256", "provider=default");

也可以设置默认属性查询字符串。以下示例将默认属性查询 fips=yes 设置为默认库上下文中的所有获取

EVP_set_default_properties(NULL, "fips=yes");

如果获取函数同时指定了显式属性查询和定义了默认属性查询,则这两个查询将合并在一起,两者都适用。如果在两者中都指定了相同的属性名称,则本地属性查询将覆盖默认属性。

有两个重要的内置属性需要注意

“provider”属性使您能够指定要从中获取实现的提供程序,例如 provider=defaultprovider=fips。提供程序中实现的所有算法都为此属性设置了值。

还有 fips 属性。所有 FIPS 算法都与属性查询 fips=yes 匹配。默认提供程序和基本提供程序中还有一些非密码学算法也定义了 fips=yes 属性。这些是编码器和解码器算法,例如,可用于将 FIPS 提供程序中生成的密钥写入文件。编码器和解码器算法本身不在 FIPS 模块中,但允许与 FIPS 算法一起使用。

可以在配置文件中指定默认属性。例如,以下配置文件自动加载默认提供程序和 FIPS 提供程序,并将默认属性值设置为 fips=yes。请注意,此配置文件不加载“base”提供程序。“base”中的所有支持算法也都在“default”中,因此在这种情况下是不必要的

config_diagnostics = 1
openssl_conf = openssl_init

.include /usr/local/ssl/fipsmodule.cnf

[openssl_init]
providers = provider_sect
alg_section = algorithm_sect

[provider_sect]
fips = fips_sect
default = default_sect

[default_sect]
activate = 1

[algorithm_sect]
default_properties = fips=yes

以编程方式加载 FIPS 模块(非默认库上下文)

除了使用属性来将 FIPS 模块的使用与其他使用分开之外,还可以使用库上下文来实现这一点。在本例中,我们创建了两个库上下文。在一个上下文中,我们假设存在一个名为 openssl-fips.cnf 的配置文件,该文件会自动加载和配置 FIPS 和基本提供程序。另一个库上下文将仅使用默认提供程序。

OSSL_LIB_CTX *fips_libctx, *nonfips_libctx;
OSSL_PROVIDER *defctxnull = NULL;
EVP_MD *fipssha256 = NULL, *nonfipssha256 = NULL;
int ret = 1;

/*
 * Create two nondefault library contexts. One for fips usage and
 * one for non-fips usage
 */
fips_libctx = OSSL_LIB_CTX_new();
nonfips_libctx = OSSL_LIB_CTX_new();
if (fips_libctx == NULL || nonfips_libctx == NULL)
    goto err;

/* Prevent anything from using the default library context */
defctxnull = OSSL_PROVIDER_load(NULL, "null");

/*
 * Load config file for the FIPS library context. We assume that
 * this config file will automatically activate the FIPS and base
 * providers so we don't need to explicitly load them here.
 */
if (!OSSL_LIB_CTX_load_config(fips_libctx, "openssl-fips.cnf"))
    goto err;

/*
 * Set the default property query on the FIPS library context to
 * ensure that only FIPS algorithms can be used.  There are a few non-FIPS
 * approved algorithms in the FIPS provider for backward compatibility reasons.
 */
if (!EVP_set_default_properties(fips_libctx, "fips=yes"))
    goto err;

/*
 * We don't need to do anything special to load the default
 * provider into nonfips_libctx. This happens automatically if no
 * other providers are loaded.
 * Because we don't call OSSL_LIB_CTX_load_config() explicitly for
 * nonfips_libctx it will just use the default config file.
 */

/* As an example get some digests */

/* Get a FIPS validated digest */
fipssha256 = EVP_MD_fetch(fips_libctx, "SHA2-256", NULL);
if (fipssha256 == NULL)
    goto err;

/* Get a non-FIPS validated digest */
nonfipssha256 = EVP_MD_fetch(nonfips_libctx, "SHA2-256", NULL);
if (nonfipssha256 == NULL)
    goto err;

/* Use the digests */

printf("Success\n");
ret = 0;

err:
EVP_MD_free(fipssha256);
EVP_MD_free(nonfipssha256);
OSSL_LIB_CTX_free(fips_libctx);
OSSL_LIB_CTX_free(nonfips_libctx);
OSSL_PROVIDER_unload(defctxnull);

return ret;

请注意,我们在此使用了特殊的“null”提供程序,我们将其加载到默认库上下文中。我们可以选择使用默认库上下文进行 FIPS 使用,并且仅为其他使用创建一个额外的库上下文 - 反之亦然。但是,如果代码尚未转换为使用库上下文,则将自动使用默认库上下文。这可能是您自己的现有应用程序以及 OpenSSL 本身的某些部分的情况。并非 OpenSSL 的所有部分都支持库上下文。如果发生这种情况,则您可能会在特定操作中“意外”使用错误的库上下文。为了确保这种情况不会发生,您可以在默认库上下文中加载“null”提供程序。因为显式加载了提供程序,所以不会自动加载默认提供程序。这意味着意外使用默认上下文的代码将失败,因为没有算法可用。

有关库上下文的更多信息,请参见 "Library Context" in ossl-guide-migration(7)

使用编码器和解码器与 FIPS 模块

编码器和解码器用于从外部格式(例如 PEM 文件)读取和写入密钥或参数,或写入外部格式。如果您的应用程序生成需要写入 PEM 或 DER 格式的密钥或参数,那么您可能需要使用编码器来执行此操作。类似地,您需要一个解码器来读取之前保存的密钥和参数。在大多数情况下,如果您使用的是 OpenSSL 1.1.1 或更早版本中存在的 API(例如 i2d_PrivateKey(3)),那么这将对您不可见。但是,适当的编码器/解码器需要在与密钥或参数对象关联的库上下文中可用。内置的 OpenSSL 编码器和解码器在默认提供程序和基本提供程序中都实现,不在 FIPS 模块边界内。但是,由于它们本身不是加密算法,因此仍然可以与 FIPS 模块一起使用,因此这些编码器/解码器具有 fips=yes 属性。在这种情况下,您应该确保默认提供程序或基本提供程序已加载到库上下文中。

在 SSL/TLS 中使用 FIPS 模块

编写一个使用 libssl 与 FIPS 模块结合的应用程序与编写一个普通的 libssl 应用程序非常相似。如果您使用全局属性和默认库上下文来指定对 FIPS 验证算法的使用,那么这将自动适用于 libssl 中的所有加密算法。如果您使用非默认库上下文来加载 FIPS 提供程序,那么您可以使用函数 SSL_CTX_new_ex(3) 将其提供给 libssl。这可以作为函数 SSL_CTX_new(3) 的直接替换,但它提供了指定要使用的库上下文的功能。您也可以使用相同的函数来指定要使用的 libssl 特定属性。

在这个第一个示例中,我们使用两个不同的库上下文创建了两个 SSL_CTX 对象。

/*
 * We assume that a nondefault library context with the FIPS
 * provider loaded has been created called fips_libctx.
 */
SSL_CTX *fips_ssl_ctx = SSL_CTX_new_ex(fips_libctx, "fips=yes", TLS_method());
/*
 * We assume that a nondefault library context with the default
 * provider loaded has been created called non_fips_libctx.
 */
SSL_CTX *non_fips_ssl_ctx = SSL_CTX_new_ex(non_fips_libctx, NULL,
                                           TLS_method());

在这个第二个示例中,我们使用不同的属性创建了两个 SSL_CTX 对象,以指定 FIPS 的使用情况

/*
 * The "fips=yes" property includes all FIPS approved algorithms
 * as well as encoders from the default provider that are allowed
 * to be used. The NULL below indicates that we are using the
 * default library context.
 */
SSL_CTX *fips_ssl_ctx = SSL_CTX_new_ex(NULL, "fips=yes", TLS_method());
/*
 * The "provider!=fips" property allows algorithms from any
 * provider except the FIPS provider
 */
SSL_CTX *non_fips_ssl_ctx = SSL_CTX_new_ex(NULL, "provider!=fips",
                                           TLS_method());

确认算法是由 FIPS 模块提供的

需要遵循一系列链接才能从算法实例到实现它的提供程序。所有算法的过程都类似。这里使用摘要的示例。

要从 EVP_MD_CTX 转到 EVP_MD,请使用 EVP_MD_CTX_md(3) 。要从 EVP_MD 转到其 OSSL_PROVIDER,请使用 EVP_MD_get0_provider(3)。要从 OSSL_PROVIDER 中提取名称,请使用 OSSL_PROVIDER_get0_name(3)

注意

某些已发布的 OpenSSL 版本不包含经过验证的 FIPS 提供程序。要确定哪些版本已通过验证过程,请参阅 OpenSSL 下载页面。如果您需要 FIPS 批准的功能,则必须使用其中列出的已验证版本之一构建您的 FIPS 提供程序。通常,可以将从已验证版本之一构建的 FIPS 提供程序与从同一主要版本系列中的任何版本编译的 libcryptolibssl 一起使用。这种灵活性使您可以解决位于 FIPS 边界之外的错误修复和 CVE。

OpenSSL 3.1 中的 FIPS 提供程序包含一些未经 FIPS 验证的算法,因此,对于想要以 FIPS 批准的方式运行的应用程序,属性查询 fips=yes 是强制性的。这些算法是

三重 DES ECB
三重 DES CBC
EdDSA

另请参阅

ossl-guide-migration(7), crypto(7), fips_config(5), https://www.openssl.org/source/

历史

FIPS 模块指南是为与 OpenSSL 3.0 中的新 FIPS 提供程序一起使用而创建的。

版权所有 2021-2023 OpenSSL 项目作者。版权所有。

根据 Apache 许可证 2.0 版(“许可证”)许可。除非符合许可证,否则您不得使用此文件。您可以在源代码分发中的 LICENSE 文件或 https://www.openssl.org/source/license.html 中获取副本。