openssl的憑證鏈結驗證

來源:互聯網
上載者:User

使用openssl驗證憑證鏈結可以用以下命令:
debian:/home/zhaoya/openssl#openssl verify -CAfile ROOT_CERT USER_CERT
其中的ROOT_CERT可以包含很多認證,可以用cat命令將多級的ca認證合并到一個檔案裡面,然後程式啟動以後會載入ROOT_CERT,ROOT_CERT會在記憶體中形成一個堆棧結構,各個認證的順序和檔案裡面的一樣。但是如果檔案中的諸多認證有若干個是同名的話,使用上述命令就會出現問題,為了改正這個不是問題的問題,還是查代碼吧。為何說這個問題不是問題呢,因為不提倡ca同名,或者即使可以同名,那也要可以通過別的欄位將其區分開來。不管怎麼說查代碼總不是壞事,起碼能對openssl有個新的理解。下面是這個混亂的憑證鏈結體繫結構,()括起來的是認證CN項的名字,而[]表示了頒發關係:
ROOT1(CN=ROOT)[SubCA1_1(CN=SubCA1_1)[User1_1(CN=User1_1)],SubCA1_2(CN=SubCA1_1)[User1_2(CN=User1_2)],SubCA1_3(CN=SubCA1_3)[User1_3(CN=User1_3)]]
ROOT2(CN=ROOT)[SubCA2_1(CN=SubCA1_1)[User2_1(CN=User2_1)],SubCA2_2(CN=SubCA2_2)[User2_2(CN=User2_2)]]
ROOT3(CN=ROOT3)[SubCA3_1(CN=SubCA3_1)[User3_1(CN=User3_1)]]

#ifdef ZHAOYA
#define MAX_DEPTH  6
struct list_entity {
    int num;
    struct list_x *first[MAX_DEPTH];
}
struct list_xit {
    void * ptr;
    struct list_xit *prev;
    struct list_xit *next;
}
//注釋0 (幹這行並且做底層的都會明白為何從0開始而不是1,正如有人數到9之後不是10,而是a一樣)
typedef struct list_entity list_e;
typedef struct list_xit list_x;
int get_isuser_all (X509 **issuer, X509_STORE_CTX *ctx, X509 *x, list_e * le)
{
    X509_NAME *xn;
    X509_OBJECT obj, *pobj;
    int i, ok, idx;
    xn=X509_get_issuer_name(x); //得到頒發者的名字
    idx = X509_OBJECT_idx_by_subject(ctx->ctx->objs, X509_LU_X509, xn);//注釋4
    X509 *xyz = NULL;
    for (i = idx; i < sk_X509_OBJECT_num(ctx->ctx->objs); i++)
    {
        pobj = sk_X509_OBJECT_value(ctx->ctx->objs, i);
        if (ctx->check_issued(ctx, x, pobj->data.x509))
        {    //得到同一層級的ca中所有名稱匹配的ca認證,依次加入到鏈表中
            xyz = pobj->data.x509;
            list_x * lx = (list_x*)calloc(sizeof(list_x));
            lx->ptr = pobj->data.x509;
            list_x * header = le->first[le->num];
            lx->next = header;
            le->first[le->num] = lx;
        }
    }   
    if (xyz) {
        le->num ++;
        *issuer = xyz;
        return 1;
    }
    return 0;
}
int vfy(X509_STORE_CTX *ctx, X509 * xi, X509 * xs)
{
    EVP_PKEY *pkey=NULL;
    int ok;
    int (*cb)(int xok,X509_STORE_CTX *xctx); //注釋3
    cb=ctx->verify_cb;
    if (xs->valid) goto last;
    if ((pkey=X509_get_pubkey(xi)) == NULL) {
        ctx->error=X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY;
        ctx->current_cert=xi;
        ok=(*cb)(0,ctx);
        if (!ok)
            return -1;
    } else if (X509_verify(xs,pkey) <= 0) {
        ctx->error=X509_V_ERR_CERT_SIGNATURE_FAILURE;
        ctx->current_cert=xs;
        EVP_PKEY_free(pkey); 
        ok=(*cb)(0,ctx);
        if (!ok) {
            return -1;
        }
    }
    xs->valid = 1;
last:
    ok = check_cert_time(ctx, xs);
    if (!ok)
        return -1;
    ctx->current_issuer=xi;
    ctx->current_cert=xs;
    ok = (*cb)(1,ctx);
    EVP_PKEY_free(pkey);
    return ok ? 1:-1;
}
int all_cert_verify(X509_STORE_CTX *ctx, list_e * le)
{
    int ok=0,;
    X509 *xs,*xi;
    ctx->error_depth=n-1;
    list_x * lx =  le->first[0];
    xs = lx->ptr;
    int co = 1, res;
    for (; co < le->num; co++) {
        ctx->error_depth = co;
        list_x *lx =  le->first[co];
        while (lx) {
            xi = lx->ptr;
            res = vfy(ctx, xi, xs);
            if (res ==1) {
                xs = xi;
                break;
            } else if (res == -1 && ctx->error != X509_V_ERR_CERT_SIGNATURE_FAILURE)
                return 0;  //注意如果是由於非簽名錯誤引發的別的錯誤的話,直接退出
            lx = lx->next;
        }
        if (lx == NULL) { //如果同一層沒有任何認證能驗證下層的認證,那麼直接退出,依據是憑證鏈結是單路徑的
            ok = 0;
            break;
        }
    }
    if (ctx->check_issued(ctx, xi, xi) && co == le->num) { //最後驗證根憑證,也就是一個自簽名的認證
        ok = vfy(ctx, xi, xi);
    }   
    return ok;
}
#endif

int X509_verify_cert(X509_STORE_CTX *ctx)
{
    X509 *x,*xtmp,*chain_ss=NULL;
    X509_NAME *xn;
    int bad_chain = 0;
    X509_VERIFY_PARAM *param = ctx->param;
    int depth,i,ok=0;
    int num;
    int (*cb)(int xok,X509_STORE_CTX *xctx);
    STACK_OF(X509) *sktmp=NULL;
#ifdef ZHAOYA
    list_e    * le = NULL;
#endif
    cb=ctx->verify_cb;
    if (ctx->chain == NULL) {
            ctx->chain=sk_X509_new_null());    
            sk_X509_push(ctx->chain,ctx->cert);
#ifdef ZHAOYA
            le = (list_e*)calloc(sizeof(list_e));
            le->num = 0;
            list_x * lx = (list_x*)malloc(sizeof(list_x));
            memset(lx, 0, sizeof(list_x));
            le->first[le->num] = lx;
            lx->ptr = ctx->cert;
            lx->next =NULL;
            le->num ++;
#endif
        CRYPTO_add(&ctx->cert->references,1,CRYPTO_LOCK_X509);
        ctx->last_untrusted=1;
    }
    if (ctx->untrusted != NULL
        && (sktmp=sk_X509_dup(ctx->untrusted)) == NULL){
        ...
    }
    num=sk_X509_num(ctx->chain);
    x=sk_X509_value(ctx->chain,num-1);
    depth=param->depth;
    for (;;) {  //注釋1
        if (depth < num) break;
        xn=X509_get_issuer_name(x);
        if (ctx->check_issued(ctx, x,x)) break;
        if (ctx->untrusted != NULL) {
            xtmp=find_issuer(ctx, sktmp,x);
            if (xtmp != NULL) {
                if (!sk_X509_push(ctx->chain,xtmp)){...
                }
                CRYPTO_add(&xtmp->references,1,CRYPTO_LOCK_X509);
                sk_X509_delete_ptr(sktmp,xtmp);
                ctx->last_untrusted++;
                x=xtmp;
                num++;
                continue;
            }
        }
        break;
    }
    i=sk_X509_num(ctx->chain);  //得到當前構造的憑證鏈結中認證的個數
    x=sk_X509_value(ctx->chain,i-1);  //取得當前構造的憑證鏈結最上面的認證
    xn = X509_get_subject_name(x);
    if (ctx->check_issued(ctx, x, x)) {
        if (sk_X509_num(ctx->chain) == 1) {
            //最上面的認證是自簽名的情況,該情況下不用繼續在CA集合中尋找它的頒發者,因為不可能找到,它是自簽發的。但是代碼還是要走到下面的internal_verify裡面的。
        }
    }
    //以下的for迴圈在認證的集合當中按照從下到上的順序根據isuser欄位尋找認證
    int co = 0;
    for (;;) {
        co += 1;
        if (depth < num) break;
        if (ctx->check_issued(ctx,x,x))
                 break;
#ifdef ZHAOYA
        ok = get_isuser_all(&xtmp, ctx, x, le);  //得到同一層次的所有的認證,而不是僅僅得到一個就返回。
#else
        ok = ctx->get_issuer(&xtmp, ctx, x); //僅僅得到一個“看似合理”的認證就返回,而實際上這裡僅僅根據

CN名稱尋找上級CA認證
#endif
        if (ok < 0) return ok;
        if (ok == 0) break;
        x = xtmp; //層層向上尋找,直到一個自簽名的根為止。
        if (!sk_X509_push(ctx->chain,x)) {
            X509_free(xtmp);
            X509err(X509_F_X509_VERIFY_CERT,ERR_R_MALLOC_FAILURE);
            return 0;
        }
        num++;
    }
    xn=X509_get_issuer_name(x);
    if (!ctx->check_issued(ctx,x,x)) {  //察看最上層的認證是否是自簽名的認證
        if ((chain_ss == NULL) || !ctx->check_issued(ctx, x, chain_ss)) {
            if (ctx->last_untrusted >= num)
                ctx->error=X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY;
            else
                ctx->error=X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT;
            ctx->current_cert=x;
        } else {
            sk_X509_push(ctx->chain,chain_ss);
            num++;
            ctx->last_untrusted=num;
            ctx->current_cert=chain_ss;
            ctx->error=X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN;
            chain_ss=NULL;
        }
        ctx->error_depth=num-1;
        bad_chain = 1;
        ok=cb(0,ctx);
        if (!ok) goto end;
    }
#ifndef ZHAOYA  //注釋2
    if (ctx->verify != NULL){
        ok=ctx->verify(ctx);
    else
        ok=internal_verify(ctx);
#else
        ok = all_cert_verify (ctx, le);
#endif
    if(!ok) goto end;
end:
#ifdef ZHAOYA
    int count = 0;
    for(; count < le->num; count++) {
        list_x *x = le->first[count];
        while (x) {
            free (x);
            x = x->next;
        }
    }
    free (le);
#endif
    return ok;
}
注釋0:憑證鏈結的擴充資料結構,設計得很粗糙,更好的設計方式是使用linux核心中的hlist_head這種嵌入式鏈表,之所以用hlist而不是list_head是因為前者是一個吊鏈結構,而從本地記憶體或者磁碟載入的認證邏輯上也是一個吊鏈結構,如果認證的名字分別是1,2,3,這三個名字的認證的個數分別是2,3,1,那麼記憶體中的認證可以按照如下的順序排列:112223,也可以123221,...雖然物理上很無序,但是邏輯上卻是吊鏈,主鏈上有3個元素,分別代表3個名字的認證,吊鏈上是名字重複的認證,類似雜湊衝突鏈表,於是憑證鏈結可以採用hlist的方式組織。
注釋1:優先使用傳入的憑證鏈結,這麼做可以優先排除諸如黑名單裡面的認證。實際上就是最不被信任的認證最優先處理,一般而言,這裡的untrusted鏈表裡面的認證不是本地的認證,而大多是從遠端傳來的認證,比如ssl中伺服器驗證用戶端認證,然而用戶端認證一般都是N級CA頒發的,於是如果伺服器端沒有用戶端憑證簽發者的CA認證的話,那麼驗證就會失敗,於是用戶端不僅僅將自己的用戶端認證傳給伺服器,它的認證的頒發者CA認證也一併傳遞過去,很顯然,這個N級CA的認證對於伺服器來說是不被信任的,優先使用這個認證可以優先拒絕惡意攻擊,這裡的關係有點微妙,如果伺服器端有這個N級CA認證,而這個用戶端僅僅想惡作劇而修改了這個N級認證,然後連同自己的用戶端認證一併發給了伺服器,那麼伺服器會發現這個惡作劇並且讓用戶端付出代價。這個untrusted鏈稍微正常些的用途是排除黑名單,還是上面的那個例子,如果用戶端認證的頒發者CA因為某原因被列入了黑名單,這件事並沒有通知用戶端,用戶端也沒有主動查詢,而伺服器知道了這件事,那麼伺服器端就會存在一個黑名單鏈表,用戶端憑證簽發者的CA認證就會在其中,當驗證用戶端認證的時候,伺服器端憑證鏈結載入順序為黑名單的認證優於用戶端傳來的CA認證,用戶端傳來的CA認證優於伺服器本地的被信任的認證,於是客戶段的CA認證在黑名單中這件事就會首先被探測到。以上僅僅是兩個例子,untrusted鏈表的本質類似於ACL,先匹配拒絕的再匹配接受的。

注釋2:這裡是常規驗證法,如果不是由於不能修改結構體和函數形參,完全可以不用宏來進行分支管理,這其實是為了保證二進位相容性而退一步的做法,list_e指標完全可以放入到X509_STORE_CTX結構體中,這樣的話,就可以實現一個verify回呼函數來管理,也就不用先行編譯宏來判斷了。all_cert_verify實現了從下到上的驗證邏輯,而internal_verify卻是單條鏈表的自上而下的驗證,注意僅僅是單條憑證鏈結,這樣可能會使一些認證在存在同名ca的情況下永遠失去被驗證通過的機會,而實際上是可以被同名ca中的某一個驗證通過的,具體能否被同名的ca驗證通過取決於這些同名ca的載入順序,一個解決方案就是在實際驗證的時候遍曆所有的被載入的認證,這樣就不存在構造鏈的操作了,我這裡沒有實現,那樣的話代碼量會少很多,當然遍曆的時候時間複雜度也會增加,演算法實現會在後面給出,一個更合理的解決方案是我寫的all_cert_verify,可以看到裡面實際上並沒有用到ctx->chain這個堆棧,之所以這麼寫是為了更少的改動原有的代碼,否則的話,直接刪除關於ctx->chain的所有代碼即可。all_cert_verify實現的還是很單純,我相信有很多異常情況沒有處理,給出的僅僅是演算法的思想罷了。另外在構造list_e的時候也沒有處理untrusted鏈表,不過我相信加上是很簡單的。
注釋3:如果簽名驗證失敗了,那麼就調用ctx的verify_cb函數,如果回呼函數的提供者不關心這個錯誤,那麼就可能清除該錯誤進而使驗證繼續下去,反之如果簽名驗證成功,那麼回呼函數的提供者也可能用自己的策略終止驗證,同樣也在回呼函數中得到體現。回呼函數的第一個參數是最近的結果值,內部實現中你可以隨意設定ctx的error值以及是否可以忽略這個error,如果可以忽略,那麼就清空ctx的error然後回呼函數返回1即可,當然也可以根據ctx的當前設定,在第一個參數為1的情況下,設定ctx的error,然後返回0,終止這次驗證操作。
注釋4:X509_OBJECT_idx_by_subject這個函數作了一點小小的最佳化,使得遍曆的時候不用從一開始進行,而是從第一個名稱匹配的認證開始。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.