建立基本的安全鏈接和非安全鏈接php
簡介: 學習如何使用 OpenSSL —— 用於安全通訊的最著名的開放庫 —— 的 API 有些強人所難,由於其文檔並不徹底。您能夠經過本文中的提示補充這方面的知識,並駕馭該 API。在創建基本的鏈接以後,就能夠查看如何使用 OpenSSL 的 BIO 庫來創建安全鏈接和非安全鏈接。與此同時,您還會學到一些關於錯誤檢測的知識。html
OpenSSL API 的文檔有些含糊不清。由於尚未多少關於 OpenSSL 使用的教程,因此對初學者來講,在應用程序中使用它可能會有一些困難。那麼怎樣才能使用 OpenSSL 實現一個基本的安全鏈接呢?本教程將幫助您解決這個問題。java
學習如何實現 OpenSSL 的困難部分在於其文檔的不徹底。不徹底的 API 文檔一般會妨礙開發人員使用該 API,而這一般意味着它註定要失敗。但 OpenSSL 仍然很活躍,並且正逐漸變得強大。這是爲何?linux
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 就不具有這一支持。編程
要充分理解並利用本文,您應該:安全
並不絕對要求您熟悉 SSL ,由於稍後將給出對 SLL 的簡短說明;不過,若是您但願獲得詳細論述 SSL 的文章的連接,請參閱 參考資料部分。擁有密碼學方面的知識當然好,但這並非必需的。服務器
SSL 是一個縮寫,表明的是 Secure Sockets Layer。它是支持在 Internet 上進行安全通訊的標準,而且將數據密碼術集成到了協議之中。數據在離開您的計算機以前就已經被加密,而後只有到達它預約的目標後才被解密。證書和密碼學算法支持了這一切的運轉,使用 OpenSSL,您將有機會切身體會它們。
理論上,若是加密的數據在到達目標以前被截取或竊聽,那些數據是不可能被破解的。不過,因爲計算機的變化一年比一年快,並且密碼翻譯方法有了新的發展,所以,SSL 中使用的加密協議被破解的可能性也在增大。
能夠將 SSL 和安全鏈接用於 Internet 上任何類型的協議,無論是 HTTP、POP3,仍是 FTP。還能夠用 SSL 來保護 Telnet 會話。雖然能夠用 SSL 保護任何鏈接,可是沒必要對每一類鏈接都使用 SSL。若是鏈接傳輸敏感信息,則應使用 SSL。
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 中列出了全部內容。其餘的頭文件 和/或 初始化函數多是其餘一些功能所必需的。
/* 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 指針。
BIO * bio; |
建立新的鏈接須要調用 BIO_new_connect
。您能夠在同一個調用中同時指定主機名和端口號。也能夠將其拆分爲兩個單獨的調用:一個是建立鏈接並設置主機名的 BIO_new_connect
調用,另外一個是設置端口號的 BIO_set_conn_port
(或者 BIO_set_conn_int_port
)調用。
無論怎樣,一旦 BIO 的主機名和端口號都已指定,該指針會嘗試打開鏈接。沒有什麼能夠影響它。若是建立 BIO 對象時遇到問題,指針將會是 NULL。爲了確保鏈接成功,必須執行 BIO_do_connect
調用。
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
來肯定是否可能重複出現該錯誤。
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
是找出問題的途徑。若是須要重試寫操做,它必須使用和前一次徹底相同的參數。
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 嵌入於一個類中,那麼應該在類的析構函數中使用這個調用。
/* 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 參數。
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。
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
的路徑參數。
/* 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 標記。
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
的調用。該調用還將執行握手來創建安全鏈接。
/* 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
選項下。
應該注意的是,驗證失敗並不意味着鏈接不能使用。是否應該使用鏈接取決於驗證結果和安全方面的考慮。例如,失敗的信任驗證可能只是意味着沒有可信任的證書。鏈接仍然可用,只是須要從思想上提升安全意識。
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
來釋放該結構。
SSL_CTX_free(ctx); |
顯然 OpenSSL 拋出了某種類型的錯誤。這意味着什麼?首先,您須要獲得錯誤代碼自己; ERR_get_error
能夠完成這項任務;而後,須要將錯誤代碼轉換爲錯誤字符串,它是一個指向由 SSL_load_error_strings
或 ERR_load_BIO_strings
加載到內存中的永久字符串的指針。能夠在一個嵌套調用中完成這項操做。
表 1 略述了從錯誤棧檢索錯誤的方法。清單 24 展現瞭如何打印文本字符串中的最後一個錯誤信息。
ERR_reason_error_string |
返回一個靜態字符串的指針,而後能夠將字符串顯示在屏幕上、寫入文件,或者以任何您但願的方式進行處理 |
ERR_lib_error_string |
指出錯誤發生在哪一個庫中 |
ERR_func_error_string |
返回致使錯誤的 OpenSSL 函數 |
printf("Error: %s\n", ERR_reason_error_string(ERR_get_error())); |
您還可讓庫給出預先格式化了的錯誤字符串。能夠調用 ERR_error_string
來獲得該字符串。該函數將錯誤代碼和一個預分配的緩衝區做爲參數。而這個緩衝區必須是 256 字節長。若是參數爲 NULL,則 OpenSSL 會將字符串寫入到一個長度爲 256 字節的靜態緩衝區中,並返回指向該緩衝區的指針。不然,它將返回您給出的指針。若是您選擇的是靜態緩衝區選項,那麼在下一次調用 ERR_error_string
時,該緩衝區會被覆蓋。
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]
是源文件中的行號。
ERR_print_errors_fp(FILE *); ERR_print_errors(BIO *); |
使用 OpenSSL 建立基本的鏈接並不困難,可是,當試着肯定該如何去作時,文檔多是一個小障礙。本文向您介紹了一些基本概念,但 OpenSSL 還有不少靈活之處有待發掘,並且您還可能須要一些高級設置,以便項目可以充分利用 SSL 的功能。
本文中有兩個樣例。一個樣例展現了到 http://www.verisign.com/ 的非安全鏈接,另外一個則展現了到 http://www.verisign.com/ 的安全 SSL 鏈接。二者都是鏈接到服務器並下載其主頁。它們沒有進行任何安全檢查,並且庫中的全部設置都是默認值 —— 做爲本文的一部分,應該只將這些用於教學目的。
在任何支持的平臺上,源代碼的編譯都應該是很是容易的,不過我建議您使用最新版本的 OpenSSL。在撰寫本文時,OpenSSL 的最新版本是 0.9.7d。