使用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这个函数作了一点小小的优化,使得遍历的时候不用从一开始进行,而是从第一个名称匹配的证书开始。
本文转自 dog250 51CTO博客,原文链接:http://blog.51cto.com/dog250/1273300