openssl框架閑談--SSL實現


BIO 和EVP的一個應用就是SSL,沒有SSL這個應用,BIO或者EVP只不過是一些底層的支撐接口,沒有任何的現實意義,正是SSL使用了BIO和EVP 的機制提供了一個已經成型的安全套接字的實現策略。其實想象一下,安全套接字有兩層含義,一層就是安全,這個由EVP接口實現了,另外一層含義就是套接 字,也就是說它必須是一個套接字,必須在操作的網絡協議棧上進行IO,這一層含義是在BIO接口體現的,這個意義上,SSL正是通過組合BIO和EVP來 實現安全套接字的,BIO除了提供底層的抽象接口之外並不和SSL存在別的方面的耦合,因此BIO可以單獨被使用,同樣的,EVP也是可以單獨被使用的。 
不過,繼續我們美妙的旅程之前首先要說的一點是,SSL本身就是一個BIO類型,並且是屬於過濾類型的,在它的下層必須有一個socket類型的源/目的類型的BIO,在openssl中自帶的sconnect實例中體現了這一點,創建過程如下: 
SSL_load_error_strings(); 
OpenSSL_add_ssl_algorithms(); 
ssl_ctx=SSL_CTX_new(SSLv23_client_method()); 
ssl=SSL_new(ssl_ctx); 
SSL_set_connect_state(ssl); 
ssl_bio=BIO_new(BIO_f_ssl()); 
BIO_set_ssl(ssl_bio,ssl,BIO_CLOSE); 
out=BIO_new(BIO_s_connect()); 
BIO_set_conn_hostname(out,host); 
BIO_set_nbio(out,1); 
out=BIO_push(ssl_bio,out);   //將socket類型的BIO置於SSL類型的BIO之下,注意,BIO_push里面會調用BIO_ctrl 
對於ssl_bio的BIO_ctrl,它的BIO_CTRL_PUSH命令實際上就是執行: 
static long ssl_ctrl(BIO *b, int cmd, long num, void *ptr) 

... 
    case BIO_CTRL_PUSH: 
        if ((b->next_bio != NULL) && (b->next_bio != ssl->rbio)) 
        { 
            SSL_set_bio(ssl,b->next_bio,b->next_bio);  //b->next_bio其實就是上面的out,它是一個 
socket類型的BIO 
            CRYPTO_add(&b->next_bio->references,1,CRYPTO_LOCK_BIO); 
        } 
        break; 
... 

可 以看出,ssl類型的BIO並沒有使用一般的過濾BIO的模式,在其write回調函數中繼續調用 SSL_write(b->next_bio,...)的模式,而是直接在SSL_push中使用回調函數將這個socket類型的BIO設置為 SSL的wbio,這樣的話就可以使用統一的SSL_write來發送數據了。因為openssl設計的正交化,它就很靈活,各種實現方式對於 openssl來說都是可能的。 
     openssl可以使用SSL和BIO兩種方式實現SSL,如果使用BIO方式,那么就是上面說的這一種,最終的IO是要調用BIO_write和 BIO_read來進行的,用BIO實現的ssl關鍵在於在io之前必須設置好套接字BIO和ssl類型的BIO以及SSL結構體之間的聯系,這是通過 BIO_set_ssl和BIO_push來實現的。 
另外一種實現ssl的方式更加簡單,大致流程如下:ssl = SSL_new (ctx)之后使用SSL_set_fd (ssl, sd),其中sd是使用socket函數創建的套接字文件描述符,之后就可以使用SSL_read和SSL_write進行io操作了。這種方式顯然很簡 單,這二種方式其實效果是一樣的,一種是構建於BIO之上,另一種是構建於一個新的特殊的SSL之上,殊途同歸。openssl對於ssl的實現很好的一 點就是將復雜的ssl協議的握手操作隱藏到了io的過程當中,不管哪種方式實現的ssl,最終都要調用sslX_write,比如ssl2_write, 這個函數中會判斷握手是否已經完成,如果沒有,那么就會調用該SSL結構的handshake_func回調函數,這個回調函數也可以手工調用,在 SSL_write之前首先調用SSL_accept或者SSL_connect來手工調用handshake_func函數,這個 handshake_func是在什么時候賦值給SSL結構的呢?可以在SSL_accept/connect中進行懶惰的賦值,也可以使用 BIO_ctrl進行手動賦值。 
     ssl的握手操作怎么實現的呢?其實和操作系統內核的TCP三次握手是一樣的,通過一個狀態機實現,其實你完全可以將ssl協議理解成協議棧的一部分,這 樣的話,ssl的握手僅僅是比tcp更高層次的協議握手罷了,狀態機的實現將握手過程和協議的其它部分隔離開來,握手的狀態機轉換過程被封裝在了一個無限 循環之中。 
     openssl代碼中最有意思的就是加密和解密算法了,相信大部分人都會被吸引的。可惜本系列文章不能大談加密算法,只能簡單說一下算法怎么結合到程序的 流程里面。簡單的說,有兩種方式在ssl中使用加密算法,第一種就是使用BIO接口,使用一個加密解密類型的BIO摞到一個套接字的BIO之上,在加密解 密的BIO之上再摞一個buffer類型的BIO就可以了,加密解密的BIO的作用就是數據加密解密,buffer類型的BIO的作用是添加協議頭部和校 驗,最下面的socket類型的BIO的作用就是將數據發往傳輸層。這種方式十分的和諧,並且和已有的協議棧相處得十分融洽,安全套接字層的意義也正是如 此。另外一種加密解密的使用方式就是隱藏這一切,直接使用SSL_write和SSL_read進行通信,每一個SSL結構體都有一個 EVP_CIPHER_CTX類型的字段,在SSL_write中實現加密,加密數據的緩沖區也在SSL結構中被分配,一個SSL結構體定義如下: 
struct ssl_st 

    int version;           //版本號 
    int type;         //是服務器還是客戶端 
    SSL_METHOD *method;     //ssl的回調函數 
#ifndef OPENSSL_NO_BIO        //如果ssl使用BIO機制 
    BIO *rbio;             //包含一個套接字,下同 
    BIO *wbio; 
    BIO *bbio; 
#else                //反之就提供一個緩沖區 
    char *rbio; 
    char *wbio; 
    char *bbio; 
#endif 
    int rwstate; 
    int in_handshake;    //是否正在握手 
    int (*handshake_func)();//握手函數,它其實在SSL_METHOD中被提供 
... 
    STACK_OF(SSL_CIPHER) *cipher_list;    //算法列表 
    STACK_OF(SSL_CIPHER) *cipher_list_by_id; 
    EVP_CIPHER_CTX *enc_read_ctx;   //解密使用的上下文環境 
... 
    struct ssl2_state_st *s2;  //版本2的特定結構,緩沖區在內部被包含 
    struct ssl3_state_st *s3;  //版本3的特定結構,緩沖區在內部被包含 
... 
    EVP_CIPHER_CTX *enc_write_ctx;  //加密使用的上下文環境 
... 
    SSL_CTX *ctx;        //此ssl的上下文環境 
... 
}; 
可 以看出這個SSL結構有點自成體系的味道,其實它本應該自成體系的,否則它就真成了BIO和EVP的demo了,所有的相關的東西都在SSL中,包括加 密,解密使用的數據結構,加密,解密使用的緩沖區,最底層的socket類型的BIO以及其他ssl協議相關聯的概念,這套代碼之所以叫做openssl 而不是openbio或者openevp的原因吧。按照這種方式使用加密和解密雖然可能沒有BIO的方式直觀,但是卻很直接,SSL指針中隨時可以取出加 密解密結構體然后操作同樣位於SSL結構中的加密解密緩沖區,最后發送給同樣位於SSL中的wbio或者從rbio接收數據。在SSL_write最終要 調用的do_ssl3_write函數中會有以下邏輯: 
s->method->ssl3_enc->enc(s,1); 
而這個>ssl3_enc是另一套回調函數集合,是關於加密和解密的,其中的enc回調函數對於ssl3就是ssl3_enc: 
int ssl3_enc(SSL *s, int send) 

    SSL3_RECORD *rec; 
    EVP_CIPHER_CTX *ds; 
... 
    const EVP_CIPHER *enc; 
    if (send) 
    { 
        ds=s->enc_write_ctx;       //得到ssl的加密解密上下文環境 
        rec= &(s->s3->wrec);    //得到ssl加密解密的緩沖區 
        enc=EVP_CIPHER_CTX_cipher(s->enc_write_ctx); 
    } 
... 
        EVP_Cipher(ds,rec->data,rec->input,l); //加密,ds為上下文,后面的參數為輸入和輸出緩沖區 
... 

可 能是由於效率的原因,openssl采用了自成體系的SSL的方式而沒有采用串聯BIO的方式,但是我個人還是很看好串聯BIO的方式的,串聯的BIO實 際上串聯了不同的過濾策略,你當然可以理解成封包規則,網絡的分層協議不就是靠這種方式實現的嗎?每一層的封裝其實就是一個過濾器,就是一個過濾策略,用 BIO來理解網絡封包再好不過了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM