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带来的安全风险。
版本间信任逻辑变化原因
- 安全规范对齐:OpenSSL 3.0默认遵循TLS最佳实践,信任锚仅应是自签名根CA,中间CA证书应由服务端在握手过程中提供,而非客户端预先信任。
- 风险规避:1.1.0及旧版本中,
SSL_CTX_load_verify_locations会将所有加载的证书(包括中间CA)标记为信任锚,若中间CA证书泄露,攻击者可利用其签发伪造证书,客户端会直接信任,存在安全隐患。 - 逻辑拆分:3.0版本明确区分了"信任锚"和"证书链辅助证书":加载的非根CA证书仅用于验证时补全证书链,但不会被视为可信的锚点,除非手动通过代码或配置标记。
内容的提问来源于stack exchange,提问作者rmcuenen
相关产品推荐
相关产品推荐

