OpenSSL 3.0 簡介(4)

        先介紹一些概念:

庫上下文(Library Context)
        庫上下文是一個在 OpenSSL 3.0 版源碼中定義的不透明的結構體,在其內部保存全局性的數據。當前對這個結構體的使用,僅限於用來保存核心用到的全局數據,將來可能會做擴展,將其他存在的全局數據也放進去。一個應用程序可以創建和銷燬一個或多個供核心使用的庫上下文。如果應用程序不創建庫上下文,這時一個內部默認的庫上下文將被使用。
        創建和銷燬庫上下文的函數分別是:
OPENSSL_CTX  *OPENSSL_CTX_new( );
void  OPENSSL_CTX_free(OPENSSL_CTX *ctx);

屬性(Properties)
        算法實現(包括密碼學算法和非密碼學算法)都具有屬性。對於 OpenSSL 3.0,兩種屬性分別被定義爲:
1)是否是默認的實現?
2)是否是 FIPS 驗證通過的實現?
        通過使用可打印的 ASCII 字符串來定義屬性,字符串是大小寫敏感的。例如對於 default=yes 這個字符串,它定義了一個屬性,含義是“這是默認的實現”,其中 default 是屬性名,yes 是屬性值。再如對於 fips=no 這個字符串,含義是“這不是 FIPS 驗證通過的實現”,其中 fips 是屬性名,no 是屬性值。

算法查詢(Algorithm Query)
        每一種算法類型都有一個對應的“獲取函數”(fetch function),例如 EVP_MD 類型算法對應的獲取函數是 EVP_MD_fetch( ),EVP_CIPHER 類型算法對應的獲取函數是 EVP_CIPHER_fetch( )。每一個獲取函數都會通過使用核心提供的服務,查找所需的算法實現。當具體的那一個算法實現被找到後,它將被存入一個與算法有關的結構體(例如 EVP_MD, EVP_CIPHER),然後返回給應用程序,供其調用。在根據名稱和屬性查詢某一個具體的算法實現時,可能會出現多個同樣好的結果被找到的情況,被返回的查詢結果是不確定的。此時一個隨機選擇的結果將被返回,而在下一次查詢時,可能另一個查詢結果會被隨機選中,然後被返回,這樣每次返回的查詢結果都有可能不一樣。

算法查詢緩存(Algorithm Query Caching)
        一般情況下查詢具體算法實現的結果將被緩存,但該緩存也可以被手動清空。

基於屬性的算法選擇(Property-based Algorithm Selection):
        提供者要對它能提供的每一種算法實現都設置屬性值。應用程序在查詢某一個具體的算法實現時,將指定的屬性和值作爲過濾規則的一部分來執行檢索。
        爲了指定屬性值,可以通過三種途徑:
1)在一個配置文件中進行全局性指定;
2)通過調用 API 進行全局性指定;
3)針對某一個對象(例如 SSL_CTX 類型的對象)指定相關的屬性。
        默認情況下,OpenSSL 3.0 會加載一個配置文件,該文件中包含全局屬性和其他設置,libcrypto 庫文件將自動加載這個配置文件。注意 OpenSSL 3.0 與 OpenSSL 1.1.1 不同,在 1.1.1 版中是由 libssl 庫文件在初始化時自動加載配置文件。
        當設置了全局屬性,但獲取函數在查詢時使用了一個與全局屬性所設值衝突的屬性值時,獲取函數使用的那個屬性值優先級更高,查詢將以獲取函數使用的屬性值作爲過濾規則,而忽略全局屬性。例如:全局屬性爲 fips=no,通過獲取函數傳入的屬性值爲 fips=yes,這時將根據 fips=yes 來進行查詢,全局屬性中使用的 fips=no 將被忽略。

“調度表”(dispatch table)

        調度表是由多個 <function-id, function-pointer> 配對組成的列表。function-id 由 OpenSSL 公開定義,與一組屬性相關聯,使用這些屬性可以識別不同的具體實現。核心可以使用某一個屬性及值作爲過濾條件,執行查詢,看看能否找到對應的函數。在代碼中用以下結構體表示 <function-id, function-pointer> 配對:
typedef struct ossl_dispatch_st {
    int function_id;
    void *(*function)();
} OSSL_DISPATCH;
        調度表在代碼中的存在形式是一個包含多個 OSSL_DISPATCH 類型元素的數組,這個數組最後一個元素的 function_id 值被設爲 0,用來標記數組的結束位置。這種使用特殊元素值來表示數組終止位置的方式與 C 語言中使用 \0 表示字符串結束符有點類似。

        OpenSSL 3.0 在工作時其內部組件的交互過程如下圖:

        工作流程大致是這樣的:
1)加載提供者;加載分爲隱式和顯式兩種,隱式加載是指使用默認的提供者或通過配置文件來指定提供者,顯式加載是指在用戶的應用程序中指定提供者。加載包含加載動態共享對象及初始化。加載提供者時,將執行以下操作:
      (a) 核心將模塊加載到內存中;如果默認的提供者已經在內存中,則不需要執行這一步。
      (b) 爲了初始化提供者,核心調用提供者的“入口點函數”(entry point function)。如果初始化成功,一個“提供者算法實現查詢回調(callback)函數”會被返回給核心。
2)爲了讓用戶的應用程序能夠調用到一個密碼算法實現,用戶應用程序必須首先執行一個算法查詢操作,即通過某個“屬性”來查詢相關算法的實現。具體過程是:用戶應用程序通過調用“獲取程序”(fetch function),發出對要使用的密碼算法的“請求”(request),接下來由 EVP 接口合併全局屬性與調用時臨時指定的屬性,用這些屬性來檢索將要使用的算法實施。當找到對應的算法實施後,創建並返回一個“庫句柄“(library handle)給應用程序,例如 EVP_MD 和 EVP_CIPHER 類型的變量都是庫句柄。搜索時,先在”調度表“(dispatch table)中查找,這是在一個內部緩存中執行的搜索過程。如果在緩存中未找到,接下來使用屬性到提供者處查詢。此時查詢結果會被放入緩存,這樣就能加速以後搜索的速度。提供者也可以選擇不允許將搜索結果放入緩存。
3)用戶應用程序通過 EVP API 調用算法。如果在上一步中搜索成功,應用程序就拿到了返回的庫句柄,接下來調用庫句柄(實質是一個函數指針),就能調用提供者中包含的算法實現、執行密碼運算了。

        對於在 OpenSSL 3.0 之前版本中就已存在的 EVP_{algorithm} 形式的函數(比如EVP_sha256( ) ),整個執行過程不完全一樣,區別在於未執行第(2)步中獲取程序操作,因爲對於這些函數,實際上在 EVP 初始化函數執行時,就完成了獲取程序操作。例如在調用 EVP_sha256( ) 計算雜湊時,在將 EVP_MD_CTX 類型的變量與 EVP 初始化函數綁定的時刻,執行了獲取程序操作。

        調用 EVP 層的函數,實質上是調用了提供者內部的、具有近似名稱的函數,EVP函數將調用提供者內部函數的過程封裝起來,使得調用提供者內部函數的過程對外不可見。例如在計算雜湊值時,通過調用 EVP_MD_fetch( ) 函數,在覈心的調度表中使用消息摘要算法名稱和其他屬性作爲關鍵字,查詢提供者提供的那個具有近似名的函數,查詢結果是一個函數指針,該指針將被放在 md 對象結構體(其類型爲 EVP_MD)的一個成員中返回,應用程序使用這個函數指針來調用提供者內部的函數實現,做雜湊運算。

小結
        OpenSSL 3.0 之前的版本,可以看成是一系列功能函數的集合,這些函數只是被動地等待用戶應用程序來調用它們。從OpenSSL 3.0 版開始,是參照 FIPS 140 系列標準來設計的,即設計的一個出發點是應符合對“密碼模塊”的安全要求。密碼模塊除了提供最基本的密碼學運算等功能之外,還要具備啓動時完整性自檢和密碼算法正確性自檢等功能,如果自檢失敗就會退出,不再對外提供服務。當各種自檢通過之後,密碼模塊才能正式向調用它的應用程序提供服務。自檢通過後的工作步驟大體如下:
1)加載 OpenSSL 3.0 庫文件,首先由核心加載各個提供者,提供者必須到核心處去註冊自己能提供的服務項目,核心相當於一個管理員,把各位提供者能提供的服務項登記造冊管理起來,即放入調度表中;
2)用戶應用程序調用 OpenSSL 3.0,實際上是通過調用獲取函數與核心通信,把用戶應用程序的需求告訴核心,核心查詢當前提供者填寫的服務列表緩存,如果在緩存中找不到服務項,就再向提供者發出查詢請求,提供者將查詢結果發給核心,核心再把查詢結果告知應用程序。
3)應用程序調用 EVP 接口,調用提供者內部的具體算法實現。
        OpenSSL 3.0 中引入的服務查詢、調度表等概念,其實不是新鮮事,在微軟組件模型(COM)的設計思想中,也有類似的概念。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章