当前位置:网站首页>openssl客户端编程:一个不起眼的函数导致的SSL会话失败问题
openssl客户端编程:一个不起眼的函数导致的SSL会话失败问题
2022-06-27 18:21:00 【51CTO】
我们目前大部分使用的openssl库还是基于TLS1.2协议的1.0.2版本系列,如果要支持更高的TLS1.3协议,就必须使用openssl的1.1.1版本或3.0版本。升级openssl库有可能会导致SSL会话失败,我在升级 wincurl 时,意外的收获了一个函数。这个函数非常的不起眼,但具有的现实意义却很大。
大部分情况下如果你不调用该函数,并不影响SSL会话和通信,但有时会被某些服务器拒绝。一旦被拒绝,查找具体的原因将变得非常痛苦。这个函数的意义好比HTTP协议中HOST字段,它和NGINX反向代理的Server name有异曲同工之妙。了解这个函数的意义,可能会让你今后在配置nginx反向代理时少走一些弯路。
这个函数是 SSL_set_tlsext_host_name,在介绍这个函数之前,我们先快速看看TLS协议和openssl的发展。
虽然TLS1.3的标准自2018年就已发布,但目前国内几乎所有网站并没有增加对TLS 1.3的支持,大部分主流网站使用的还是基于TLS1.2的版本。目前国内使用TLS1.3的好像只有知乎,其它诸如:新浪、网易、搜狐、腾讯、华为、京东、百度…都还是采用TLS1.2的标准。
网站是否支持TLS1.3其实并不重要,重要的是浏览器是否支持TLS1.3协议。好在目前市场上所有主流浏览器都已升级到了TLS1.3。因此对于用户而言,不管是支持TLS1.3的网站还是支持TLS1.2的网站,访问起来都不是问题,或者说对终端用户而言这种访问是无感的。
为何TLS3.0已经出现5年了,各大网络平台还是首选TLS1.2呢?估计还是出于对兼容和稳定的考虑。如果贸然升级到1.3导致大量客户端无法访问从而丢失用户,是任何平台所无法承受的。此外,支持TLS1.3的openssl版本也在不断完善中,在其未达到稳定前,最好还是使用稳定的1.2版本。这大概就是目前几乎所有平台类网站还在使用TLS1.2的原因。
此外,即使有的网站采用TLS1.3协议,也会继续提供对1.2的访问支持(向下兼容并保留所有TLS1.2的加密套件),除非该网站的运维人员强行使用TLS1.3的加密套件。但如果这样做的话就会导致大量仍然使用1.2标准的客户端程序无法继续访问该网站,这也意味着该运维人员离下岗再就业不远了。
这也是为什么我们即使不升级老版本的openssl库,也依然可以访问TLS1.3网站的原因。
上面说的TLS1.2,1.3指的是TLS的协议,实现上述协议的是openssl库。 其中支持TLS1.3的openssl库目前有两个版本系列:一个是1.1.1系列,一个是3.0系列。其中1.1.1更像是一个过渡版本,openssl团队承诺会支撑该版本到2023年9月11日,也就是明年911事件22周年之际(确实是一个值得纪念的日子)将停止更新1.1.1版本。这就意味着大家还没开始普及使用1.1.1,它就已经结束了。
这个其实也不重要,因为3.0才是最终openssl的终极版本,直接跳过了2.0,从而体现了openssl的跨越式发展。 3.0版本是openssl团队主推版本,也是一个长期维护的版本,该版本会维护到2026年9月7日。
虽然主流web浏览器都已支持TLS1.3,但浏览器只是客户端的一种,其它大部分客户端使用的还是openssl 1.0.2系列版本,甚至是1.0.1系列版本,也就是我们经常看到的libeay32.dll和ssleay32.dll 库,这些版本是不支持TLS1.3协议的。比如工具类的postman、curl,以及应用类QQ、Foxmail、微信以及各种下载客户端程序,使用的可能还是老的openssl库。openssl库在1.1.1及以上版本已经将库的导出名称改为libcrypto和libssl,不过这只是前缀名,如果是1.1.1系列,则后缀为-1.1.dll,如果是3.0系列,则后缀为-3.dll,我们通过openssl的库名就能判断出该应用程序是否支持TLS1.3。
升级到TLS1.3可以从openssl的 官网( https://openssl.org)下载1.1.1或3.0版本的源码进行编译。编译完毕后,我们只要替换openssl库,并重新配置并编译你的客户端程序即可。
使用openssl库编写客户端程序的一般流程如下:先创建套接字,并连接到服务器,然后创建ssl,并绑定套接字,通过调动SSL_connect函数进行握手操作,成功后使用SSL_read和SSL_write进行业务通信。
代码通常如下:
上述代码中SSL_connect函数是最重要的,它内部实现了SSL的握手过程,openssl在内部采用状态机的方式实现了整个握手,代码还是相当的晦涩。几乎所有的SSL通信失败都是在握手阶段产生的,比如加密套件不匹配,服务器证书没有可靠的签名等。
openssl中有个s_client命令集合,该命令用于实现客户端的通信,它的实现在s_client.c文件中,在这个庞大代码中有一个不起眼的函数调用,这个函数是SSL_set_tlsext_host_name。该函数的作用是在客户端在发送ClientHello消息时,将所访问的主机(服务器)名称写入到server name的扩展字段中。
调用该函数后会在ClientHello的扩展字段中增加一个Server Name字段。如下图所示:
我们在客户端编程时,通常不会调用该函数,因为会觉得该字段可有可无,既然已经连接到Host服务器上,为何还要将Host的名称告诉给服务器呢?如果我们不调用SSL_set_tlsext_host_name 函数,难道SSL会话会失败么?实际上即使不调用该函数,也是可以握手成功的,并不影响TLS的正常通信。
但如果服务器强制校验该字段时,就会导致握手失败,这就好比HTTP协议中请求的HOST字段一样。那么为什么有的服务器要强制校验该字段呢?如果一台服务器绑定不同的DSN名称(也就是一个IP地址绑定多个域名),这个扩展字段的意义就出来了,服务器会根据不同的域名提供不同的证书。
举个例子,假设BAT都破产了,他们穷到共用一台服务器的地步,也就是一个IP地址绑定了百度、阿里和腾讯三个公司域名,并且这三家公司都给域名申请了数字证书,此时扩展字段的HOST的作用就体现出来了,openssl服务器端会跟据不同的host名称决定返回哪家公司的数字证书。这也是Ngnix反向代理的原理,根据不同域名进行不同访问地址映射。
写到这里,本文也即将结束,也许我们调用了SSL_set_tlsext_host_name函数也无法知道它的最终目的是什么,这也是我写这篇文章的原因之一吧。
边栏推荐
猜你喜欢
UE4实现长按功能
SQL审核平台权限模块介绍和账号创建教程
【debug】平台工程接口调试
Connection integration development theme month | drivers of enterprise master data governance
Type the URL to the web page display. What happened during this period?
数智化进入“深水区”,数据治理是关键
Summary of submarine cable detection technology
Pointers and structs
主键选择选择自增还是序列?
database engine
随机推荐
Rust 中的枚举和控制流运算
安装NFS服务
在开发数字藏品时,文博机构该如何把握公益和商业的尺度?如何确保文物数据的安全?
Observable, reliable: the first shot of cloudops series Salon of cloud automation operation and maintenance
Talk about graduation season
1023 Have Fun with Numbers
Safety is the last word, Volvo xc40 recharge
Question brushing record: easy array (continuously updated)
连接集成开发专题月 | 企业主数据治理的驱动因素
数据库优化
1028 List Sorting
SQL审核平台权限模块介绍和账号创建教程
ABAP随笔-通过api获取新冠数据
Linux系统ORACLE 19C OEM监控管理
redis数据结构
Summary of redis big key problem handling
云原生安全指南: 从零开始学 Kubernetes 攻防
Manage rust project through cargo
Pointers and structs
字典树(复习)