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 模組的應用程式不應使用任何避免 FIPS 模組的舊式 API 或功能。具體來說,這包括

  • 低階密碼學 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 模組,除非這些應用程式採取明確步驟以避免此預設行為。請注意,此組態也會啟用「基礎」提供者。基礎提供者不包含任何加密演算法(因此不會影響任何加密操作的驗證狀態),但包含可能需要的其他支援演算法。它被設計為與 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 和預設提供者。如果有多個實作可用,且您尚未透過屬性查詢(請參閱下文)明確指定應使用哪一個實作,則無法定義將使用哪個演算法實作。

另請注意,在此範例中,我們還載入了「基本」提供者。這會載入預設提供者中也提供的演算法子集,特別是非加密演算法,這些演算法可以與 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。請注意,此設定檔不會載入「基本」提供者。所有在「基本」中的支援演算法也在「預設」中,因此在這種情況下沒有必要

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」提供者載入預設函式庫內容。由於已明確載入提供者,預設提供者將不會自動載入。這表示意外使用預設內容的程式碼將會失敗,因為沒有演算法可用。

請參閱 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=yes 對想要以 FIPS 核准方式運作的應用程式來說是必要的。這些演算法是

3DES ECB
3DES CBC
EdDSA

另請參閱

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

歷程

FIPS 模組指南是為與 OpenSSL 3.0 中的新 FIPS 提供者一起使用而建立的。

版權所有 2021-2023 The OpenSSL Project Authors。保留所有權利。

根據 Apache 授權 2.0(「授權」)授權。您不得使用此檔案,除非符合授權。您可以在原始程式碼散佈中的 LICENSE 檔案或 https://www.openssl.org/source/license.html 取得副本。