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

OpenSSL 3.0 TLS握手失败但1.1.0成功的原因及兼容方案咨询

OpenSSL 3.0迁移:SSL_connect握手失败(verify error:num=20)问题解决

问题背景

将代码从OpenSSL 1.1.0迁移至3.0后,编译链接正常,但调用SSL_connect时TLS握手失败,报错verify error:num=20:unable to get local issuer certificate。环境为三级证书链:Root CA(SS)→ Intermediate CA(RCA)→ Server(ICA),客户端已将Root和Intermediate证书合并为ca-chain.cert.pem并加载,且:

  • OpenSSL 1.1.0环境下无需修改即可正常握手
  • OpenSSL 3.0的verify命令验证证书链显示正常:
    openssl verify -CAfile ca-chain.cert.pem server.cert.pem 
    server.cert.pem: OK
    
  • 服务端使用命令:
    openssl s_server -key server.key.pem -cert server.cert.pem -accept 5301
    

添加-cert_chain ca.cert.pem参数到服务端命令可临时解决,但希望保留OpenSSL 1.1.0的原有行为。

解决方案

客户端代码调整(恢复1.1.0信任逻辑)

OpenSSL 3.0默认仅将自签名根CA标记为信任锚,中间CA证书仅作为链构建辅助而非可信锚点。要恢复1.1.0将所有加载证书视为信任锚的行为,需手动将ca-chain.cert.pem中的每个证书添加到信任存储:

#include <openssl/ssl.h>
#include <openssl/bio.h>
#include <openssl/x509.h>

// 推荐用TLS_client_method替代SSLv23_client_method,适配现代TLS规范
SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());
if (ctx == NULL) {
    // 错误处理
}

SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);

// 加载证书链文件并逐个添加为信任锚
BIO *bio = BIO_new_file("ca-chain.cert.pem", "r");
if (bio == NULL) {
    // 错误处理
    SSL_CTX_free(ctx);
    return -1;
}

X509 *cert;
while ((cert = PEM_read_bio_X509(bio, NULL, NULL, NULL)) != NULL) {
    // 将证书添加到信任存储并标记为信任锚
    if (X509_STORE_add_cert(SSL_CTX_get_cert_store(ctx), cert) != 1) {
        // 错误处理
        X509_free(cert);
        BIO_free(bio);
        SSL_CTX_free(ctx);
        return -1;
    }
    X509_free(cert);
}
BIO_free(bio);

SSL *ssl = SSL_new(ctx);
SSL_set_fd(ssl, sock_fd);

int ret = SSL_connect(ssl);
if (ret <= 0) {
    // 打印详细错误信息
    int err_code = SSL_get_error(ssl, ret);
    char err_buf[256];
    ERR_error_string_n(err_code, err_buf, sizeof(err_buf));
    fprintf(stderr, "SSL_connect failed: %s\n", err_buf);
}

// 后续资源清理
SSL_free(ssl);
SSL_CTX_free(ctx);

规范方案(推荐)

从TLS最佳实践角度,更合理的做法是让服务端在握手时发送完整证书链(包含中间CA),即服务端启动命令添加-cert_chain参数:

openssl s_server -key server.key.pem -cert server.cert.pem -cert_chain intermediate.cert.pem -accept 5301

此方案无需修改客户端代码,且符合安全规范——客户端仅需信任根CA,中间CA由服务端提供,避免客户端预先信任中间CA带来的安全风险。

版本间信任逻辑变化原因

  1. 安全规范对齐:OpenSSL 3.0默认遵循TLS最佳实践,信任锚仅应是自签名根CA,中间CA证书应由服务端在握手过程中提供,而非客户端预先信任。
  2. 风险规避:1.1.0及旧版本中,SSL_CTX_load_verify_locations会将所有加载的证书(包括中间CA)标记为信任锚,若中间CA证书泄露,攻击者可利用其签发伪造证书,客户端会直接信任,存在安全隐患。
  3. 逻辑拆分:3.0版本明确区分了"信任锚"和"证书链辅助证书":加载的非根CA证书仅用于验证时补全证书链,但不会被视为可信的锚点,除非手动通过代码或配置标记。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 17:17:47