因爲公司系統與其餘系統進行通信,時不時報出「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的支持