You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

OpenSSL本地CA存储证书验证失败:C代码实现求助

问题描述

我希望理解CA存储(CA store)的概念,并在不影响系统全局的前提下测试自定义证书链。我在本地主目录下创建了ca_store目录,其中包含根CA证书root_ca.crt与中间CA证书intermediate_ca.crt:

$ ls /home/my/ca_store
root_ca.crt  intermediate_ca.crt

我同时拥有一份服务器证书server.crt,使用OpenSSL命令可成功验证该证书,但使用以下C代码基于CA存储概念实现验证时却失败了:

SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_CTX* ctx = SSL_CTX_new(TLS_server_method());
SSL_CTX_load_verify_locations(ctx, NULL, "/home/my/ca_store");
BIO* bio = BIO_new_file("server.crt", "rb");
X509* cert = X509_new();
PEM_read_bio_X509(bio, &cert, NULL, NULL);
X509_STORE_CTX* store_ctx = X509_STORE_CTX_new();
X509_STORE_CTX_init(store_ctx, SSL_CTX_get_cert_store(ctx), cert, NULL);
int ret = X509_verify_cert(store_ctx);
if (ret != 1)
{
   int error = X509_STORE_CTX_get_error(store_ctx);
   const char *error_str = X509_verify_cert_error_string(error);
   printf("Certificate verification failed. Error code: %d, Error message: %s\n", error, error_str);
   ERR_print_errors_fp(stdout);
}
else
{
   printf("Certificate verification successful\n");
}

代码执行输出如下:

Certificate verification failed. Error code: 20, Error message: unable to get local issuer certificate

请问如何修改上述代码以实现正确的证书验证?


解决方案

问题根源

错误的核心原因有两个:

  1. OpenSSL通过目录加载CA证书时,要求证书文件名必须符合哈希命名规则(即证书主题哈希值加.0/.1后缀),否则无法自动检索到对应的签发者证书
  2. 服务器证书由中间CA签发时,验证过程需要明确将中间CA纳入信任链,原代码仅加载目录但未确保中间CA被加入验证上下文

修改方案

方案1:手动加载目录内所有CA证书(无需修改证书文件名)

直接读取目录下的所有PEM格式证书,手动添加到X509_STORE中,避免依赖哈希文件名规则:

#include <stdio.h>
#include <openssl/ssl.h>
#include <openssl/x509.h>
#include <openssl/bio.h>
#include <dirent.h>
#include <string.h>

// 辅助函数:批量加载目录内的CA证书到X509_STORE
int load_certs_from_dir(X509_STORE *store, const char *dir_path) {
    DIR *dir = opendir(dir_path);
    if (!dir) {
        perror("Failed to open CA directory");
        return 0;
    }

    struct dirent *entry;
    while ((entry = readdir(dir)) != NULL) {
        // 跳过目录本身和上级目录,只处理普通文件
        if (entry->d_type != DT_REG || strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
            continue;
        }

        char file_path[256];
        snprintf(file_path, sizeof(file_path), "%s/%s", dir_path, entry->d_name);
        
        BIO *bio = BIO_new_file(file_path, "rb");
        if (!bio) {
            fprintf(stderr, "Failed to open cert file: %s\n", file_path);
            continue;
        }

        X509 *cert = PEM_read_bio_X509(bio, NULL, NULL, NULL);
        if (cert) {
            if (!X509_STORE_add_cert(store, cert)) {
                fprintf(stderr, "Failed to add cert %s to store\n", entry->d_name);
            }
            X509_free(cert);
        } else {
            fprintf(stderr, "Failed to parse cert %s\n", entry->d_name);
        }
        BIO_free(bio);
    }
    closedir(dir);
    return 1;
}

int main() {
    SSL_library_init();
    OpenSSL_add_all_algorithms();
    SSL_CTX* ctx = SSL_CTX_new(TLS_server_method());
    if (!ctx) {
        fprintf(stderr, "Failed to create SSL_CTX\n");
        return 1;
    }

    // 获取SSL上下文的CA存储,手动加载目录内证书
    X509_STORE *store = SSL_CTX_get_cert_store(ctx);
    if (!load_certs_from_dir(store, "/home/my/ca_store")) {
        fprintf(stderr, "Failed to load CA certificates\n");
        SSL_CTX_free(ctx);
        return 1;
    }

    // 读取服务器证书
    BIO* bio = BIO_new_file("server.crt", "rb");
    if (!bio) {
        perror("Failed to open server.crt");
        SSL_CTX_free(ctx);
        return 1;
    }
    X509* cert = PEM_read_bio_X509(bio, NULL, NULL, NULL);
    BIO_free(bio);
    if (!cert) {
        fprintf(stderr, "Failed to parse server.crt\n");
        SSL_CTX_free(ctx);
        return 1;
    }

    // 初始化验证上下文
    X509_STORE_CTX* store_ctx = X509_STORE_CTX_new();
    if (!X509_STORE_CTX_init(store_ctx, store, cert, NULL)) {
        fprintf(stderr, "Failed to initialize verification context\n");
        X509_free(cert);
        SSL_CTX_free(ctx);
        X509_STORE_CTX_free(store_ctx);
        return 1;
    }

    // 执行证书验证
    int ret = X509_verify_cert(store_ctx);
    if (ret != 1) {
        int error = X509_STORE_CTX_get_error(store_ctx);
        const char *error_str = X509_verify_cert_error_string(error);
        printf("Certificate verification failed. Error code: %d, Error message: %s\n", error, error_str);
        ERR_print_errors_fp(stdout);
    } else {
        printf("Certificate verification successful\n");
    }

    // 释放资源
    X509_STORE_CTX_free(store_ctx);
    X509_free(cert);
    SSL_CTX_free(ctx);
    return 0;
}

方案2:给CA证书生成哈希文件名(无需修改代码)

使用OpenSSL命令生成符合要求的哈希链接,让原代码能自动找到签发者证书:

cd /home/my/ca_store
# 生成根CA的哈希链接
openssl x509 -hash -noout -in root_ca.crt
ln -s root_ca.crt $(openssl x509 -hash -noout -in root_ca.crt).0
# 生成中间CA的哈希链接
openssl x509 -hash -noout -in intermediate_ca.crt
ln -s intermediate_ca.crt $(openssl x509 -hash -noout -in intermediate_ca.crt).0

执行完上述命令后,原代码即可正常完成证书验证。

CA存储概念补充

CA存储是OpenSSL中用于管理信任根的核心容器(对应X509_STORE结构体),它存储了所有被系统/应用信任的CA证书。证书验证时,OpenSSL会从待验证证书的签发者字段出发,在CA存储中逐层查找匹配的证书,直到回溯到已信任的根CA,以此确认证书的合法性。

内容的提问来源于stack exchange,提问作者user180574

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 03:38:15