author: "Techsum"
title: "OpenSSL Engine加載"
date: 2020-05-07T14:03:23
description: "OpenSSL Engine插件的加載過程源碼分析"
draft: false
hideToc: false
enableToc: true
enableTocContent: false
author: Techsum
categories:
- 密碼學
tags: - OpenSSL
問題來源
OpenSSL Engine是啥,在這個地方就不細說了,資料很多,可以看看知乎這篇中文文檔:
https://zhuanlan.zhihu.com/p/70444766
英文文檔:
直接進入正題,我們首先查看一個OpenSSL Engine的例子:
https://github.com/nibrunie/OSSL_EngineX
直接查看bind代碼:
static int bind(ENGINE* e, const char* id)
{
int ret = 0;
if (!ENGINE_set_id(e, engine_id)) {
fprintf(stderr, "ENGINE_set_id failed\n");
goto end;
}
if (!ENGINE_set_name(e, engine_name)) {
printf("ENGINE_set_name failed\n");
goto end;
}
if (!ENGINE_set_digests(e, digest_selector)) {
printf("ENGINE_set_digest failed\n");
goto end;
}
ret = 1;
end:
return ret;
}
IMPLEMENT_DYNAMIC_BIND_FN(bind)
IMPLEMENT_DYNAMIC_CHECK_FN()
可以看到OpenSSL去加載Engine的動態庫時,需要動態庫去調用 IMPLEMENT_DYNAMIC_BIND_FN
完成engine綁定初始化。
基本上所以教你寫engine的教程到這就結束了,但是內部到底是怎么要關聯上這個函數,并且觸發上面的bind
函數的呢?我們先來看看這個宏的具體定義:
\# define IMPLEMENT_DYNAMIC_BIND_FN(fn) \
OPENSSL_EXPORT \
int bind_engine(ENGINE *e, const char *id, const dynamic_fns *fns); \
OPENSSL_EXPORT \
int bind_engine(ENGINE *e, const char *id, const dynamic_fns *fns) { \
if (ENGINE_get_static_state() == fns->static_state) goto skip_cbs; \
CRYPTO_set_mem_functions(fns->mem_fns.malloc_fn, \
fns->mem_fns.realloc_fn, \
fns->mem_fns.free_fn); \
skip_cbs: \
if (!fn(e, id)) return 0; \ /* 調用了上面例子中的bind函數 */
return 1; }
可以看到此處定義了函數bind_engine
,他會去執行用宏包裹住的函數,以完成初始化。然而你去搜索這個函數在OpenSSL中調用你一定會很失望,肯定沒有你想要的結果。果然不是這么簡單的,又是什么鉤子掛在了什么ctx
上吧,應該也不難。
我找了不少資料,基本沒發現啥靠譜的分析,沒辦法自己看源碼吧。結果經過分析,我深刻的理解了OpenSSL的魔鬼調用,鉤子的掛載可以說是很魔幻。此處源碼分析基于目前的主線master,應該也是未來OpenSSL 3.0的架構了。
至于Engine是怎么設置上重置后的密碼算法的,將在后續更新。
從加載Engine的main函數分析起
還是上面的例子,我們查看執行engine加載的可執行程序的源碼:
int main(void)
{
// initializing OpenSSL library
OPENSSL_load_builtin_modules();
ENGINE_load_dynamic();
// building OpenSSL's configuration file path
char openssl_cnf_path[] = "./openssl.cnf";
// loading configuration
if (CONF_modules_load_file(openssl_cnf_path, "openssl_conf", 0) != 1) {
fprintf(stderr, "OpenSSL failed to load required configuration\n");
ERR_print_errors_fp(stderr);
return 1;
}
ENGINE* eng = ENGINE_by_id("engineX");
if(NULL == eng) {
printf("failed to retrieve engine by id (mppa)\n");
return 1;
}
printf("EngineX has been successfully loaded \n");
...
}
可以看到我們這個例子是從一個cnf
配置文件去加載對應的engine的,這里提一句,加載engine有幾個方式,如命令行加載,手動代碼加載等。這里用配置文件加載做例子是因為這個場景更加接近實際業務場景,而且流程基本涵蓋全流程,值得源碼去分析。接下來我們按照調用順序來分析這樣一個漫長的調用過程。
OPENSSL_load_builtin_modules
第一個函數,初始化了一個默認的conf_module
, 且名字叫做'engines'。直接看源碼:
void OPENSSL_load_builtin_modules(void)
{
...
/* 我們其他的都不重要,直接看這個和Engine相關的 */
#ifndef OPENSSL_NO_ENGINE
ENGINE_add_conf_module();
#endif
...
}
void ENGINE_add_conf_module(void)
{
CONF_module_add("engines",
int_engine_module_init, int_engine_module_finish);
}
來到我們的第一個大坑,OpenSSL的動態配置文件加載,但這里我們不需要細致了解,先簡單分析下:
int CONF_module_add(const char *name, conf_init_func *ifunc,
conf_finish_func *ffunc)
{
if (module_add(NULL, name, ifunc, ffunc))
return 1;
else
return 0;
}
/* 重要的結構體與全局變量 */
static STACK_OF(CONF_MODULE) *supported_modules = NULL;
static STACK_OF(CONF_IMODULE) *initialized_modules = NULL;
struct conf_module_st {
/* DSO of this module or NULL if static */
DSO *dso;
/* Name of the module */
char *name;
/* Init function */
conf_init_func *init;
/* Finish function */
conf_finish_func *finish;
/* Number of successfully initialized modules */
int links;
void *usr_data;
};
typedef struct conf_module_st CONF_MODULE;
static CONF_MODULE *module_add(DSO *dso, const char *name,
conf_init_func *ifunc, conf_finish_func *ffunc)
{
CONF_MODULE *tmod = NULL;
/* 若supported_modules為空, 則初始化此全局變量,即堆棧的初始化 */
if (supported_modules == NULL)
supported_modules = sk_CONF_MODULE_new_null();
if (supported_modules == NULL)
return NULL;
/* 申請配置文件模塊結構體conf_module_st的空間 */
if ((tmod = OPENSSL_zalloc(sizeof(*tmod))) == NULL) {
CONFerr(CONF_F_MODULE_ADD, ERR_R_MALLOC_FAILURE);
return NULL;
}
/*
* 此處第一次調用,dso為NULL;
* dso = dynamic shared object, 可以理解為是一個OpenSSL去加載動態庫的結構體;
*/
tmod->dso = dso;
/* 此處記住,將初始化一個叫"engines"的conf_module */
tmod->name = OPENSSL_strdup(name);
/* 配置文件init函數, 此處即int_engine_module_init。這個函數是關鍵 */
tmod->init = ifunc;
/* 配置文件finish函數, 此處即int_engine_module_finish */
tmod->finish = ffunc;
if (tmod->name == NULL) {
OPENSSL_free(tmod);
return NULL;
}
/* 將這個的conf_module結構體入棧進supported_modules這個全局變量棧中 */
if (!sk_CONF_MODULE_push(supported_modules, tmod)) {
OPENSSL_free(tmod->name);
OPENSSL_free(tmod);
return NULL;
}
return tmod;
}
此處有一個OpenSSL的一個知識點,OpenSSL中可以定義任意類型的安全棧,并且生成操作這個類型棧的函數族。例如有一個結構體叫XX,則可以通過DEFINE_STACK_OF(XX)
這個宏來定義XX結構體的棧和函數族,通過STACK_OF(XX)
來聲明一個棧。事實上,當我們看OpenSSL源碼時看到sk_
這種前綴的都是堆棧操作,而且是搜索不到實現的 (1.0.2版本應該可以找到,之后的版本都泛化了,代碼寫的秀,看代碼的自閉)。
詳見官方文檔:https://www.openssl.org/docs/man1.1.0/man3/DEFINE_STACK_OF.html
此處有兩個棧操作: 初始化時supported_modules
為空,所以將調用sk_CONF_MODULE_new_null
先建立上一個空容器。之后sk_CONF_MODULE_push
使上面初始化的的CONF_MODULE
入棧,之后想要取到這個module則需要通過supported_modules
這個全局棧來取。
此處多提一句,OpenSSL還有一個類似的結構體LHASH
,它是OpenSSL內部的哈希表,如果這篇文章有下我們應該會碰到它,直接理解成是一個kv_map就好。所有lh_
前綴開頭的也都是哈希表操作。
ENGINE_load_dynamic
第二個函數,比較繞,簡單理解就是:初始化了一個engine
, 名字叫做dynamic
,OpenSSL用這個engine來動態加載別的engine...
順便提一句,ENGINE_load_dynamic
在1.1.x版本已經廢棄了,統一都是調用OPENSSL_init_crypto
這個函數,opts = OPENSSL_INIT_ENGINE_DYNAMIC
。這又是OpenSSL非常惡心的地方了,版本兼容可以說是相當emmmmmmmm
\# define ENGINE_load_dynamic() \
OPENSSL_init_crypto(OPENSSL_INIT_ENGINE_DYNAMIC, NULL)
int OPENSSL_init_crypto(uint64_t opts, const OPENSSL_INIT_SETTINGS *settings)
{
...
/*
* RUN_ONCE是多線程時需要關心的,我們這里不關心,就等于調用ossl_init_engine_dynamic
* 最后一波宏展開,調用的是 engine_load_dynamic_int 這個函數
*/
if ((opts & OPENSSL_INIT_ENGINE_DYNAMIC)
&& !RUN_ONCE(&engine_dynamic, ossl_init_engine_dynamic))
return 0;
...
}
void engine_load_dynamic_int(void)
{
ENGINE *toadd = engine_dynamic(); /* 這命名真是絕了Orz */
if (!toadd)
return;
ENGINE_add(toadd);
/*
* If the "add" worked, it gets a structural reference. So either way, we
* release our just-created reference.
*/
ENGINE_free(toadd);
/*
* If the "add" didn't work, it was probably a conflict because it was
* already added (eg. someone calling ENGINE_load_blah then calling
* ENGINE_load_builtin_engines() perhaps).
*/
ERR_clear_error();
}
engine_dynamic
兩個核心函數,第一個 engine_dynamic
新建了一個id
叫做'dynamic'
的engine,掛上了這個engine的具體處理函數:
static ENGINE *engine_dynamic(void)
{
/* OpenSSL申請結構體空間經常使用的xx_new */
ENGINE *ret = ENGINE_new();
if (ret == NULL)
return NULL;
if (!ENGINE_set_id(ret, engine_dynamic_id) ||
!ENGINE_set_name(ret, engine_dynamic_name) ||
!ENGINE_set_init_function(ret, dynamic_init) ||
!ENGINE_set_finish_function(ret, dynamic_finish) ||
!ENGINE_set_ctrl_function(ret, dynamic_ctrl) ||
!ENGINE_set_flags(ret, ENGINE_FLAGS_BY_ID_COPY) ||
!ENGINE_set_cmd_defns(ret, dynamic_cmd_defns)) {
ENGINE_free(ret);
return NULL;
}
return ret;
}
我們掃一眼ENGINE
結構體,首先要有一個概念,ENGINE_set_xx
就是去設置這個結構體的相應字段,所以可以記錄一下這個結構體被初始化成啥樣了:
struct engine_st {
const char *id;
const char *name;
const RSA_METHOD *rsa_meth;
const DSA_METHOD *dsa_meth;
const DH_METHOD *dh_meth;
const EC_KEY_METHOD *ec_meth;
const RAND_METHOD *rand_meth;
/* Cipher handling is via this callback */
ENGINE_CIPHERS_PTR ciphers;
/* Digest handling is via this callback */
ENGINE_DIGESTS_PTR digests;
/* Public key handling via this callback */
ENGINE_PKEY_METHS_PTR pkey_meths;
/* ASN1 public key handling via this callback */
ENGINE_PKEY_ASN1_METHS_PTR pkey_asn1_meths;
ENGINE_GEN_INT_FUNC_PTR destroy;
ENGINE_GEN_INT_FUNC_PTR init;
ENGINE_GEN_INT_FUNC_PTR finish;
ENGINE_CTRL_FUNC_PTR ctrl;
ENGINE_LOAD_KEY_PTR load_privkey;
ENGINE_LOAD_KEY_PTR load_pubkey;
ENGINE_SSL_CLIENT_CERT_PTR load_ssl_client_cert;
const ENGINE_CMD_DEFN *cmd_defns;
int flags;
/* reference count on the structure itself */
CRYPTO_REF_COUNT struct_ref;
/*
* reference count on usability of the engine type. NB: This controls the
* loading and initialisation of any functionality required by this
* engine, whereas the previous count is simply to cope with
* (de)allocation of this structure. Hence, running_ref <= struct_ref at
* all times.
*/
int funct_ref;
/* A place to store per-ENGINE data */
CRYPTO_EX_DATA ex_data;
/* Used to maintain the linked-list of engines. */
struct engine_st *prev;
struct engine_st *next;
}
整理如下:
static const char *engine_dynamic_id = "dynamic";
static const char *engine_dynamic_name = "Dynamic engine loading support";
static const ENGINE_CMD_DEFN dynamic_cmd_defns[] = {
{DYNAMIC_CMD_SO_PATH,
"SO_PATH",
"Specifies the path to the new ENGINE shared library",
ENGINE_CMD_FLAG_STRING},
{DYNAMIC_CMD_NO_VCHECK,
"NO_VCHECK",
"Specifies to continue even if version checking fails (boolean)",
ENGINE_CMD_FLAG_NUMERIC},
{DYNAMIC_CMD_ID,
"ID",
"Specifies an ENGINE id name for loading",
ENGINE_CMD_FLAG_STRING},
{DYNAMIC_CMD_LIST_ADD,
"LIST_ADD",
"Whether to add a loaded ENGINE to the internal list (0=no,1=yes,2=mandatory)",
ENGINE_CMD_FLAG_NUMERIC},
{DYNAMIC_CMD_DIR_LOAD,
"DIR_LOAD",
"Specifies whether to load from 'DIR_ADD' directories (0=no,1=yes,2=mandatory)",
ENGINE_CMD_FLAG_NUMERIC},
{DYNAMIC_CMD_DIR_ADD,
"DIR_ADD",
"Adds a directory from which ENGINEs can be loaded",
ENGINE_CMD_FLAG_STRING},
{DYNAMIC_CMD_LOAD,
"LOAD",
"Load up the ENGINE specified by other settings",
ENGINE_CMD_FLAG_NO_INPUT},
{0, NULL, NULL, 0}
}; /* 加載動態engine時的命令 */
# define ENGINE_FLAGS_BY_ID_COPY (int)0x0004
ENGINE dynamic = {.id = engine_dynamic_id,
.name = engine_dynamic_name,
.init = dynamic_init, /* 空函數,直接return 0 */
.finish = dynamic_finish, /* 空函數,直接return 0 */
.ctrl = dynamic_ctrl, /* 最重要的函數,后文將分析如何調用到這來 */
.flags = ENGINE_FLAGS_BY_ID_COPY,
.cmd_defns = dynamic_cmd_defns /*定義了dynamic這個engine ctrl下的合法cmd*/
.prev = NULL, .next = NULL /* 說明engine都是以雙向鏈表形式管理 */
};
完成初始化后,將返回上這個new出來的ENGINE結構體。隨后丟到ENGINE_add
里。
ENGINE_add
上面結構體分析其實已經可以看到,所有的engine都將以雙向鏈表形式管理,鏈表建立簡單粗暴,直接定義全局變量一頭一尾,添加時就往尾巴加,搜索就從頭結點開始搜索:
static ENGINE *engine_list_head = NULL;
static ENGINE *engine_list_tail = NULL;
/* Add another "ENGINE" type into the list. */
int ENGINE_add(ENGINE *e)
{
int to_return = 1;
/* 一些入參檢查,omit */
...
/* 全局變量操作時需要加鎖以支持多線程 */
CRYPTO_THREAD_write_lock(global_engine_lock);
/* 核心函數,將剛剛new出來的dynamic加入全局鏈表中 */
if (!engine_list_add(e)) {
ENGINEerr(ENGINE_F_ENGINE_ADD, ENGINE_R_INTERNAL_LIST_ERROR);
to_return = 0;
}
CRYPTO_THREAD_unlock(global_engine_lock);
return to_return;
}
static int engine_list_add(ENGINE *e)
{
int conflict = 0;
ENGINE *iterator = NULL;
if (e == NULL) {
ENGINEerr(ENGINE_F_ENGINE_LIST_ADD, ERR_R_PASSED_NULL_PARAMETER);
return 0;
}
/* 從鏈表頭開始迭代 */
iterator = engine_list_head;
/* 直接遍歷到尾部查看有沒有重id的情況,重id直接報錯退出 */
while (iterator && !conflict) {
conflict = (strcmp(iterator->id, e->id) == 0);
iterator = iterator->next;
}
if (conflict) {
ENGINEerr(ENGINE_F_ENGINE_LIST_ADD, ENGINE_R_CONFLICTING_ENGINE_ID);
return 0;
}
if (engine_list_head == NULL) {
/* We are adding to an empty list. */
if (engine_list_tail) {
ENGINEerr(ENGINE_F_ENGINE_LIST_ADD, ENGINE_R_INTERNAL_LIST_ERROR);
return 0;
}
/* engine_list為空的話則鏈表頭為新建的engine */
engine_list_head = e;
e->prev = NULL;
/*
* The first time the list allocates, we should register the cleanup.
*/
engine_cleanup_add_last(engine_list_cleanup);
} else {
/* We are adding to the tail of an existing list. */
if ((engine_list_tail == NULL) || (engine_list_tail->next != NULL)) {
ENGINEerr(ENGINE_F_ENGINE_LIST_ADD, ENGINE_R_INTERNAL_LIST_ERROR);
return 0;
}
/* 將新engine加到隊尾的后面 */
engine_list_tail->next = e;
e->prev = engine_list_tail;
}
/*
* Having the engine in the list assumes a structural reference.
*/
e->struct_ref++;
engine_ref_debug(e, 0, 1);
/* 將隊尾指向新engine */
engine_list_tail = e;
e->next = NULL;
return 1;
}
這樣,id
為'dynamic'
被加入了全局engine
列表當中,被管理起來。
CONF
我們這里對OpenSSL的動態配置conf
不需要細致分析,隨著代碼分析即可。官方文檔其實對conf
格式講解的很清楚,可以學習:
https://www.openssl.org/docs/man1.1.1/man5/config.html
Engine Configuration Module這個小節
例子中conf文件
首先我們來看engineX
例子中的conf
是怎么寫的:
openssl_conf = openssl_def
[openssl_def]
engines = engine_section
[engine_section]
engine_x = engine_x_section
[engine_x_section]
engine_id = engineX
dynamic_path = ${ENV::PWD}/build/engine_ex.so
default_algorithms = ALL
init = 1
簡單學習一下conf
之后,我們之后這個配置文件核心的section
就是engine_section
,其中dynamic_path
定義上了該engine共享庫的路徑。我們看看例子中是如何根據這個配置文件去加載對應的engine的
CONF_modules_load_file
...
char openssl_cnf_path[] = "./openssl.cnf";
// loading configuration
if (CONF_modules_load_file(openssl_cnf_path, "openssl_conf", 0) != 1) {
...
}
...
CONF_modules_load_file
是去加載配置并使能配置的接口,這里我們主要關心如何去根據配置文件去加載動態庫,具體怎么完成配置文件解析的流程這里不討論。
int CONF_modules_load_file(const char *filename,
const char *appname, unsigned long flags)
{
return CONF_modules_load_file_with_libctx(NULL, filename, appname, flags);
}
int CONF_modules_load_file_with_libctx(OPENSSL_CTX *libctx,
const char *filename,
const char *appname, unsigned long flags)
{
char *file = NULL;
CONF *conf = NULL;
int ret = 0;
conf = NCONF_new_with_libctx(libctx, NULL);
if (conf == NULL)
goto err;
if (filename == NULL) {
file = CONF_get1_default_config_file();
if (file == NULL)
goto err;
} else {
file = (char *)filename;
}
if (NCONF_load(conf, file, NULL) <= 0) {
if ((flags & CONF_MFLAGS_IGNORE_MISSING_FILE) &&
(ERR_GET_REASON(ERR_peek_last_error()) == CONF_R_NO_SUCH_FILE)) {
ERR_clear_error();
ret = 1;
}
goto err;
}
ret = CONF_modules_load(conf, appname, flags);
err:
if (filename == NULL)
OPENSSL_free(file);
NCONF_free(conf);
if (flags & CONF_MFLAGS_IGNORE_RETURN_CODES)
return 1;
return ret;
}
可以看到這里主要有三步操作NCONF_new_with_libctx
、NCONF_load
、CONF_modules_load
,我們一個一個分析。
NCONF_new_with_libctx
這個函數主要是初始化上了一個CONF
結構體,同時將這個結構體的METHOD
定義成了默認方法。
/* 配置文件的method模板 */
struct conf_method_st {
const char *name;
CONF *(*create) (CONF_METHOD *meth);
int (*init) (CONF *conf);
int (*destroy) (CONF *conf);
int (*destroy_data) (CONF *conf);
int (*load_bio) (CONF *conf, BIO *bp, long *eline);
int (*dump) (const CONF *conf, BIO *bp);
int (*is_number) (const CONF *conf, char c);
int (*to_int) (const CONF *conf, char c);
int (*load) (CONF *conf, const char *name, long *eline);
};
/*
* 所有的 AA = BB 都會按照這個格式保存
* 如[openssl_def] engines = engine_section
* 此時這個底下conf_st的哈希表中將保存上一份
* {.section = "openssl_def", .name = "engines", value = "engine_section"}
*/
typedef struct {
char *section;
char *name;
char *value;
} CONF_VALUE;
struct conf_st {
CONF_METHOD *meth; /* 動態配置的方法,這里使用default */
void *meth_data;
LHASH_OF(CONF_VALUE) *data; /* 上文有提到的哈希表 */
unsigned int flag_dollarid:1;
OPENSSL_CTX *libctx;
};
/*
* The following section contains the "New CONF" functions. They are
* completely centralised around a new CONF structure that may contain
* basically anything, but at least a method pointer and a table of data.
* These functions are also written in terms of the bridge functions used by
* the "CONF classic" functions, for consistency.
*/
CONF *NCONF_new_with_libctx(OPENSSL_CTX *libctx, CONF_METHOD *meth)
{
CONF *ret;
if (meth == NULL)
meth = NCONF_default();
ret = meth->create(meth);
if (ret == NULL) {
CONFerr(0, ERR_R_MALLOC_FAILURE);
return NULL;
}
/* 這個流程中是NULL,不需要分析 */
ret->libctx = libctx;
return ret;
}
我們先看NCONF_default
:
/* 標記上這些方法,相關定義后續會給出,且將會使用 */
static CONF_METHOD default_method = {
"OpenSSL default",
def_create,
def_init_default,
def_destroy,
def_destroy_data,
def_load_bio,
def_dump,
def_is_number,
def_to_int,
def_load
};
CONF_METHOD *NCONF_default(void)
{
return &default_method;
}
第一個在default_method
被使用的方法就是def_create
, 很明顯是去申請一塊CONF
結構體內存,之后調用def_init_default
去初始化結構體 :
static CONF *def_create(CONF_METHOD *meth)
{
CONF *ret;
ret = OPENSSL_malloc(sizeof(*ret));
if (ret != NULL)
/* 這里調用`def_init_default` */
if (meth->init(ret) == 0) {
OPENSSL_free(ret);
ret = NULL;
}
return ret;
}
static int def_init_default(CONF *conf)
{
if (conf == NULL)
return 0;
memset(conf, 0, sizeof(*conf));
/* 將新申請的CONF結構體的method字段設置為默認method */
conf->meth = &default_method;
/* meth_data的設置,這個是.conf文件字符解析時候使用的,我們這里不講 */
conf->meth_data = (void *)CONF_type_default;
return 1;
}
NCONF_load
初始化好CONF
結構體,確定好對應配置文件名,開始對配置文件進行解析,NCONF_load
(OpenSSL連配置文件格式都自己定義自己解析,硬核硬核)將調用到默認方法之 def_load
int NCONF_load(CONF *conf, const char *file, long *eline)
{
if (conf == NULL) {
CONFerr(CONF_F_NCONF_LOAD, CONF_R_NO_CONF);
return 0;
}
return conf->meth->load(conf, file, eline);
}
static int def_load(CONF *conf, const char *name, long *line)
{
int ret;
BIO *in = NULL;
/* 這里通過BIO讀入文件(Binary IO, openssl自己定義的io,簡單理解就是一塊內存Orz) */
#ifdef OPENSSL_SYS_VMS
in = BIO_new_file(name, "r");
#else
in = BIO_new_file(name, "rb");
#endif
...
/* 正式解析,按段解析;
* 這里不分析咋解析的,很復雜很長,甚至能處理一些環境變量$(xxx)... 服
* 最后結果都存在哈希表data中
*/
ret = def_load_bio(conf, in, line);
BIO_free(in);
return ret;
}
CONF_modules_load
核心過程,從CONF
去加載第一部分提到的'engines'
這個module:
int CONF_modules_load(const CONF *cnf, const char *appname,
unsigned long flags)
{
STACK_OF(CONF_VALUE) *values;
CONF_VALUE *vl;
char *vsection = NULL;
int ret, i;
if (!cnf)
return 1;
/* 先獲取到對應的section名,這里就是"openssl_conf" */
if (appname)
vsection = NCONF_get_string(cnf, NULL, appname);
if (!appname || (!vsection && (flags & CONF_MFLAGS_DEFAULT_SECTION)))
vsection = NCONF_get_string(cnf, NULL, "openssl_conf");
if (!vsection) {
ERR_clear_error();
return 1;
}
OSSL_TRACE1(CONF, "Configuration in section %s\n", vsection);
/*
* 找到第一個段 openssl_conf
* [openssl_def]
* engines = engine_section
*/
values = NCONF_get_section(cnf, vsection);
if (!values)
return 0;
for (i = 0; i < sk_CONF_VALUE_num(values); i++) {
vl = sk_CONF_VALUE_value(values, i);
/* 遍歷所有的value,這里只有一個 'engines' */
ret = module_run(cnf, vl->name, vl->value, flags);
OSSL_TRACE3(CONF, "Running module %s (%s) returned %d\n",
vl->name, vl->value, ret);
if (ret <= 0)
if (!(flags & CONF_MFLAGS_IGNORE_ERRORS))
return ret;
}
return 1;
}
static int module_run(const CONF *cnf, const char *name, const char *value,
unsigned long flags)
{
CONF_MODULE *md;
int ret;
if (!RUN_ONCE(&load_builtin_modules, do_load_builtin_modules))
return -1;
/* 這里會在supported_modules這個棧上找到'engines'這個CONF_MODULE,開始魔幻表演 */
md = module_find(name);
...
/* init這個module,這里將去調用到'dynamic'這個engine,下面將分析 */
ret = module_init(md, name, value, cnf);
...
return ret;
}
/* initialize a module */
/* 此處將申請上一個所謂的initialized module,
* 之后調用'engines'的init函數
* 若成功,將'engines' push進的全局變量棧 initialized_modules */
static int module_init(CONF_MODULE *pmod, const char *name, const char *value,
const CONF *cnf)
{
int ret = 1;
int init_called = 0;
CONF_IMODULE *imod = NULL;
/* Otherwise add initialized module to list */
imod = OPENSSL_malloc(sizeof(*imod));
if (imod == NULL)
goto err;
imod->pmod = pmod;
imod->name = OPENSSL_strdup(name); /* 即'engines' */
imod->value = OPENSSL_strdup(value);
imod->usr_data = NULL;
if (!imod->name || !imod->value)
goto memerr;
/* Try to initialize module */
if (pmod->init) {
/* 調用engines的init,即第一部分提到的int_engine_module_init函數 */
ret = pmod->init(imod, cnf);
init_called = 1;
/* Error occurred, exit */
if (ret <= 0)
goto err;
}
if (initialized_modules == NULL) {
initialized_modules = sk_CONF_IMODULE_new_null();
if (!initialized_modules) {
CONFerr(CONF_F_MODULE_INIT, ERR_R_MALLOC_FAILURE);
goto err;
}
}
/* 將'engines' push進的全局變量棧 initialized_modules */
if (!sk_CONF_IMODULE_push(initialized_modules, imod)) {
CONFerr(CONF_F_MODULE_INIT, ERR_R_MALLOC_FAILURE);
goto err;
}
pmod->links++;
return ret;
err:
...
}
CONF
的第一部分處理完畢,開始查看如何繼續解析這個配置
int_engine_module_init
這部分開始取engines
這個section下的數據:
static int int_engine_module_init(CONF_IMODULE *md, const CONF *cnf)
{
STACK_OF(CONF_VALUE) *elist;
CONF_VALUE *cval;
int i;
OSSL_TRACE2(CONF, "Called engine module: name %s, value %s\n",
CONF_imodule_get_name(md), CONF_imodule_get_value(md));
/* Value is a section containing ENGINEs to configure */
elist = NCONF_get_section(cnf, CONF_imodule_get_value(md));
/*
* 獲取engine_section下的列表,這里就一個section叫做engine_x_section
* [engine_section]
* engine_x = engine_x_section
*/
if (!elist) {
ENGINEerr(ENGINE_F_INT_ENGINE_MODULE_INIT,
ENGINE_R_ENGINES_SECTION_ERROR);
return 0;
}
for (i = 0; i < sk_CONF_VALUE_num(elist); i++) {
cval = sk_CONF_VALUE_value(elist, i);
/*
* name: engine_x, value: engine_x_section
* 準備開始加載了
*/
if (!int_engine_configure(cval->name, cval->value, cnf))
return 0;
}
return 1;
}
int_engine_configure
是加載engine的主要流程,我們按順序來一步一步分析內部的循環
int_engine_configure
- 首先加載上value的section:
static int int_engine_configure(const char *name, const char *value, const CONF *cnf)
{
int i;
int ret = 0;
long do_init = -1;
STACK_OF(CONF_VALUE) *ecmds;
CONF_VALUE *ecmd = NULL;
const char *ctrlname, *ctrlvalue;
ENGINE *e = NULL;
int soft = 0;
name = skip_dot(name);
OSSL_TRACE1(CONF, "Configuring engine %s\n", name);
/* Value is a section containing ENGINE commands */
/* 在conf的哈希表中找 叫做engine_x_section的section */
ecmds = NCONF_get_section(cnf, value);
/*
* 此時ecmds是一個棧,按順序有以下CONF_VALUE (共有section = "engine_x_section")
* {.name = "engine_id", .value = "engineX"}
* {.name = "dynamic_path", .value = "${ENV::PWD}/build/engine_ex.so"(這里已經通配符解析 * 了)}
* {.name = "default_algorithms", .value = "ALL"}
* {.name = "init", .value = "1"}
*/
if (!ecmds) {
ENGINEerr(ENGINE_F_INT_ENGINE_CONFIGURE,
ENGINE_R_ENGINE_SECTION_ERROR);
return 0;
}
...
}
-
按照順序解析:
第一個是engine_id:
static int int_engine_configure(const char *name, const char *value, const CONF *cnf) { ... /* 開始對ecmds中棧上的CONF_VALUE遍歷,這部分代碼都在這個for循環中 */ for (i = 0; i < sk_CONF_VALUE_num(ecmds); i++) { ecmd = sk_CONF_VALUE_value(ecmds, i); /* 解析出ctrlname和ctrlvalue,對應結構體中.name和.value, 下同 */ ctrlname = skip_dot(ecmd->name); ctrlvalue = ecmd->value; OSSL_TRACE2(CONF, "ENGINE: doing ctrl(%s,%s)\n", ctrlname, ctrlvalue); /* First handle some special pseudo ctrls */ /* Override engine name to use */ if (strcmp(ctrlname, "engine_id") == 0) /* 把name制成conf文件中engine_id */ name = ctrlvalue; ... } ... }
第二個是
dynamic_path
, 這個定義最關鍵,找到這個name,開始按照指定路徑加載動態庫engine:for(...) { ... else if (strcmp(ctrlname, "dynamic_path") == 0) { /* * 看到這里是不是豁然開朗,首先找到第二部分初始化的叫做dynamic的engine * 但這個地方有個值得注意的點,底下分析ENGINE_by_id */ e = ENGINE_by_id("dynamic"); /* 拿到'dynamic'這個ENGINE結構體后,進行三步操作,完成了engineX這個so的加載 */ /* 之后我們將單獨把ENGINE_ctrl_cmd_string拿出來分析,觀察它是如何去加載的*/ if (!e) goto err; if (!ENGINE_ctrl_cmd_string(e, "SO_PATH", ctrlvalue, 0)) goto err; if (!ENGINE_ctrl_cmd_string(e, "LIST_ADD", "2", 0)) goto err; if (!ENGINE_ctrl_cmd_string(e, "LOAD", NULL, 0)) goto err; ... } /* * 完成這三步操作后,'dynamic'副本這個engine已經被重寫成了 'engineX'! * 同時這個engineX也加入了engines的隊列中。 */ ENGINE *ENGINE_by_id(const char *id) { /* 入參檢查和環境初始化檢查 omit */ ... /* 加鎖后開始遍歷鏈表,匹配id = "dynamic" */ CRYPTO_THREAD_write_lock(global_engine_lock); iterator = engine_list_head; while (iterator && (strcmp(id, iterator->id) != 0)) iterator = iterator->next; if (iterator != NULL) { /* * We need to return a structural reference. If this is an ENGINE * type that returns copies, make a duplicate - otherwise increment * the existing ENGINE's reference count. */ /* 匹配成功后的小操作:看ENGINE_load_dynamic源碼可以看到 dynamic->flag 被設置成了 ENGINE_FLAGS_BY_ID_COPY */ if (iterator->flags & ENGINE_FLAGS_BY_ID_COPY) { ENGINE *cp = ENGINE_new(); if (cp == NULL) iterator = NULL; else { /* 此處很重要! */ /* 此處取出的dynamic,不是直接取出鏈表中的engine節點,而是復制了一個節點 */ engine_cpy(cp, iterator); iterator = cp; } } else { iterator->struct_ref++; engine_ref_debug(iterator, 0, 1); } } CRYPTO_THREAD_unlock(global_engine_lock); if (iterator != NULL) /* 作為取出返回值,得到了一個dynamic的副本 */ return iterator; }
注意,此時e
這個局部變量已經是一個id
為'engineX'
的ENGINE結構體了,也就是完成了動態加載的engine!
第三步是default_algorithms
:
for (...) {
else if (strcmp(ctrlname, "default_algorithms") == 0) {
if (!ENGINE_set_default_string(e, ctrlvalue))
...
}
第四步,完成Init
:
for (...) {
if (strcmp(ctrlname, "init") == 0) {
if (!NCONF_get_number_e(cnf, value, "init", &do_init))
goto err;
if (do_init == 1) {
/*
* 此處為1,完成engine init,
* 具體代碼就是調用ENGINE_init去執行e->init, 增加引用數之類的,我們這里其實是空的
* 之后去把這個engine同時加入initialized_engines這個全局變量棧中。代碼不看了
*/
if (!int_engine_init(e))
goto err;
...
}
就此CONF_modules_load
全部運行完成,engineX
加載完畢。后續只需要像main
函數中的使用ENGINE_by_id("engineX");
就可以取得這個engine了。圓滿。
但是 bind_engine
在哪調用的呢,還是沒看到,那必然是在ENGINE_ctrl_cmd_string
流程中。所以下面重點講講這個函數。
ENGINE_ctrl_cmd_string
從cmd_name去獲取cmd_num
int ENGINE_ctrl_cmd_string(ENGINE *e, const char *cmd_name, const char *arg,
int cmd_optional)
{
int num, flags;
long l;
char *ptr;
...
/* 宏的命名已經暴露了一切,通過cmd_name得到cmd_num */
if (e->ctrl == NULL
|| (num = ENGINE_ctrl(e, ENGINE_CTRL_GET_CMD_FROM_NAME,
0, (void *)cmd_name, NULL)) <= 0) {
...
}
...
}
int ENGINE_ctrl(ENGINE *e, int cmd, long i, void *p, void (*f) (void))
{
...
/*
* Intercept any "root-level" commands before trying to hand them on to
* ctrl() handlers.
*/
switch (cmd) {
/* 這部分是通用的ctrl,范圍為10 ~ 18, 全部進入int_ctrl_helper */
case ENGINE_CTRL_HAS_CTRL_FUNCTION:
return ctrl_exists;
case ENGINE_CTRL_GET_FIRST_CMD_TYPE:
case ENGINE_CTRL_GET_NEXT_CMD_TYPE:
case ENGINE_CTRL_GET_CMD_FROM_NAME:
case ENGINE_CTRL_GET_NAME_LEN_FROM_CMD:
case ENGINE_CTRL_GET_NAME_FROM_CMD:
case ENGINE_CTRL_GET_DESC_LEN_FROM_CMD:
case ENGINE_CTRL_GET_DESC_FROM_CMD:
case ENGINE_CTRL_GET_CMD_FLAGS:
/*
* 這里dynamic的flag為ENGINE_FLAGS_BY_ID_COPY,0x0004
* ENGINE_FLAGS_MANUAL_CMD_CTRL = 0x0002,與的結果為0
*/
if (ctrl_exists && !(e->flags & ENGINE_FLAGS_MANUAL_CMD_CTRL))
return int_ctrl_helper(e, cmd, i, p, f);
if (!ctrl_exists) {
ENGINEerr(ENGINE_F_ENGINE_CTRL, ENGINE_R_NO_CONTROL_FUNCTION);
/*
* For these cmd-related functions, failure is indicated by a -1
* return value (because 0 is used as a valid return in some
* places).
*/
return -1;
}
default:
break;
}
/* Anything else requires a ctrl() handler to exist. */
/* 這里是確定當前engine->ctrl != NULL */
if (!ctrl_exists) {
ENGINEerr(ENGINE_F_ENGINE_CTRL, ENGINE_R_NO_CONTROL_FUNCTION);
return 0;
}
/* 調用上面看到的 dynamic->ctrl = dynamic_ctrl, 后面會調用到這來 */
return e->ctrl(e, cmd, i, p, f);
}
/* 這個函數也將反復調用(吐槽下openssl這鬼之設計),我們這里先看當前的cmd */
static int int_ctrl_helper(ENGINE *e, int cmd, long i, void *p,
void (*f) (void))
{
int idx;
char *s = (char *)p;
const ENGINE_CMD_DEFN *cdp;
...
/* Now handle cmd_name -> cmd_num conversion */
if (cmd == ENGINE_CTRL_GET_CMD_FROM_NAME) {
/* 從dynamic的cmd_defns中去匹配cmd_name,假設是"SO_PATH",
直接去查第二部分的dynamic_cmd_defns,剛好匹配上idx = 0 */
if ((e->cmd_defns == NULL)
|| ((idx = int_ctrl_cmd_by_name(e->cmd_defns, s)) < 0)) {
ENGINEerr(ENGINE_F_INT_CTRL_HELPER, ENGINE_R_INVALID_CMD_NAME);
return -1;
}
/* 查idx = 0時的 cmd_num = 200 = DYNAMIC_CMD_SO_PATH */
return e->cmd_defns[idx].cmd_num;
}
...
}
可以看到這里的num
返回回來的DYNAMIC_CMD_SO_PATH
,是靠dynamic.cmd_defns
中的ENGINE_CMD_DEFN
數組表查詢得到的。往下接著看ENGINE_ctrl_cmd_string
int ENGINE_ctrl_cmd_string(ENGINE *e, const char *cmd_name, const char *arg,
int cmd_optional)
{
/* 繼續調用公用ctrl,進入到int_ctrl_helper
(看底下開源的注釋,兩個函數做的ctrl操作一樣的,為啥這么搞也許就是未解之謎吧) */
...
if (!ENGINE_cmd_is_executable(e, num)) {
ENGINEerr(ENGINE_F_ENGINE_CTRL_CMD_STRING,
ENGINE_R_CMD_NOT_EXECUTABLE);
return 0;
}
/* 顧名思義,拿到dynamic的flag,這里將得到idx = 0時,cmd_defns表中0處的第四個元素 */
flags = ENGINE_ctrl(e, ENGINE_CTRL_GET_CMD_FLAGS, num, NULL, NULL);
if (flags < 0) {
/*
* Shouldn't happen, given that ENGINE_cmd_is_executable() returned
* success.
*/
ENGINEerr(ENGINE_F_ENGINE_CTRL_CMD_STRING,
ENGINE_R_INTERNAL_LIST_ERROR);
return 0;
}
}
static int int_ctrl_helper(ENGINE *e, int cmd, long i, void *p,
void (*f) (void))
{
...
if ((e->cmd_defns == NULL)
|| ((idx = int_ctrl_cmd_by_num(e->cmd_defns, (unsigned int)i)) < 0)) {
ENGINEerr(ENGINE_F_INT_CTRL_HELPER, ENGINE_R_INVALID_CMD_NUMBER);
return -1;
}
/* Now the logic splits depending on command type */
cdp = &e->cmd_defns[idx];
switch (cmd) {
...
case ENGINE_CTRL_GET_CMD_FLAGS:
/* 可以查出來上面的是 ENGINE_CMD_FLAG_STRING = 0x0002 */
return cdp->cmd_flags;
}
...
}
別問為啥不一次查出來,要多次遍歷,問就是架構。繼續看ENGINE_ctrl_cmd_string
,終于要做真正的操作了, 可以看到,最后進入了dynamic_ctrl
:
int ENGINE_ctrl_cmd_string(ENGINE *e, const char *cmd_name, const char *arg,
int cmd_optional)
{
...
/* ENGINE_CMD_FLAG_NO_INPUT = 0x0004 */
if (flags & ENGINE_CMD_FLAG_NO_INPUT) {
/* 如果命令查出來的flag應該沒有arg_input, 但arg非空,直接退出???? */
if (arg != NULL) {
ENGINEerr(ENGINE_F_ENGINE_CTRL_CMD_STRING,
ENGINE_R_COMMAND_TAKES_NO_INPUT);
return 0;
}
/*
* We deliberately force the result of ENGINE_ctrl() to 0 or 1 rather
* than returning it as "return data". This is to ensure usage of
* these commands is consistent across applications and that certain
* applications don't understand it one way, and others another.
*/
/* 最后"LOAD"命令走的這 */
if (ENGINE_ctrl(e, num, 0, (void *)arg, NULL) > 0)
return 1;
return 0;
}
/* So, we require input */
if (arg == NULL) {
ENGINEerr(ENGINE_F_ENGINE_CTRL_CMD_STRING,
ENGINE_R_COMMAND_TAKES_INPUT);
return 0;
}
/* 一定有更好的寫法吧,這種判斷也太迷惑了。。 */
/* If it takes string input, that's easy */
if (flags & ENGINE_CMD_FLAG_STRING) {
/* Same explanation as above */
/* 所以應該調用到這,注意此時num 將大于200, 肯定不是默認的流程,
這就走到了return e->ctrl(e, cmd, i, p, f); 即 dynamic_ctrl */
if (ENGINE_ctrl(e, num, 0, (void *)arg, NULL) > 0)
return 1;
return 0;
}
/* 此時arg是數字,需要從str轉int,LIST_ADD走這 */
if (!(flags & ENGINE_CMD_FLAG_NUMERIC)) {
ENGINEerr(ENGINE_F_ENGINE_CTRL_CMD_STRING,
ENGINE_R_INTERNAL_LIST_ERROR);
return 0;
}
l = strtol(arg, &ptr, 10);
if ((arg == ptr) || (*ptr != '\0')) {
ENGINEerr(ENGINE_F_ENGINE_CTRL_CMD_STRING,
ENGINE_R_ARGUMENT_IS_NOT_A_NUMBER);
return 0;
}
/*
* Force the result of the control command to 0 or 1, for the reasons
* mentioned before.
*/
if (ENGINE_ctrl(e, num, l, NULL, NULL) > 0)
return 1;
...
}
所以這個函數的主要步驟就是根據輸入的cmd_name
從dynamic
中掛載的cmd_defns
取出對應的cmd_num
和flag
,之后用cmd_num
調用到dynamic
掛載的ctrl
字段函數去做真正的操作。我們用一張表統計下三次取到的結果:
cmd_name | cmd_num | flag |
---|---|---|
"SO_PATH" | DYNAMIC_CMD_SO_PATH = 200 | ENGINE_CMD_FLAG_STRING 0x0002 |
"LIST_ADD" | DYNAMIC_CMD_LIST_ADD = 203 | ENGINE_CMD_FLAG_NUMERIC 0x0001 |
"LOAD" | DYNAMIC_CMD_LOAD = 206 | ENGINE_CMD_FLAG_NO_INPUT 0x0004 |
根據這個表,我們去看對于dynamic->ctrl
即dynamic_ctrl
函數對這幾個cmd
的操作
dynamic_ctrl
先看這個函數的公共部分,對相同的engine會初始化上一個ctx
上下文:
/* 動態庫加載的上下文 */
struct st_dynamic_data_ctx {
/* The DSO object we load that supplies the ENGINE code */
DSO *dynamic_dso;
/*
* The function pointer to the version checking shared library function
*/
dynamic_v_check_fn v_check;
/*
* The function pointer to the engine-binding shared library function
*/
dynamic_bind_engine bind_engine;
/* The default name/path for loading the shared library */
char *DYNAMIC_LIBNAME;
/* Whether to continue loading on a version check failure */
int no_vcheck;
/* If non-NULL, stipulates the 'id' of the ENGINE to be loaded */
char *engine_id;
/*
* If non-zero, a successfully loaded ENGINE should be added to the
* internal ENGINE list. If 2, the add must succeed or the entire load
* should fail.
*/
int list_add_value;
/* The symbol name for the version checking function */
const char *DYNAMIC_F1;
/* The symbol name for the "initialise ENGINE structure" function */
const char *DYNAMIC_F2;
/*
* Whether to never use 'dirs', use 'dirs' as a fallback, or only use
* 'dirs' for loading. Default is to use 'dirs' as a fallback.
*/
int dir_load;
/* A stack of directories from which ENGINEs could be loaded */
STACK_OF(OPENSSL_STRING) *dirs;
};
static int dynamic_ctrl(ENGINE *e, int cmd, long i, void *p, void (*f) (void))
{
/* 這個函數將會初始化并保存動態庫數據的ctx,這也是為什么可以反復調用這個接口的原因 */
dynamic_data_ctx *ctx = dynamic_get_data_ctx(e);
int initialised;
if (!ctx) {
ENGINEerr(ENGINE_F_DYNAMIC_CTRL, ENGINE_R_NOT_LOADED);
return 0;
}
/* 可以看到,加載完成的標志是dynamic_dso鉤子已經掛上了 */
initialised = ((ctx->dynamic_dso == NULL) ? 0 : 1);
/* All our control commands require the ENGINE to be uninitialised */
if (initialised) {
ENGINEerr(ENGINE_F_DYNAMIC_CTRL, ENGINE_R_ALREADY_LOADED);
return 0;
}
/* cmd解析,底下逐個分析 */
...
}
/*
* This function retrieves the context structure from an ENGINE's "ex_data",
* or if it doesn't exist yet, sets it up.
*/
static dynamic_data_ctx *dynamic_get_data_ctx(ENGINE *e)
{
dynamic_data_ctx *ctx;
if (dynamic_ex_data_idx < 0) {
/*
* Create and register the ENGINE ex_data, and associate our "free"
* function with it to ensure any allocated contexts get freed when
* an ENGINE goes underground.
*/
int new_idx = ENGINE_get_ex_new_index(0, NULL, NULL, NULL,
dynamic_data_ctx_free_func);
if (new_idx == -1) {
ENGINEerr(ENGINE_F_DYNAMIC_GET_DATA_CTX, ENGINE_R_NO_INDEX);
return NULL;
}
CRYPTO_THREAD_write_lock(global_engine_lock);
/* Avoid a race by checking again inside this lock */
if (dynamic_ex_data_idx < 0) {
/* Good, someone didn't beat us to it */
dynamic_ex_data_idx = new_idx;
new_idx = -1;
}
CRYPTO_THREAD_unlock(global_engine_lock);
/*
* In theory we could "give back" the index here if (new_idx>-1), but
* it's not possible and wouldn't gain us much if it were.
*/
}
ctx = (dynamic_data_ctx *)ENGINE_get_ex_data(e, dynamic_ex_data_idx);
/* Check if the context needs to be created */
if ((ctx == NULL) && !dynamic_set_data_ctx(e, &ctx))
/* "set_data" will set errors if necessary */
return NULL;
return ctx;
}
/*
* 簡單的說就是去查掛在engine->ex_data,
* 這個就是動態庫加載的上下文,ex_data是個??赡苡卸鄠€上下文,
* 根據一個全局變量dynamic_ex_data_idx確定當前使用上下文
* 當然第一次調用ctx是空的,所以需要調用一下dynamic_set_data_ctx初始化
*/
static int dynamic_set_data_ctx(ENGINE *e, dynamic_data_ctx **ctx)
{
/* 申請ctx的mem */
dynamic_data_ctx *c = OPENSSL_zalloc(sizeof(*c));
int ret = 1;
if (c == NULL) {
ENGINEerr(ENGINE_F_DYNAMIC_SET_DATA_CTX, ERR_R_MALLOC_FAILURE);
return 0;
}
c->dirs = sk_OPENSSL_STRING_new_null();
if (c->dirs == NULL) {
ENGINEerr(ENGINE_F_DYNAMIC_SET_DATA_CTX, ERR_R_MALLOC_FAILURE);
OPENSSL_free(c);
return 0;
}
/* 初始化一些字段,下面總結 */
c->DYNAMIC_F1 = "v_check", ;
c->DYNAMIC_F2 = "bind_engine";
c->dir_load = 1;
CRYPTO_THREAD_write_lock(global_engine_lock);
/* 第一次進來為NULL(然而正常是為ctx = NULL才會調用這個函數,可能是冗余校驗)*/
if ((*ctx = (dynamic_data_ctx *)ENGINE_get_ex_data(e,
dynamic_ex_data_idx))
== NULL) {
/* Good, we're the first */
/* 把ctx掛在engine->ex_data上 */
ret = ENGINE_set_ex_data(e, dynamic_ex_data_idx, c);
if (ret) {
*ctx = c;
c = NULL;
}
}
CRYPTO_THREAD_unlock(global_engine_lock);
/*
* If we lost the race to set the context, c is non-NULL and *ctx is the
* context of the thread that won.
*/
if (c)
sk_OPENSSL_STRING_free(c->dirs);
OPENSSL_free(c);
return ret;
}
/*
* 得到最后的結果 dynamic->ex_data = ctx;
* ctx = {.DYNAMIC_F1 = "v_check", .DYNAMIC_F2 = "bind_engine", c->dir_load = 1}
* 驚奇的發現了 bind_engine 雖然他只是個字符串,但是我相信你已經知道原因了
* 他需要在動態庫中去尋找這個符號
*/
之后我們逐一分析這三個cmd
DYNAMIC_CMD_SO_PATH和DYNAMIC_CMD_LIST_ADD
static int dynamic_ctrl(ENGINE *e, int cmd, long i, void *p, void (*f) (void))
{
...
switch (cmd) {
/* 注意, p就是ctrlvalue,即從conf中取下來的值 */
case DYNAMIC_CMD_SO_PATH:
/* a NULL 'p' or a string of zero-length is the same thing */
if (p && (strlen((const char *)p) < 1))
p = NULL;
OPENSSL_free(ctx->DYNAMIC_LIBNAME);
if (p)
/* 很明顯只是做了個簡單的復制,此時路徑已經賦值上了 */
ctx->DYNAMIC_LIBNAME = OPENSSL_strdup(p);
else
ctx->DYNAMIC_LIBNAME = NULL;
return (ctx->DYNAMIC_LIBNAME ? 1 : 0);
case DYNAMIC_CMD_LIST_ADD:
if ((i < 0) || (i > 2)) {
ENGINEerr(ENGINE_F_DYNAMIC_CTRL, ENGINE_R_INVALID_ARGUMENT);
return 0;
}
/* 很簡單,賦值而已 */
ctx->list_add_value = (int)i;
return 1;
...
}
}
這兩個都很簡單,最后難點都給了LOAD
DYNAMIC_CMD_LOAD
最關鍵的函數,完成了全部的加載,解釋都在注釋里:
static int dynamic_ctrl(ENGINE *e, int cmd, long i, void *p, void (*f) (void))
{
...
switch (cmd) {
case DYNAMIC_CMD_LOAD:
return dynamic_load(e, ctx);
...
}
}
static int dynamic_load(ENGINE *e, dynamic_data_ctx *ctx)
{
ENGINE cpy;
dynamic_fns fns;
/*
* 先new一個DSO結構體,DSO這一套函數怎么玩的這里先不講了,
* 可以理解為內部也有一個加載鉤子,有4個掛載點,估計再展開講讀者瘋了
*/
if (ctx->dynamic_dso == NULL)
ctx->dynamic_dso = DSO_new();
if (ctx->dynamic_dso == NULL)
return 0;
/* 此處檢查DYNAMIC_LIBNAME不能為空,這個就是dso的加載地址 */
if (!ctx->DYNAMIC_LIBNAME) {
if (!ctx->engine_id)
return 0;
DSO_ctrl(ctx->dynamic_dso, DSO_CTRL_SET_FLAGS,
DSO_FLAG_NAME_TRANSLATION_EXT_ONLY, NULL);
ctx->DYNAMIC_LIBNAME =
DSO_convert_filename(ctx->dynamic_dso, ctx->engine_id);
}
/* 核心加載函數int_load,看下面分析 */
if (!int_load(ctx)) {
ENGINEerr(ENGINE_F_DYNAMIC_LOAD, ENGINE_R_DSO_NOT_FOUND);
DSO_free(ctx->dynamic_dso);
ctx->dynamic_dso = NULL;
return 0;
}
/* We have to find a bind function otherwise it'll always end badly */
/*
* 此時engine動態庫已經加載如內存,符號表與對應地址也準備完成
* 所以肯定是需要去尋找這個綁定engine完成加載的函數了,勝利的曙光
* DSO_bind_func會在符號表中去匹配第二個參數字符串,這里就是我們要的"bind_engine"
* 并返回上它的函數地址,掛載在ctx->bind_engine上
*/
if (!
(ctx->bind_engine =
(dynamic_bind_engine) DSO_bind_func(ctx->dynamic_dso,
ctx->DYNAMIC_F2))) {
ctx->bind_engine = NULL;
DSO_free(ctx->dynamic_dso);
ctx->dynamic_dso = NULL;
ENGINEerr(ENGINE_F_DYNAMIC_LOAD, ENGINE_R_DSO_FAILURE);
return 0;
}
/* Do we perform version checking? */
if (!ctx->no_vcheck) {
unsigned long vcheck_res = 0;
/*
* Now we try to find a version checking function and decide how to
* cope with failure if/when it fails.
*/
ctx->v_check =
(dynamic_v_check_fn) DSO_bind_func(ctx->dynamic_dso,
ctx->DYNAMIC_F1);
if (ctx->v_check)
vcheck_res = ctx->v_check(OSSL_DYNAMIC_VERSION);
/*
* We fail if the version checker veto'd the load *or* if it is
* deferring to us (by returning its version) and we think it is too
* old.
*/
if (vcheck_res < OSSL_DYNAMIC_OLDEST) {
/* Fail */
ctx->bind_engine = NULL;
ctx->v_check = NULL;
DSO_free(ctx->dynamic_dso);
ctx->dynamic_dso = NULL;
ENGINEerr(ENGINE_F_DYNAMIC_LOAD,
ENGINE_R_VERSION_INCOMPATIBILITY);
return 0;
}
}
/*
* First binary copy the ENGINE structure so that we can roll back if the
* hand-over fails
*/
memcpy(&cpy, e, sizeof(ENGINE));
/*
* Provide the ERR, "ex_data", memory, and locking callbacks so the
* loaded library uses our state rather than its own. FIXME: As noted in
* engine.h, much of this would be simplified if each area of code
* provided its own "summary" structure of all related callbacks. It
* would also increase opaqueness.
*/
fns.static_state = ENGINE_get_static_state();
CRYPTO_get_mem_functions(&fns.mem_fns.malloc_fn, &fns.mem_fns.realloc_fn,
&fns.mem_fns.free_fn);
/*
* Now that we've loaded the dynamic engine, make sure no "dynamic"
* ENGINE elements will show through.
*/
engine_set_all_null(e);
/* Try to bind the ENGINE onto our own ENGINE structure */
/* !!!!Attension, 終于調用成功了,我們的engineX終于被設置好了! */
if (!ctx->bind_engine(e, ctx->engine_id, &fns)) {
ctx->bind_engine = NULL;
ctx->v_check = NULL;
DSO_free(ctx->dynamic_dso);
ctx->dynamic_dso = NULL;
ENGINEerr(ENGINE_F_DYNAMIC_LOAD, ENGINE_R_INIT_FAILED);
/* Copy the original ENGINE structure back */
memcpy(e, &cpy, sizeof(ENGINE));
return 0;
}
/* Do we try to add this ENGINE to the internal list too? */
/* 把這個engine的副本add進上面engine全局鏈表,大功告成!*/
if (ctx->list_add_value > 0) {
if (!ENGINE_add(e)) {
/* Do we tolerate this or fail? */
if (ctx->list_add_value > 1) {
/*
* Fail - NB: By this time, it's too late to rollback, and
* trying to do so allows the bind_engine() code to have
* created leaks. We just have to fail where we are, after
* the ENGINE has changed.
*/
ENGINEerr(ENGINE_F_DYNAMIC_LOAD,
ENGINE_R_CONFLICTING_ENGINE_ID);
return 0;
}
/* Tolerate */
ERR_clear_error();
}
}
return 1;
}
static int int_load(dynamic_data_ctx *ctx)
{
int num, loop;
/* Unless told not to, try a direct load */
/*
* DSO_load去打開ctx->DYNAMIC_LIBNAME,把egine對應的lib庫加載進內存
* 解析符號表和對應地址到上面申請好的ctx->dynamic_dso結構體中
*/
if ((ctx->dir_load != 2) && (DSO_load(ctx->dynamic_dso,
ctx->DYNAMIC_LIBNAME, NULL,
0)) != NULL)
return 1;
/* If we're not allowed to use 'dirs' or we have none, fail */
if (!ctx->dir_load || (num = sk_OPENSSL_STRING_num(ctx->dirs)) < 1)
return 0;
for (loop = 0; loop < num; loop++) {
/* 還有鏈接的dso這里會處理遞歸的去加載,對應的需要在ctx->dirs中 */
const char *s = sk_OPENSSL_STRING_value(ctx->dirs, loop);
char *merge = DSO_merge(ctx->dynamic_dso, ctx->DYNAMIC_LIBNAME, s);
if (!merge)
return 0;
if (DSO_load(ctx->dynamic_dso, merge, NULL, 0)) {
/* Found what we're looking for */
OPENSSL_free(merge);
return 1;
}
OPENSSL_free(merge);
}
return 0;
}
終于終于終于,找到目標了,這個叫做'dynamic'
的engine副本完成了變成engineX
的蛻變。
后續
難怪這么多人噴OpenSSL爛,這復雜的流程,這一個又一個的鉤子。不過這一串源碼讀下來看明白的時候還是有神清氣爽的感覺。
有緣后面會分析密碼算法具體掛載,如ENGINE_set_digests
。
我很菜,有錯誤的地方歡迎指正