1. 程式人生 > >怎麼 在客戶端 驗證 self-signed 證書. -- FTPS

怎麼 在客戶端 驗證 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);
            }