mozilla的安全架構

科技小能手發表於2017-11-12
mozilla在安全方面主要分為三大塊:1.SSL協議的實現;2.Crypto庫的實現;3.PKCS#11的實現最頂層是ssl的實現,和openssl不同,mozilla是自上而下設計的,因此mozilla並沒有抽象出諸如BIO或者EVP之類的通用機制,可以它實現了一個PRFileDesc結構體,和BIO是很類似的;涉及到安全的實現就是2和3了,其中3定義了介面,2給與了軟實現,當然你可以載入plugin使用硬體實現。三者合一形成了mozilla的安全架構。
     有幾個重要的結構體先了解一下:sslSocketStr結構和openssl中的ssl_st結構體非常類似:
struct sslSocketStr {
        PRFileDesc *    fd;  //一個檔案IO分發表,類似linux核心的file_operations,僅僅作為介面定義。
        const sslSocketOps * ops //實現PRFileDesc的介面定義;  
    …
    sslSecurityInfo  sec; //操作ssl通道的方式,比如加密,解密之類
    …
        const char      *url;    //目的地址,瀏覽器接受的url
    …
    sslHandshakeFunc handshake;        //函式可以作為鎖來使用
    sslHandshakeFunc nextHandshake;                
    sslHandshakeFunc securityHandshake;    
      //回撥函式,timeout,lock,暫存緩衝,執行緒,ssl握手方法,狀態
};
上面的一個sslSocketStr代表了一個連線,並且描述了表示層即ssl協議,和openssl類似,mozilla也永術語“PUSH”來將一個安全描述符壓入一個普通的傳輸層套接字(類似OpenSSL的BIO機制,後面會說),比如你訪問http://www.google.com.hk,那麼敲下回車的時候就會建立一個sslSocketStr,其中url就是你要訪問的google的網址,某些網站需要安全連線,如果需要安全的話隨之就會初始化sslSocketStr中的fd中的PRIOMethods為ssl_methods,它完成了套接字IO的幾乎所有的語義,在openssl中,該結構體由SSL_METHOD實現,原理和此處的mozilla幾乎一致:
static const PRIOMethods ssl_methods = {
    PR_DESC_LAYERED,
        …
    ssl_Read,                //read
    ssl_Write,               //write
   ssl_Recv,                //recv
    ssl_Send,                //send
    …
};
static const sslSocketOps ssl_secure_ops = {
    …
        ssl_SecureRecv,
        ssl_SecureSend,
        …
};
為何要有PRIOMethods和sslSocketOps兩個結構體呢?看起來一個就夠了,答案是為了實現分層模型,PRIOMethods並沒有具體的實現策略,只是表示一個“協議層”的語義,對於ssl的具體實現當然沒有必要在ssl層就全做掉,因為即使選擇ssl也可以有不同的策略,也就需要再來一個結構體了,特別是,這樣可以不加區分地統一使用一致的SSL介面,在不實現ssl的情況下,可以再實現一個ssl_default_ops(security/nss/lib/ssl/sslsock.c)。
     ssl_Do1stHandshake完成了ssl握手時的狀態機轉換,mozilla並不區分client方法和server方法,而是完全按照RFC2246的規定來實現ssl,對於client端,在ssl_SecureConnect中會發起對server的連線,也就是傳送一個ClientHello訊息,此時會將sslSocketStr的securityHandshake設定為ssl2_BeginClientHandshake(僅對ssl2來說),接下來任何的讀寫ssl,都會呼叫PRIOMethods中的recv/send函式,然後就回進入到ssl_Do1stHandshake狀態機,在其中client會呼叫到這個剛剛設定的ssl2_BeginClientHandshake函式,在該函式最後,按照RFC2246的規定,會進行如下設定:
ss->handshake     = ssl_GatherRecord1stHandshake;
ss->nextHandshake = ssl2_HandleServerHelloMessage;
握手回撥函式handshake自己維護狀態機,這一點和openssl不同,在handshake的呼叫最後,函式根據呼叫結果和當前狀態將nexthandshake按照RFC2246設定成下一個狀態,然後在ssl_Do1stHandshake將狀態機向前推進:
int ssl_Do1stHandshake(sslSocket *ss)
{
    do {
        if (ss->handshake == 0) {
            ss->handshake = ss->nextHandshake;
            ss->nextHandshake = 0;
        }
        if (ss->handshake == 0) {
                ss->handshake = ss->securityHandshake;
                ss->securityHandshake = 0;
        }
        …
        //握手完畢時要退出迴圈
        rv = (*ss->handshake)(ss); 
        ++loopCount;
        } while (rv != SECFailure);  
        …
}
ssl握手的後半部分會根據協商內容設定cipher,設定cipher也就部分初始化了sslSocketStr的sec欄位,在ssl2_CreateSessionCypher(由狀態處理函式ssl2_XYZSetupSessionCypher呼叫)確定ss->sec.enc函式指標PK11_CipherOp,從此,所有的資料傳輸都要經過加密,加密的方法就是PK11_CipherOp,mozilla通過封裝p11介面來統一處理安全通道的配置以及使用過程。
     PRFileDesc作為一個解耦層(很重要,不僅僅解除了各個模組耦合,而且還實現了一個分層的模型),將操作路由到sslSocketOps,而sslSocketOps中同樣可以根據ssl的不同狀態或者不同配置將操作繼續路由,比如在ssl_SecureSend中會呼叫:
rv = (*ss->sec.send)(ss, buf, len, flags);
可見sec欄位的send也是一個可變更的回撥函式,總體的過程就是:
首先ss->fd->methods->send[ssl_Send]:
rv = (*ss->ops->send)(ss, (const unsigned char*)buf, len, flags);[ssl_SecureSend]:
rv = (*ss->sec.send)(ss, buf, len, flags);[ssl2_SendBlock]:
ssl_DefSend.
最終ssl_DefSend將資料傳送了出去,通過socket將資料傳送了出去。

     上述的過程不是很難理解,而且實現的很合理,mozilla更合理的一個地方就是通過PKCS#11介面來統一封裝所有的安全操作,為何通過p11封裝呢?我覺得p11主要用於客戶端,解決了客戶端安全加密的一系列問題,而瀏覽器也在客戶端,因此使用p11會十分方便,雖然可能機器上沒有usbkey,但是軟實現的P11一樣好用,畢竟p11只是一個規範,p11的軟實現會呼叫mozilla安全框架的另一部分,那就是crypto介面(類似openssl的EVP)。



 本文轉自 dog250 51CTO部落格,原文連結:http://blog.51cto.com/dog250/1271800


相關文章