zoukankan      html  css  js  c++  java
  • 走读OpenSSL代码从一张奇怪的证书说起(七)

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

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

    View Code
    // 初始入口为 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<argc; i++) -- argc 为待验证证书个数
        check(cert_ctx,argv[i], untrusted, trusted, purpose, e); // 证书验证函数, argv[i] 指向当前要验证的证书
        // 原型: static int check(X509_STORE *ctx, char *file, STACK_OF(X509) *uchain, STACK_OF(X509) *tchain, int purpose, ENGINE *e)
        {
            X509 *x=NULL;
            int i=0,ret=0;
            X509_STORE_CTX *csc;
    
            x = load_cert(bio_err, file, FORMAT_PEM, NULL, e, "certificate file"); // 待验证证书转化为 X509 结构
            csc = X509_STORE_CTX_new(); // 创建证书库上下文结构 X509_STORE_CTX
            X509_STORE_CTX_init(csc,ctx,x,uchain) // 关联 X509_STORE(其 objs 成员包含 CA 证书)和待验证证书(X509_STORE_CTX.cert 成员)
            i=X509_verify_cert(csc); // 验证 -- 如果校验成功 打印 "OK\n", 否则 ERR_print_errors(bio_err);
        }
    }

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

    View Code
    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 对证书链进行验证,见下面的说明

    View Code
    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 及后续函数

    View Code
    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)开始,顺藤摸瓜,找出真正的“幕后凶手”。这就是我们后面章节的主要思路。
    在这之前,我们先顺便解决一个小问题。

  • 相关阅读:
    494. Target Sum 添加标点符号求和
    636. Exclusive Time of Functions 进程的执行时间
    714. Best Time to Buy and Sell Stock with Transaction Fee有交易费的买卖股票
    377. Combination Sum IV 返回符合目标和的组数
    325. Maximum Size Subarray Sum Equals k 和等于k的最长子数组
    275. H-Index II 递增排序后的论文引用量
    274. H-Index论文引用量
    RabbitMQ学习之HelloWorld(1)
    java之struts2的数据处理
    java之struts2的action的创建方式
  • 原文地址:https://www.cnblogs.com/efzju/p/2640909.html
Copyright © 2011-2022 走看看