故障解决|解决Cipher Suites导致的“未能创建 SSL/TLS 安全通道”异常问题

故障描述 昨天晚上在生产环境的某台计算机遇到了访问第三方应用报“未能创建 SSL/TLS 安全通道”的异常。开发的同事重新写了两个命令控制台程序(.net framework 4.5 和 .netcore 3.1),问题可以100%重现。同样的代码在本地或者其它服务器上运行,可以正常使用。更为奇怪的是,同事使用 curl 工具或者 Python 写的测试代码竟然都可以正常运行。
环境描述 操作系统: windows server 2016 Datecenter (Azure标准镜像)
Host: China Azure
.Net Runtime: .net framework 4.7.2 和 .netcore 3.1
Target Framework:.net framework 4.5.1 和 .netcore 3.1均可以重现
测试程序核心代码(.net framework 4.5.1):

ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CheckValidationResult); ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; HttpWebRequest req = WebRequest.Create(url) as HttpWebRequest; HttpWebResponse res = (HttpWebResponse)req.GetResponse()

分析 先不考虑 curl 工具或者 Python 写的测试代码为什么可以正常运行,首先还是要先抓取服务器上的网络包进行分析。抓取后发现在Client Hello之后出现了Encrypted Alert。
故障解决|解决Cipher Suites导致的“未能创建 SSL/TLS 安全通道”异常问题
文章图片

回顾一下SSL/TLS握手流程(网图侵删):
故障解决|解决Cipher Suites导致的“未能创建 SSL/TLS 安全通道”异常问题
文章图片

【故障解决|解决Cipher Suites导致的“未能创建 SSL/TLS 安全通道”异常问题】 在client hello的时候提供了 version、cipher suites等信息。然后继续看一下网络抓包发现错误代码40(十六进制28转十进制)
故障解决|解决Cipher Suites导致的“未能创建 SSL/TLS 安全通道”异常问题
文章图片

查找到错误代码解释:Indicates that the sender was unable to negotiate an acceptable set of security parameters given the options available. This is a fatal error.
(SSL/TLS Alert Protocol and the Alert Codes )
第一个参数02表示 AlertLevel,第二个参数28(十进制的40)表示 AlertDescription。那么问题因该出在client hello的时候传入的参数上了。比较常见的是version问题,但是代码里面已经明确声明了使用 TLS 1.2 的版本,而且在其它环境运行也正常说明 TLS 的版本不会有问题。这时候查到一篇文章(How to fix the SSL / TLS handshake failed error)对我起了很大作用。按顺序我开始排查Encryption suite mismatch问题。
因为对方网站可以被外网访问到,所以我考虑使用ssllabs网站的工具进行一下检测,生成一份SSL Report。这里可以看到目标网站支持哪些 Cipher Suites。
故障解决|解决Cipher Suites导致的“未能创建 SSL/TLS 安全通道”异常问题
文章图片

然后发现抓取的网络包里面client hello时没有匹配的 Cipher Suites!
故障解决|解决Cipher Suites导致的“未能创建 SSL/TLS 安全通道”异常问题
文章图片

然后检查本地的成功的请求和服务器上使用 curl 或者 Python 写的测试代码的请求,发现在这一步Cipher Suites的差异很大。下图是使用 curl 的网络包情况。
故障解决|解决Cipher Suites导致的“未能创建 SSL/TLS 安全通道”异常问题
文章图片

这个时候网上了一下 .Net 程序如何设置Cipher Suites,找到一个记录(issues 22507),发现可以通过以下方式指定。
var sslOptions = new SslClientAuthenticationOptions { CipherSuitesPolicy= new CipherSuitesPolicy(new List{ TlsCipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 }) }; var socketsHttpHandler = new SocketsHttpHandler { SslOptions = sslOptions }; var httpClient = new HttpClient(socketsHttpHandler, true); httpClient.GetAsync(url).Wait();

不过实际的程序使用的是HttpWebRequest(.net framework 4.5.1)而且其它环境是可以正常使用的,并没有声明过Cipher Suites,所以又发现 The build-in classes HttpWebRequest and the sort all uses Windows native SChannel to implement SSL encryption, therefore has no programmatic way to control the cipher suite list. (原文),所以开始考虑 OS 配置的问题。Windows Server 2016 Cipher Suite list 我没有查到,但可以参考其它版本的说明(Windows2022)和 How to deploy custom cipher suite ordering in Windows Server 2016 的文章。
实际上我没有担心过windows server 2016不支持相应Cipher Suites的问题,最简单的证明就是其它服务器(同OS版本)是可以正常使用的,所以这台机器应该是进行了特殊的配置。查了一下如何配置(How to Update Your Windows Server Cipher Suite for Better Security),然后打开组策略发现果然被人修改过了。
故障解决|解决Cipher Suites导致的“未能创建 SSL/TLS 安全通道”异常问题
文章图片

先恢复成默认值(未配置),再次运行测试程序,通过?。
感言 从发现故障到找到原因,说起来很简单,但实际用了将近四个小时。主要的问题就是基础知识不牢固,SSL的流程原理什么的都是重新学习一遍,大量知识都是现查,甚至一开始方向都没有头绪。工作了十几年了,越发觉得基础知识的重要性。

    推荐阅读