怎麼 在客戶端 驗證 self-signed 證書. -- FTPS
- 在 Linux Server 上安裝好了 FTPS server, ( vsftpd 工具 )
- 在Server上生成 SSL 證書, 簽證的時候我是 自簽名的 self-signed。(當然,可以花錢由CA中心簽發電子證書)
如何 生成 self-signed certificate 見我前面 敘述的文章.
假定我們現在生成的 證書 為 server.cer
- 然後在 Client 上根據 server.cer 證書生成客戶端的 trustStore
如果 是self-signed 的證書,一定要在客戶端生成相應的trustStore, 因為
Man-In-Middle-Attact, 就是說,在Server返回Certificate的時候,第三者在中間截斷,然後生成一個偽冒的證書傳送給Client,這樣,Client就會用這張偽冒的證書裡面的Public Key去加密生成的傳送給Server的Secret Key. 這樣Client傳送的任何東西,第三方均可以獲取.
所以,我們需要實現建立好trustStore,並用它去驗證Server證書的合法性.
keytool -import -alias ca -file server.cer -keystore jassecacerts
這裡需要密碼來 import,可以隨便自己定義,但得牢記
這樣我們根據伺服器端的 self-signed certificate 生成了 自己的trustStore 叫 jassecacerts
- Client 建立FPTS例項連結 Server. 驗證過程如何?
根據官方文件,我們可以有兩種選擇
1. 把 jassecacerts 部署到 %JAVA_HOME%/lib/security 目錄中
2. 或者用程式 System.setProperty( "javax.net.ssl.trustStore", ".../jssecacerts"); 匯入
然後當 Client 連結 Server (hand shakes) 的時候, jsse 會根據 設定的 TrustStore 去自動校驗 certificate的合法性. 具體步驟是這樣的, 先在 %JAVA_HOME%/lib/security裡面查詢 jssecacerts裡面有沒有,然後再找cacerts裡面有沒有. 這樣去驗證 合法性...
事實真是這樣的嗎???? 經過我具體的測試,JSSE會去找,但是合法性驗證了沒,這裡,我用的是FTPS做的實驗,我打包票的說,沒有.... 那JSSE只做了些什麼呢? 就是 去找了下有沒有..... 如果沒有,就直接加入.
看如下的驗證過程:
我先匯入Gmail的證書, 用這個證書作為我自己Server證書的一個fake版本的.
Gmail證書如何生成的,很簡單,需要用到 Openssl,
1. Get the certificate from Gmail
openssl s_client -showcerts -connect gmail.google.com:443
2. 然後將 -----BEGIN CERTIFICATE----- 和 -----END CERTIFICATE----- 之間,也包括這兩行 直接拷貝到 gmail.cer
3. keytool -import -alias fake gmail.cert -keystore fakestore
這樣,我們就生成了一個 針對我自己server的一個假冒的truststore
然後我把這個假冒的trust store 匯入到 %JAVA_HOME%/lib/security中,當然得重新命名為 jssecacerts,然後我執行下面的這段測試程式碼:
FTPS _ftps = new FTPSClient();
_ftps.connect( _myserveraddr, 21 );
System.out.println( "Reply Code:" +_ftps.getReplyCode + "; Reply String: "+ _ftps.getReplyString() );
String[] enabledProtocols = _ftps.getEnabledProtocols();
for( String p : enabledProtocols ){
System.out.println("Enabled protocol for this connection: "+p);
}
呵呵,居然返回 234 Proceed with negotiation. 連結成功............
Enabled protocol for this connection: TLS
Enabled protocol for this connection: TLSv1
這裡我用了一個假的證書放到我的truststore裡面,而真的證書根本就沒放,它也連線成功。
然後我用 Djavax
java -Djavax.net.debug=SSL,handshake,data,trustmanager MyApp
從控制檯裡面發現,似乎 證書被載入到 truststore裡面了..
所以試想,如果這裡真的有第三者,充當 Man-In-Middle-attact 的角色,
當伺服器 將它自己的self-signed證書給客戶端的時候,被擷取,而擷取者將證書改成自己的假冒證書(類似於上面的gmail.cer),裡面有自己的public key,
這樣,如果當shake hands結束的時候,客戶端 exchange的private key就會被 擷取這截獲了..
所以,我們不能用 JSSE 預設的驗證方法.
- 所以,我們應該怎樣去驗證證書的合法性呢?我們需要手動建立TrustManager去驗證.
來吧,我們自己去驗證合法性... 通過建立一個自己的 TrustManager, 根據我們從伺服器上的cer匯入的 trustStore
看下面的程式碼
下面是我的核心程式碼片段.
_true_cert 是我真正的非仿冒的 truststore
_true_cert_pwd 是根據server.cer匯入truststore時用的密碼
注意,我們建立 一個keystore例項,然後倒入truely證書的truststore,然後根據匯入了的keystore生成我們的TrustManager,然後將這個trustmanger set 到我們的FTPSClient中... OK.. 這樣我們的客戶端在connect的時候,會根據這個TrustManager去做驗證了,如果從Server上得到的self-singed certificate和我本地Truststore裡面放的certificate不同,就會出錯...
有興趣可以將_true_cert換成我上面的那個假的證書_fake_cert,一驗證,發現通過不了.....
javax.net.ssl.SSLHandshakeException: com.ibm.jsse2.util.h: No trusted certificate found
呵呵....................... 仔細看下面的程式碼吧.. 原來真理都那麼簡單,可是找的過程卻如此複雜
// Set the trust store by System Property
System.setProperty( "com.ibm.ssl.trustStore", new File( _true_cert ).getAbsolutePath() );
System.setProperty( "javax.net.ssl.trustStore", new File( _true_cert ).getAbsolutePath() );
FTPSClient _ftps = null;
try{
_ftps = new FTPSClient();
KeyStore keyStore = KeyStore.getInstance( (KeyStore.getDefaultType()) );
keyStore.load(new FileInputStream( _true_cert ), _true_cert_pwd.toCharArray());
java.security.cert.Certificate ca = keyStore.getCertificate( "ca" );
// To check the certificate is really a fake one.
System.out.println(ca.toString());
// We need the trustStore to verify the server's certificate manual if it's a self-signed certificate.
// Set the keyStore for performance enhance when connecting. Optional step for the key manager.
KeyManagerFactory kmf = KeyManagerFactory.getInstance( "IbmX509" );
kmf.init( keyStore, _true_cert_pwd.toCharArray());
KeyManager[] kms = kmf.getKeyManagers();
_ftps.setKeyManager( kms[0] );
// Create the trustStore, It's must for verify the self-signed certificate
TrustManagerFactory tmf = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm() );
tmf.init(keyStore);
TrustManager[] tms = tmf.getTrustManagers();
_ftps.setTrustManager( tms[0] ); // -- very important step to verify the self-signed certificate
//Otherwise, if not set the trust manager to verify the self-signed certificate
//The FTPS client is not so smart to know whether it's a fake or not but just to loaded the certificate into its trustStore
/*
* Shake hands starting
*/
_ftps.connect( "prtdevmq.pok.ibm.com", 21);
Assert.assertEquals("Connect sucessfull", true, true );
System.out.println( "Connect sucessfull, Reply Code:" + _ftps.getReplyCode() +"; Reply String:"+ _ftps.getReplyString() );
String[] enabledProtocols = _ftps.getEnabledProtocols();
for( String p : enabledProtocols ){
System.out.println("Enabled protocol for this connection: "+p);
}