我是如何解決java.security.cert.CertPathValidatorException異常的
問題來了
昨天,我還在我的工位上愉快的敲的程式碼,有位開發組的同事Z給我發訊息。
開發組同事Z:大哥,這個PKI的登入功能是你做的嗎?
我:是呀!N年前的事了。。。
開發組同事Z:PKI登入功能出問題了!有位客戶使用他的PKI登入我們做的系統,登入時報錯,換了幾臺電腦也不行。但是他使用他的PKI能登入其他的系統。能幫忙看一下嗎,我找過好多同事看過了,他們看了下,都不知道如何入手
我:報錯是報什麼錯?能具體的描述下嗎?
開發組同事Z:訪問系統時,能彈出證書的選擇框,選擇完證書後,Chrome瀏覽器的報錯資訊是:ERR_SSL_PROTOCOL_ERROR
我:行吧,我待會過去瞧瞧
問題分析
之前在配置tomcat的SSL時,我已經把客戶證書的根證書已經匯入到伺服器信任證書列表內(配置見附錄),“能彈出證書的選擇框”說明伺服器端是能識別出客戶證書,但是為什麼在登入的過程中,就報錯了,可能有如下的幾種原因:
- 伺服器端的證書和伺服器端的信任證書配置有誤;(我一直以為是伺服器端證書中的IP地址和伺服器的IP不一致,後來才發現這個是不可能的)
- 登入過程中,tomcat能讀取到客戶端的證書,在程式處理的過程中,拒絕該證書;(客戶端的證書的格式不被伺服器端接受)
- 客戶端證書過期了?(通過上面的描述好像不是這個錯誤)
- tomcat或者JDK有問題?(這個不太可能,其他使用者使用PKI能登入呀)
解決問題
博主當時也是有點懵逼,不知道該怎麼解決這個問題。好在博主有敢於嘗試的精神,決定一個一個試一下。
重新安裝伺服器端證書
首先使用keytool
工具生成伺服器端證書,再使用keytool
匯入客戶的根證書到伺服器端的信任證書庫中。然後把伺服器端證書和伺服器端的信任證書庫放入tomcat,配置、重啟tomcat(這裡省略了具體的配置過程,具體的配置步驟見附錄)。經過一番嘗試,發現選擇完證書還是報錯。又經過一番折騰,還是沒有找過是啥原因,正在灰心喪氣的時候,靈機一動,程式設計師不是應該從日誌中找錯誤嗎?於是從網上找了一下java中如何開啟SSL的日誌資訊。
日誌帶來曙光
在tomcat中加入JVM引數:-Djavax.net.debug=SSL,handshake,data,trustmanager
,重啟tomcat。使用客戶的證書重新登入,看到後臺打印出了很多的日誌資訊,瀏覽了一遍後,發現瞭如下的異常:
http-nio-8443-exec-4, fatal error: 46: General SSLEngine problem sun.security.validator.ValidatorException: PKIX path validation failed: java.security.cert.CertPathValidatorException: algorithm constraints check failed %% Invalidated:[Session-7, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256] http-nio-8443-exec-4, SEND TLSv1.2 ALERT:fatal, description = certificate_unknown http-nio-8443-exec-4, WRITE: TLSv1.2 Alert, length = 2 http-nio-8443-exec-4, fatal: engine already closed.Rethrowing javax.net.ssl.SSLHandshakeException: General SSLEngine problem http-nio-8443-exec-4, called closeOutbound() http-nio-8443-exec-4, closeOutboundInternal()
拿著這個異常資訊java.security.cert.CertPathValidatorException: algorithm constraints check failed
去google上搜索了一下,找到了如下的這兩個網站上的解決方法:
- https://stackoverflow.com/questions/21218217/ssl-handshake-exception-algorithm-constraints-check-failed-md5withrsa
- https://developer.ibm.com/answers/questions/366684/why-am-i-getting-algorithm-constraints-check-faile/
於是乎,按照上面的描述修改了JDK的配置資訊,重啟tomcat,使用證書登入,居然能正常登入了。心中暗自高興了好一會兒,滿滿的成就感,想不到居然修改了一點配置就解決了......在高興之餘,想著搞清楚這到底是怎麼一回事!接下來,容我慢慢道來。
刨根到底
到底是做了什麼神奇的操作,就解決這問題了呢!首先找到JDK安裝目錄下的這個檔案:
$JAVA_HOME/jre/lib/security/java.security
然後修改其中的兩項配置(為了確保不出報錯,把這兩項禁用的演算法全置為空):
# 處理證書路徑時禁用的演算法 jdk.certpath.disabledAlgorithms= # 處理SSL/TLS時禁用的演算法 jdk.tls.disabledAlgorithms=
然後重啟tomcat就行了。
先看一下禁用的演算法的語法,如下:
# 總體的語法 禁用的演算法1, 禁用的演算法2, 禁用的演算法3... # 禁用的每一項演算法語法 演算法名稱 keySize 操作符 數值值
例如:DSA
表示禁用DSA演算法,RSA keySize < 2048
表示禁用金鑰長度小於2048的RSA演算法,RSA keySize > 1024, RSA keySize < 2048
表示禁用金鑰長度大於1024小於2048的RSA演算法。
後來,仔細看了一下日誌,發現在客戶證書的資訊有這麼一行:
Signature Algorithm: SHA1withRSA, OID = 1.2.***.11***9.1.1.* Key:Sun RSA public key, 1023 bits
客戶證書的金鑰長度居然是1023,而JDK8中為了安全性預設禁用了金鑰長度小於2048的演算法。最終修改的配置如下:
jdk.certpath.disabledAlgorithms=MD2, RSA keySize < 512
問題就這樣解決了,哈哈!!!
總結
有時遇到問題,真的是讓人摸不到頭腦,這時彆著急,把自己能想到的方法先嚐試一遍,說不定就能行了呢。遇到問題首先可以藉助日誌來分析問題(就像上面那樣開啟JDK的SSL的除錯日誌的開關),通過日誌大概就能定位到時哪裡出問題了。在實際的開發中,很多程式猿基本上不會記錄日誌,稍微好一點的可能會在控制檯使用System.out.println(xxxx)
輸出日誌,不習慣使用像log4j這樣的日誌框架來記錄日誌。有人會說,出了問題,我可以直接debug呀!你有沒想過在生產環境中,你還能用IDE來進行除錯嗎?
附錄
tomcat的SSL配置
編輯conf/server.xml檔案加入如下的配置:
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol" SSLEnabled="true" maxThreads="150" scheme="https" secure="true" clientAuth="true" sslProtocol="TLS" keystoreFile="${catalina.base}/server.ks" keystorePass="123456" truststoreFile ="${catalina.base}/server_trust.ks" truststorePass="123456"/>
說明:
- clientAuth為true表示開啟SSL雙向認證
- keystoreFile指定伺服器端的證書位置
- truststoreFile指定伺服器端信任證書庫
伺服器端證書配置
-
生成伺服器端證書
keytool -genkeypair -v -alias server -keyalg RSA -validity 3650 -keystore ./server.ks-storepass 123456 -keypass 123456 -dname "CN=192.168.2.89,OU=zfx,O=zfx,L=gz,ST=gd,C=cn"
-
匯出伺服器端證書
keytool -exportcert -alias server-keystore ./server.ks-file ./server.cer-storepass 123456
-
匯入伺服器端證書到伺服器信任證書列表
keytool -importcert -alias serverca-keystore ./server_trust.ks-file ./server.cer-storepass 123456
-
匯入客戶根證書到伺服器信任證書列表
keytool -importcert -alias urootca -keystore ./server_trust.ks-file ./uroot.cer-storepass 123456
使用如下的命令檢視信任的證書資訊:
keytool -list -keystore ./server_trust.ks -storepass 123456
Keytool命令常用引數
- -genkeypair在使用者主目錄中建立一個預設檔案”.keystore”,還會產生一個mykey的別名,mykey中包含使用者的公鑰、私鑰和證書(在沒有指定生成位置的情況下,keystore會存在使用者系統預設目錄)
- -alias 產生別名 每個keystore都關聯這一個獨一無二的alias,這個alias通常不區分大小寫
- -keystore 指定金鑰庫的路徑(產生的各類資訊將不在.keystore檔案中)
- -keyalg 指定金鑰的演算法 (如 RSA,DSA,預設值為:DSA)
- -validity 指定建立的證書有效期多少天(預設 90)
- -keysize 指定金鑰長度 (預設 1024
- -storepass 指定金鑰庫的密碼(獲取keystore資訊所需的密碼)
- -keypass 指定別名條目的密碼(私鑰的密碼)
- -dname 指定證書發行者資訊 其中: “CN=名字與姓氏,OU=組織單位名稱,O=組織名稱,L=城市或區域名 稱,ST=州或省份名稱,C=單位的兩字母國家程式碼”
- -list 顯示金鑰庫中的證書資訊如:keytool -list -v –keystore path/to/keystore -storepass password
- -v 顯示金鑰庫中的證書詳細資訊
- -exportcert 匯出指定別名的證書,如:keytool - exportcert -alias theAlias -keystore path/to/keystore -file path/to/keystore/cert -storepass pass
- -file 引數指定匯出到檔案的檔名
- -delete 刪除金鑰庫中某條目 keytool -delete -alias theAlias -keystore path/to/keystore –storepass pass
- -printcert 控制檯列印證書的詳細資訊,如:keytool -printcert -file path/to/keystore/cert -v
- -keypasswd 修改金鑰庫中指定條目口令 keytool -keypasswd -alias theAlias -keypass oldPass -new newPass -storepass keystorePass -keystore path/to/keystore
- -storepasswd 修改keystore口令 keytool -storepasswd -keystore path/to/keystore -storepass oldPass -new newPass
- -importcert 將已簽名數字證書匯入金鑰庫 keytool -importcert -alias certAlias -keystore path/to/keystore -file path/to/keystore/cert