openssl之BIO系列之24---SSL類型的BIO,openssl24---ssl
SSL類型的BIO
---根據openssl doc\crypto\bio_f_ssl.pod翻譯和自己的理解寫成
(作者:DragonKing, Mail: wzhah@263.net ,發佈於:http://openssl.126.com之
openssl專業論壇)
從名字就可以看出,這是一個非常重要的BIO類型,它封裝了openssl裡面的ssl規則
和函數,相當於提供了一個使用SSL很好的有效工具,一個很好的助手。其定義(opens
sl\bio.h,openssl\ssl.h)如下:
BIO_METHOD *BIO_f_ssl(void);
#define BIO_set_ssl(b,ssl,c) BIO_ctrl(b,BIO_C_SET_SSL,c,(char *)ssl)
#define BIO_get_ssl(b,sslp) BIO_ctrl(b,BIO_C_GET_SSL,0,(char *)sslp)
#define BIO_set_ssl_mode(b,client) BIO_ctrl(b,BIO_C_SSL_MODE,client,NUL
L)
#define BIO_set_ssl_renegotiate_bytes(b,num) BIO_ctrl(b,BIO_C_SET_SSL_R
ENEGOTIATE_BYTES,num,NULL);
#define BIO_set_ssl_renegotiate_timeout(b,seconds) BIO_ctrl(b,BIO_C_SET
_SSL_RENEGOTIATE_TIMEOUT,seconds,NULL);
#define BIO_get_num_renegotiates(b) BIO_ctrl(b,BIO_C_SET_SSL_NUM_RENEGO
TIATES,0,NULL);
BIO *BIO_new_ssl(SSL_CTX *ctx,int client);
BIO *BIO_new_ssl_connect(SSL_CTX *ctx);
BIO *BIO_new_buffer_ssl_connect(SSL_CTX *ctx);
int BIO_ssl_copy_session_id(BIO *to,BIO *from);
void BIO_ssl_shutdown(BIO *bio);
#define BIO_do_handshake(b) BIO_ctrl(b,BIO_C_DO_STATE_MACHINE,0,NULL)
該類型BIO的實現檔案在ssl\bio_ssl.c裡面,大家可以參看這個檔案得到詳細的函
數實現資訊。
【BIO_f_ssl】
該函數返回一個SSL類型的BIO_METHOD結構,其定義如下:
static BIO_METHOD methods_sslp=
{
BIO_TYPE_SSL,"ssl",
ssl_write,
ssl_read,
ssl_puts,
NULL, /* ssl_gets, */
ssl_ctrl,
ssl_new,
ssl_free,
ssl_callback_ctrl,
};
可見,SSL類型BIO不支援BIO_gets的功能。
BIO_read和BIO_write函數調用的時候,SSL類型的BIO會使用SSL協議進行底層的I/
O操作。如果此時SSL串連並沒有建立,那麼就會在調用第一個IO函數的時候先進行串連
的建立。
如果使用BIO_push將一個BIO附加到一個SSL類型的BIO上,那麼SSL類型的BIO讀寫數
據的時候,它會被自動調用。
BIO_reset調用的時候,會調用SSL_shutdown函數關閉目前所有處於串連狀態的SSL
,然後再對下一個BIO調用BIO_reset,這功能一般就是將底層的傳輸串連斷開。調用完
成之後,SSL類型的BIO就處於初始的接受或串連狀態。
如果設定了BIO關閉標誌,那麼SSL類型BIO釋放的時候,內部的SSL結構也會被SSL_
free函數釋放。
【BIO_set_ssl】
該函數設定SSL類型BIO的內部ssl指標指向ssl,同時使用參數c設定了關閉標誌。
【BIO_get_ssl】
該函數返回SSL類型BIO的內部的SSL結構指標,得到該指標後,可以使用標誌的SSL
函數對它進行操作。
【BIO_set_ssl_mode】
該函數設定SSL的工作模式,如果參數client是1,那麼SSL工作模式為用戶端模式,
如果client為0,那麼SSL工作模式為伺服器模式。
【BIO_set_ssl_renegotiate_bytes】
該函數設定需要重新進行session協商的讀寫資料的長度為num。當設定完成後,在
沒讀寫的資料一共到達num位元組後,SSL串連就會自動重新進行session協商,這可以加強
SSL串連的安全性。參數num最少為512位元組。
【BIO_set_ssl_renegotiate_timeout】
該函數跟上述函數一樣都是為了加強SSL串連的安全性的。不同的是,該函數採用的
參數是時間。該函數設定重新進行session協商的時間,其單位是秒。當SSL session連
接建立的時間到達其設定的時間時,串連就會自動重新進行session協商。
【BIO_get_num_renegotiates】
該函數返回SSL串連在因為位元組限制或時間限制導致session重新協商之前總共讀寫
的資料長度。
【BIO_new_ssl】
該函數使用ctx參數所代表的SSL_CTX結構建立一個SSL類型的BIO,如果參數client
為非零值,就使用用戶端模式。
【BIO_new_ssl_connect】
該函數建立一個包含SSL類型BIO的新BIO鏈,並在後面附加了一個連線類型的BIO。
方便而且有趣的是,因為在filter類型的BIO裡,如果是該BIO不知道(沒有實現)
BIO_ctrl操作,它會自動把該操作傳到下一個BIO進行調用,所以我們可以在調用本函數
得到BIO上直接調用BIO_set_host函數來設定伺服器名字和連接埠,而不需要先找到串連B
IO。
【BIO_new_buffer_ssl_connect】
建立一個包含buffer型的BIO,一個SSL類型的BIO以及一個連線類型的BIO。
【BIO_ssl_copy_session_id】
該函數將BIO鏈from的SSL Session ID拷貝到BIO鏈to中。事實上,它是通過尋找到
兩個BIO鏈中的SSL類型BIO,然後調用SSL_copy_session_id來完成操作的。
【BIO_ssl_shutdown】
該函數關閉一個BIO鏈中的SSL串連。事實上,該函數通過尋找到該BIO鏈中的SSL類
型BIO,然後調用SSL_shutdown函數關閉其內部的SSL指標。
【BIO_do_handshake】
該函數在相關的BIO上啟動SSL握手過程並建立SSL串連。串連成功建立返回1,否則
返回0或負值,如果串連BIO是非阻塞型的BIO,此時可以調用BIO_should_retry函數以決
定釋放需要重試。如果調用該函數的時候SSL串連已經建立了,那麼該函數不會做任何事
情。一般情況下,應用程式不需要直接調用本函數,除非你希望將握手過程跟其它IO操
作分離開來。
需要注意的是,如果底層是阻塞型(openssl協助文檔寫的是非阻塞型,non blocki
ng,但是根據上下文意思已經BIO的其它性質,我個人認為是阻塞型,blocking才是正確
的)的BIO,在一些意外的情況SSL類型BIO下也會發出意外的重試請求,如在執行BIO_r
ead操作的時候如果啟動了session重新協商的過程就會發生這種情況。在0.9.6和以後的
版本,可以通過SSL的標誌SSL_AUTO_RETRY將該類行為禁止,這樣設定之後,使用阻塞型
傳輸的SSL類型BIO就永遠不會發出重試的請求。
【例子】
1.一個SSL/TLS用戶端的例子,完成從一個SSL/TLS伺服器返回一個頁面的功能。其
中IO操作的方法跟連線類型BIO裡面的例子是相同的。
BIO *sbio, *out;
int len;
char tmpbuf[1024];
SSL_CTX *ctx;
SSL *ssl;
ERR_load_crypto_strings();
ERR_load_SSL_strings();
OpenSSL_add_all_algorithms();
//如果系統平台不支援自動進行隨機數種子的設定,這裡應該進行設定(seed PRN
G)
ctx = SSL_CTX_new(SSLv23_client_method());
//通常應該在這裡設定一些驗證路徑和模式等,因為這裡沒有設定,所以該例子可
以跟使用任意CA簽發認證的任意伺服器建立串連
sbio = BIO_new_ssl_connect(ctx);
BIO_get_ssl(sbio, &ssl);
if(!ssl) {
fprintf(stderr, "Can't locate SSL pointer\n");
}
/* 不需要任何重試請求*/
SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
//這裡你可以添加對SSL的其它一些設定
BIO_set_conn_hostname(sbio, "localhost:https");
out = BIO_new_fp(stdout, BIO_NOCLOSE);
if(BIO_do_connect(sbio) <= 0) {
fprintf(stderr, "Error connecting to server\n");
ERR_print_errors_fp(stderr);
}
if(BIO_do_handshake(sbio) <= 0) {
fprintf(stderr, "Error establishing SSL connection\n");
ERR_print_errors_fp(stderr);
}
/* 這裡可以添加檢測SSL串連的代碼,得到一些串連資訊*/
BIO_puts(sbio, "GET / HTTP/1.0\n\n");
for(;;) {
len = BIO_read(sbio, tmpbuf, 1024);
if(len <= 0) break;
BIO_write(out, tmpbuf, len);
}
BIO_free_all(sbio);
BIO_free(out);
2.一個簡單的伺服器的例子。它使用了buffer類型的BIO,從而可以使用BIO_gets從
一個SSL類型的BIO讀取資料。它建立了一個包含用戶端請求的隨機web頁,並把請求資訊
輸出到標準輸出裝置。
BIO *sbio, *bbio, *acpt, *out;
int len;
char tmpbuf[1024];
SSL_CTX *ctx;
SSL *ssl;
ERR_load_crypto_strings();
ERR_load_SSL_strings();
OpenSSL_add_all_algorithms();
//可能需要進行隨機數的種子處理(seed PRNG)
ctx = SSL_CTX_new(SSLv23_server_method());
if (!SSL_CTX_use_certificate_file(ctx,"server.pem",SSL_FILETYPE_PEM)
|| !SSL_CTX_use_PrivateKey_file(ctx,"server.pem",SSL_FILETYPE_PEM)
|| !SSL_CTX_check_private_key(ctx)) {
fprintf(stderr, "Error setting up SSL_CTX\n");
ERR_print_errors_fp(stderr);
return 0;
}
//可以在這裡設定驗證路徑,DH和DSA演算法的臨時密鑰回呼函數等等
/* 建立一個新的伺服器模式的SSL類型BIO*/
sbio=BIO_new_ssl(ctx,0);
BIO_get_ssl(sbio, &ssl);
if(!ssl) {
fprintf(stderr, "Can't locate SSL pointer\n");
}
/* 不需要任何重試請求 */
SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
/* 建立一個Buffer類型BIO */
bbio = BIO_new(BIO_f_buffer());
/* 加到BIO鏈上*/
sbio = BIO_push(bbio, sbio);
acpt=BIO_new_accept("4433");
/*
當一個新串連建立的時候,我們可以將sbio鏈自動插入到串連所在的BIO鏈中去。
這時候,這個BIO鏈(sbio)就被accept類型BIO吞併了,並且當accept類型BIO釋放的時候
,它會自動被釋放。
*/
BIO_set_accept_bios(acpt,sbio);
out = BIO_new_fp(stdout, BIO_NOCLOSE);
/* 設定 accept BIO */
if(BIO_do_accept(acpt) <= 0) {
fprintf(stderr, "Error setting up accept BIO\n");
ERR_print_errors_fp(stderr);
return 0;
}
/* 等待串連的建立 */
if(BIO_do_accept(acpt) <= 0) {
fprintf(stderr, "Error in connection\n");
ERR_print_errors_fp(stderr);
return 0;
}
/*
因為我們只想處理一個串連,所以可以刪除和釋放 accept BIO了
*/
sbio = BIO_pop(acpt);
BIO_free_all(acpt);
if(BIO_do_handshake(sbio) <= 0) {
fprintf(stderr, "Error in SSL handshake\n");
ERR_print_errors_fp(stderr);
return 0;
}
BIO_puts(sbio, "HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n");
BIO_puts(sbio, "<pre>\r\nConnection Established\r\nRequest headers:\r\n
");
BIO_puts(sbio, "--------------------------------------------------\r\n"
);
for(;;) {
len = BIO_gets(sbio, tmpbuf, 1024);
if(len <= 0) break;
BIO_write(sbio, tmpbuf, len);
BIO_write(out, tmpbuf, len);
/* 尋找要求標頭的結束標準空白行*/
if((tmpbuf[0] == '\r') || (tmpbuf[0] == '\n')) break;
}
BIO_puts(sbio, "--------------------------------------------------\r\n"
);
BIO_puts(sbio, "</pre>\r\n");
/* 因為使用了buffer類型的BIO,我們最好調用BIO_flush函數 */
BIO_flush(sbio);
BIO_free_all(sbio);