本系列文章的前兩部分討論了使用 OpenSSL 來建立客戶機端應用程序的內容。第 1 部分 討論了使用 OpenSSL 建立基本安全客戶機的問題,而 第 2 部分 則深刻討論了有關數字證書的問題。在閱讀本文的讀者給我發回不少 e-mail 和正面反饋以後,我很是清楚,接下來的一期理論介紹應該是有關服務器的。html
服務器爲網絡和 Internet 提供了訪問諸如文件和設備之類的資源的訪問能力。有時咱們必需要經過一個安全通道來提供這些服務。OpenSSL 讓咱們可使用安全通道和開放通道來編寫服務。linux
使用 OpenSSL 來建立基本的服務器應用程序從本質上來講幾乎 等同於建立一個基本的客戶機應用程序。兩者之間的區別很少。顯然,區別之一就是服務器將被設置爲接收到達的鏈接,而不是創建外發的鏈接。而且,正如咱們能夠從本系列的第 2 部分有關數字證書的討論中看到的同樣,服務器還必需要在握手過程當中提供安全證書。ios
等待編程
服務器基本上就是呆在那裏等待到達的鏈接。畢竟,這就是服務器存在的緣由。Web 服務器要等待瀏覽器請求頁面,FTP 服務器要等待客戶機請求文件,聊天服務器要等待聊天客戶機所發出的鏈接。所以服務器要作的事情就是等待。瀏覽器
在客戶機和服務器通訊之間差異不大,唯一的差異就是對於握手來講,服務器就像是硬幣的反面。其餘東西都是相同的。安全
這讓咱們可使用 OpenSSL 來編寫安全的服務器應用程序,再次假設您已經瞭解如何使用 OpenSSL 來編寫客戶機應用程序。(若是您還不瞭解相關知識,請參閱本系列第 1 部分 「API 概述」 來學習如何設置 OpenSSL 庫。)服務器
回頁首網絡
兩種形式的標識app
也能夠說是標識的兩部分。
服務器要負責提供在握手過程當中使用的安全證書。完整的服務器證書包括兩個部分:公鑰和私鑰。公鑰是發送給客戶機的,而私鑰則是保密的。
就像是信任證書必需要提供給客戶機應用程序使用的庫同樣,服務器密鑰也必需要提供給服務器應用程序使用的庫。有幾個函數都提供了這種功能:
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_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
兩次。
/* 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 以後,握手須要使用一個到 BIO_do_handshake
的調用進行處理。若是前面幾節中的設置成功了,那麼握手在這裏也應該會成功。
服務器會經過 BIO 庫的各類讀寫函數真正與客戶機進行通訊。在 第 1 部分 中咱們已經討論了有關這些問題的內容,所以咱們能夠在本文中找到更多討論內容。
整體來講,一旦理解這一切是如何工做的,使用 OpenSSL 建立安全的服務器應用程序就沒什麼困難了。從如今開始,咱們就能夠對所提供的樣例代碼進行擴展,從而建立一個徹底版本的 SSL 服務器應用程序來知足咱們的要求了。不過要預先警告一下,此處以及下載一節 所提供的代碼樣例都進行了儘可能簡化,應該只適用於實驗的目的。在真正建立一個完整的 SSL 服務器應用程序以前,請確保閱讀並研究最新的安全建議。
下載
描述 | 名字 | 大小 | 下載方法 |
---|---|---|---|
本文的示例代碼 | openssl3.tar.gz | 4KB | HTTP |
學習
得到產品和技術
討論
Kenneth Ballard 擁有 Peru State College(位於 Peru, Nebraska)的計算機科學專業學士學位,在這裏,他是校報 The Peru State Times 的專職做者。他還擁有 Southwestern Community College(位於 Creston, Iowa)的計算機編程專業副學士學位。Kenneth 已經編寫了多個應用程序和編程庫。