天天看點

ssl證書鍊的驗證的其它方式

ssl 證書鍊的驗證主要分為兩種方式,自上而下和自下而上,其中自上而下又可以分為兩種方式,其中一種就是 openssl 的實作方式,也就是《 openssl 的證書鍊的驗證 》中介紹的其中一種方式,另一種是自上而下與自下而上結合的方式;在自下而上的驗證方式中也可以分為兩種方式,一種是平坦周遊的方式,另一種是分級别的周遊方式。鑒于自上而下的單鍊方式和基于等級的自下而上的方式已經在《 openssl 的證書鍊的驗證 》中有所介紹,本文僅介紹餘下來的另外兩種方式,首先看看自上而下與自下而上結合的方式,下圖為一個 CA 體系的真實邏輯結構:

root[a1[b1[c1],b2,b3[c2],b4],a2[b5],a3[b6,b7[c3]]]

下面是 openssl 架構将上述結構載入記憶體的實際結構,可以看出,具體的頒發關系已經沒有了,隻剩下了一個隻有級别的吊鍊結構,我們要做的就是在這個吊鍊結構中周遊,而且是自上而下的周遊,尋找一切有可能的驗證路徑,這就涉及到了圖算法,也就是圖的周遊算法,但是實際上這個圖并沒有想象的那麼複雜,而可以被看做是一棵樹,具體的算法見下面的僞代碼和文字解釋:

root

a1-a2-a3

b1-b2-b3-b4-b5-b6-b7

c1-c2-c3

僞代碼如下,如不具體,請指導或添加,可以道明的是,肯定不完整,我真心希望有志同道合的家夥和我一起探讨linux核心或者openssl的點點滴滴:

Root->A1->B1->C2(failed)

                B2->C2(failed)

                …. (可能導緻複雜的回溯算法,見下面的解釋)

          B3->C2(right)

Get upper level and set to A

Set B3 to To_cert

RETRY:

For-each in A as a

       If a can verify To_cert

              Get upper level and set to A

Set a to To_cert

RETRY.

If all upper failed

       Failed!

首先從 root 依次往下驗證,每一層按照從連結清單頭到尾的順序,直到驗證失敗,然後回到驗證方的兄弟,将之作為新的驗證方重新驗證被驗證方,如果所有兄弟都已用盡,也就是說全部驗證失敗,那麼被驗證方做上标記,廢棄之,并且回到全軍覆沒的兄弟的再上一層的已經參加過驗證的兄弟,然後将之作為新的驗證方,重新開始驗證全軍覆沒的兄弟的第一個,依次類推!注意有個例外情況,那就是驗證最下層使用者證書的時候,因為使用者證書隻有一個,并且是确定的一個,那麼如果上層的所有兄弟都驗證失敗,就可以證明結果的失敗,沒有必要往上繼續回溯了,否則就要回溯。隻要在回溯的過程中有驗證正确的,那麼就要開始向上驗證,一直到根,如果到不了根,那麼這條回溯路徑上的所有的證書将被做上标記,以後不再參與驗證,全部廢棄,因為一個證書隻能由一個 ca 頒發,是以從 root 到使用者證書的路徑如果有的話隻有一條,這個看似複雜的基于圖的回溯算法實際上很少有需要完全周遊的情況。  

       最後一種方式就是平坦的驗證方式,就是針對每個證書,都要盡可能周遊store中的所有證書進行驗證,代碼如下,比較簡單,但是對于用int類型做depth來說,最大的證書鍊深度也就限定為了32

static int OpenSSL_VerifyChain(X509 **chain, int n, X509 *cert)

{

        int i;

        unsigned long flag = 0;

        int rv = 0;

        X509_NAME *name1, *name2;

        EVP_PKEY *pkey;

...

        while(1) {

                if(i == n)

                        return -rv;

                name1 = X509_get_issuer_name(cert);

                name2 = X509_get_subject_name(cert);

                if(OpenSSL_X509NameEqual(name1,name2)) {

                        pkey = X509_get_pubkey(cert);

                        if(!X509_verify(cert, pkey))

                                return -rv;

                        return rv;

                }

                for(i = 0; i < n; i++) {

                        if(flag & (1<<i)) //跳過已經驗證過的證書

                                continue;

                        name2 = X509_get_subject_name(*(chain+i));

                        if(OpenSSL_X509NameEqual(name1,name2)){

                                if(!OpenSSL_CertValidity(*(chain+i)))

                                        return -rv;

                                pkey = X509_get_pubkey(*(chain+i));

                                if(!X509_verify(cert, pkey))

            //一個位作為索引,以該索引取得的證書以後不用再參與驗證了,已經完成關于它的驗證了

                                flag |= 1<<i; 

                                cert = *(chain+i); //繼續下一個

                                break;

                        }

                rv++;

    }

}

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

繼續閱讀