使用 OpenSSL API 進行安全編程,第 3 部分: 提供安全服務

本系列文章的前兩部分討論了使用 OpenSSL 來建立客戶機端應用程序的內容。第 1 部分 討論了使用 OpenSSL 建立基本安全客戶機的問題,而 第 2 部分 則深刻討論了有關數字證書的問題。在閱讀本文的讀者給我發回不少 e-mail 和正面反饋以後,我很是清楚,接下來的一期理論介紹應該是有關服務器的。html

服務器爲網絡和 Internet 提供了訪問諸如文件和設備之類的資源的訪問能力。有時咱們必需要經過一個安全通道來提供這些服務。OpenSSL 讓咱們可使用安全通道和開放通道來編寫服務。linux

使用 OpenSSL 來建立基本的服務器應用程序從本質上來講幾乎 等同於建立一個基本的客戶機應用程序。兩者之間的區別很少。顯然,區別之一就是服務器將被設置爲接收到達的鏈接,而不是創建外發的鏈接。而且,正如咱們能夠從本系列的第 2 部分有關數字證書的討論中看到的同樣,服務器還必需要在握手過程當中提供安全證書。ios

等待編程

服務器基本上就是呆在那裏等待到達的鏈接。畢竟,這就是服務器存在的緣由。Web 服務器要等待瀏覽器請求頁面,FTP 服務器要等待客戶機請求文件,聊天服務器要等待聊天客戶機所發出的鏈接。所以服務器要作的事情就是等待。瀏覽器

在客戶機和服務器通訊之間差異不大,唯一的差異就是對於握手來講,服務器就像是硬幣的反面。其餘東西都是相同的。安全

這讓咱們可使用 OpenSSL 來編寫安全的服務器應用程序,再次假設您已經瞭解如何使用 OpenSSL 來編寫客戶機應用程序。(若是您還不瞭解相關知識,請參閱本系列第 1 部分 「API 概述」 來學習如何設置 OpenSSL 庫。)服務器

回頁首網絡

兩種形式的標識app

SSL 上下文

要設置本文中使用的 SSL 上下文,請使用下面的代碼。這個函數在出錯時會返回 NULL:jsp

SSL_CTX *ctx = SSL_CTX_new(SSLv23_server_method());

也能夠說是標識的兩部分。

服務器要負責提供在握手過程當中使用的安全證書。完整的服務器證書包括兩個部分:公鑰和私鑰。公鑰是發送給客戶機的,而私鑰則是保密的。

就像是信任證書必需要提供給客戶機應用程序使用的庫同樣,服務器密鑰也必需要提供給服務器應用程序使用的庫。有幾個函數都提供了這種功能:


清單 1. 加載服務器證書的函數
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 編碼的證書。

要加載私鑰,請使用下面函數之一:


清單 2. 加載私鑰使用的函數
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 爲得到密碼提供了一種回調機制。

回調格式以下:


清單 3. 回調格式
int password_callback(char *buf, int size, int rwflag, void *userdata);

就本文的目的來講,最後一個參數 userdata 是不須要的。緩衝區在調用這個函數以前被調用,所以對於這個緩衝區的大小咱們沒法控制。

服務器私鑰

注意對於服務器證書來講,私鑰不該該加密進行保存。不然,管理員可能會被過於頻繁地要求輸入密碼。

參數 rwflag 是讀/寫標記。使用它的目的是使咱們能夠編程肯定這個密碼是正在用來加密信息(rwflag = 1)仍是解密信息(rwflag = 0)。若是正在使用回調函數來請求對數據進行加密使用的密碼,最好是以某種方式請求兩次,這樣能夠多一次機會接收用戶的輸入。

在證書加載時,這個密碼只會請求一次,這樣它就能夠進行解密並保存到內存中了。如何從用戶那裏獲取密碼,徹底取決於您的實現。

一旦咱們建立好密碼回調函數,就能夠按照下面的方法使用 SSL_CTX_set_default_passwd_cb 將其安裝到 SSL 環境中:


清單 4. 安裝回調函數
/* 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 部分,那麼加載證書的的方式就很是相似於在前面文章中給出的加載信任存儲的方式。

咱們將從公用證書開始介紹,這個證書會發送給客戶機。


清單 5. 加載公用證書
/**
 * 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 */
}

在加載公用證書以後,就必須加載私有證書了。這是在握手過程當中須要的,由於客戶機在這個過程當中正將這些信息發送給對公鑰進行加密的服務器。這些數據只能使用私鑰進行解密。一樣,爲了保持一致,咱們也將從文件中加載密鑰。


清單 6. 加載私鑰
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 通訊的。爲了與這篇文章保持一致,咱們在本文中也將實現一樣的功能。


清單 7. BIO 指針
BIO *bio, *abio, *out;

3 個 BIO 對象?爲何咱們須要使用 3 個 BIO 對象呢?這麼作有一個目的,請相信我。(記住,信任和安全性的目標是一致的。)

第一個指針 bio 是主要的 BIO 對象,它能夠從 SSL 環境中建立。第二個對象 abio 是接受鏈接使用的 BIO,用來接收到達的鏈接。第三個 BIO out,是服務器發往客戶機使用的對象。


清單 8. 設置主要的 BIO 對象 
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。


清單 9. 設置接收 BIO
abio = BIO_new_accept("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 兩次。


清單 10. 告訴服務器 「坐下來」
/* 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。


清單 11. 彈出鏈接進行通訊
out = BIO_pop(abio);

if(BIO_do_handshake(out) <= 0)
{
    /* Handle fail here */
}

在彈出到達的鏈接到接收 BIO 以後,握手須要使用一個到 BIO_do_handshake 的調用進行處理。若是前面幾節中的設置成功了,那麼握手在這裏也應該會成功。

服務器會經過 BIO 庫的各類讀寫函數真正與客戶機進行通訊。在 第 1 部分 中咱們已經討論了有關這些問題的內容,所以咱們能夠在本文中找到更多討論內容。

回頁首

提供卓越的服務

整體來講,一旦理解這一切是如何工做的,使用 OpenSSL 建立安全的服務器應用程序就沒什麼困難了。從如今開始,咱們就能夠對所提供的樣例代碼進行擴展,從而建立一個徹底版本的 SSL 服務器應用程序來知足咱們的要求了。不過要預先警告一下,此處以及下載一節 所提供的代碼樣例都進行了儘可能簡化,應該只適用於實驗的目的。在真正建立一個完整的 SSL 服務器應用程序以前,請確保閱讀並研究最新的安全建議。


回頁首

下載

描述 名字 大小 下載方法
本文的示例代碼 openssl3.tar.gz 4KB HTTP

關於下載方法的信息


參考資料

學習

得到產品和技術

  • 下載最新的 OpenSSL 庫 及相關文檔。

  • 在您的下一個開發項目中採用 IBM 試用版軟件,這能夠從 developerWorks 上直接下載。 

討論

關於做者

Kenneth Ballard 擁有 Peru State College(位於 Peru, Nebraska)的計算機科學專業學士學位,在這裏,他是校報 The Peru State Times 的專職做者。他還擁有 Southwestern Community College(位於 Creston, Iowa)的計算機編程專業副學士學位。Kenneth 已經編寫了多個應用程序和編程庫。

相關文章
相關標籤/搜索