执子之手

与子偕老


  • 首页

  • 分类

  • 归档

  • 标签

  • 关于

  • 搜索
close

Java SSL握手记录分析

时间: 2017-04-28   |   分类: 开发     |   阅读: 2707 字 ~6分钟   |   访问: 0

公司项目当中经常使用CXF库连接WebService服务,而且我们自己提供的服务也是基于CXF的SOAP服务,经常需要指导客户怎么连接我们的服务。CXF库整个体系架构比较庞大,相关的类、知识点都比较多。尤其是连接SSL双向认证服务的时候,经常碰到问题。

使用双向SSL认证的时候,在Spring中配置起来非常简单,但是一旦出错就比较难找问题。经过多次尝试,最后发现还是分析SSL握手记录比较靠谱,比较容易找出真正的问题所在。特此记录一下自己的一些心得,以作备忘并希望能够帮到其他人。

1. Spring中的配置

使用CXF连接SOAP WebService,在Spring中配置起来非常简单。

1.1 spring配置

 1...
 2<context:property-placeholder location="classpath:config.properties" />
 3
 4<jaxws:client id="devicelinkService"
 5   serviceClass="com.service.api_3_0.ApiCustomerService30"
 6   address="${devicelink.url}">
 7</jaxws:client>
 8
 9<http-conf:conduit name="${devicelink.serveraddress}/.*">
10   <!-- 客户端证书认证相关配置 //-->
11   <http-conf:tlsClientParameters disableCNCheck="true">
12       <sec:keyManagers keyPassword="${cert.file.pwd}">
13           <sec:keyStore type="PKCS12" password="${cert.file.pwd}" resource="${cert.file}"/>
14       </sec:keyManagers>
15       <sec:trustManagers>
16           <sec:keyStore type="JKS" password="changeit" resource="cacerts" />
17       </sec:trustManagers>
18   </http-conf:tlsClientParameters>
19   
20   <http-conf:client Connection="Keep-Alive"
21       MaxRetransmits="1" AllowChunking="false"/>
22   <!-- 如果服务支持Basic认证,则可以使用下面的配置 //-->
23   <!--
24   <http-conf:authorization>
25       <sec:UserName>${devicelink.username}</sec:UserName>
26       <sec:Password>${devicelink.password}</sec:Password>
27       
28       <sec:AuthorizationType>Basic</sec:AuthorizationType>
29   </http-conf:authorization>
30   //-->
31</http-conf:conduit>
32...

几个配置项:

  • jaxws:client 用来声明一个Spring Bean。可以在代码中使用@Autowired引用;
  • http-conf:conduit 用来配置认证相关的内容;
  • http-conf:tlsClientParameters 配置SSL认证相关的内容。可以配置Java SSL用的keyManager/trustManager;
  • http-conf:authorization 配置Basic认证相关信息,可以指定Basic认证所需的用户名、密码等。

上面的代码中使用了几个变量,他们是通过context:property-placeholder引入的property文件定义的。包括:

  • deviceLink.url 服务访问地址
  • deviceLink.serveraddress 服务器地址
  • cert.file 证书地址
  • cert.file.pwd 证书密码
  • deviceLink.username 访问服务的用户名
  • deviceLink.password 访问服务的密码

1.2 config.properties

这是properties文件中定义的变量,通过context:property-placeholder配置被引入Spring配置中。

1devicelink.serveraddress=http://server.address
2devicelink.url=http://server.address/api/CustomerApi30
3devicelink.username=...
4devicelink.password=...
5cert.file.pwd=...
6cert.file=....p12

使用CXF的相关配置就是这么多,确实比较简单。这是因为CXF隐藏了很多实现的细节。但是这也带来难以排错的弊端。下面将介绍一下如何通过SSL握手日志找到问题所在。

2. 握手过程

通常的握手过程是这样的: Java SSL也是同样的步骤。粗略分为三步:

  • ClientHello
  • ServerHello
  • KeyExchange
  • 握手结束

3. 打开SSL握手日志

首先说一下如何打开SSL握手日志。方法是在Java启动命令行中增加-Djavax.net.debug=SSL或者-Djavax.net.debug=ALL即可打开SSL握手日志。

4. 日志分析

Java的握手日志中,使用***作为每一段内容的分隔符。使用这个分隔符可以把几步内容大体上分隔开,所以阅读起来还是比较方便的。

4.0. 准备阶段

4.0.1 找到客户端证书

当配置了客户端证书的时候,首先打印的就是找到了key。如下:

1found key for : did.xwf-id.com key
2chain [0] = [
3[
4  ...

如果没有配置客户端证书,或者配置有问题,则不会有这一部分日志出现。

4.0.2 添加信任证书

然后就是从系统中添加可信任的CA证书,这个过程会添加很多证书进来。来源主要有两个:

  • $JAVA_HOME/jre/lib/security/cacerts
  • 程序中配置的trustStore指定的证书库
1adding as trusted cert:
2  Subject: CN=Equifax Secure Global eBusiness CA-1, O=Equifax Secure Inc., C=US
3...

4.1. ClientHello

准备阶段不算是正式的SSL握手过程,只是创建了SSL握手需要的环境(Context)。从ClientHello开始,SSL握手正式开始:

1*** ClientHello, TLSv1
2RandomCookie: ...
3Session ID:  {}
4Cipher Suites: [TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,...]
5Compression Methods:  { 0 }
6Extension elliptic_curves, curve names: {secp256r1, ...}
7Extension ec_point_formats, formats: [uncompressed]

4.2. ServerHello

ClientHello消息被发送给服务提供方,然后收到的消息就是服务器返回的ServerHello消息报文。在Java的SSL握手日志中是看不到服务器上的处理流程的,能看到的只是Java处理这个报文的解析过程。这个一定要清楚:日志中显示的都是调用者处理的日志,并不是服务器端的实际处理顺序,所以顺序可能和服务器端不一致,这是正常的。

首先显示的是Server选中的加密算法,这里是TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:

1*** ServerHello, TLSv1
2RandomCookie:  GMT: -1611070835 bytes = { ...}
3Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
4Compression Method: 0
5Extension renegotiation_info, renegotiated_connection: <empty>
6Extension ec_point_formats, formats: [uncompressed, ansiX962_compressed_prime, ansiX962_compressed_char2]

收到ServerHello之后,就开始根据收到的内容创建Session,证书验证、交换密钥等操作了。

4.2.1 初始化Session

初始化Session:

1***
2%% Initialized:  [Session-1, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA]
3...

4.2.2 网站证书链

从之前添加的CA证书中找到了证书链,说明服务器证书是合法的:

 1*** Certificate chain
 2chain [0] = [
 3[
 4  Version: V3
 5  Subject: CN=*.xwf-id.com, OU=IT DEPT, O="Iraid Finance Information & Technology (Shanghai) Co.,Ltd", L=Shanghai, ST=Shanghai, C=CN
 6  Signature Algorithm: SHA256withRSA, OID = 1.2.840.113549.1.1.11
 7...
 8]
 9chain [1] = [
10[
11  Version: V3
12  Subject: CN=Symantec Class 3 Secure Server CA - G4, OU=Symantec Trust Network, O=Symantec Corporation, C=US
13  Signature Algorithm: SHA256withRSA, OID = 1.2.840.113549.1.1.11
14  ...
15]

然后就提示找到了可信任的证书,也就是上面chain [1]中对应的证书:

1***
2Found trusted certificate:
3[
4[
5  Version: V3
6  Subject: CN=Symantec Class 3 Secure Server CA - G4, OU=Symantec Trust Network, O=Symantec Corporation, C=US
7  Signature Algorithm: SHA256withRSA, OID = 1.2.840.113549.1.1.11
8...

如果服务提供方的证书是自签名证书,那么要注意日志中是否出现了上述日志。如果没有,则很有可能握手失败。需要将提供方的证书添加到trustManager中对应的证书库中来解决问题。

4.2.3 ECDH ServerKeyExchange

密钥交换算法初始化,这里是ECDH算法的:

1*** ECDH ServerKeyExchange
2Server key: Sun EC public key, 256 bits
3  public x coord: 43374987853469150916971894311198082576230263269301289184949927385170106253395
4  public y coord: 92135670098354144912439154833828289482869500140098079447653500663810560580845
5  parameters: secp256r1 [NIST P-256, X9.62 prime256v1] (1.2.840.10045.3.1.7)

4.2.4 CertificateRequest

ServerHelloDone报文中包含了服务器可以接受的客户端证书的CA列表。客户端需要根据这个CA列表从本地筛选可用的客户端证书。

1*** CertificateRequest
2Cert Types: RSA, DSS, ECDSA
3Cert Authorities:
4<EMAILADDRESS=mailadmin@xwf-id.com, CN=devborgen.xwf-id.com, OU=Operation Department, O=iRaid, L=Shanghai, ST=Shanghai, C=CN>
5<EMAILADDRESS=mailadmin@xwf-id.com, CN=devcap.xwf-id.com, OU=Development Department, O=iRaid, L=Shanghai, ST=Shanghai, C=CN>

如果服务器端没有要求客户端证书验证,则没有上述内容。

4.2.5 ServerHelloDone

ServerHello信息解析完毕。

 1*** ServerHelloDone
 2[read] MD5 and SHA1 hashes:  len = 4
 30000: 0E 00 00 00                                        ....
 4matching alias: did.xwf-id.com key
 5*** Certificate chain
 6chain [0] = [
 7[
 8[
 9  Version: V3
10  Subject: CN=did.xwf-id.com
11  Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5
12  ...

上面代表找到了客户端证书。如果服务器端开启了强制要求客户端证书,而本地没有配置,则会出现这样的提示:

1*** ServerHelloDone
2[read] MD5 and SHA1 hashes:  len = 4
30000: 0E 00 00 00                                        ....
4Warning: no suitable certificate found - continuing without client authentication
5*** Certificate chain
6<Empty>

出现了上述提示,就需要检查keyManager中的配置是否正确,查查为什么找不到客户端证书了。

4.3. ClientKeyExchange

最后一步就是密钥交换:

1***
2*** ECDHClientKeyExchange
3ECDH Public value:  { ... }
4...
5*** CertificateVerify
6...
7*** Finished
8verify_data:  { 234, 76, 169, 101, 128, 33, 222, 63, 185, 227, 150, 56 }
9...

至此握手结束,生成了session并进行了缓存。

1***
2%% Cached client session: [Session-1, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA]

通过上面的日志分析,可以有效地发现经常碰到的问题,也容易定位问题究竟出在哪儿。

附录A. 参考资料

  • 深入研究SSL【第二章 part-1】-SSL握手协议的研究
  • SSL/TLS 握手优化详解

附录B. 补充知识-证书链

配置服务端证书链时,有两点需要注意:1)证书是在握手期间发送的,由于 TCP 初始拥塞窗口的存在,如果证书内容太长可能会产生额外的往返开销;2)如果配置的证书没包含中间证书,大部分浏览器可以正常工作,方法是暂停验证并根据站点证书指定的父证书 URL 自己获取中间证书。这个过程会产生额外的 DNS 解析、建立 TCP 连接等开销,非常影响性能。

基于此,配置证书链的的最佳实践是只包含站点证书和中间证书,不要包含根证书,但不要漏掉中间证书。大部分证书都是「站点证书 – 中间证书 – 根证书」这样三级,这时服务端只需要发送前两个证书(站点证书 - 中间证书)即可。但也有的证书有四级,那就需要发送站点证书外加两个中间证书了。

#Java# #SSL#
在Tomcat中使用ThreadLocal以及Session
通过Zabbix监控业务数据(续)
  • 文章目录
  • 站点概览
Orchidflower

Orchidflower

Do one thing at a time, and do well.

77 日志
6 分类
84 标签
GitHub 知乎 OSC 豆瓣
  • 1. Spring中的配置
    • 1.1 spring配置
    • 1.2 config.properties
  • 2. 握手过程
  • 3. 打开SSL握手日志
  • 4. 日志分析
    • 4.0. 准备阶段
      • 4.0.1 找到客户端证书
      • 4.0.2 添加信任证书
    • 4.1. ClientHello
    • 4.2. ServerHello
      • 4.2.1 初始化Session
      • 4.2.2 网站证书链
      • 4.2.3 ECDH ServerKeyExchange
      • 4.2.4 CertificateRequest
    • 4.2.5 ServerHelloDone
    • 4.3. ClientKeyExchange
  • 附录A. 参考资料
  • 附录B. 补充知识-证书链
© 2009 - 2024 执子之手
Powered by - Hugo v0.113.0
Theme by - NexT
ICP - 鲁ICP备17006463号-1
0%