C++基于OpenSSL API实现TLS客户端报sslv3 alert handshake failure错误
简易HTTP客户端HTTPS握手失败问题排查
问题现象
自主实现的简易HTTP客户端在开发HTTPS支持功能时,访问google、httpbin.org等站点可正常获得响应,但访问jsonplaceholder.typicode.com时在BIO_do_connect执行阶段抛出错误,触发SSL握手失败。
现有SSL模块实现代码
void HttpClient::ssl_setup() { SSL_library_init(); if ((_ctx = SSL_CTX_new(SSLv23_client_method())) == NULL) { throw std::runtime_error("Failed to create SSL context"); } SSL_CTX_set_options(_ctx, SSL_OP_NO_SSLv2); _bio = BIO_new_ssl_connect(_ctx); std::string tmp = fmt::format("{}:{}", _url.domain(), std::to_string(_url.port())); BIO_set_conn_hostname(_bio, tmp.c_str()); if (BIO_do_connect(_bio) != 1) { std::cout << ERR_error_string(ERR_get_error(), NULL) << '\n'; throw std::runtime_error("Failed to do ssl connect"); } }
报错信息
error:0A000410:SSL routines::sslv3 alert handshake failure libc++abi: terminating with uncaught exception of type std::runtime_error: Failed to do ssl connect
问题根因
现有SSL配置存在三个核心问题,刚好触发了jsonplaceholder.typicode.com服务端的严格TLS校验规则:
- 协议版本配置老旧:使用的
SSLv23_client_method()是兼容老旧SSL协议的遗留方法,仅禁用了SSLv2,未禁用SSLv3、TLS1.0、TLS1.1等已被淘汰的不安全协议,客户端握手时会携带老旧协议的兼容标识,被配置严格的服务端直接拒绝。 - 缺失SNI配置:当前代码仅给BIO设置了连接的目标地址,没有配置TLS扩展的SNI(服务器名称指示)字段,jsonplaceholder.typicode.com是基于虚拟主机托管的站点,没有SNI信息时服务端无法匹配到对应站点的证书和配置,直接返回握手失败。
- 未配置证书校验逻辑:代码没有加载系统信任的CA根证书,也没有开启服务端证书校验,不仅存在中间人攻击的安全风险,部分严格配置的服务端也会直接拒绝无证书校验能力的客户端握手请求。
修复方案
按照以下步骤修改SSL初始化逻辑即可解决问题:
- 替换老旧的协议方法,收紧允许的TLS版本范围
将SSL_CTX_new的入参替换为TLS_client_method(),同时在选项中禁用所有低于TLS1.2的不安全协议:
if ((_ctx = SSL_CTX_new(TLS_client_method())) == NULL) { throw std::runtime_error("Failed to create SSL context"); } SSL_CTX_set_options(_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1);
- 补充SNI和证书校验主机名配置
在调用BIO_do_connect之前,从BIO对象中取出SSL句柄,设置SNI主机名和证书校验的目标域名:
_bio = BIO_new_ssl_connect(_ctx); std::string tmp = fmt::format("{}:{}", _url.domain(), std::to_string(_url.port())); BIO_set_conn_hostname(_bio, tmp.c_str()); // 新增以下配置 SSL* ssl_ptr = nullptr; BIO_get_ssl(_bio, &ssl_ptr); // 设置SNI扩展字段 SSL_set_tlsext_host_name(ssl_ptr, _url.domain().c_str()); // 设置证书校验的目标域名 SSL_set1_host(ssl_ptr, _url.domain().c_str());
- 加载系统CA证书,开启服务端证书校验
在SSL上下文初始化完成后,加载系统默认信任的根证书列表,开启对等端证书校验,修复安全隐患:
if (SSL_CTX_set_default_verify_paths(_ctx) != 1) { throw std::runtime_error("Failed to load system default CA certificates"); } SSL_CTX_set_verify(_ctx, SSL_VERIFY_PEER, nullptr);
完成以上修改后重新编译运行,即可正常完成和jsonplaceholder.typicode.com的TLS握手,发起HTTPS请求。
内容的提问来源于stack exchange,提问作者donut321
相关产品推荐
相关产品推荐

