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 都使用了一個名為 BIO 的抽象庫來處理包括文件和套接字在內的各種類型的通信。您還可以將 OpenSSL 設置成為一個過濾器,比如用于 UU 或 Base64 編碼的過濾器。
在這里對 BIO 庫進行全面說明有點麻煩,所以我將根據需要一點一點地介紹它。首先, 我將向您展示如何建立一個標準的套接字連接。相對于使用 BSD 套接字庫,該操作需要 的代碼行更少一些。
在建立連接(無論安全與否)之前,要創建一個指向 BIO 對象的指針。這類似于在標準 C 中 為文件流創建 FILE 指針。
清單 2. 指針
1BIO * bio;
打開連接
創建新的連接需要調用 BIO_new_connect 。您可以在同一個調用中同時 指定主機名和端口號。也可以將其拆分為兩個單獨的調用:一個是創建連接并設置主機名的 BIO_new_connect 調用,另一個是設置端口號的 BIO_set_conn_port (或者BIO_set_conn_int_port )調用。
不管怎樣,一旦 BIO 的主機名和端口號都已指定,該指針會嘗試打開連接。沒有什么可以影響它。如果創建 BIO 對象時遇到問題,指針將會是 NULL。為了確保連接成功,必須執行 BIO_do_connect 調用。
清單 3. 創建并打開連接
在這里,第一行代碼使用指定的主機名和端口創建了一個新的 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. 從連接讀取
BIO_write 會試著將字節寫入套接字。它將返回實際寫入的 字節數、0 或者 -1。同 BIO_read ,0 或 -1 不一定表示錯誤。BIO_should_retry 是找出問題的途徑。如果需要重試寫操作,它必須 使用和前一次完全相同的參數。
清單 5. 寫入到連接
關閉連接
關閉連接也很簡單。您可以使用以下兩種方式之一來關閉連接: BIO_reset 或 BIO_free_all 。如果您還需要重新使用對象,那么請使用第一種方式。 如果您不再重新使用它,則可以使用第二種方式。
BIO_reset 關閉連接并重新設置 BIO 對象的內部狀態,以便可以重新使用連接。如果要在整個應用程序中使用同一對象,比如使用一臺安全的聊天 客戶機,那么這樣做是有益的。該函數沒有返回值。
BIO_free_all 所做正如其所言:它釋放內部結構體,并釋放 所有相關聯的內存,其中包括關閉相關聯的套接字。如果將 BIO 嵌入于一個類中,那么應該在類的 析構函數中使用這個調用。
清單 6. 關閉連接
建立安全連接
現在需要給出建立安全連接需要做哪些事情。惟一要改變的地方就是建立并進行連接。其他所有內容都是相同的。
安全連接要求在連接建立后進行握手。在握手過程中,服務器向客戶機發送一個證書, 然后,客戶機根據一組可信任證書來核實該證書。它還將檢查證書,以確保它沒有過期。要 檢驗證書是可信任的,需要在連接建立之前提前加載一個可信任證書庫。
只有在服務器發出請求時,客戶機才會向服務器發送一個證書。該過程叫做客戶機認證。使用證書, 在客戶機和服務器之間傳遞密碼參數,以建立安全連接。盡管握手是在建立連接之后才進行的,但是客戶機或服務器可以在任何時刻請求進行一次新的握手。
參考資料 部分中列出的 Netscasp 文章 和 RFC 2246 ,對握手以及建立安全連接的其他方面的知識進行了更詳盡的論述。
為安全連接進行設置
為安全連接進行設置要多幾行代碼。同時需要有另一個類型為 SSL_CTX 的指針。該結構保存了一些 SSL 信息。您也可以利用它通過 BIO 庫建立 SSL 連接。可以通過使用 SSL 方法函數調用 SSL_CTX_new 來創建這個結構,該方法函數通常是SSLv23_client_method 。
還需要另一個 SSL 類型的指針來保持 SSL 連接結構(這是短時間就能完成的一些連接所必需的)。以后還可以用該 SSL 指針來檢查連接信息或設置其他 SSL 參數。
清單 7. 設置 SSL 指針
1 SSL_CTX * ctx = SSL_CTX_new(SSLv23_client_method());
2 SSL * ssl;
加載可信任證書庫
在創建上下文結構之后,必須加載一個可信任證書庫。這是成功驗證每個證書所必需的。如果 不能確認證書是可信任的,那么 OpenSSL 會將證書標記為無效(但連接仍可以繼續)。
OpenSSL 附帶了一組可信任證書。它們位于源文件樹的 certs 目錄中。 不過,每個證書都是一個獨立的文件 —— 也就是說,需要單獨加載每一個證書。在 certs 目錄下,還有一個存放過期證書的子目錄。試圖加載這些證書將會出錯。
如果您愿意,可以分別加載每一個文件,但為了簡便起見,最新的 OpenSSL 發行版本的可信任證書 通常存放在源代碼檔案文件中,這些檔案文件位于名為“TrustStore.pem”的單個文件中。如果已經有了一個可信任證書庫, 并打算將它用于特定的項目中,那么只需使用您的文件替換清單 8 中的“TrustStore.pem”(或者使用 單獨的函數調用將它們全部加載)即可。
可以調用 SSL_CTX_load_verify_locations 來加載可信任證書庫文件。這里要用到 三個參數:上下文指針、可信任庫文件的路徑 和文件名,以及證書所在目錄的路徑。必須指定可信任庫文件或證書的目錄。 如果指定成功,則返回 1,如果遇到問題,則返回 0。
清單 8. 加載信任庫
如果打算使用目錄存儲可信任庫,那么必須要以特定的方式命名文件。OpenSSL 文檔清楚 地說明了應該如何去做,不過,OpenSSL 附帶了一個名為 c_rehash 的工具, 它可以將文件夾配置為可用于 SSL_CTX_load_verify_locations 的 路徑參數。
清單 9. 配置證書文件夾并使用它
為了指定所有需要的驗證證書,您可以根據需要命名任意數量的單獨文件或文件夾。您還可以同時指定 文件和文件夾。
創建連接
將指向 SSL 上下文的指針作為惟一參數,使用 BIO_new_ssl_connect 創建 BIO 對象。還需要獲得指向 SSL 結構的指針。在本文中,只將該指針用于 SSL_set_mode 函數。而這個函數是用來設置 SSL_MODE_AUTO_RETRY 標記的。使用這個選項進行設置,如果服務器突然希望進行 一次新的握手,那么 OpenSSL 可以在后臺處理它。如果沒有這個選項,當服務器希望進行一次新的握手時, 進行讀或寫操作都將返回一個錯誤,同時還會在該過程中設置 retry 標記。
清單 10. 設置 BIO 對象
設置 SSL 上下文結構之后,就可以創建連接了。主機名是使用 BIO_set_conn_hostname 函數 設置的。主機名和端口的指定格式與前面的相同。該函數還可以打開到主機的連接。為了確認已經成功打開連接,必須 執行對 BIO_do_connect 的調用。該調用還將執行握手來建立安全連接。
清單 11. 打開安全連接
連接建立后,必須檢查證書,以確定它是否有效。實際上,OpenSSL 為我們完成了這項任務。如果證書有致命的 問題(例如,哈希值無效),那么將無法建立連接。但是,如果證書的問題并不是致命的(當它已經過期 或者尚不合法時),那么仍可以繼續使用連接。
可以將 SSL 結構作為惟一參數,調用 SSL_get_verify_result 來查 明證書是否通過了 OpenSSL 的檢驗。如果證書通過了包括信任檢查在內的 OpenSSL 的內部檢查,則返回 X509_V_OK。如果有地方出了問題,則返回一個錯誤代碼,該代碼被記錄在命令行工具的 verify 選項下。
應該注意的是,驗證失敗并不意味著連接不能使用。是否應該使用連接取決于驗證結果和安全方面的考慮。例如, 失敗的信任驗證可能只是意味著沒有可信任的證書。連接仍然可用,只是需要從思想上提高安全意識。
清單 12. 檢查證書是否有效
這就是所需要的全部操作。通常,與服務器進行通信都要使用 BIO_read 和 BIO_write 。并且只需調用 BIO_free_all 或BIO_reset ,就可以關閉 連接,具體調用哪一個方法取決于是否重用 BIO。
必須在結束應用程序之前的某個時刻釋放 SSL 上下文結構。可以調用 SSL_CTX_free 來釋放該結構。
清單 13. 清除 SSL 上下文
1 SSL_CTX_free(ctx);
錯誤檢測
顯然 OpenSSL 拋出了某種類型的錯誤。這意味著什么?首先,您需要得到錯誤代碼本身; ERR_get_error 可以完成這項任務;然后,需要將錯誤代碼轉換為錯誤 字符串,它是一個指向由 SSL_load_error_strings 或 ERR_load_BIO_strings 加載到內存中的永久字符串的指針。 可以在一個嵌套調用中完成這項操作。
表 1 略述了從錯誤棧檢索錯誤的方法。清單 24 展示了如何打印文本字符串中的最后一個 錯誤信息。
表 1. 從棧中檢索錯誤
清單 14. 打印出最后一個錯誤
1 printf(“Error: %s\n”, ERR_reason_error_string(ERR_get_error()));
您還可以讓庫給出預先格式化了的錯誤字符串。可以調用 ERR_error_string 來 得到該字符串。該函數將錯誤代碼和一個預分配的緩沖區作為參數。而這個緩沖區必須是 256 字節長。如果參數 為 NULL,則 OpenSSL 會將字符串寫入到一個長度為 256 字節的靜態緩沖區中,并返回指向該緩沖區的 指針。否則,它將返回您給出的指針。如果您選擇的是靜態緩沖區選項,那么在下一次調用 ERR_error_string 時,該緩沖區會被覆蓋。
清單 15. 獲得預先格式化的錯誤字符串
1 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. 轉儲錯誤隊列
1 ERR_print_errors_fp(FILE *);
2 ERR_print_errors(BIO *);
開始做吧
使用 OpenSSL 創建基本的連接并不困難,但是,當試著確定該如何去做時,文檔可能是一個小障礙。本文向您介紹了一些基本概念,但 OpenSSL 還有很多靈活之處有待發掘,而且 您還可能需要一些高級設置,以便項目能夠充分利用 SSL 的功能。
本文中有兩個樣例。一個樣例展示了到 http://www.verisign.com/ 的非安全連接,另一個則展示了到 http://www.verisign.com/ 的安全 SSL 連接。兩者都是連接到服務器并下載其主頁。它們沒有進行 任何安全檢查,而且庫中的所有設置都是默認值 —— 作為本文的一部分,應該只將這些用于教學目的。
在任何支持的平臺上,源代碼的編譯都應該是非常容易的,不過我建議您使用最新版本的 OpenSSL。在撰寫本文時,OpenSSL 的最新版本是 0.9.7d。
-
OpenSSL
+關注
關注
0文章
20瀏覽量
8666
原文標題:使用 OpenSSL API 進行安全編程
文章出處:【微信號:C_Expert,微信公眾號:C語言專家集中營】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論