目录

背景

https网络协议交互

net.debug查看信息

Apache Httpclient与 Netty的http请求

server_name

错误原因

修复方案

方案一

方案二


背景

通知某个商户时,突然出现大量的https握手失败了,出现received fatal alert: internal_error错误。商户sre那边当时将多个域名绑定在一个主机上,开了SNI(Server Name Indication)。

https网络协议交互

https的握手阶段图,如下:

对于每一步的解释如下:
步骤 1: 客户端通过发送 Client Hello 报文开始 SSL 通信。报文中包含客户端支持的 SSL 的指定版本、加密组件(Cipher Suite)列表(所使用的加密算法及密钥长度等)。

步骤 2: 服务器可进行 SSL 通信时,会以 Server Hello 报文作为应答。和客户端一样,在报文中包含 SSL 版本以及加密组件。服务器的加密组件内容是从接收到的客户端加密组件内筛选出来的。

步骤 3: 之后服务器发送 Certificate 报文。报文中包含公开密钥证书。

步骤 4: 最后服务器发送 Server Hello Done 报文通知客户端,最初阶段的SSL握手协商部分结束。

步骤 5: SSL 第一次握手结束之后,客户端以 Client Key Exchange 报文作为回应。报文中包含通信加密中使用的一种被称为 Pre-master secret 的随机密码串。该报文已用步骤 3 中的公开密钥进行加密。

步骤 6: 接着客户端继续发送 Change Cipher Spec 报文。该报文会提示服务器,在此报文之后的通信会采用 Pre-master secret 密钥加密。

步骤 7: 客户端发送 Finished 报文。该报文包含连接至今全部报文的整体校验值。这次握手协商是否能够成功,要以服务器是否能够正确解密该报文作为判定标准。

步骤 8: 服务器同样发送 Change Cipher Spec 报文。

步骤 9: 服务器同样发送 Finished 报文。

步骤 10: 服务器和客户端的 Finished 报文交换完毕之后,SSL 连接就算建立完成。当然,通信会受到 SSL 的保护。从此处开始进行应用层协议的通信,即发送 HTTP请求。

步骤 11: 应用层协议通信,即发送 HTTP 响应。

步骤 12: 最后由客户端断开连接。断开连接时,发送 close_notify 报文。上图做了一些省略,这步之后再发送 TCP FIN 报文来关闭与 TCP 的通信。在以上流程中,应用层发送数据时会附加一种叫做 MAC(Message Authentication Code)的报文摘要。MAC 能够查知报文是否遭到篡改,从而保护报文的完整性。

net.debug查看信息

了解了如上https的交互内容,那我们可以强行前进了。到底出现在了哪一部分?

在程序启动脚本中我们增加配置项:-djavax.net.debug=all

javax.net.debug=[ssl|all]If ssl, turns on SSL debugging. If all, turns on SSL debugging with verbose messages.

开启了网络SSL的debugging,我们看到可客户端和服务端交互的更多细节,如下:

javax.net.ssl|FINE|3D|New I/O client worker #4-1|2022-01-23 15:53:10.703 CST|ClientHello.java:567|Produced ClientHello handshake message (
"ClientHello": {"client version"      : "TLSv1.2","random"              : "F9 D9 EC 33 BD B7 E3 5F B2 0E F8 7B 5E AE 10 F0 FC 6F 89 5A B6 C6 F4 5C 17 EC A4 A6 85 60 CB 75","session id"          : "","cipher suites"       : "[TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384(0xC02C), TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256(0xC02B), TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384(0xC030), TLS_RSA_WITH_AES_256_GCM_SHA384(0x009D), TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384(0xC02E), TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384(0xC032), TLS_DHE_RSA_WITH_AES_256_GCM_SHA384(0x009F), TLS_DHE_DSS_WITH_AES_256_GCM_SHA384(0x00A3), TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256(0xC02F), TLS_RSA_WITH_AES_128_GCM_SHA256(0x009C), TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256(0xC02D), TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256(0xC031), TLS_DHE_RSA_WITH_AES_128_GCM_SHA256(0x009E), TLS_DHE_DSS_WITH_AES_128_GCM_SHA256(0x00A2), TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384(0xC024), TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384(0xC028), TLS_RSA_WITH_AES_256_CBC_SHA256(0x003D), TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384(0xC026), TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384(0xC02A), TLS_DHE_RSA_WITH_AES_256_CBC_SHA256(0x006B), TLS_DHE_DSS_WITH_AES_256_CBC_SHA256(0x006A), TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA(0xC00A), TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA(0xC014), TLS_RSA_WITH_AES_256_CBC_SHA(0x0035), TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA(0xC005), TLS_ECDH_RSA_WITH_AES_256_CBC_SHA(0xC00F), TLS_DHE_RSA_WITH_AES_256_CBC_SHA(0x0039), TLS_DHE_DSS_WITH_AES_256_CBC_SHA(0x0038), TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256(0xC023), TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256(0xC027), TLS_RSA_WITH_AES_128_CBC_SHA256(0x003C), TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256(0xC025), TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256(0xC029), TLS_DHE_RSA_WITH_AES_128_CBC_SHA256(0x0067), TLS_DHE_DSS_WITH_AES_128_CBC_SHA256(0x0040), TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA(0xC009), TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA(0xC013), TLS_RSA_WITH_AES_128_CBC_SHA(0x002F), TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA(0xC004), TLS_ECDH_RSA_WITH_AES_128_CBC_SHA(0xC00E), TLS_DHE_RSA_WITH_AES_128_CBC_SHA(0x0033), TLS_DHE_DSS_WITH_AES_128_CBC_SHA(0x0032), TLS_EMPTY_RENEGOTIATION_INFO_SCSV(0x00FF)]","compression methods" : "00","extensions"          : ["supported_groups (10)": {"versions": [secp256r1, secp384r1, secp521r1, ffdhe2048, ffdhe3072, ffdhe4096, ffdhe6144, ffdhe8192]},"ec_point_formats (11)": {"formats": [uncompressed]},"signature_algorithms (13)": {"signature schemes": [ecdsa_secp256r1_sha256, ecdsa_secp384r1_sha384, ecdsa_secp521r1_sha512, rsa_pss_rsae_sha256, rsa_pss_rsae_sha384, rsa_pss_rsae_sha512, rsa_pss_pss_sha256, rsa_pss_pss_sha384, rsa_pss_pss_sha512, rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pkcs1_sha512, dsa_sha256, ecdsa_sha1, rsa_pkcs1_sha1, dsa_sha1]},"signature_algorithms_cert (50)": {"signature schemes": [ecdsa_secp256r1_sha256, ecdsa_secp384r1_sha384, ecdsa_secp521r1_sha512, rsa_pss_rsae_sha256, rsa_pss_rsae_sha384, rsa_pss_rsae_sha512, rsa_pss_pss_sha256, rsa_pss_pss_sha384, rsa_pss_pss_sha512, rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pkcs1_sha512, dsa_sha256, ecdsa_sha1, rsa_pkcs1_sha1, dsa_sha1]},"extended_master_secret (23)": {<empty>},"supported_versions (43)": {"versions": [TLSv1.2, TLSv1.1, TLSv1]}]
}
)
javax.net.ssl|FINE|3D|New I/O client worker #4-1|2022-01-23 15:53:10.704 CST|SSLEngineOutputRecord.java:505|WRITE: TLS12 handshake, length = 250
javax.net.ssl|FINE|3D|New I/O client worker #4-1|2022-01-23 15:53:10.779 CST|SSLEngineOutputRecord.java:523|Raw write (0000: 16 03 03 00 FA 01 00 00   F6 03 03 F9 D9 EC 33 BD  ..............3.0010: B7 E3 5F B2 0E F8 7B 5E   AE 10 F0 FC 6F 89 5A B6  .._....^....o.Z.0020: C6 F4 5C 17 EC A4 A6 85   60 CB 75 00 00 56 C0 2C  ..\.....`.u..V.,0030: C0 2B C0 30 00 9D C0 2E   C0 32 00 9F 00 A3 C0 2F  .+.0.....2...../0040: 00 9C C0 2D C0 31 00 9E   00 A2 C0 24 C0 28 00 3D  ...-.1.....$.(.=0050: C0 26 C0 2A 00 6B 00 6A   C0 0A C0 14 00 35 C0 05  .&.*.k.j.....5..0060: C0 0F 00 39 00 38 C0 23   C0 27 00 3C C0 25 C0 29  ...9.8.#.'.<.%.)0070: 00 67 00 40 C0 09 C0 13   00 2F C0 04 C0 0E 00 33  .g.@...../.....30080: 00 32 00 FF 01 00 00 77   00 0A 00 12 00 10 00 17  .2.....w........0090: 00 18 00 19 01 00 01 01   01 02 01 03 01 04 00 0B  ................00A0: 00 02 01 00 00 0D 00 22   00 20 04 03 05 03 06 03  .......". ......00B0: 08 04 08 05 08 06 08 09   08 0A 08 0B 04 01 05 01  ................00C0: 06 01 04 02 02 03 02 01   02 02 00 32 00 22 00 20  ...........2.". 00D0: 04 03 05 03 06 03 08 04   08 05 08 06 08 09 08 0A  ................00E0: 08 0B 04 01 05 01 06 01   04 02 02 03 02 01 02 02  ................00F0: 00 17 00 00 00 2B 00 07   06 03 03 03 02 03 01     .....+.........
)
javax.net.ssl|FINE|3D|New I/O client worker #4-1|2022-01-23 15:53:10.791 CST|SSLEngineInputRecord.java:177|Raw read (0000: 15 03 03 00 02 02 50                               ......P
)
javax.net.ssl|FINE|3D|New I/O client worker #4-1|2022-01-23 15:53:10.791 CST|SSLEngineInputRecord.java:214|READ: TLSv1.2 alert, length = 2
javax.net.ssl|FINE|3D|New I/O client worker #4-1|2022-01-23 15:53:10.792 CST|Alert.java:238|Received alert message (
"Alert": {"level"      : "fatal","description": "internal_error"
}
)

Apache Httpclient与 Netty的http请求

Apache Httpclient 请求,建立SSL连接时握手正常

​
CloseableHttpClient httpClient = HttpClients.custom().build();String url = "https://test.com";HttpGet httpGet = new HttpGet(url);HttpResponse response = httpClient.execute(httpGet);​

Netty的http异步请求,建立SSL连接时握手失败

private final AsyncHttpClient asyncHttpClient = new AsyncHttpClient(new NettyAsyncHttpProvider(new AsyncHttpClientConfig.Builder().build()));AsyncHttpClient.BoundRequestBuilder boundRequestBuilder = asyncHttpClient.preparePost(url);ListenableFuture<Response> future = boundRequestBuilder.execute();Response response = future.get(5, TimeUnit.SECONDS);

两者的错误日志相比较,Apache httpClient的请求中,扩展字段多了个server_name。

server_name

根据文档TLS扩展字段文档对server_name的描述,如下图片:

如果一台服务器托管多个域,那么很明显server_name是对于每个域的所有者来说,这是必要的,以确保满足他们的安全需要。
因为客户端可以显示不同的server_name。在应用程序协议中,应用程序服务器实现了这一点,必须检查这些名称是相同的,以确保客户端在应用程序协议中没有显示不同的名称。

错误原因

商户那边将多个域名绑定在一个主机上,开了SNI(Server Name Indication)。米币请求建立ssl连接时,没有传送上server name,而商户服务端检查了server_name扩展,则nginx不知道使用哪个server,直接抛出异常。
在Client Hello阶段,通过SNI扩展,将域名信息提前告诉服务器,服务器根据域名取得对应的证书返回给客户端已完成校验过程。

导致没有SNI的原因

  1. jdk版本过低,1.8的低版本和1.7或者1.6,均有可能,参考https://www.codetd.com/article/9814188
  2. httpclient,4.3.12这个版本之前的,会有这个bug
  3. 系统类设置了System.setProperty("jsse.enableSNIExtension", "false");

修复方案

方案一

继续使用 下面依赖的相关异步请求方法

<dependency><groupId>com.ning</groupId><artifactId>async-http-client</artifactId><version>1.7.14</version></dependency>

让请求携带上server_name, 创建特定的asyncHttpClient供该商户使用,其他商户依旧保持用之前的aysncHttpClient来调用。

SSLContext sslcontext = SSLContexts.createSystemDefault();SSLEngine sslEngine = sslcontext.createSSLEngine("test.com", 443);
// 配置sslEngine在握手时使用客户端模式。
sslEngine.setUseClientMode(true);AsyncHttpClientConfig.Builder builder = new AsyncHttpClientConfig.Builder().setSSLEngineFactory(new SSLEngineFactory() {@Overridepublic SSLEngine newSSLEngine() throws GeneralSecurityException {return sslEngine;}
});AsyncHttpClient asyncHttpClient = new AsyncHttpClient(new NettyAsyncHttpProvider(builder.build()));

方案二

用apache的HttpAsyncClient来处理

maven依赖

<dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.4.1</version>
</dependency>
<dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpasyncclient</artifactId><version>4.1.4</version>
</dependency>

相关调用代码如下:

CloseableHttpAsyncClient client = HttpAsyncClients.custom().setSSLHostnameVerifier(new NoopHostnameVerifier()).setDefaultRequestConfig(REQUEST_CONFIG).build();HttpPost httpPost = new HttpPost(url);
try {client.start();Future<HttpResponse> future = client.execute(httpPost, new FutureCallback<HttpResponse> {@Overridepublic void completed(HttpResponse result) {}@Overridepublic void failed(Exception e) {}@Overridepublic void cancelled() {}});HttpResponse response = future.get(5, TimeUnit.SECONDS);
} catch (Exception e) {logger.error("httpAysncPost exception, url: {}, message: {}", url, e.getMessage(), e);
}

注意:需要防止HttpAsyncClient资源没关闭引起的内存泄漏(吃过这方面的亏,哭唧唧)。
诊断由 Apache HttpAsyncClient 引起的内存泄漏 - Think different - 生活的美好

批量执行完一部分请求后,需要进行client.close(); 不然线程会飙升到几万,然后服务会挂掉。

方案三:

System.setProperty("jsse.enableSNIExtension", "true");

排查https请求出现received fatal alert: internal_error的问题相关推荐

  1. SoapUI 请求 https 报 javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure

    SoapUI 请求 https 报 javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure 在 E:\ ...

  2. HTTPS请求 Received fatal alert: handshake_failure异常---与众不同的原因

    今天在项目遇到了Received fatal alert: handshake_failure 的问题,直译就是握手失败了,说实话,我也是第一次碰到这种情况.在各大论坛,各种技术博客的海洋里,仍然没有 ...

  3. Could not transfer artifact (https://repo.maven.apache.org/maven2): Received fatal alert: protocol_v

    异常信息 Failed to read artifact descriptor for org.slf4j:slf4j-api:jar:1.7.2 org.eclipse.aether.resolut ...

  4. 使用Java访问https接口javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure

    jdk1.6,证书:SHA256+TLSv1.2 使用Java访问https://**************** 接口     控制台提示握手失败错误:javax.net.ssl.SSLHandsh ...

  5. javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure

    问题描述:在jdk1.7环境中使用HttpURLConnection发送https请求时,异常了 javax.net.ssl.SSLHandshakeException: Received fatal ...

  6. javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure问题解决

    最近在生产环境调用Https接口出现这个问题javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure,经 ...

  7. Received fatal alert: protocol_version

    1.问题: 由于后台第三方服务器进行了变更,所以服务地址进行了切换,但是切换地址后我本地(JDK1.8)是可以连通的,但是上测试服务器(JDK1.7)发送报文后,第三方运维人员说没有收到请求,并且返回 ...

  8. netty整合websocket支持自签证书出现netty websocket ssl Received fatal alert: certificate_unknown

    自签证书 win+r cmd 生成自己jks文件,指向自己要生成jks的文件位置下,我直接生成到项目resources下 #换成自己的本地ip keytool -genkey -alias serve ...

  9. 解决javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure报错

    背景 java版本1.8.0,今天尝试用Java去访问一个https接口,但抛出下面的异常: javax.net.ssl.SSLHandshakeException: Received fatal a ...

最新文章

  1. 再见,Python 2.x
  2. Spring Cloud剖析--云平台技术栈17
  3. 一种用XAML写Data Converter的方式
  4. 无休止加班的真正原因!你们公司是这样吗?
  5. 一台加密货币ATM机月营收额高达3万美金
  6. 为什么说红外热成像测温设备是2020年最火的弱电设备?
  7. c++计算eigen随笔(10)-数组、矩阵、向量(3)
  8. vtun 接收和发送数据流程图
  9. [国嵌攻略][085][共享内存通讯]
  10. 【7】AccessDB快速数据访问
  11. [scrum]2011/9/24-----第四天
  12. javascript 日期控件
  13. 了解JUnit的Runner架构
  14. windows8 开发教程 教你制作 多点触控Helper可将任意容器内任意对象进行多点缩放...
  15. Generate a Simulator Build command
  16. W ndows7安装Hp1020,hp1020打印机驱动
  17. 免费分享全套java学习资料
  18. 大华相机SDK调用——主动采图、外触发、参数
  19. 中兴网络设备交换机路由器查看日志命令方法
  20. 史上最“犯贱”的十首情歌

热门文章

  1. 实用技巧 | Chrome浏览器如何对标签页进行分组整理?
  2. PS色彩算法理解记录 1 Darken Lighten
  3. 查看网络交换机光口的光功率
  4. Linux进程的详细内容
  5. 央行紧急通知:你在用的这种支付方式将有重大变化
  6. 第一章 第一节 可充当主语的词类
  7. python 月球上物体的体重,1.重量计算。月球上物体体重是在地球上的16.5%,假如你在地球上每年增长0.5KG,输出未来十年你在地球和月球上的体重状况...
  8. 解决 Linux 系统,出现“不在sudoers文件中,此事将被报告”的问题
  9. 记录Pytorch在Linux环境下安装出现的pillow相关问题解决
  10. Zabbix(一):server,agent端 安装配置及主机,监控项,触发器,动作,媒介配置步骤...