使用OpenSSL API 進行安全編程

搜索 developerWorks developerWorks 中國技術主題Linux文檔庫使用 OpenSSL API 進行安全編程創建基本的安全連接和非安全連接學習如何使用 OpenSSL —— 用於安全通信的最著名的開放庫 —— 的 API 有些強人所難,因爲其文檔並不完全。您可以通過本文中的提示補充這方面的知識,並駕馭該 API。在建立基本的連接之後,就可以查看如何使用 OpenSSL 的 BIO 庫來建立安全連接和非安全連接。與此同時,您還會學到一些關於錯誤檢測的知識。Kenneth Ballard([email protected]), 自由程序員2004 年 8 月 09 日+內容在 IBM Bluemix 雲平臺上開發並部署您的下一個應用。開始您的試用OpenSSL API 的文檔有些含糊不清。因爲還沒有多少關於 OpenSSL 使用的教程,所以對初學者來說,在 應用程序中使用它可能會有一些困難。那麼怎樣才能使用 OpenSSL 實現一個基本的安全連接呢? 本教程將幫助您解決這個問題。學習如何實現 OpenSSL 的困難部分在於其文檔的不完全。不完全的 API 文檔通常會妨礙開發人員 使用該 API,而這通常意味着它註定要失敗。但 OpenSSL 仍然很活躍,而且正逐漸變得強大。這是爲什麼?OpenSSL 是用於安全通信的最著名的開放庫。在 google 中搜索“SSL library”得到的返回結果中, 列表最上方就是 OpenSSL。它誕生於 1998 年,源自 Eric Young 和 Tim Hudson 開發的 SSLeay 庫。其他 SSL 工具包包括遵循 GNU General Public License 發行的 GNU TLS,以及 Mozilla Network Security Services(NSS)(請參閱本文後面的 參考資料 ,以獲得 其他信息)。那麼,是什麼使得 OpenSSL 比 GNU TLS、Mozilla NSS 或其他所有的庫都優越呢?許可是一方面因素 (請參閱 參考資料)。此外,GNS TLS(迄今爲止)只支持 TLS v1.0 和 SSL v3.0 協議,僅此而已。Mozilla NSS 的發行既遵循 Mozilla Public License 又遵循 GNU GPL,它允許開發人員進行選擇。 不過,Mozilla NSS 比 OpenSSL 大,並且需要其他外部庫來對庫進行編譯,而 OpenSSL 是完全 自包含的。與 OpenSSL 相同,大部分 NSS API 也沒有文檔資料。Mozilla NSS 獲得了 PKCS #11 支持,該支持可以用於諸如智能卡這樣的加密標誌。OpenSSL 就不具備這一支持。先決條件要充分理解並利用本文,您應該:精通 C 編程。熟悉 Internet 通信和支持 Internet 的應用程序的編寫。並不絕對要求您熟悉 SSL ,因爲稍後將給出對 SLL 的簡短說明;不過,如果您希望得到詳細論述 SSL 的文章的鏈接,請參閱 參考資料部分。擁有密碼學方面的知識固然好,但這 並不是必需的。什麼是 SSL?SSL 是一個縮寫,代表的是 Secure Sockets Layer。它是支持在 Internet 上進行安全通信的 標準,並且將數據密碼術集成到了協議之中。數據在離開您的計算機之前就已經被加密,然後只有 到達它預定的目標後才被解密。證書和密碼學算法支持了這一切的運轉,使用 OpenSSL,您將 有機會切身體會它們。理論上,如果加密的數據在到達目標之前被截取或竊聽,那些數據是不可能被破解的。不過, 由於計算機的變化一年比一年快,而且密碼翻譯方法有了新的發展,因此,SSL 中使用的加密協議 被破解的可能性也在增大。可以將 SSL 和安全連接用於 Internet 上任何類型的協議,不管是 HTTP、POP3,還是 FTP。還可以用 SSL 來保護 Telnet 會話。雖然可以用 SSL 保護任何連接,但是不必對每一類連接都使用 SSL。 如果連接傳輸敏感信息,則應使用 SSL。回頁首什麼是 OpenSSL?OpenSSL 不僅僅是 SSL。它可以實現消息摘要、文件的加密和解密、數字證書、數字簽名 和隨機數字。關於 OpenSSL 庫的內容非常多,遠不是一篇文章可以容納的。OpenSSL 不只是 API,它還是一個命令行工具。命令行工具可以完成與 API 同樣的工作, 而且更進一步,可以測試 SSL 服務器和客戶機。它還讓開發人員對 OpenSSL 的能力有一個 認識。要獲得關於如何使用 OpenSSL 命令行工具的資料,請參閱 參考資料部分。回頁首您需要什麼首先需要的是最新版本的 OpenSSL。查閱參考資料部分,以確定從哪裏可以獲得最新的可以自己編譯的源代碼, 或者最新版本的二進制文件(如果您不希望花費時間來編譯的話)。不過,爲了安全起見, 我建議您下載最新的源代碼並自己編譯它。二進制版本通常是由第三方而不是由 OpenSSL 的開發人員來編譯和發行的。一些 Linux 的發行版本附帶了 OpenSSL 的二進制版本,對於學習如何使用 OpenSSL 庫來說,這足夠了;不過, 如果您打算去做一些實際的事情,那麼一定要得到最新的版本,並保持該版本一直是最新的。對於以 RPM 形式安裝的 Linux 發行版本(Red Hat、Mandrake 等),建議您通過從發行版本製造商那裏獲得 RPM 程序包來更新您的 OpenSSL 發行版本。出於安全方面的原因,建議您使用 最新版本的發行版本。如果您的發行版本不能使用最新版本的 OpenSSL,那麼建議您只覆蓋庫文件,不要覆蓋 可執行文件。OpenSSL 附帶的 FAQ 文檔中包含了有關這方面的細節。還要注意的是,OpenSSL 並沒有在所有的平臺上都獲得官方支持。雖然製造商已經盡力使其能夠跨平臺兼容, 但仍然存在 OpenSSL 不能用於您的計算機 和/或 操作系統的可能。請參閱 OpenSSL 的 Web 站點( 參考資料 中 的鏈接),以獲得關於哪些平臺可以得到支持的信息。如果想使用 OpenSSL 來生成證書請求和數字證書,那麼必須創建一個配置文件。在 OpenSSL 程序包 的 apps 文件夾中,有一個名爲 openssl.cnf 的 可用模板文件。我不會對該文件進行討論,因爲這不在本文要求範圍之內。不過,該模板文件有一些非常好的註釋,而且如果 在 Internet 上搜索,您可以找到很多討論修改該文件的教程。回頁首頭文件和初始化本教程所使用的頭文件只有三個:ssl.h、bio.h 和 err.h。它們都位於 openssl 子目錄中,而且都是開發您的項目 所必需的。要初始化 OpenSSL 庫,只需要三個代碼行即可。清單 1 中列出了所有內容。其他的頭文件 和/或 初始化函數可能 是其他一些功能所必需的。清單 1. 必需的頭文件/* OpenSSL headers */#include "openssl/bio.h"#include "openssl/ssl.h"#include "openssl/err.h"/* Initializing OpenSSL */SSL_load_error_strings();ERR_load_BIO_strings();OpenSSL_add_all_algorithms();回頁首建立非安全連接不管連接是 安全的還是不安全的,OpenSSL 都使用了一個名爲 BIO 的抽象庫來處理包括文件和套接字在內的各種類型的通信。您還可以將 OpenSSL 設置成爲一個過濾器,比如用於 UU 或 Base64 編碼的過濾器。在這裏對 BIO 庫進行全面說明有點麻煩,所以我將根據需要一點一點地介紹它。首先, 我將向您展示如何建立一個標準的套接字連接。相對於使用 BSD 套接字庫,該操作需要 的代碼行更少一些。在建立連接(無論安全與否)之前,要創建一個指向 BIO 對象的指針。這類似於在標準 C 中 爲文件流創建 FILE 指針。清單 2. 指針BIO * bio;打開連接創建新的連接需要調用 BIO_new_connect 。您可以在同一個調用中同時 指定主機名和端口號。也可以將其拆分爲兩個單獨的調用:一個是創建連接並設置主機名的 BIO_new_connect 調用,另一個是設置端口號的 BIO_set_conn_port (或者 BIO_set_conn_int_port )調用。不管怎樣,一旦 BIO 的主機名和端口號都已指定,該指針會嘗試打開連接。沒有什麼可以影響它。如果創建 BIO 對象時遇到問題,指針將會是 NULL。爲了確保連接成功,必須執行 BIO_do_connect 調用。清單 3. 創建並打開連接bio = BIO_new_connect("hostname:port");if(bio == NULL){ /* Handle the failure */}if(BIO_do_connect(bio) <= 0){ /* Handle failed connection */}在這裏,第一行代碼使用指定的主機名和端口創建了一個新的 BIO 對象,並以所示風格對該對象進行 格式化。例如, 如果您要連接到 www.ibm.com 的 80 端口,那麼該字符串將是 www.ibm.com:80 。調用 BIO_do_connect 檢查連接是否成功。如果出錯,則返回 0 或 -1。與服務器進行通信不管 BIO 對象是套接字還是文件,對其進行的讀和寫操作都是通過以下兩個函數來完成的: BIO_read 和 BIO_write 。 很簡單,對吧?精彩之處就在於它始終如此。BIO_read 將嘗試從服務器讀取一定數目的字節。它返回讀取的字節數、 0 或者 -1。在受阻塞的連接中,該函數返回 0,表示連接已經關閉,而 -1 則表示連接出現錯誤。在非阻塞連接的情況下,返回 0 表示沒有可以獲得的數據,返回 -1 表示連接出錯。可以調用 BIO_should_retry 來確定是否可能重複出現該錯誤。清單 4. 從連接讀取int x = BIO_read(bio, buf, len);if(x == 0){ /* Handle closed connection */}else if(x < 0){ if(! BIO_should_retry(bio)) { /* Handle failed read here */ } /* Do something to handle the retry */}BIO_write 會試着將字節寫入套接字。它將返回實際寫入的 字節數、0 或者 -1。同 BIO_read ,0 或 -1 不一定表示錯誤。 BIO_should_retry 是找出問題的途徑。如果需要重試寫操作,它必須 使用和前一次完全相同的參數。清單 5. 寫入到連接if(BIO_write(bio, buf, len) <= 0){ if(! BIO_should_retry(bio)) { /* Handle failed write here */ } /* Do something to handle the retry */}關閉連接關閉連接也很簡單。您可以使用以下兩種方式之一來關閉連接: BIO_reset 或 BIO_free_all 。如果您還需要重新使用對象,那麼請使用第一種方式。 如果您不再重新使用它,則可以使用第二種方式。BIO_reset 關閉連接並重新設置 BIO 對象的內部狀態,以便可以重新使用連接。如果要在整個應用程序中使用同一對象,比如使用一臺安全的聊天 客戶機,那麼這樣做是有益的。該函數沒有返回值。BIO_free_all 所做正如其所言:它釋放內部結構體,並釋放 所有相關聯的內存,其中包括關閉相關聯的套接字。如果將 BIO 嵌入於一個類中,那麼應該在類的 析構函數中使用這個調用。清單 6. 關閉連接/* To reuse the connection, use this line */BIO_reset(bio);/* To free it from memory, use this line */BIO_free_all(bio);回頁首建立安全連接現在需要給出建立安全連接需要做哪些事情。惟一要改變的地方就是建立並進行連接。其他所有內容都是相同的。安全連接要求在連接建立後進行握手。在握手過程中,服務器向客戶機發送一個證書, 然後,客戶機根據一組可信任證書來覈實該證書。它還將檢查證書,以確保它沒有過期。要 檢驗證書是可信任的,需要在連接建立之前提前加載一個可信任證書庫。只有在服務器發出請求時,客戶機纔會向服務器發送一個證書。該過程叫做客戶機認證。使用證書, 在客戶機和服務器之間傳遞密碼參數,以建立安全連接。儘管握手是在建立連接之後才進行的,但是客戶機或服務器可以在任何時刻請求進行一次新的握手。參考資料 部分中列出的 Netscasp 文章 和 RFC 2246 ,對握手以及建立安全連接的其他方面的知識進行了更詳盡的論述。爲安全連接進行設置爲安全連接進行設置要多幾行代碼。同時需要有另一個類型爲 SSL_CTX 的指針。該結構保存了一些 SSL 信息。您也可以利用它通過 BIO 庫建立 SSL 連接。可以通過使用 SSL 方法函數調用 SSL_CTX_new 來創建這個結構,該方法函數通常是 SSLv23_client_method 。還需要另一個 SSL 類型的指針來保持 SSL 連接結構(這是短時間就能完成的一些連接所必需的)。以後還可以用該 SSL 指針來檢查連接信息或設置其他 SSL 參數。清單 7. 設置 SSL 指針SSL_CTX * ctx = SSL_CTX_new(SSLv23_client_method());SSL * ssl;加載可信任證書庫在創建上下文結構之後,必須加載一個可信任證書庫。這是成功驗證每個證書所必需的。如果 不能確認證書是可信任的,那麼 OpenSSL 會將證書標記爲無效(但連接仍可以繼續)。OpenSSL 附帶了一組可信任證書。它們位於源文件樹的 certs 目錄中。 不過,每個證書都是一個獨立的文件 —— 也就是說,需要單獨加載每一個證書。在 certs 目錄下,還有一個存放過期證書的子目錄。試圖加載這些證書將會出錯。如果您願意,可以分別加載每一個文件,但爲了簡便起見,最新的 OpenSSL 發行版本的可信任證書 通常存放在源代碼檔案文件中,這些檔案文件位於名爲“TrustStore.pem”的單個文件中。如果已經有了一個可信任證書庫, 並打算將它用於特定的項目中,那麼只需使用您的文件替換清單 8 中的“TrustStore.pem”(或者使用 單獨的函數調用將它們全部加載)即可。可以調用 SSL_CTX_load_verify_locations 來加載可信任證書庫文件。這裏要用到 三個參數:上下文指針、可信任庫文件的路徑 和文件名,以及證書所在目錄的路徑。必須指定可信任庫文件或證書的目錄。 如果指定成功,則返回 1,如果遇到問題,則返回 0。清單 8. 加載信任庫if(! SSL_CTX_load_verify_locations(ctx, "/path/to/TrustStore.pem", NULL)){ /* Handle failed load here */}如果打算使用目錄存儲可信任庫,那麼必須要以特定的方式命名文件。OpenSSL 文檔清楚 地說明了應該如何去做,不過,OpenSSL 附帶了一個名爲 c_rehash 的工具, 它可以將文件夾配置爲可用於 SSL_CTX_load_verify_locations 的 路徑參數。清單 9. 配置證書文件夾並使用它/* Use this at the command line */c_rehash /path/to/certfolder/* then call this from within the application */if(! SSL_CTX_load_verify_locations(ctx, NULL, "/path/to/certfolder")){ /* Handle error here */}爲了指定所有需要的驗證證書,您可以根據需要命名任意數量的單獨文件或文件夾。您還可以同時指定 文件和文件夾。創建連接將指向 SSL 上下文的指針作爲惟一參數,使用 BIO_new_ssl_connect 創建 BIO 對象。還需要獲得指向 SSL 結構的指針。在本文中,只將該指針用於 SSL_set_mode 函數。而這個函數是用來設置 SSL_MODE_AUTO_RETRY 標記的。使用這個選項進行設置,如果服務器突然希望進行 一次新的握手,那麼 OpenSSL 可以在後臺處理它。如果沒有這個選項,當服務器希望進行一次新的握手時, 進行讀或寫操作都將返回一個錯誤,同時還會在該過程中設置 retry 標記。清單 10. 設置 BIO 對象bio = BIO_new_ssl_connect(ctx);BIO_get_ssl(bio, & ssl);SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);設置 SSL 上下文結構之後,就可以創建連接了。主機名是使用 BIO_set_conn_hostname 函數 設置的。主機名和端口的指定格式與前面的相同。該函數還可以打開到主機的連接。爲了確認已經成功打開連接,必須 執行對 BIO_do_connect 的調用。該調用還將執行握手來建立安全連接。清單 11. 打開安全連接/* Attempt to connect */BIO_set_conn_hostname(bio, "hostname:port");/* Verify the connection opened and perform the handshake */if(BIO_do_connect(bio) <= 0){ /* Handle failed connection */}連接建立後,必須檢查證書,以確定它是否有效。實際上,OpenSSL 爲我們完成了這項任務。如果證書有致命的 問題(例如,哈希值無效),那麼將無法建立連接。但是,如果證書的問題並不是致命的(當它已經過期 或者尚不合法時),那麼仍可以繼續使用連接。可以將 SSL 結構作爲惟一參數,調用 SSL_get_verify_result 來查 明證書是否通過了 OpenSSL 的檢驗。如果證書通過了包括信任檢查在內的 OpenSSL 的內部檢查,則返回 X509_V_OK。如果有地方出了問題,則返回一個錯誤代碼,該代碼被記錄在命令行工具的 verify 選項下。應該注意的是,驗證失敗並不意味着連接不能使用。是否應該使用連接取決於驗證結果和安全方面的考慮。例如, 失敗的信任驗證可能只是意味着沒有可信任的證書。連接仍然可用,只是需要從思想上提高安全意識。清單 12. 檢查證書是否有效if(SSL_get_verify_result(ssl) != X509_V_OK){ /* Handle the failed verification */}這就是所需要的全部操作。通常,與服務器進行通信都要使用 BIO_read 和 BIO_write 。並且只需調用 BIO_free_all 或 BIO_reset ,就可以關閉 連接,具體調用哪一個方法取決於是否重用 BIO。必須在結束應用程序之前的某個時刻釋放 SSL 上下文結構。可以調用 SSL_CTX_free 來釋放該結構。清單 13. 清除 SSL 上下文SSL_CTX_free(ctx);回頁首錯誤檢測顯然 OpenSSL 拋出了某種類型的錯誤。這意味着什麼?首先,您需要得到錯誤代碼本身; ERR_get_error 可以完成這項任務;然後,需要將錯誤代碼轉換爲錯誤 字符串,它是一個指向由 SSL_load_error_strings 或 ERR_load_BIO_strings 加載到內存中的永久字符串的指針。 可以在一個嵌套調用中完成這項操作。表 1 略述了從錯誤棧檢索錯誤的方法。清單 24 展示瞭如何打印文本字符串中的最後一個 錯誤信息。表 1. 從棧中檢索錯誤ERR_reason_error_string 返回一個靜態字符串的指針,然後可以將字符串顯示在屏幕上、寫入文件,或者以任何您希望的方式進行處理ERR_lib_error_string 指出錯誤發生在哪個庫中ERR_func_error_string 返回導致錯誤的 OpenSSL 函數清單 14. 打印出最後一個錯誤printf("Error: %s\n", ERR_reason_error_string(ERR_get_error()));您還可以讓庫給出預先格式化了的錯誤字符串。可以調用 ERR_error_string 來 得到該字符串。該函數將錯誤代碼和一個預分配的緩衝區作爲參數。而這個緩衝區必須是 256 字節長。如果參數 爲 NULL,則 OpenSSL 會將字符串寫入到一個長度爲 256 字節的靜態緩衝區中,並返回指向該緩衝區的 指針。否則,它將返回您給出的指針。如果您選擇的是靜態緩衝區選項,那麼在下一次調用 ERR_error_string 時,該緩衝區會被覆蓋。清單 15. 獲得預先格式化的錯誤字符串printf("%s\n", ERR_error_string(ERR_get_error(), NULL));您還可以將整個錯誤隊列轉儲到文件或 BIO 中。可以通過 ERR_print_errors 或 ERR_print_errors_fp 來實現這項操作。隊列是以可讀格式被轉儲的。第一個函數將隊列發送到 BIO ,第二個函數將隊列發送到 FILE 。 字符串格式如下(引自 OpenSSL 文檔):[pid]:error:[error code]:[library name]:[function name]:[reason string]:[file name]:[line]:[optional text message]其中, [pid] 是進程 ID, [error code] 是一個 8 位十六進制代碼, [file name] 是 OpenSSL 庫中的源代碼文件, [line] 是源文件中的行號。清單 16. 轉儲錯誤隊列ERR_print_errors_fp(FILE *);ERR_print_errors(BIO *);回頁首開始做吧使用 OpenSSL 創建基本的連接並不困難,但是,當試着確定該如何去做時,文檔可能是一個小障礙。本文向您介紹了一些基本概念,但 OpenSSL 還有很多靈活之處有待發掘,而且 您還可能需要一些高級設置,以便項目能夠充分利用 SSL 的功能。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章