openssl的证书链验证

简介:

使用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

相关文章
|
算法 网络安全 数据安全/隐私保护
使用 openssl 生成证书
一、openssl 简介 openssl 是目前最流行的 SSL 密码库工具,其提供了一个通用、健壮、功能完备的工具套件,用以支持SSL/TLS 协议的实现。官网:https://www.openssl.
8080 0
|
6月前
|
Java
使用jdk生成证书使用openssl来导出公钥信息
使用jdk生成证书使用openssl来导出公钥信息
28 0
|
6月前
openssl生成密钥
openssl生成密钥
69 0
|
算法 安全 网络安全
客户端如何验证ssl/tls证书的合法性
客户端是如何验证ssl/tls证书的合法性
511 1
|
Go
使用certstrap快速生成自签名证书
使用certstrap快速生成自签名证书
304 0
|
应用服务中间件 网络安全 开发工具
|
数据安全/隐私保护 存储 iOS开发
|
Java 应用服务中间件 数据安全/隐私保护
|
算法 安全 数据安全/隐私保护
|
Kubernetes 网络协议 前端开发