openssl 证书验证源码分析

主要分析了openssl 如何验证证书的有效性。对源码的分析以注释的方式给出。

文章目录

    • 代码入口 verify.c
      • verify_main方法分析
      • 分析check方法
    • x509_verify.c 分析
      • X509_verify_cert方法
      • verify_chain方法分析
      • internal_verify方法分析

代码入口 verify.c

verify_main方法分析

此方法主要是对用户传入的参数进行分析,组装验证参数

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);
}

分析check方法

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;
}

x509_verify.c 分析

X509_verify_cert方法

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;
}

verify_chain方法分析

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;
}

internal_verify方法分析


/* 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;
}

你可能感兴趣的:(openssl,java,算法,开发语言)