如何整合 openSSL custom provider (以 TRNG 舉例)
文章目录
- 目標
- Provider interface for RNG (Random Number Generator)
- OSSL_DISPATCH to init provider
- RNG 類型介紹
- RNG algorithm life cycle
- 随机数生成器(RNG)生命周期阶段
- Dynamic loadable module and build-in module
- Provider 如何使用 build-in module
- Provider 如何使用 dynamic loadable module
- Openssl RNG 入口函數
- FAQ
- Reference
目標
提供方法將 SE TRNG(安全元件真隨機數生成器) 集成到 OpenSSL 加密框架中,通過 Provider 或 Engine 接口,利用SE的TRNG能力,實現安全、高效能的隨機數生成。
Provider interface for RNG (Random Number Generator)
The concept of providers and everything surrounding them was introduced in OpenSSL 3.0.
Provider 可以提供例如加密與解密、密鑰衍生、MAC 計算、簽署與驗證、隨機數產生器(Random Number Generator, RNG)等的替換,下文僅描述provider 有關RNG的介面。
A provider offers an initialization function, as a set of base functions in the form of an OSSL_DISPATCH array, and by extension, a set of OSSL_ALGORITHMs
OSSL_LIB_CTX *ctx (libopenssl 上下文)裡面的組成
- OSSL_DISPATCH 是一個用來描述特定功能實作的結構體,提供功能ID與對應的函數指針。
提供者通過一組 OSSL_DISPATCH 來註冊它所實作的功能,每個功能對應於一個 function_id 和其具體的函數實作。
typedef struct ossl_dispatch_st OSSL_DISPATCH;
struct ossl_dispatch_st {int function_id;void (*function)(void);
};
- OSSL_ALGORITHM 是一個用來描述提供者支持的演算法的結構體,它與 OSSL_DISPATCH 一起使用,將演算法名稱與功能對應。
provider 宣告其支持的演算法名稱及對應的實作(由OSSL_DISPATCH array組成)。
#include <openssl/core.h>typedef struct ossl_algorithm_st OSSL_ALGORITHM;
struct ossl_algorithm_st {const char *algorithm_names; /* key */const char *property_definition; /* openssl 定義一套搜尋方法property,當有多種演算法的時候可用於檢索 */const OSSL_DISPATCH *implementation;const char *algorithm_description; /* 簡短描述 */
};
Member | Description | Mandatory/Optional |
---|---|---|
algorithm_name | The name of the algorithm (e.g., “AES-128-CBC”, “SHA256”, “RAND”). | Mandatory |
property_definition | A property string used to describe the algorithm (e.g., “default=yes”, “fips=yes”). | Optional |
implementation_function | Pointer to the implementation function for the algorithm (e.g., cipher, digest, RNG interface). | Mandatory |
extra_data | Reserved for future use; typically set to NULL. | Optional |
OSSL_DISPATCH to init provider
Provider 的初始化需要實現 OSSL_provider_init 函數,這是每個 Provider 的入口點。
// Provider 的上下文結構
typedef struct {const OSSL_CORE_HANDLE *handle; // 核心句柄
} CUSTOM_PROVIDER_CTX;// 初始化函數
int OSSL_provider_init(const OSSL_CORE_HANDLE *handle,const OSSL_DISPATCH *in,const OSSL_DISPATCH **out,void **provctx) {// 分配上下文CUSTOM_PROVIDER_CTX *ctx = OPENSSL_malloc(sizeof(CUSTOM_PROVIDER_CTX));if (ctx == NULL) {return 0; // 初始化失敗}ctx->handle = handle;*provctx = ctx;// 將功能表導出給 OpenSSL*out = custom_provider_functions;return 1; // 初始化成功
}
參數說明:
- handle (IN):由 OpenSSL 核心傳遞的句柄,用於與核心功能交互。
- in (IN):指向核心功能的 OSSL_DISPATCH 陣列,供 Provider 調用。
- out (OUT):指向 Provider 提供的 OSSL_DISPATCH 陣列,OpenSSL 通過它訪問 Provider 的功能。
- provctx (OUT):Provider 的上下文,用於維護 Provider 的狀態信息。
每個 Provider 都需要提供一個 OSSL_DISPATCH 陣列,描述其支持的功能。
static const OSSL_DISPATCH custom_provider_functions[] = {{ OSSL_FUNC_PROVIDER_GETTABLE_PARAMS, (void (*)(void))custom_gettable_params },{ OSSL_FUNC_PROVIDER_GET_PARAMS, (void (*)(void))custom_get_params },{ OSSL_FUNC_PROVIDER_TEARDOWN, (void (*)(void))custom_teardown },{ 0, NULL }
};
以下是按照要求生成的表格,包含功能ID、说明、必填/选填状态及对应的libopenssl API:
功能 ID | 说明 | 必填或选填 | libopenssl 会使用到的API |
---|---|---|---|
OSSL_FUNC_PROVIDER_GETTABLE_PARAMS | 返回该 Provider 支持的参数名称列表,通常用于查询 Provider 的静态属性,例如名称或版本信息。 | 选填(Optional) | OSSL_PROVIDER_gettable_params() |
OSSL_FUNC_PROVIDER_GET_PARAMS | 提供具体的参数值,例如返回 Provider 的名称或版本信息,与 GETTABLE_PARAMS 配合使用。 | 选填(Optional) | OSSL_PROVIDER_get_params() |
OSSL_FUNC_PROVIDER_QUERY_OPERATION | 用于查询 Provider 支持的特定算法实现。这是 OpenSSL 与 Provider 交互的核心功能。 | 必填(Required) | OSSL_PROVIDER_query_operation() |
OSSL_FUNC_PROVIDER_TEARDOWN | 用于释放 Provider 的资源,当 Provider 被卸载时调用。 | 选填(Optional) | OSSL_PROVIDER_unload() |
- | - | - | OSSL_PROVIDER_free() |
RNG 類型介紹
libssl 提供的RNG 根據使用場景不同,需要分別註冊不同的 OSSL_ALGORITHM。
硬體 TRNG 在實際應用中,主要作為種子源,提供高熵輸入以提升整體隨機數的不可預測性與安全性。
- 完全適合使用硬體 TRNG:CTR-DRBG、HASH-DRBG、HMAC-DRBG、SEED-SRC、通用 RNG。
- 不適合使用硬體 TRNG:TEST-RAND(僅用於測試環境)。
RNG algorithm life cycle
libssl 使用EVP_RAND_來呼叫RNG provider 提供的OSSL_DISPATCH ID 對應的函數指針
随机数生成器(RNG)生命周期阶段
阶段(Stage) | 说明 | OSSL_DISPATCH ID | 分类 |
---|---|---|---|
start | 表示 RNG 尚未被分配,是任何生命周期过渡的起始状态。 | - | - |
newed | 表示 RNG 已经分配完成,但尚无法生成任何输出。 | OSSL_FUNC_RAND_NEWCTX | Context Management Functions |
instantiated | 表示 RNG 已完成设置,能够生成输出。 | OSSL_FUNC_RAND_INSTANTIATE | Random Number Generator Functions: NIST |
uninstantiated | 表示 RNG 已被关闭,无法再生成输出。 | OSSL_FUNC_RAND_UNINSTANTIATE | Random Number Generator Functions: NIST |
freed | 表示 RNG 已被释放,为所有生命周期过渡的终止状态。 | OSSL_FUNC_RAND_FREECTX | Context Management Functions |
除了生命週期的函數指針以外,RNG 完成設置以後可呼叫EVP_RAND_,會呼叫到OSSL_DISPATCH OSSL_FUNC_RAND_,
但不同的RNG依照其特性,不一定包含所有ID
(舉例 EVP_RAND_get_params 呼叫到函數指針OSSL_FUNC_RAND_GET_PARAMS)
以下是基于提供内容生成的表格:
ID | 說明 |
---|---|
OSSL_FUNC_RAND_NONCE | 生成指定強度的隨機數字元(nonce),長度介於 min_noncelen 到 max_noncelen 之間。若輸出緩衝區 out 為 NULL,則返回隨機數字元長度。 |
OSSL_FUNC_RAND_GET_SEED | 確定性生成器從父生成器獲取種子材料。生成的種子字節數符合安全熵位數,總字節數介於 min_len 到 max_len 之間。種子材料指針存儲於 *buffer,需後續用 OSSL_FUNC_RAND_CLEAR_SEED 釋放。 |
OSSL_FUNC_RAND_CLEAR_SEED | 釋放由 OSSL_FUNC_RAND_GET_SEED 分配的長度為 b_len 的種子緩衝區。 |
OSSL_FUNC_RAND_VERIFY_ZEROIZATION | 檢查 RNG 內部狀態是否已歸零。此功能為 NIST 自測的一部分,其他情況下用途較少。 |
OSSL_FUNC_RAND_ENABLE_LOCKING | 為 RNG 及其所有父 RNG 啟用鎖定。此後 RNG 可線程安全使用。 |
OSSL_FUNC_RAND_LOCK | 鎖定 RNG,確保獨占訪問。 |
OSSL_FUNC_RAND_UNLOCK | 解鎖 RNG。 |
Dynamic loadable module and build-in module
Provider 如何使用 build-in module
- 完成下列實作 OSSL_provider_init , OSSL_DISPATCH 與宣告OSSL_ALGORITHM的.c
- 修改 crypto/provider 路徑,加入第一步的.c
- 撰寫build.info
SOURCE[../libcustom.a]=custom.c # It is necessary to have an explicit entry point
SOURCE[../custom]=custom_entry.c
- openssl (或其他使用此module的application) 使用custom build-in module,需要使用下列API
以下是整理后的 OpenSSL Provider API 功能表格:
API 名稱 | 用途 | 參數說明 |
---|---|---|
OSSL_PROVIDER_add_builtin() | 註冊內建或自訂 Provider | ctx : OpenSSL 上下文(NULL 表示全局)name : 模組名稱init_fn : 初始化函數指標 |
OSSL_PROVIDER_try_load() | 嘗試加載已註冊模組(未加載時才執行) | ctx : OpenSSL 上下文name : 模組名稱retain_fallbacks : 是否保留回退選項(1=是) |
OSSL_PROVIDER_load() | 強制加載指定模組(無論當前狀態) | ctx : OpenSSL 上下文name : 模組名稱 |
OSSL_PROVIDER_unload() | 卸載模組並釋放資源 | prov : 已加載模組的對象指針 |
OSSL_PROVIDER_available() | 檢查模組是否已加載且可用 | ctx : OpenSSL 上下文name : 模組名稱 |
結合上文Provider 初始化的段落舉例如何指定初始化OSSL_DISPATCH並加入OSSL_ALGORITHM
static const OSSL_DISPATCH custom_rand_functions[] = {{ OSSL_FUNC_RAND_NEWCTX, (void (*)(void))custom_rand_newctx },{ OSSL_FUNC_RAND_FREECTX, (void (*)(void))custom_rand_freectx },{ OSSL_FUNC_RAND_INSTANTIATE, (void (*)(void))custom_rand_instantiate },{ OSSL_FUNC_RAND_UNINSTANTIATE, (void (*)(void))custom_rand_uninstantiate },{ OSSL_FUNC_RAND_GENERATE, (void (*)(void))custom_rand_generate },{ OSSL_FUNC_RAND_ENABLE_LOCKING, (void (*)(void))custom_rand_enable_locking },{ OSSL_FUNC_RAND_GETTABLE_CTX_PARAMS, (void (*)(void))custom_rand_gettable_ctx_params },{ OSSL_FUNC_RAND_GET_CTX_PARAMS, (void (*)(void))custom_rand_get_ctx_params },{ 0, NULL }
};static const OSSL_ALGORITHM custom_rand_algs[] = { { "CUSTOM", "provider=custom", custom_rand_functions },{ NULL, NULL, NULL } };static const OSSL_ALGORITHM *custom_query(void *provctx, int operation_id, int *no_cache)
{*no_cache = 0;switch (operation_id) {case OSSL_OP_RAND:return custom_rand_algs;default:return NULL;}
}
static const OSSL_DISPATCH custom_dispatch[] = { { OSSL_FUNC_PROVIDER_TEARDOWN, (void (*)(void))OSSL_LIB_CTX_free },{ OSSL_FUNC_PROVIDER_QUERY_OPERATION, (void (*)(void))custom_query },{ 0, NULL } };static int custom_provider_init(const OSSL_CORE_HANDLE *handle, const OSSL_DISPATCH *in, const OSSL_DISPATCH **out,void **provctx)
{OSSL_LIB_CTX *libctx = OSSL_LIB_CTX_new();if (libctx == NULL) {return 0;}*provctx = libctx;*out = custom_dispatch;return 1;
}
Provider 如何使用 dynamic loadable module
- 將帶有 OSSL_provider_init 以及 OSSL_DISPATCH 與宣告OSSL_ALGORITHM的.c 檔案,編譯為.so
- 配置 OpenSSL 確保可以找到動態模組的位置
export OPENSSL_MODULES=/path/to/modules
Dynamic loadable module 配置文件 openssl.cnf
# 全局 OpenSSL 初始化配置
openssl_conf = openssl_init# 初始化設置
[openssl_init]
providers = provider_section# 提供者加載區域
[provider_section]
custom = custom_provider # 加載自訂提供者
default = default_provider # 同時保留默認提供者,除非我已經實現了所有必需的加密、密鑰衍生、MAC 計算、簽署與驗證演算法,否則不能刪除# 自訂提供者配置
[custom_provider]
module = $ENV::OPENSSL_MODULES/custom_provider.so
activate = 1 # 1 表示啟用 custom_provider
- 動態模組的初始化邏輯由 OSSL_provider_init 驅動
Openssl RNG 入口函數
實現provider 並正確加載以後,使用libssl 的application 調用 RAND_bytes 產生RNG,詳細 API 呼叫流程:
- 應用層調用 RAND_bytes,用於生成隨機數。RAND_bytes 定義在 crypto/rand/rand_lib.c,它是一個封裝函數,用於調用 RAND 方法
int RAND_bytes(unsigned char *buf, int num) {return RAND_bytes_ex(NULL, buf, num, 0);
}
- RAND_bytes_ex 會從當前的 OpenSSL 全局上下文中獲取活躍的 Provider,並調用相應的 RAND 接口。
通過 RAND_bytes_ex 調用隨機數生成 RAND_bytes_ex 會檢查 RAND 方法是否正確加載,然後調用 EVP_RAND_generate。
int RAND_bytes_ex(OSSL_LIB_CTX *ctx, unsigned char *buf, size_t num, unsigned int strength) {// 查詢 RAND ProviderEVP_RAND_CTX *rand = EVP_RAND_fetch(ctx, "default", NULL);if (!rand)return 0;// 使用 RAND Provider 的生成方法return EVP_RAND_generate(rand, buf, num, strength, 0, NULL, 0);
}
- EVP_RAND_generate 會通過 RAND Provider 的 OSSL_DISPATCH 表,調用 OSSL_FUNC_RAND_GENERATE。
int EVP_RAND_generate(EVP_RAND_CTX *ctx, unsigned char *out, size_t outlen,unsigned int strength, int prediction_resistance,const unsigned char *addin, size_t addinlen) {const EVP_RAND *rand = ctx->rand;if (rand->generate)return rand->generate(ctx->provctx, out, outlen, strength,prediction_resistance, addin, addinlen);return 0;
}
FAQ
- 已經使用openssl.cnf 指定某個custom provider(dynamic loadable module), application openssl 還需要使用OSSL_PROVIDER_load(顯式調用) 嗎?
不需要顯式調用的情況:
默認情況下自動加載所有配置的 Provider,當 OpenSSL 啟動時,會自動解析 openssl.cnf 中定義的所有 Provider,無需手動干預。如果在配置文件中通過 module 和 activate 配置了目標 Provider,OpenSSL 在啟動時會自動加載,應用程序可以直接使用。 - 需要顯式調用的情況
- 需要動態調整 Provider 行為:
如果應用程序需要在運行期間動態切換或加載新的 Provider,而不是依賴啟動時的自動加載,就需要調用 OSSL_PROVIDER_load()。 - 需要在多上下文中使用不同的 Provider:
如果應用程序使用了多個 OpenSSL 上下文(OSSL_LIB_CTX),且不同上下文需要不同的 Provider 配置,應用程序需要使用 OSSL_PROVIDER_load() 為每個上下文顯式加載 Provider。 - 需要加載未在配置文件中定義的 Provider:
如果某個 Provider 未在 openssl.cnf 中配置,但需要在運行期間使用,應用程序可以通過顯式調用 OSSL_PROVIDER_load() 來加載它。
- 需要動態調整 Provider 行為:
Reference
https://docs.openssl.org/master/man7/provider-rand/
https://docs.openssl.org/master/man7/life_cycle-rand/
https://docs.openssl.org/master/man3/OSSL_PROVIDER
https://docs.openssl.org/master/man3/EVP_RAND