jdk6出现peer not authenticated问题原因分析与解决

由于公司系统与其他系统进行通讯,时不时报出“peer not authenticated”这个错误,于是对这个错误进行分析。

一、场景重现

  1. 一台tomcat,自制证书,配置https方式访问,怎么用jdk生成密钥,怎么配置,网上很多文章,tomcat的server.xml配置如下(不需要放项目,空项目启动即可)
  2. <Connector port="443" protocol="HTTP/1.1" SSLEnabled="true"
                   maxThreads="150" scheme="https" secure="true"
                   clientAuth="false" sslProtocol="TLS"
    			   keystoreFile="e:/tomcat.keystore" keystorePass="tomcat"/>
  3. 浏览器可以直接访问221519_vUxz_1164681.png
  4. java客户端用httpclient访问(httpURLConnection一样的,底层都是socket)
  5. 客户端用jdk6访问,报错:peer not authenticated
  6. 客户端将jdk改为jdk8,可以成功请求。

二、先说解决方案

针对服务端用tomcat,客户端用jdk6造成这个报错的情况,先说下解决方案,主要有以下几个,1和2为客户端修改,3和4为服务端修改

  1. 客户端将jdk修改为1.8
  2. 客户端修改jdk源码,将sun.security.ssl.ProtocolVersion(jdk1.6)的DEFAULT_HELLO设置为“TLSv1”,并替换原有jdk中该类(1.8在sun.security.ssl包中),jdk8默认就是使用TSLv1 
  3. 服务端改用nginx做代理,在nginx上配置https(亲测可行)
  4. 服务端tomcat的service.xml的connector中配置sslEnabledProtocols="TLSv1,TLSv1.1,TLSv1.2,SSLv2Hello",即增加SSLv2Hello的支持  

 211937_uKZl_1164681.png

(1.6的ProtocolVersion类,设置FIPS为true也可)

三、问题分析流程

    1. 网上找资料,发现很多说要自己配置一个X509TrustManager跳过证书验证,实际上这一点已经做了,然而没有解决问题。

    2. 分析https的socket工作原理流程图如下

       

(https通讯流程)

      

    3. 经过堆栈查看,发现证书不存在,也就是说重写X509TrustManager是绕过证书验证,但问题是连服务器的证书都未获取到,如何绕过验证呢?所以网上这些文章未解决这个问题。(图1为jdk6时的堆栈信息,certs为null,图2为jdk8时候的堆栈信息,获取到了certs)

    164447_6Eey_1164681.png

(jkd6 httpclient未获取到服务器证书)

165120_gAAC_1164681.png

(jdk8 httpclient获取到了服务器证书)

    4. ok,那既然jdk6不行,jdk8可以,那么这两者发的消息有什么不一样呢?客户端请求代码中加上这一段:

System.setProperty("javax.net.debug","ssl");

将socket通讯记录打印出来,发现在client发送Hello请求的时候,

jdk6是这样的:

main, WRITE: TLSv1 Handshake, length = 81
main, WRITE: SSLv2 client hello message, length = 110
main, READ: TLSv1 Alert, length = 2
main, RECV TLSv1 ALERT:  fatal, handshake_failure

 165632_M5tS_1164681.png

(jdk6打印socket日志)

jdk8是这样的:

main, WRITE: TLSv1.2 Handshake, length = 235
main, READ: TLSv1 Handshake, length = 686;

 170304_KH8O_1164681.png

 (jdk8打印socket日志)

jdk6用的SSLv2,jdk8用的TLSv1

    5. 那么服务端的tomcat是如何处理的呢?(服务端用的tomcat6+jdk6,经测试,tomcat8+jdk8是一样的情况)

jdk1.6发送过来的SSLV2Hello请求,被handleUnknowRecord这个方法处理,其中有个判断

if(this.helloVersion != ProtocolVersion.SSL20Hello){

    throw new SSLHandshakeException("SSLv2Hello is disabled").

 }

从这里抛出了异常,所以没有返回证书,握手失败。但是jdk1.8则走的readV3Record方法,正常执行,就不深入看代码了。

 173415_kUwz_1164681.png

 173503_Rb9e_1164681.png

173839_3RIf_1164681.png

(jdk6请求时,服务端走handleUnknowRecord,可以看到,this.helloVersion是TLSv1,而不是SSLv2Hello,抛出异常,服务端不返回证书)

 173539_lwRF_1164681.png

173557_uWWs_1164681.png

    6.  那么就有疑问了,客户端发出的是SSLV2的握手请求,但是服务端说“你的握手请求,不是SSLV2的请求,所以不允许通过”,!!!!!???!?!?!??!?!?这不是自己打脸么。。

好吧,那么继续看服务端源代码解答疑问。

    7. tomcat接收socket处理流程

        1)tomcat接收socket,tomcat是采用2个线程在处理的,一个是接收线程,一个是任务处理线程,接收线程接收请求后进行初始化,然后交给任务线程去处理(两者处理的socket对象id相同),所以我们看到当有socket请求的时候,tomcat这边的堆栈是这样一个情况:

200551_s6gA_1164681.png

    2)接收线程接收到后,要进行初始化,在com.sun.net.ssl.internal.ssl.InputRecord的setHelloVersion方法打个断点,跟进堆栈。接收者通过阻塞队列,接收到socket请求

204409_kv9D_1164681.png

    3)进入serverSocketFactory.acceptSocket(serverSocket)方法,acceptSocket接收到socket,并执行doneConnect这个方法进行一些参数设置。

204238_Y56l_1164681.png

    4)那么我们看下localSSLSocketImpl.doneConnect();这段代码中,localSSLSocketImpl的握手协议是什么?会发现,刚进入时候,握手协议是SSLv2Hello

 210022_867p_1164681.png

    5)一层层跟进,发现有一处对这个对象的helloVersion做了改动,代码如下:

213410_oXbg_1164681.png

    6)那么这个localProtocolVersion是哪里来的呢?一层层往外找,发现最外层传进来的,为this.enabledProtocols,并且看到this.enabledProtocols里面的值即为TLSv1

 212755_GtZD_1164681.png

   8.修改下tomcat server.xml配置,增加sslEnabledProtocols="TLSv1,TLSv1.1,TLSv1.2,SSLv2Hello",明确使其支持SSLv2Hello协议,再调试一遍,发现this.enabledProtocols的helloVersion变为了SSLv2Hello

<Connector port="443" protocol="HTTP/1.1" SSLEnabled="true"
               maxThreads="150" scheme="https" secure="true"
               clientAuth="false" sslProtocol="TLS" 
               sslEnabledProtocols="TLSv1,TLSv1.1,TLSv1.2,SSLv2Hello"
               keystoreFile="e:/tomcat.keystore" keystorePass="tomcat"/>

214023_1dqr_1164681.png

结论

通过分析发现

1、jdk6如果FIPS是false(干啥用的我也不清楚,其他时间再学习),默认用SSLv2Hello协议发送hello请求,而jdk8则默认用TLSv1

2、如果服务器是tomcat,tomcat如果未明确配置sslEnabledProtocols支持"SSLv2Hello",则默认为TLSV1,不支持SSLv2Hello

3、tomcat接收到客户端socket的hello请求后,强制将协议改为TLSV1

4、根据socket的握手报文数组中第一个元素的内容是否为20或22,jdk6发出来的请求将会调用handleUnknowRecord方法(jdk8发出来的走readV3Record方法,具体为何没必要继续研究)

5、handleUnknowRecord方法中会判断,如果协议非SSLv2Hello,则不允许通过,不会给客户端返回证书,服务端报错“SSLV2Hello is disabled”,客户端报错“peer not authenticated”

其他说明

1、tomcat源码运行,只需自己新建maven项目,并将下载的源码中conf和java包分别放入新项目的代码和conf中即可,并且需要配置conf的输出目录,tomcat有读文件的控制

2、推荐不要用tomcat配置证书,用nginx配置证书比较好

3、TLS可以理解为SSL的升级版本,更安全,SSL有漏洞,所以tomcat开启也不要开启对SSL的支持,而是只开启SSLv2Hello的支持

 

转载于:https://my.oschina.net/u/1164681/blog/863363

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值