使用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);
if (!ok) {
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;
int bad_chain = 0;
X509_VERIFY_PARAM *param = ctx->param;
int depth,i,ok=0;
int (*cb)(int xok,X509_STORE_CTX *xctx);
STACK_OF(X509) *sktmp=NULL;
list_e * le = NULL;
if (ctx->chain == NULL) {
ctx->chain=sk_X509_new_null());
sk_X509_push(ctx->chain,ctx->cert);
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));
lx->ptr = ctx->cert;
lx->next =NULL;
le->num ++;
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;
ok = get_isuser_all(&xtmp, ctx, x, le); //得到同一層次的所有的證書,而不是僅僅得到一個就傳回。
#else
ok = ctx->get_issuer(&xtmp, ctx, x); //僅僅得到一個“看似合理”的證書就傳回,而實際上這裡僅僅根據
CN名稱查找上級CA憑證
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);
ok = all_cert_verify (ctx, le);
if(!ok) goto end;
end:
int count = 0;
for(; count < le->num; count++) {
list_x *x = le->first[count];
while (x) {
free (x);
x = x->next;
free (le);
注釋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這個函數作了一點小小的優化,使得周遊的時候不用從一開始進行,而是從第一個名稱比對的證書開始。
本文轉自 dog250 51CTO部落格,原文連結:http://blog.51cto.com/dog250/1273300