博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
openssl 证书验证
阅读量:4167 次
发布时间:2019-05-26

本文共 11241 字,大约阅读时间需要 37 分钟。

本节中我们快速浏览一下证书验证的主干代码。读者可以采用上节中生成的VC工程进行验证。

下面列出关键部分代码,为方便阅读,仅保留与证书验证强相关的代码,去掉了诸如变量定义、错误处理、资源释放等非主要代码,并修改了排版格式。

复制代码

// 初始入口为 apps\verify.c 中的 MAIN 函数// 为利于代码阅读,下面尝试将相关代码放在一起(采用函数调用栈的形式,被调用函数的代码排版缩进一层),希望能讲得更为清楚int MAIN(int argc, char **argv){    X509_STORE *cert_ctx=NULL;    X509_LOOKUP *lookup=NULL;    cert_ctx=X509_STORE_new(); // 创建 X509 证书库    // 解析命令行参数    // 创建 X509_LOOKUP, 该结构的 store_ctx 成员关联刚刚创建的证书库 cert_ctx    lookup=X509_STORE_add_lookup(cert_ctx,X509_LOOKUP_file());    // 省去前行的 if (CAfile)    i=X509_LOOKUP_load_file(lookup,CAfile,X509_FILETYPE_PEM); // 所有证书都是 PEM 格式    // 实际上是宏 -- #define X509_LOOKUP_load_file(x,name,type) X509_LOOKUP_ctrl((x),X509_L_FILE_LOAD,(name),(long)(type),NULL)    // 原型: int X509_LOOKUP_ctrl(X509_LOOKUP *ctx, int cmd, const char *argc, long argl, char **ret) -- 解析CA文件, 一个文件中可以包含多个CA证书    {        return ctx->method->ctrl(ctx,cmd,argc,argl,ret);        // 函数指针实际指向 by_file_ctrl 函数        // 原型: static int by_file_ctrl(X509_LOOKUP *ctx, int cmd, const char *argp, long argl, char **ret)        {            ok = (X509_load_cert_crl_file(ctx,argp,X509_FILETYPE_PEM) != 0);            // 原型: int X509_load_cert_crl_file(X509_LOOKUP *ctx, const char *file, int type)            {                STACK_OF(X509_INFO) *inf;                X509_INFO *itmp;                BIO *in;                int i, count = 0;                in = BIO_new_file(file, "r");                inf = PEM_X509_INFO_read_bio(in, NULL, NULL, NULL); // 创建 STACK_OF(X509_INFO), 以文件中 CA 证书的出现顺序压栈                // 原型: STACK_OF(X509_INFO) *PEM_X509_INFO_read_bio(BIO *bp, STACK_OF(X509_INFO) *sk, pem_password_cb *cb, void *u)                {                    X509_INFO *xi=NULL;                    STACK_OF(X509_INFO) *ret=NULL;                    ret = sk_X509_INFO_new_null() // 创建 X509_INFO 证书栈 ret                    xi = X509_INFO_new() // 创建 X509_INFO, 其成员 xi->x509 == NULL, 为进入下面的 for 循环作准备                    for(;;)                    {                        i = PEM_read_bio(bp,&name,&header,&data,&len); // 从 PEM 文件读证书, 一次读入一个证书(文件中可以包含多个证书)                    start:                        // 省略其他不相关的 if 分支                        if (   (strcmp(name,PEM_STRING_X509) == 0)                            || (strcmp(name,PEM_STRING_X509_OLD) == 0)                        ) // 发现是证书类型: name == "BEGIN CERTIFICATE", 来自 -----BEGIN CERTIFICATE-----                        {                            d2i=(D2I_OF(void))d2i_X509; // 证书信息内部转换格式函数 d2i_X509                            if (xi->x509 != NULL) // 上一次循环中已解析过证书,首次调用 PEM_read_bio 则不进入                            {                                sk_X509_INFO_push(ret,xi) // 将已解析的证书信息压栈 ret                                xi=X509_INFO_new() // 重新分配 X509_INFO,为后面的 d2i 调用做准备                                goto start; // 跳转回去重新执行                            }                            pp=&(xi->x509); // 设置出参                        }                        ...... // 省略不相关代码                        if (d2i != NULL)                        {                            p=data;                            d2i(pp,&p,len) // 调用 d2i_X509 将证书信息转化为内部格式(结果放在 xi->x509 中)                        }                        ......                    }                    sk_X509_INFO_push(ret,xi) // 最后一个证书压栈 ret                    ok=1;                    ......                    return(ret); // 返回 CA 证书栈                }                for(i = 0; i < sk_X509_INFO_num(inf); i++) { // 将栈中的证书加入到 X509_STORE                    itmp = sk_X509_INFO_value(inf, i);                    if(itmp->x509) {                        X509_STORE_add_cert(ctx->store_ctx, itmp->x509);                        // 将 X509_INFO 中的 X509 用 X509_OBJECT 形式封装,压栈到 X509_STORE 的成员 objs                        count++;                    }                }                return count; // 返回从 const char *file 读出的证书个数            }        }    }    // 省去 for (i=0; i

复制代码

验证证书的重任落在函数 X509_verify_cert 身上,我们单独把该函数拎出来,进行讲解。

复制代码

int X509_verify_cert(X509_STORE_CTX *ctx){    sk_X509_push(ctx->chain,ctx->cert);    // ctx->cert 作为不信任证书压入 ctx->chain    // STACK_OF(X509) *chain 将被构造为证书链, 并最终送到 internal_verify() 中去验证, 链内容如下    //   data[0] -- 待验证证书 ctx->cert          位置称呼    //   data[1] -- 签发 data[0] 的上级 CA 证书   证书链链首(最底端)    //   data[2] -- 签发 data[1] 的上级 CA 证书    //   ...    //   data[n] -- 自签名证书(根 CA 证书)        证书链链尾(最顶端)(最后一张)    if (ctx->untrusted != NULL) // 如果有不信任证书列表(例如在 SSL 连接中获取的对端证书), 复制一份        sktmp=sk_X509_dup(ctx->untrusted); // verify 命令后跟的 -untrusted 参数也会填充 ctx->untrusted    num=sk_X509_num(ctx->chain); // num == 1    x=sk_X509_value(ctx->chain,num-1); // 取出被验证证书(位于 chain 证书链链首)    for (;;) // 如果有不信任证书列表, 继续构造(延长) chain 证书链 -- 不信任证书1, 不信任证书2 ...    {        if (ctx->check_issued(ctx, x,x)) break; // 当前证书是自签名证书(已到达证书链最顶端), 退出        if (ctx->untrusted != NULL) // 存在不信任证书列表        {            xtmp=find_issuer(ctx, sktmp,x); // 当前证书是否由 不信任证书列表中的证书 颁发            if (xtmp != NULL) // 当前证书 是由 不信任证书(CA证书) 颁发            {                sk_X509_push(ctx->chain,xtmp); // 将不信任的 CA 证书加入 chain 证书链                x=xtmp; // 置 CA 证书为当前证书                continue; // 下一轮循环            }        }        break; // 不存在不信任证书列表 或 存在不信任证书列表但 chain 证书链增长到顶(找不到上级 CA)    }    // 检查 chain 证书链的最顶端证书    i=sk_X509_num(ctx->chain);    x=sk_X509_value(ctx->chain,i-1);    if (ctx->check_issued(ctx, x, x)) // 最顶端证书是自签名证书    {        if (sk_X509_num(ctx->chain) == 1) // 证书链中只有一张证书, 此时只能是被验证证书, 而且是自签名证书        {            ok = ctx->get_issuer(&xtmp, ctx, x); // 在信任证书列表 X509_STORE *ctx 中查找证书 xtmp, 满足: xtmp 签发 被验证的自签名证书            if ((ok <= 0) || X509_cmp(x, xtmp))  // 没找到(ok <= 0) 或者 虽然找到但 xtmp 与 x 不是同一本证书            {                // 失败回调函数 -- self signed certificate            }            else            {                x = xtmp;                sk_X509_set(ctx->chain, i - 1, x); // 用信任证书替换被验证证书(实际为同一张证书)            }        }        else // 证书链的最顶端是自签名证书 且 证书链长度>1, 剔除自签名证书 -- 不相信对方传来的自签名证书        {            chain_ss=sk_X509_pop(ctx->chain); // 弹出自签名证书            ctx->last_untrusted--;            num--;            x=sk_X509_value(ctx->chain,num-1); // x 是弹出后证书链上的最顶端证书        }    }    // 利用信任证书列表, 继续构建 chain 证书链 -- 不信任证书1 ... 不信任证书n, 信任证书1, 信任证书2 ...    for (;;) // x 指向当前待验证证书    {        if (ctx->check_issued(ctx,x,x)) break; // x 是自签名证书, 退出循环        ok = ctx->get_issuer(&xtmp, ctx, x); // 在 信任证书列表中 查找 x 的上级 CA 证书        if (ok < 0) return ok; // 出错,返回        if (ok == 0) break; // 没找到,退出循环        x = xtmp; // 上级 CA 证书设置为当前证书        sk_X509_push(ctx->chain,x) // 上级 CA 证书压栈    }    // 检查 chain 是否构成一条完整的证书链 -- x 是证书链最后一张证书    if (!ctx->check_issued(ctx,x,x)) // 证书链不完整 -- x 不是自签名证书    {        if ((chain_ss == NULL) || !ctx->check_issued(ctx, x, chain_ss))        {   // [剔除的自签名证书 chain_ss 不存在] 或 [chain_ss 存在但未签发证书 x] -- chain_ss 见上面注释            // 这两种情况都导致 chain 无法构成完整的证书链            if (ctx->last_untrusted >= num) // ctx->last_untrusted -- 链中不信任证书总数, num -- 链中证书总数(信任与不信任总和)                // 错误信息: unable to get local issuer certificate -- 命令[openssl verify openssl.cert.verify.error.pem]将走到此处            else                // 错误信息: unable to get issuer certificate                // 如果证书链结构为: 根CA-->subca.pem-->user.pem, 则命令[openssl verify -CAfile subca.pem user.pem]将走到此处        }        else // 剔除的自签名证书 chain_ss 签发了证书 x        {          sk_X509_push(ctx->chain,chain_ss); // chain_ss 重新加到链中, 错误信息: self signed certificate in certificate chain          // 命令[openssl verify -untrusted cacert.pem openssl.cert.verify.error.pem]将走到此处        }        ok=cb(0,ctx); // 错误回调函数    }    // 现在已经拥有完整的证书链, 作进一步的检查    check_chain_extensions(ctx);    check_trust(ctx);    X509_get_pubkey_parameters(NULL,ctx->chain);    ctx->check_revocation(ctx);    // 前面的工作是构造证书链, 下面开始验证证书链    if (ctx->verify != NULL)        ok=ctx->verify(ctx); // 实际上调用的是 internal_verify    else        ok=internal_verify(ctx); // internal_verify 验证 ctx->chain 证书链    ......}

复制代码

函数 X509_verify_cert 的一大功能是构造证书链(被验证证书 <-- 不信任证书列表 <-- 信任证书列表)

构造完成后, X509_verify_cert 调用函数 internal_verify 对证书链进行验证,见下面的说明

复制代码

static int internal_verify(X509_STORE_CTX *ctx) // 验证证书链 ctx->chain{    n=sk_X509_num(ctx->chain); // chain 是证书堆栈(证书链)    n--;                       // 索引从小到大顺序: 被验证证书-->根 CA(自签名)证书    xi=sk_X509_value(ctx->chain,n);    if (ctx->check_issued(ctx, xi, xi)) // 最顶端为根 CA 证书        xs=xi; // 自签名证书: Subject == Issuer    else // 按理说不会走到 else 分支    {    // 因为调用者 X509_verify_cert 已经保证: if (!ctx->check_issued(ctx,x,x))        if (n <= 0)        {            // 出错处理: 无法验证叶子证书        }        else        {            n--; // 取下级证书            xs=sk_X509_value(ctx->chain,n);        }    }    while (n >= 0) // 从证书链最顶端开始, 逐层向下验证, 直到链终端的用户证书    {        if (!xs->valid) // 如果当前证书未经验证        {            pkey=X509_get_pubkey(xi) // 取得颁发者证书的公钥            X509_verify(xs,pkey) // 用公钥验证证书 -- 如果验证失败将返回 0        }        xs->valid = 1; // 验证通过后打上标记        ok = check_cert_time(ctx, xs); // 检查有效时间        n--;        if (n >= 0)        {            xi=xs; // 当前验证通过的证书作为上级 CA 证书            xs=sk_X509_value(ctx->chain,n); // 下级证书作为被验证证书        }    }    ok=1;end:    return ok;}

复制代码

压力继续传递给函数 X509_verify 及后续函数

复制代码

int X509_verify(X509 *a, EVP_PKEY *r){   // 用颁发者公钥 r 验证证书信息 a->cert_info 对应签名 a->signature 的合法性    return(ASN1_item_verify(ASN1_ITEM_rptr(X509_CINF),a->sig_alg,        a->signature,a->cert_info,r));}int ASN1_item_verify(const ASN1_ITEM *it, X509_ALGOR *a, ASN1_BIT_STRING *signature,       void *asn, EVP_PKEY *pkey){    // 由签名算法 X509_ALGOR 得到 HASH 类型并初始化 EVP_MD_CTX    EVP_MD_CTX_init(&ctx);    i=OBJ_obj2nid(a->algorithm);    type=EVP_get_digestbyname(OBJ_nid2sn(i));    EVP_VerifyInit_ex(&ctx,type, NULL)    //【恢复出 X509_CINF(即证书的 tbsCertificate)的 ASN1 编码, 但发现出错】    inl = ASN1_item_i2d(asn, &buf_in, it);    EVP_VerifyUpdate(&ctx,(unsigned char *)buf_in,inl); // 喂入 tbsCertificate    EVP_VerifyFinal(&ctx,(unsigned char *)signature->data, (unsigned int)signature->length,pkey); // 继续验证}int EVP_VerifyFinal(EVP_MD_CTX *ctx, const unsigned char *sigbuf,       unsigned int siglen, EVP_PKEY *pkey){    EVP_MD_CTX_init(&tmp_ctx); // 验证前准备: 计算 HASH(tbsCertificate)    EVP_MD_CTX_copy_ex(&tmp_ctx,ctx);    EVP_DigestFinal_ex(&tmp_ctx,&(m[0]),&m_len);    return(ctx->digest->verify(ctx->digest->type,m,m_len,        sigbuf,siglen,pkey->pkey.ptr)); // 实际调用 RSA_verify 完成最后一击}int RSA_verify(int dtype, const unsigned char *m, unsigned int m_len, // 进行 RSA 签名验证       unsigned char *sigbuf, unsigned int siglen, RSA *rsa){    // 用公钥还原得到 signatureValue^e (并去掉 PKCS1 PADDING)    i=RSA_public_decrypt((int)siglen,sigbuf,s,rsa,RSA_PKCS1_PADDING);    const unsigned char *p=s;    sig=d2i_X509_SIG(NULL,&p,(long)i); // X509_SIG 的内部表示    // 比较 signatureValue^e 和 HASH(tbsCertificate)    if ( ((unsigned int)sig->digest->length != m_len) ||         (memcmp(m,sig->digest->data,m_len) != 0    )       )    {        RSAerr(RSA_F_RSA_VERIFY,RSA_R_BAD_SIGNATURE);    }    else // 验证成功        ret=1;}

复制代码

到此为止,所有主要函数流程讲解完毕。与此同时,最大的嫌疑还未解开:到底是什么原因导致证书验证出错?

在继续之前,我们总结一下 OpenSSL 的证书验证步骤

(1) 将证书内容从文件中读出,并转换保存在内部数据结构中(见代码中的 d2i_X509 函数)
(2) 将证书内部数据的 X509_CINF(tbsCertificate) 部分转换为 DER 编码格式(见代码中的 ASN1_item_i2d 函数)
(3) 依据证书验证公式进行校验

由于在步骤(2)后就发现了错误,因此只能有两种可能:步骤(1)出错 或者 步骤(2)出错。

正常的逻辑就是从步骤(1)开始,顺藤摸瓜,找出真正的“幕后凶手”。这就是我们后面章节的主要思路。

在这之前,我们先顺便解决一个小问题。

转载地址:http://ktrxi.baihongyu.com/

你可能感兴趣的文章
《python基础教程》答案(第四章)
查看>>
2018.5.53
查看>>
2018.5.54
查看>>
2018.5.55
查看>>
2018.5.58
查看>>
2018.12.5
查看>>
2018.12.6
查看>>
人智导(四):约束满足问题
查看>>
2018.12.7
查看>>
2018.12.8
查看>>
2018.12.9
查看>>
2018.12.29
查看>>
2018.12.31
查看>>
2019.1.2
查看>>
2019.1.4
查看>>
2019.1.9
查看>>
2019.1.12
查看>>
Java语言程序设计与数据结构》编程练习答案(第二十章)(二)
查看>>
2019.2.25
查看>>
2019.2.26
查看>>