基於OpenSSL實現的安全連線
阿新 • • 發佈:2018-12-22
Web伺服器為了支援https訪問,通常會使用第三方庫openssl實現,而且為了高效能採用非同步事件驅動模型,因此連線套接字被設為非阻塞型別,本文在nginx ssl模組的基礎上,簡化提取它的核心框架,使用面向物件的方式描述,從握手、讀寫和關閉3個方面進行了分析,由於這3個操作都是非同步的,因此操作失敗後要呼叫SSL_get_error來獲取錯誤碼,有如下4種情況。
● SSL_ERROR_WANT_READ:操作沒完成,需要在下一次讀事件中繼續
● SSL_ERROR_WANT_WRITE:操作沒完成,需要在下一次寫事件中繼續
● SSL_ERROR_ZERO_RETURN:連線正常關閉
● 其它:連接出錯
下面的示例程式碼使用了libevent實現IO事件驅動,connection表示普通連線類,假設已經處理好了http資料的邏輯,實現在成員函式handle_read和handle_write內,虛擬函式recv、send和close分別呼叫系統的API recv、send和close;ssl_conn_t表示安全連線類,由於只是IO處理不同:收到資料後解密,傳送資料前加密。解密後的操作和connection是一樣的,因此ssl_conn_t繼承connection,重寫了recv、send和close,其中close呼叫了shutdown。
非同步握手 當在SSL埠接受到連線時,首先要進行握手,握手成功後才能收發資料,如果握手失敗而且返回前2種錯誤碼,那麼要在下一次操作中繼續握手。
1void ssl_conn_t::empty_handler(short ev)
2{
3} 4
5void ssl_conn_t::handshake_handler(short ev)
6{
7 handshake();
8} 9
10void ssl_conn_t::handshake()
11{
12 int ret = do_handshake();
13
14 switch (ret){
15 case OP_OK:
16 read_handler_ =&connection::handle_read;
17 write_handler_ =&connection::handle_write;
18 handle_read(EV_READ);
19 break;
20
21 }22}23
24int ssl_conn_t::do_handshake()
25{
26 ssl_clear_error();
27
28 int ret = SSL_do_handshake(ssl_);
29 if(1==ret){
30
31 return OP_OK;
32 }33
34 int sslerr = SSL_get_error(ssl_,ret), err;
35 switch(sslerr){
36 case SSL_ERROR_WANT_READ:
37 read_handler_ = (io_handler)&ssl_conn_t::handshake_handler;38 write_handler_ = (io_handler)&ssl_conn_t::empty_handler;39 return OP_AGAIN;
40
41 case SSL_ERROR_WANT_WRITE:
42 read_handler_ = (io_handler)&ssl_conn_t::empty_handler;43 write_handler_ = (io_handler)&ssl_conn_t::handshake_handler;44 return OP_AGAIN;
45
46 }47} 以上do_handshake是核心函式,呼叫了SSL_do_handshake來實現握手,當SSL_do_handshake失敗時,如果返回SSL_ERROR_WANT_READ,就改變讀函式指標為handshake_handler,寫函式指標為empty_handler;如果返回SSL_ERROR_WANT_WRITE,就改變讀函式指標為empty_handler,寫函式指標為handshake_handler。handshake_handler實現在讀寫事件中繼續處理握手,而empty_handler是個空函式,什麼也不做。
非同步讀寫
對於讀操作失敗,如果返回SSL_ERROR_WANT_WRITE,那麼說明要在下一次寫事件中繼續讀資料,因此要改變寫函式指標,使其讀資料,當讀成功後,要還原寫函式針,並激發一次寫事件;對於寫操作失敗,如果返回SSL_ERROR_WANT_READ,那麼說明要在下一次讀事件中繼續寫資料,因此要改變讀函式指標,使其寫資料,當寫成功後,要還原讀函式指標,並激發一次讀事件。如果不還原讀或寫函式指標,那麼會發生寫或讀混亂;還原後,要激發一次讀或寫事件,這是為了延續IO事件的進行,防止讀寫餓死。 1ssize_t ssl_conn_t::recv(void*buf,size_t len)
2{
3 ssl_clear_error();
4
5 int ret = SSL_read(ssl_,buf,len);
6 if(ret>0){
7 if(old_write_handler_){
8 write_handler_ = old_write_handler_;
9 old_write_handler_ = NULL;
10 active_event(false);
11 }12 return ret;
13 }14
15 int sslerr = SSL_get_error(ssl_,ret), err;
16 switch(sslerr){
17 case SSL_ERROR_WANT_READ:
18 return OP_AGAIN;
19
20 case SSL_ERROR_WANT_WRITE:
21 if(NULL==old_write_handler_){
22 old_write_handler_ = write_handler_;23 write_handler_ = (io_handler)&ssl_conn_t::write_handler;24 }25 return OP_AGAIN;
26
27 }28}29
30void ssl_conn_t::write_handler(short ev)
31{
32 (this->*read_handler_)(EV_WRITE);
33}34
35ssize_t ssl_conn_t::send(constvoid*buf,size_t len)
36{
37 ssl_clear_error();
38
39 int ret = SSL_write(ssl_,buf,len);
40 if(ret>0){
41 if(old_read_handler_){
42 read_handler_ = old_read_handler_;
43 old_read_handler_ = NULL;
44 active_event(true);
45 }46 return ret;
47 }48
49 int sslerr = SSL_get_error(ssl_,ret), err;
50 switch(sslerr){
51 case SSL_ERROR_WANT_WRITE:
52 return OP_AGAIN;
53
54 case SSL_ERROR_WANT_READ:
55 if(NULL==old_read_handler_){
56 old_read_handler_ = read_handler_;57 read_handler_ = (io_handler)&ssl_conn_t::read_handler;58 }59 return OP_AGAIN;
60
61 }62}63
64void ssl_conn_t::read_handler(short ev)
65{
66 (this->*write_handler_)(EV_READ);
67} 以上recv呼叫SSL_read,如果失敗並且返回SSL_ERROR_WANT_WRITE,就儲存老的寫函式指標,改變寫函式指標為write_handler,write_handler實現在寫事件中繼續讀資料;send呼叫SSL_write,如果失敗並且返回SSL_ERROR_WANT_READ,就儲存老的讀函式指標,改變讀函式指標為read_handler,read_handler實現在讀事件中繼續寫資料。
非同步關閉
當握手或讀寫因連線關閉或出錯而失敗時,就要關閉連線了,如果失敗並且返回SSL_ERROR_WANT_READ或SSL_ERROR_WANT_WRITE,那麼要在下一次讀或寫事件中繼續關閉。在這裡,為了收到對方傳送的協議退出包而完全退出,等待30秒再關閉,如果超時就直接關閉。 1void ssl_conn_t::shutdown(bool is_timeout/*=false*/)
2{
3 if (do_shutdown(is_timeout) != OP_AGAIN)
4 delete this;
5} 6
7int ssl_conn_t::do_shutdown(bool is_timeout)
8
● SSL_ERROR_WANT_READ:操作沒完成,需要在下一次讀事件中繼續
● SSL_ERROR_WANT_WRITE:操作沒完成,需要在下一次寫事件中繼續
● SSL_ERROR_ZERO_RETURN:連線正常關閉
● 其它:連接出錯
下面的示例程式碼使用了libevent實現IO事件驅動,connection表示普通連線類,假設已經處理好了http資料的邏輯,實現在成員函式handle_read和handle_write內,虛擬函式recv、send和close分別呼叫系統的API recv、send和close;ssl_conn_t表示安全連線類,由於只是IO處理不同:收到資料後解密,傳送資料前加密。解密後的操作和connection是一樣的,因此ssl_conn_t繼承connection,重寫了recv、send和close,其中close呼叫了shutdown。
非同步握手
2{
3} 4
5void ssl_conn_t::handshake_handler(short ev)
6{
7 handshake();
8} 9
10void ssl_conn_t::handshake()
11{
12 int ret = do_handshake();
13
14 switch
15 case OP_OK:
16 read_handler_ =&connection::handle_read;
17 write_handler_ =&connection::handle_write;
18 handle_read(EV_READ);
19 break;
20
21 }22}23
24int ssl_conn_t::do_handshake()
25{
26 ssl_clear_error();
28 int ret = SSL_do_handshake(ssl_);
29 if(1==ret){
30
31 return OP_OK;
32 }33
34 int sslerr = SSL_get_error(ssl_,ret), err;
35 switch(sslerr){
36 case SSL_ERROR_WANT_READ:
37 read_handler_ = (io_handler)&ssl_conn_t::handshake_handler;38 write_handler_ = (io_handler)&ssl_conn_t::empty_handler;39 return OP_AGAIN;
40
41 case SSL_ERROR_WANT_WRITE:
42 read_handler_ = (io_handler)&ssl_conn_t::empty_handler;43 write_handler_ = (io_handler)&ssl_conn_t::handshake_handler;44 return OP_AGAIN;
45
46 }47} 以上do_handshake是核心函式,呼叫了SSL_do_handshake來實現握手,當SSL_do_handshake失敗時,如果返回SSL_ERROR_WANT_READ,就改變讀函式指標為handshake_handler,寫函式指標為empty_handler;如果返回SSL_ERROR_WANT_WRITE,就改變讀函式指標為empty_handler,寫函式指標為handshake_handler。handshake_handler實現在讀寫事件中繼續處理握手,而empty_handler是個空函式,什麼也不做。
非同步讀寫
對於讀操作失敗,如果返回SSL_ERROR_WANT_WRITE,那麼說明要在下一次寫事件中繼續讀資料,因此要改變寫函式指標,使其讀資料,當讀成功後,要還原寫函式針,並激發一次寫事件;對於寫操作失敗,如果返回SSL_ERROR_WANT_READ,那麼說明要在下一次讀事件中繼續寫資料,因此要改變讀函式指標,使其寫資料,當寫成功後,要還原讀函式指標,並激發一次讀事件。如果不還原讀或寫函式指標,那麼會發生寫或讀混亂;還原後,要激發一次讀或寫事件,這是為了延續IO事件的進行,防止讀寫餓死。 1ssize_t ssl_conn_t::recv(void*buf,size_t len)
2{
3 ssl_clear_error();
4
5 int ret = SSL_read(ssl_,buf,len);
6 if(ret>0){
7 if(old_write_handler_){
8 write_handler_ = old_write_handler_;
9 old_write_handler_ = NULL;
10 active_event(false);
11 }12 return ret;
13 }14
15 int sslerr = SSL_get_error(ssl_,ret), err;
16 switch(sslerr){
17 case SSL_ERROR_WANT_READ:
18 return OP_AGAIN;
19
20 case SSL_ERROR_WANT_WRITE:
21 if(NULL==old_write_handler_){
22 old_write_handler_ = write_handler_;23 write_handler_ = (io_handler)&ssl_conn_t::write_handler;24 }25 return OP_AGAIN;
26
27 }28}29
30void ssl_conn_t::write_handler(short ev)
31{
32 (this->*read_handler_)(EV_WRITE);
33}34
35ssize_t ssl_conn_t::send(constvoid*buf,size_t len)
36{
37 ssl_clear_error();
38
39 int ret = SSL_write(ssl_,buf,len);
40 if(ret>0){
41 if(old_read_handler_){
42 read_handler_ = old_read_handler_;
43 old_read_handler_ = NULL;
44 active_event(true);
45 }46 return ret;
47 }48
49 int sslerr = SSL_get_error(ssl_,ret), err;
50 switch(sslerr){
51 case SSL_ERROR_WANT_WRITE:
52 return OP_AGAIN;
53
54 case SSL_ERROR_WANT_READ:
55 if(NULL==old_read_handler_){
56 old_read_handler_ = read_handler_;57 read_handler_ = (io_handler)&ssl_conn_t::read_handler;58 }59 return OP_AGAIN;
60
61 }62}63
64void ssl_conn_t::read_handler(short ev)
65{
66 (this->*write_handler_)(EV_READ);
67} 以上recv呼叫SSL_read,如果失敗並且返回SSL_ERROR_WANT_WRITE,就儲存老的寫函式指標,改變寫函式指標為write_handler,write_handler實現在寫事件中繼續讀資料;send呼叫SSL_write,如果失敗並且返回SSL_ERROR_WANT_READ,就儲存老的讀函式指標,改變讀函式指標為read_handler,read_handler實現在讀事件中繼續寫資料。
非同步關閉
當握手或讀寫因連線關閉或出錯而失敗時,就要關閉連線了,如果失敗並且返回SSL_ERROR_WANT_READ或SSL_ERROR_WANT_WRITE,那麼要在下一次讀或寫事件中繼續關閉。在這裡,為了收到對方傳送的協議退出包而完全退出,等待30秒再關閉,如果超時就直接關閉。 1void ssl_conn_t::shutdown(bool is_timeout/*=false*/)
2{
3 if (do_shutdown(is_timeout) != OP_AGAIN)
4 delete this;
5} 6
7int ssl_conn_t::do_shutdown(bool is_timeout)
8