如果沒有安全的服務器應用程序,那么也就不需要安全的客戶機應用程序。使用 OpenSSL,我們可以創建安全的服務器應用程序,盡管文檔讓這一切看起來非常復雜,但實際上并非如此。本文中我們將學習如何使用在這個 3 部分系列文章 的 第 1 部分 中學習到的概念來構建安全的服務器應用程序。
本系列文章的前兩部分討論了使用 OpenSSL 來創建客戶機端應用程序的內容。第 1 部分 討論了使用 OpenSSL 創建基本安全客戶機的問題,而 第 2 部分 則深入討論了有關數字證書的問題。在閱讀本文的讀者給我發回很多 e-mail 和正面反饋之后,我非常清楚,接下來的一期理論介紹應該是有關服務器的。
服務器為網絡和 Internet 提供了訪問諸如文件和設備之類的資源的訪問能力。有時我們必須要通過一個安全通道來提供這些服務。OpenSSL 讓我們可以使用安全通道和開放通道來編寫服務。
使用 OpenSSL 來創建基本的服務器應用程序從本質上來說幾乎 等同于創建一個基本的客戶機應用程序。二者之間的區別不多。顯然,區別之一就是服務器將被設置為接收到達的連接,而不是建立外發的連接。并且,正如我們可以從本系列的第 2 部分有關數字證書的討論中看到的一樣,服務器還必須要在握手過程中提供安全證書。
服務器基本上就是呆在那里等待到達的連接。畢竟,這就是服務器存在的原因。Web 服務器要等待瀏覽器請求頁面,FTP 服務器要等待客戶機請求文件,聊天服務器要等待聊天客戶機所發出的連接。因此服務器要做的事情就是等待。
在客戶機和服務器通信之間差別不大,惟一的差別就是對于握手來說,服務器就像是硬幣的反面。其他東西都是相同的。
這讓我們可以使用 OpenSSL 來編寫安全的服務器應用程序,再次假設您已經了解如何使用 OpenSSL 來編寫客戶機應用程序。(如果您還不了解相關知識,請參閱本系列第 1 部分 “API 概述” 來學習如何設置 OpenSSL 庫。)
![]() |
|
也可以說是標識的兩部分。
服務器要負責提供在握手過程中使用的安全證書。完整的服務器證書包括兩個部分:公鑰和私鑰。公鑰是發送給客戶機的,而私鑰則是保密的。
就像是信任證書必須要提供給客戶機應用程序使用的庫一樣,服務器密鑰也必須要提供給服務器應用程序使用的庫。有幾個函數都提供了這種功能:
SSL_CTX_use_certificate(SSL_CTX *, X509 *) SSL_CTX_use_certificate_ASN1(SSL_CTX *ctx, int len, unsigned char *d); SSL_CTX_use_certificate_file(SSL_CTX *ctx, const char *file, int type); |
這個函數的 ASN1
變種可以將指定內存位置處的使用 ASN1 編碼的數字證書加載到 SSL 環境中。這個函數會加載給定內存結構中所提供的一個 X.509 證書;而最后一個函數,即帶有 _file
的那個,會從文件中加載一個使用 PEM 編碼的數字證書。這個函數的 type
參數讓我們可以加載使用 DER 編碼的證書。
要加載私鑰,請使用下面函數之一:
SSL_CTX_use_PrivateKey(SSL_CTX *ctx, EVP_PKEY *pkey); SSL_CTX_use_PrivateKey_ASN1(int pk, SSL_CTX *ctx, unsigned char *d, long len); SSL_CTX_use_PrivateKey_file(SSL_CTX *ctx, const char *file, int type); SSL_CTX_use_RSAPrivateKey(SSL_CTX *ctx, RSA *rsa); SSL_CTX_use_RSAPrivateKey_ASN1(SSL_CTX *ctx, unsigned char *d, long len); SSL_CTX_use_RSAPrivateKey_file(SSL_CTX *ctx, const char *file, int type); |
私鑰最適合用來加密存儲。不過,問題是加載證書的函數并沒有請求使用加密證書的密碼。相反,OpenSSL 為獲得密碼提供了一種回調機制。
回調格式如下:
int password_callback(char *buf, int size, int rwflag, void *userdata); |
就本文的目的來說,最后一個參數 userdata
是不需要的。緩沖區在調用這個函數之前被調用,因此對于這個緩沖區的大小我們無法控制。
![]() |
|
參數 rwflag
是讀/寫標記。使用它的目的是使我們可以編程確定這個密碼是正在用來加密信息(rwflag
= 1)還是解密信息(rwflag
= 0)。如果正在使用回調函數來請求對數據進行加密使用的密碼,最好是以某種方式請求兩次,這樣可以多一次機會接收用戶的輸入。
在證書加載時,這個密碼只會請求一次,這樣它就可以進行解密并保存到內存中了。如何從用戶那里獲取密碼,完全取決于您的實現。
一旦我們創建好密碼回調函數,就可以按照下面的方法使用 SSL_CTX_set_default_passwd_cb
將其安裝到 SSL 環境中:
/* ctx is a pointer to a previously created SSL context, and cb is the pointer * to the callback function you created. */ SSL_CTX_set_default_passwd_cb(ctx, cb); |
現在提示用戶輸入密碼的回調函數已經創建好了,然后我們就可以使用真正導入證書的函數了。證書可以從現有的內存結構或文件中導入。
為了更加符合處理數字證書的常用情況,例如 Apache HTTP Server Project 項目所作的一樣,我將展示如何從文件中加載證書。如果我們已經閱讀了本系列文章的 第 1 部分,那么加載證書的的方式就非常類似于在前面文章中給出的加載信任存儲的方式。
我們將從公用證書開始介紹,這個證書會發送給客戶機。
/** * ctx is the SSL context created earlier */ if(SSL_CTX_use_certificate_file(ctx, "/path/to/certificate.pem", SSL_FILETYPE_PEM) < 1) { /* Handle failed load here */ } |
在加載公用證書之后,就必須加載私有證書了。這是在握手過程中需要的,因為客戶機在這個過程中正將這些信息發送給對公鑰進行加密的服務器。這些數據只能使用私鑰進行解密。同樣,為了保持一致,我們也將從文件中加載密鑰。
if(SSL_CTX_use_PrivateKey_file(ctx, "/path/to/private.key", SSL_FILETYPE_PEM) < 1) { /* Handle failed load here */ } |
在設置好環境(請參看上面的 SSL 環境)和加載密鑰之后,現在應該通過創建 BIO 對象來完成設置了。我們可以回想一下在 第 1 部分 中是如何使用 OpenSSL BIO 庫來建立 SSL 和非 SSL 通信的。為了與這篇文章保持一致,我們在本文中也將實現同樣的功能。
BIO *bio, *abio, *out; |
3 個 BIO 對象?為什么我們需要使用 3 個 BIO 對象呢?這么做有一個目的,請相信我。(記住,信任和安全性的目標是一致的。)
第一個指針 bio
是主要的 BIO 對象,它可以從 SSL 環境中創建。第二個對象 abio
是接受連接使用的 BIO,用來接收到達的連接。第三個 BIO out
,是服務器發往客戶機使用的對象。
bio = BIO_new_ssl(ctx, 0); if(bio == NULL) { /* Handle failure here */ } /* Here, ssl is an SSL* (see Part 1) */ BIO_get_ssl(bio, &ssl); SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); |
此處對 BIO 對象的設置與客戶機連接使用的 BIO 設置稍有不同。我們可以回想一下第 1 部分中客戶機連接是使用 BIO_new_ssl_connect
建立的。
此處,設置是使用 BIO_new_ssl
加上兩個參數建立的:一個指向 SSL_CTX 對象的指針和一個標記。這個標記告訴 OpenSSL 要創建哪種 BIO 對象:0 用于服務器,1 用于客戶機。由于這段代碼試圖建立客戶機連接,因此這個標記就應該設置為 0。
abio = BIO_new_aclearcase/" target="_blank" >ccept("4422"); BIO_set_accept_bios(abio, bio); |
其中 BIO_do_connect
為客戶機連接創建 BIO, BIO_new_accept
為服務器連接創建 BIO。它只需要一個參數,就是監聽的端口,它被編碼在字符串中。
由于這假設正在監聽安全連接,因此我們需要將一個安全 BIO 鏈接到這個接收 BIO 上。這是第二個函數調用 BIO_set_accept_bios
使用的地方。她將前面創建的 SSL BIO 連接到接收 BIO 上。
這個函數調用不需要釋放 SSL BIO。在銷毀接收 BIO 之后,它會自動被釋放。
服務器就像是一個漁夫;它只需要坐在那里等待客戶機上鉤就好了。服務器玩的就是等待游戲,只需要等待客戶機連接到達即可。
如果曾經有過使用 Winsock 或 BSD Socket 進行編程的經驗,就很可能具備 accept
函數的使用經驗。OpenSSL 中的對應部分是 BIO_do_accept
,不過我們不是只調用一次 accept
然后等待,而是在等待之前,必須要調用 BIO_do_accept
兩次。
/* First call to set up for accepting incoming connections... */ if(BIO_do_accept(abio) <= 0) { /* Handle fail here */ } /* Second call to actually wait */ if(BIO_do_accept(abio) <= 0) { /* Handle fail here */ } /* Any other call will cause it to wait automatically */ |
第一次調用 BIO_do_accept
會設置 BIO 來接收到達連接。第二次調用需要真正坐下來等待。此后任何時候都允許它等待。
BIO_do_accept
在接收到到達連接時會返回 1
。不過我們不能只通過接收 BIO 進行通信。相反,OpenSSL 會創建另外一個 BIO,它必須使用 BIO_pop
來彈出接收 BIO。
out = BIO_pop(abio); if(BIO_do_handshake(out) <= 0) { /* Handle fail here */ } |
BIO_do_handshake
的調用進行處理。如果前面幾節中的設置成功了,那么握手在這里也應該會成功。
服務器會通過 BIO 庫的各種讀寫函數真正與客戶機進行通信。在 第 1 部分 中我們已經討論了有關這些問題的內容,因此我們可以在本文中找到更多討論內容。
總體來說,一旦理解這一切是如何工作的,使用 OpenSSL 創建安全的服務器應用程序就沒什么困難了。從現在開始,我們就可以對所提供的樣例代碼進行擴展,從而創建一個完全版本的 SSL 服務器應用程序來滿足我們的要求了。不過要預先警告一下,此處以及 下載一節 所提供的代碼樣例都進行了盡量簡化,應該只適用于實驗的目的。在真正創建一個完整的 SSL 服務器應用程序之前,請確保閱讀并研究最新的安全建議。