因为公司系统与其余系统进行通信,时不时报出“peer not authenticated”这个错误,因而对这个错误进行分析。java
<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"/>
针对服务端用tomcat,客户端用jdk6形成这个报错的状况,先说下解决方案,主要有如下几个,1和2为客户端修改,3和4为服务端修改nginx
(1.6的ProtocolVersion类,设置FIPS为true也可)数组
1. 网上找资料,发现不少说要本身配置一个X509TrustManager跳过证书验证,实际上这一点已经作了,然而没有解决问题。浏览器
2. 分析https的socket工做原理流程图以下tomcat
(https通信流程)安全
3. 通过堆栈查看,发现证书不存在,也就是说重写X509TrustManager是绕过证书验证,但问题是连服务器的证书都未获取到,如何绕过验证呢?因此网上这些文章未解决这个问题。(图1为jdk6时的堆栈信息,certs为null,图2为jdk8时候的堆栈信息,获取到了certs)服务器
(jkd6 httpclient未获取到服务器证书)socket
(jdk8 httpclient获取到了服务器证书)maven
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
(jdk6打印socket日志)
jdk8是这样的:
main, WRITE: TLSv1.2 Handshake, length = 235 main, READ: TLSv1 Handshake, length = 686;
(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方法,正常执行,就不深刻看代码了。
(jdk6请求时,服务端走handleUnknowRecord,能够看到,this.helloVersion是TLSv1,而不是SSLv2Hello,抛出异常,服务端不返回证书)
6. 那么就有疑问了,客户端发出的是SSLV2的握手请求,可是服务端说“你的握手请求,不是SSLV2的请求,因此不容许经过”,!!!!!???!?!?!??!?!?这不是本身打脸么。。
好吧,那么继续看服务端源代码解答疑问。
7. tomcat接收socket处理流程
1)tomcat接收socket,tomcat是采用2个线程在处理的,一个是接收线程,一个是任务处理线程,接收线程接收请求后进行初始化,而后交给任务线程去处理(二者处理的socket对象id相同),因此咱们看到当有socket请求的时候,tomcat这边的堆栈是这样一个状况:
2)接收线程接收到后,要进行初始化,在com.sun.net.ssl.internal.ssl.InputRecord的setHelloVersion方法打个断点,跟进堆栈。接收者经过阻塞队列,接收到socket请求
3)进入serverSocketFactory.acceptSocket(serverSocket)方法,acceptSocket接收到socket,并执行doneConnect这个方法进行一些参数设置。
4)那么咱们看下localSSLSocketImpl.doneConnect();这段代码中,localSSLSocketImpl的握手协议是什么?会发现,刚进入时候,握手协议是SSLv2Hello
5)一层层跟进,发现有一处对这个对象的helloVersion作了改动,代码以下:
6)那么这个localProtocolVersion是哪里来的呢?一层层往外找,发现最外层传进来的,为this.enabledProtocols,而且看到this.enabledProtocols里面的值即为TLSv1
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"/>
经过分析发现
一、jdk6若是FIPS是false(干啥用的我也不清楚,其余时间再学习),默认用SSLv2Hello协议发送hello请求,而jdk8则默认用TLSv1
二、若是服务器是tomcat,tomcat若是未明确配置sslEnabledProtocols支持"SSLv2Hello",则默认为TLSV1,不支持SSLv2Hello
三、tomcat接收到客户端socket的hello请求后,强制将协议改成TLSV1
四、根据socket的握手报文数组中第一个元素的内容是否为20或22,jdk6发出来的请求将会调用handleUnknowRecord方法(jdk8发出来的走readV3Record方法,具体为什么不必继续研究)
五、handleUnknowRecord方法中会判断,若是协议非SSLv2Hello,则不容许经过,不会给客户端返回证书,服务端报错“SSLV2Hello is disabled”,客户端报错“peer not authenticated”
一、tomcat源码运行,只需本身新建maven项目,并将下载的源码中conf和java包分别放入新项目的代码和conf中便可,而且须要配置conf的输出目录,tomcat有读文件的控制
二、推荐不要用tomcat配置证书,用nginx配置证书比较好
三、TLS能够理解为SSL的升级版本,更安全,SSL有漏洞,因此tomcat开启也不要开启对SSL的支持,而是只开启SSLv2Hello的支持