主要分析了openssl 如何验证证书的有效性。对源码的分析以注释的方式给出。
此方法主要是对用户传入的参数进行分析,组装验证参数
int verify_main(int argc, char **argv)
{
ENGINE *e = NULL;
STACK_OF(X509) *untrusted = NULL, *trusted = NULL;
STACK_OF(X509_CRL) *crls = NULL;
X509_STORE *store = NULL;
X509_VERIFY_PARAM *vpm = NULL;
const char *prog, *CApath = NULL, *CAfile = NULL;
int noCApath = 0, noCAfile = 0;
int vpmtouched = 0, crl_download = 0, show_chain = 0, i = 0, ret = 1;
OPTION_CHOICE o;
if ((vpm = X509_VERIFY_PARAM_new()) == NULL)
goto end;
prog = opt_init(argc, argv, verify_options);
while ((o = opt_next()) != OPT_EOF) {
switch (o) {
case OPT_EOF:
case OPT_ERR:
BIO_printf(bio_err, "%s: Use -help for summary.\n", prog);
goto end;
case OPT_HELP:
opt_help(verify_options);
BIO_printf(bio_err, "Recognized usages:\n");
for (i = 0; i < X509_PURPOSE_get_count(); i++) {
X509_PURPOSE *ptmp;
ptmp = X509_PURPOSE_get0(i);
BIO_printf(bio_err, "\t%-10s\t%s\n",
X509_PURPOSE_get0_sname(ptmp),
X509_PURPOSE_get0_name(ptmp));
}
BIO_printf(bio_err, "Recognized verify names:\n");
for (i = 0; i < X509_VERIFY_PARAM_get_count(); i++) {
const X509_VERIFY_PARAM *vptmp;
vptmp = X509_VERIFY_PARAM_get0(i);
BIO_printf(bio_err, "\t%-10s\n",
X509_VERIFY_PARAM_get0_name(vptmp));
}
ret = 0;
goto end;
case OPT_V_CASES:
//NOTE:初始化验证参数
if (!opt_verify(o, vpm))
goto end;
vpmtouched++;
break;
case OPT_CAPATH:
CApath = opt_arg();
break;
case OPT_CAFILE:
CAfile = opt_arg();
break;
case OPT_NOCAPATH:
noCApath = 1;
break;
case OPT_NOCAFILE:
noCAfile = 1;
break;
case OPT_UNTRUSTED:
/* Zero or more times */
//加载受信任的证书
if (!load_certs(opt_arg(), &untrusted, FORMAT_PEM, NULL,
"untrusted certificates"))
goto end;
break;
case OPT_TRUSTED:
/* Zero or more times */
noCAfile = 1;
noCApath = 1;
//加载不收信人的证书
if (!load_certs(opt_arg(), &trusted, FORMAT_PEM, NULL,
"trusted certificates"))
goto end;
break;
case OPT_CRLFILE:
/* Zero or more times */
//加载吊销列表
if (!load_crls(opt_arg(), &crls, FORMAT_PEM, NULL,
"other CRLs"))
goto end;
break;
case OPT_CRL_DOWNLOAD:
crl_download = 1;
break;
case OPT_ENGINE:
if ((e = setup_engine(opt_arg(), 0)) == NULL) {
/* Failure message already displayed */
goto end;
}
break;
case OPT_SHOW_CHAIN:
show_chain = 1;
break;
case OPT_NAMEOPT:
if (!set_nameopt(opt_arg()))
goto end;
break;
case OPT_VERBOSE:
v_verbose = 1;
break;
}
}
argc = opt_num_rest();
argv = opt_rest();
if (trusted != NULL && (CAfile || CApath)) {
BIO_printf(bio_err,
"%s: Cannot use -trusted with -CAfile or -CApath\n",
prog);
goto end;
}
//将加载指定的证书,noCAfile表示不要加载默认的证书,noCApath不要加载默认证书路径的证书
//最后将证书保存在store.objs堆栈中。
if ((store = setup_verify(CAfile, CApath, noCAfile, noCApath)) == NULL)
goto end;
//设置默认的回调方法,如果有验证不通过的时候,通过用户提供的回调函数,判断函数返回值是继续下面的验证,还是终止验证
X509_STORE_set_verify_cb(store, cb);
//设置验证参数,验证参数中保存了都需要哪些验证,比如证书的颁发者,证书的有效期等
if (vpmtouched)
X509_STORE_set1_param(store, vpm);
ERR_clear_error();
if (crl_download)
//设置吊销列表的下载方法回调,可以自定义下载方式,比如http
store_setup_crl_download(store);
ret = 0;
//如果没有验证的证书,那么从标准输入中获取证书
if (argc < 1) {
if (check(store, NULL, untrusted, trusted, crls, show_chain) != 1)
ret = -1;
} else {
//如果有证书,遍历进行check
for (i = 0; i < argc; i++)
if (check(store, argv[i], untrusted, trusted, crls,
show_chain) != 1)
ret = -1;
}
end:
X509_VERIFY_PARAM_free(vpm);
X509_STORE_free(store);
sk_X509_pop_free(untrusted, X509_free);
sk_X509_pop_free(trusted, X509_free);
sk_X509_CRL_pop_free(crls, X509_CRL_free);
release_engine(e);
return (ret < 0 ? 2 : ret);
}
static int check(X509_STORE *ctx, const char *file,
STACK_OF(X509) *uchain, STACK_OF(X509) *tchain,
STACK_OF(X509_CRL) *crls, int show_chain)
{
X509 *x = NULL;
int i = 0, ret = 0;
X509_STORE_CTX *csc;
STACK_OF(X509) *chain = NULL;
int num_untrusted;
//证书文件加载失败
x = load_cert(file, FORMAT_PEM, "certificate file");
if (x == NULL)
goto end;
csc = X509_STORE_CTX_new();
if (csc == NULL) {
printf("error %s: X.509 store context allocation failed\n",
(file == NULL) ? "stdin" : file);
goto end;
}
X509_STORE_set_flags(ctx, vflags);
//使用不受信证书链初始化ctx
if (!X509_STORE_CTX_init(csc, ctx, x, uchain)) {
X509_STORE_CTX_free(csc);
printf("error %s: X.509 store context initialization failed\n",
(file == NULL) ? "stdin" : file);
goto end;
}
//设置受信证书证书链,将tchain放在在ctx的other_ctx字段中。
if (tchain != NULL)
X509_STORE_CTX_set0_trusted_stack(csc, tchain);
//设置吊销列表
if (crls != NULL)
X509_STORE_CTX_set0_crls(csc, crls);
//调用验证方法进行验证
i = X509_verify_cert(csc);
if (i > 0 && X509_STORE_CTX_get_error(csc) == X509_V_OK) {
printf("%s: OK\n", (file == NULL) ? "stdin" : file);
ret = 1;
if (show_chain) {
int j;
chain = X509_STORE_CTX_get1_chain(csc);
num_untrusted = X509_STORE_CTX_get_num_untrusted(csc);
printf("Chain:\n");
for (j = 0; j < sk_X509_num(chain); j++) {
X509 *cert = sk_X509_value(chain, j);
printf("depth=%d: ", j);
X509_NAME_print_ex_fp(stdout,
X509_get_subject_name(cert),
0, get_nameopt());
if (j < num_untrusted)
printf(" (untrusted)");
printf("\n");
}
sk_X509_pop_free(chain, X509_free);
}
} else {
printf("error %s: verification failed\n", (file == NULL) ? "stdin" : file);
}
X509_STORE_CTX_free(csc);
end:
if (i <= 0)
ERR_print_errors(bio_err);
X509_free(x);
return ret;
}
int X509_verify_cert(X509_STORE_CTX *ctx)
{
SSL_DANE *dane = ctx->dane;
int ret;
if (ctx->cert == NULL) {
X509err(X509_F_X509_VERIFY_CERT, X509_R_NO_CERT_SET_FOR_US_TO_VERIFY);
ctx->error = X509_V_ERR_INVALID_CALL;
return -1;
}
if (ctx->chain != NULL) {
/*
* This X509_STORE_CTX has already been used to verify a cert. We
* cannot do another one.
*/
X509err(X509_F_X509_VERIFY_CERT, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
ctx->error = X509_V_ERR_INVALID_CALL;
return -1;
}
if (!X509_up_ref(ctx->cert)) {
X509err(X509_F_X509_VERIFY_CERT, ERR_R_INTERNAL_ERROR);
ctx->error = X509_V_ERR_UNSPECIFIED;
return -1;
}
/*
* first we make sure the chain we are going to build is present and that
* the first entry is in place
*/
//将要验证的证书加入到要构建的证书链中。
if ((ctx->chain = sk_X509_new_null()) == NULL
|| !sk_X509_push(ctx->chain, ctx->cert)) {
X509_free(ctx->cert);
X509err(X509_F_X509_VERIFY_CERT, ERR_R_MALLOC_FAILURE);
ctx->error = X509_V_ERR_OUT_OF_MEM;
return -1;
}
ctx->num_untrusted = 1;
//检测证书的公钥bits数是否满足检测要求,1成功,0失败。公钥的bit有80, 112, 128, 192, 256
//verify_cb_cert 这里面就会调用上面设置的cb回调,当key太小时你是否要继续进行验证,还是直接终止验证。
/* If the peer's public key is too weak, we can stop early. */
if (!check_key_level(ctx, ctx->cert) &&
!verify_cb_cert(ctx, ctx->cert, 0, X509_V_ERR_EE_KEY_TOO_SMALL))
return 0;
if (DANETLS_ENABLED(dane))
ret = dane_verify(ctx);
else
//进入真正证书链的验证方法
ret = verify_chain(ctx);
/*
* Safety-net. If we are returning an error, we must also set ctx->error,
* so that the chain is not considered verified should the error be ignored
* (e.g. TLS with SSL_VERIFY_NONE).
*/
if (ret <= 0 && ctx->error == X509_V_OK)
ctx->error = X509_V_ERR_UNSPECIFIED;
return ret;
}
static int verify_chain(X509_STORE_CTX *ctx)
{
int err;
int ok;
/*
* Before either returning with an error, or continuing with CRL checks,
* instantiate chain public key parameters.
*/
if ((ok = build_chain(ctx)) == 0 || //建链
(ok = check_chain_extensions(ctx)) == 0 ||//检测证书扩展,将扩展信息缓存到X509中,检测扩展是否符合要求,不如我需要一个V3版本的证书,但是这里链里面的证书是V1版本的证书
(ok = check_auth_level(ctx)) == 0 ||//之前检测过目标证书的公钥key的bits数量是否符合要求,这里是检测所有签发证书的的bits是否符合要求,还有检测所有证书的签名算法的bits是否符合要求
(ok = check_id(ctx)) == 0 || 1)//检测所有id是否符合要求,不如ip,email,hosts
X509_get_pubkey_parameters(NULL, ctx->chain);
if (ok == 0 || (ok = ctx->check_revocation(ctx)) == 0)//检查吊销列表是否符合要求
return ok;
//检测密码套件B,也就是ECC(椭圆曲线算法),比如他的他的曲线是不是规定的那几种,他的版本是不是正确等
err = X509_chain_check_suiteb(&ctx->error_depth, NULL, ctx->chain,
ctx->param->flags);
if (err != X509_V_OK) {
if ((ok = verify_cb_cert(ctx, NULL, ctx->error_depth, err)) == 0)
return ok;
}
/* Verify chain signatures and expiration times */
//验证签名和过期时间,如果你没有传入自定义的验证方法,那么走openssl内置的方法。
ok = (ctx->verify != NULL) ? ctx->verify(ctx) : internal_verify(ctx);
if (!ok)
return ok;
if ((ok = check_name_constraints(ctx)) == 0)
return ok;
#ifndef OPENSSL_NO_RFC3779
/* RFC 3779 path validation, now that CRL check has been done */
if ((ok = X509v3_asid_validate_path(ctx)) == 0)
return ok;
if ((ok = X509v3_addr_validate_path(ctx)) == 0)
return ok;
#endif
/* If we get this far evaluate policies */
if (ctx->param->flags & X509_V_FLAG_POLICY_CHECK)
ok = ctx->check_policy(ctx);
return ok;
}
/* verify the issuer signatures and cert times of ctx->chain */
static int internal_verify(X509_STORE_CTX *ctx)
{
int n = sk_X509_num(ctx->chain) - 1;
X509 *xi = sk_X509_value(ctx->chain, n);
X509 *xs;
/*
* With DANE-verified bare public key TA signatures, it remains only to
* check the timestamps of the top certificate. We report the issuer as
* NULL, since all we have is a bare key.
*/
if (ctx->bare_ta_signed) {
xs = xi;
xi = NULL;
goto check_cert_time;
}
if (ctx->check_issued(ctx, xi, xi))//如果证书链最后一个证书是自签证书,那么赋值给主体证书赋值为当前证书
xs = xi; /* the typical case: last cert in the chain is self-issued */
else {
if (ctx->param->flags & X509_V_FLAG_PARTIAL_CHAIN) {//如果运行不完整证书,直接调到验证证书时间
xs = xi;
goto check_cert_time;
}
if (n <= 0) {//证书链中没有叶子证书
if (!verify_cb_cert(ctx, xi, 0,
X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE))
return 0;
xs = xi;
goto check_cert_time;
}
n--;
ctx->error_depth = n;
xs = sk_X509_value(ctx->chain, n);//获取下一个证书作为主体证书
}
/*
* Do not clear ctx->error=0, it must be "sticky", only the user's callback
* is allowed to reset errors (at its own peril).
*/
while (n >= 0) {
/*
* For each iteration of this loop:
* n is the subject depth
* xs is the subject cert, for which the signature is to be checked
* xi is the supposed issuer cert containing the public key to use
* Initially xs == xi if the last cert in the chain is self-issued.
*
* Skip signature check for self-signed certificates unless explicitly
* asked for because it does not add any security and just wastes time.
*/
//跳过自签证书的签名检测,除非明确要求,因为这样不会增加安全保证,只是在浪费时间
if (xs != xi || ((ctx->param->flags & X509_V_FLAG_CHECK_SS_SIGNATURE)
&& (xi->ex_flags & EXFLAG_SS) != 0)) {
EVP_PKEY *pkey;
/*
* If the issuer's public key is not available or its key usage
* does not support issuing the subject cert, report the issuer
* cert and its depth (rather than n, the depth of the subject).
*/
int issuer_depth = n + (xs == xi ? 0 : 1);
/*
* According to https://tools.ietf.org/html/rfc5280#section-6.1.4
* step (n) we must check any given key usage extension in a CA cert
* when preparing the verification of a certificate issued by it.
* According to https://tools.ietf.org/html/rfc5280#section-4.2.1.3
* we must not verify a certifiate signature if the key usage of the
* CA certificate that issued the certificate prohibits signing.
* In case the 'issuing' certificate is the last in the chain and is
* not a CA certificate but a 'self-issued' end-entity cert (i.e.,
* xs == xi && !(xi->ex_flags & EXFLAG_CA)) RFC 5280 does not apply
* (see https://tools.ietf.org/html/rfc6818#section-2) and thus
* we are free to ignore any key usage restrictions on such certs.
*/
//如果是自签证书的终端证书返回X509_V_OK,否则查看签发者证书是否有签发其他证书的功能
int ret = xs == xi && (xi->ex_flags & EXFLAG_CA) == 0
? X509_V_OK : x509_signing_allowed(xi, xs);
if (ret != X509_V_OK && !verify_cb_cert(ctx, xi, issuer_depth, ret))
return 0;
//获取签发者的公钥
if ((pkey = X509_get0_pubkey(xi)) == NULL) {
ret = X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY;
if (!verify_cb_cert(ctx, xi, issuer_depth, ret))
return 0;
//使用签发者公钥对主体证书进行验签
} else if (X509_verify(xs, pkey) <= 0) {
ret = X509_V_ERR_CERT_SIGNATURE_FAILURE;
if (!verify_cb_cert(ctx, xs, n, ret))
return 0;
}
}
check_cert_time: /* in addition to RFC 5280, do also for trusted (root) cert */
/* Calls verify callback as needed */
//验证主体证书的有效期,
if (!x509_check_cert_time(ctx, xs, n))
return 0;
/*
* Signal success at this depth. However, the previous error (if any)
* is retained.
*/
ctx->current_issuer = xi;//保存签发者证书和主体证书到ctx
ctx->current_cert = xs;
ctx->error_depth = n;
if (!ctx->verify_cb(1, ctx))
return 0;
if (--n >= 0) {//证书链还有证书未验证,对n减减
xi = xs; //当当前主体证书赋值给签发者证书。
xs = sk_X509_value(ctx->chain, n);//获取证书链的下一个证书作为主体证书。
}
}
return 1;
}