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;
}
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