本节中我们快速浏览一下证书验证的主干代码。读者可以采用上节中生成的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)开始,顺藤摸瓜,找出真正的“幕后凶手”。这就是我们后面章节的主要思路。
在这之前,我们先顺便解决一个小问题。