問題來了
昨天,我還在我的工位上愉快的敲的代碼,有位開發組的同事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