IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> openssl客户端编程:一个不起眼的函数导致的SSL会话失败问题 -> 正文阅读

[网络协议]openssl客户端编程:一个不起眼的函数导致的SSL会话失败问题

我们目前大部分使用的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.dllssleay32.dll 库,这些版本是不支持TLS1.3协议的。比如工具类的postman、curl,以及应用类QQ、Foxmail、微信以及各种下载客户端程序,使用的可能还是老的openssl库。openssl库在1.1.1及以上版本已经将库的导出名称改为libcryptolibssl,不过这只是前缀名,如果是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_readSSL_write进行业务通信。
代码通常如下:

	// 创建套接字,并连接到服务器
	SOCKET s = socket(AF_INET, SOCK_STREAM, 0);
	struct hostent* hst = ::gethostbyname(pszServer);
	if(NULL == hst)
		return FALSE;
	unsigned long addr;
	struct sockaddr_in sockAddr;
	memcpy(&addr, hst->h_addr, hst->h_length);
	sockAddr.sin_family = AF_INET;
	sockAddr.sin_port = htons(nPort);
	sockAddr.sin_addr.S_un.S_addr = addr; 
	// 连接到服务器
	if(SOCKET_ERROR != ::connect(s, (struct sockaddr *)&sockAddr, sizeof(struct sockaddr_in)))
		return FALSE;
	
	// Openssl库初始化
	OpenSSL_add_ssl_algorithms();
	SSL_load_error_strings(); 
	SSLeay_add_ssl_algorithms();
	ERR_load_BIO_strings();
	ctx = SSL_CTX_new(SSLv23_client_method());
	
	// 创建ssl上下文,并绑定套接字
	SSL* ssl = SSL_new (m_ctx);
	SSL_set_fd(m_ssl, s);
	// 开始ssl握手
	int iRet = SSL_connect(m_ssl);
	if(1 != iRet)
		return FALSE;
		
	// 下面使用SSL_read或SSL_write进行通信
	

上述代码中SSL_connect函数是最重要的,它内部实现了SSL的握手过程,openssl在内部采用状态机的方式实现了整个握手,代码还是相当的晦涩。几乎所有的SSL通信失败都是在握手阶段产生的,比如加密套件不匹配,服务器证书没有可靠的签名等。

openssl中有个s_client命令集合,该命令用于实现客户端的通信,它的实现在s_client.c文件中,在这个庞大代码中有一个不起眼的函数调用,这个函数是SSL_set_tlsext_host_name。该函数的作用是在客户端在发送ClientHello消息时,将所访问的主机(服务器)名称写入到server name的扩展字段中。

   if (!noservername && (servername != NULL || dane_tlsa_domain == NULL)) {
        if (servername == NULL) {
            if(host == NULL || is_dNS_name(host)) 
                servername = (host == NULL) ? "localhost" : host;
        }
        if (servername != NULL && !SSL_set_tlsext_host_name(con, servername)) {
            BIO_printf(bio_err, "Unable to set TLS servername extension.\n");
            ERR_print_errors(bio_err);
            goto end;
        }
    }

调用该函数后会在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函数也无法知道它的最终目的是什么,这也是我写这篇文章的原因之一吧。

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2022-07-03 11:09:04  更:2022-07-03 11:09:21 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/28 20:09:38-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计