2015-09-04 68 views
8

環境:我在64位Windows 7上使用Sun Java JDK 1.8.0_60,使用Spring Integration 4.1.6(內部似乎使用Apache Commons Net 3.3進行FTPS訪問)。如何使用相同的TLS會話連接到具有數據連接的FTPS服務器?

我試圖與我們的應用程序集成從我們客戶的FTPS服務器自動下載。我已經成功地使用了Spring Integration的SFTP服務器,沒有任何問題,但沒有任何問題,但這是客戶第一次要求我們使用FTPS,並且讓它連接非常令人費解。雖然在我的真實應用程序中,我使用XML beans配置Spring Integration,試圖瞭解什麼不起作用我正在使用以下測試代碼(儘管我在此匿名實際的主機/用戶名/密碼):

final DefaultFtpsSessionFactory sessionFactory = new DefaultFtpsSessionFactory(); 
sessionFactory.setHost("XXXXXXXXX"); 
sessionFactory.setPort(990); 
sessionFactory.setUsername("XXXXXXX"); 
sessionFactory.setPassword("XXXXXXX"); 
sessionFactory.setClientMode(2); 
sessionFactory.setFileType(2); 
sessionFactory.setUseClientMode(true); 
sessionFactory.setImplicit(true); 
sessionFactory.setTrustManager(TrustManagerUtils.getAcceptAllTrustManager()); 
sessionFactory.setProt("P"); 
sessionFactory.setProtocol("TLSv1.2"); 
sessionFactory.setProtocols(new String[]{"TLSv1.2"}); 
sessionFactory.setSessionCreation(true); 
sessionFactory.setCipherSuites(new String[]{"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"}); 

final FtpSession session = sessionFactory.getSession(); 
//try { 
    final FTPFile[] ftpFiles = session.list("/"); 
    logger.debug("FtpFiles: {}", (Object[]) ftpFiles); 
//} catch (Exception ignored) {} 
session.close(); 

我使用-Djavax.net.debug=all運行此代碼以獲取打印的所有TLS調試信息。

到FTPS服務器的主要「控制」連接工作正常,但當它試圖打開列表(或任何其他數據連接我試過)的數據連接時,我得到javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake,由java.io.EOFException: SSL peer shut down incorrectly造成。如果我去掉吞嚥的例外趕上圍繞session.list命令塊,然後我可以看到(雖然javax.net.debug輸出)服務器拒絕數據連接SSL握手後發送的以下消息:

main, READ: TLSv1.2 Application Data, length = 129 
Padded plaintext after DECRYPTION: len = 105 
0000: 34 35 30 20 54 4C 53 20 73 65 73 73 69 6F 6E 20 450 TLS session 
0010: 6F 66 20 64 61 74 61 20 63 6F 6E 6E 65 63 74 69 of data connecti 
0020: 6F 6E 20 68 61 73 20 6E 6F 74 20 72 65 73 75 6D on has not resum 
0030: 65 64 20 6F 72 20 74 68 65 20 73 65 73 73 69 6F ed or the sessio 
0040: 6E 20 64 6F 65 73 20 6E 6F 74 20 6D 61 74 63 68 n does not match 
0050: 20 74 68 65 20 63 6F 6E 74 72 6F 6C 20 63 6F 6E the control con 
0060: 6E 65 63 74 69 6F 6E 0D 0A      nection.. 

看起來發生了什麼(這是我第一次處理FTPS,雖然我之前已經處理過普通的FTP)是服務器通過控制和數據連接確保身份驗證和加密的方式是,在「正常」 TLS連接建立控制連接和身份驗證發生在那裏,每個數據連接都需要客戶端連接相同的TLS會話。這對我來說很有意義,因爲它應該如何工作,但Apache Commons Net FTPS實現似乎沒有這樣做。它似乎試圖建立一個新的TLS會話,因此服務器拒絕嘗試。

根據this question about resuming SSL sessions in JSSE,似乎Java爲每個主機/帖子組合設定或需要不同的會話。我的假設是,由於FTPS數據連接位於與控制連接不同的端口上,因此它不會找到現有會話並嘗試建立新連接,因此連接失敗。

我看到三個主要的可能性:

  1. 服務器未按照要求數據端口上的相同TLS會話控制端口上FTPS標準。我可以使用FileZilla 3.13.1連接到服務器(使用與我在代碼中使用的主機/用戶/密碼相同的服務器)。服務器在登錄時將自己標識爲「FileZilla Server 0.9.53 beta」,所以這可能是某種專有的FileZilla做事方式,我需要做一些奇怪的事情來說服Java使用相同的TLS會話。
  2. Apache Commons Net客戶端實際上並不遵循FTPS標準,只允許一些不允許保護數據連接的子集。這看起來很奇怪,因爲它似乎是從Java內部連接到FTPS的標準方式。
  3. 我完全錯過了一些東西,並且誤診了這件事。

我很感謝你提供關於如何連接到這種FTPS服務器的任何方向。謝謝。

+0

請參閱此鏈接瞭解詳情https://stackoverflow.com/questions/46631315/when-using-java-apache-ftpclient-for-ftp-tls-getting-remote-host-closed -connect/48616779#48616779 –

回答

13

事實上,某些FTP(S)服務器確實需要TLS/SSL會話重新用於數據連接。這是一種安全措施,通過該措施,服務器可以驗證數據連接是否由同一個客戶端用作控制連接。

常見的FTP服務器的一些參考:


什麼可以幫助你與執行是tha牛逼Cyber​​duck的FTP(S)客戶端不支持TLS/SSL會話重用,它使用Apache共享網庫:

  • https://trac.cyberduck.io/ticket/5087 - 數據連接

  • 上重用會話密鑰看到它的FTPClient.java代碼(擴展共享網FTPSClient),特別是其override of _prepareDataSocket_ method

    @Override 
    protected void _prepareDataSocket_(final Socket socket) throws IOException { 
        if(preferences.getBoolean("ftp.tls.session.requirereuse")) { 
         if(socket instanceof SSLSocket) { 
          // Control socket is SSL 
          final SSLSession session = ((SSLSocket) _socket_).getSession(); 
          if(session.isValid()) { 
           final SSLSessionContext context = session.getSessionContext(); 
           context.setSessionCacheSize(preferences.getInteger("ftp.ssl.session.cache.size")); 
           try { 
            final Field sessionHostPortCache = context.getClass().getDeclaredField("sessionHostPortCache"); 
            sessionHostPortCache.setAccessible(true); 
            final Object cache = sessionHostPortCache.get(context); 
            final Method method = cache.getClass().getDeclaredMethod("put", Object.class, Object.class); 
            method.setAccessible(true); 
            method.invoke(cache, String.format("%s:%s", socket.getInetAddress().getHostName(), 
              String.valueOf(socket.getPort())).toLowerCase(Locale.ROOT), session); 
            method.invoke(cache, String.format("%s:%s", socket.getInetAddress().getHostAddress(), 
              String.valueOf(socket.getPort())).toLowerCase(Locale.ROOT), session); 
           } 
           catch(NoSuchFieldException e) { 
            // Not running in expected JRE 
            log.warn("No field sessionHostPortCache in SSLSessionContext", e); 
           } 
           catch(Exception e) { 
            // Not running in expected JRE 
            log.warn(e.getMessage()); 
           } 
          } 
          else { 
           log.warn(String.format("SSL session %s for socket %s is not rejoinable", session, socket)); 
          } 
         } 
        } 
    } 
    
  • 看來,_prepareDataSocket_方法,將共享網絡FTPSClient明確允許TLS/SSL會話重用實現:
    https://issues.apache.org/jira/browse/NET-426

    的再利用本機支持仍懸而未決:
    https://issues.apache.org/jira/browse/NET-408

  • 您顯然需要重寫Spring集成DefaultFtpsSessionFactory.createClientInstance()以將您的自定義FTPSClient實現返回到會話重用支持。

+0

非常感謝。我一直在瀏覽Apache Commons Net JIRA,但顯然並沒有像你那樣挖掘。並且來自工作項目的示例代碼看起來效果很好 –

+0

不客氣。我過去實際上有[同樣的問題](http://stackoverflow.com/q/7786352/850848)(只是在C++/OpenSSL中,我不是在做Java),所以我知道該怎麼辦。 –

+0

不知道爲什麼,但這是與openjdk版本「1.8.0_151」 和不使用openjdk版本「1.8.0_161」 –

2

爲了讓馬丁Prikryl的建議工作,爲我,我有存儲不僅在socket.getInetAddress().getHostName()還要根據socket.getInetAddress().getHostAddress()的關鍵。 (解決方案從here被盜。)

0

您可以使用此SSLSessionReuseFTPSClient類:

import java.io.IOException; 
import java.lang.reflect.Field; 
import java.lang.reflect.Method; 
import java.net.Socket; 
import java.util.Locale; 

import javax.net.ssl.SSLSession; 
import javax.net.ssl.SSLSessionContext; 
import javax.net.ssl.SSLSocket; 

import org.apache.commons.net.ftp.FTPSClient; 

public class SSLSessionReuseFTPSClient extends FTPSClient { 

    // adapted from: 
    // https://trac.cyberduck.io/browser/trunk/ftp/src/main/java/ch/cyberduck/core/ftp/FTPClient.java 
    @Override 
    protected void _prepareDataSocket_(final Socket socket) throws IOException { 
     if (socket instanceof SSLSocket) { 
      // Control socket is SSL 
      final SSLSession session = ((SSLSocket) _socket_).getSession(); 
      if (session.isValid()) { 
       final SSLSessionContext context = session.getSessionContext(); 
       try { 
        final Field sessionHostPortCache = context.getClass().getDeclaredField("sessionHostPortCache"); 
        sessionHostPortCache.setAccessible(true); 
        final Object cache = sessionHostPortCache.get(context); 
        final Method method = cache.getClass().getDeclaredMethod("put", Object.class, Object.class); 
        method.setAccessible(true); 
        method.invoke(cache, String 
          .format("%s:%s", socket.getInetAddress().getHostName(), String.valueOf(socket.getPort())) 
          .toLowerCase(Locale.ROOT), session); 
        method.invoke(cache, String 
          .format("%s:%s", socket.getInetAddress().getHostAddress(), String.valueOf(socket.getPort())) 
          .toLowerCase(Locale.ROOT), session); 
       } catch (NoSuchFieldException e) { 
        throw new IOException(e); 
       } catch (Exception e) { 
        throw new IOException(e); 
       } 
      } else { 
       throw new IOException("Invalid SSL Session"); 
      } 
     } 
    } 
} 

,並與OpenJDK的1.8.0_161:

我們必須設置:

System.setProperty("jdk.tls.useExtendedMasterSecret", "false"); 

根據 http://www.oracle.com/technetwork/java/javase/8u161-relnotes-4021379.html

添加了TLS會話散列和擴展主密鑰擴展支持

如果出現兼容性問題,應用程序可能會通過設置系統屬性jdk.tls來禁用此擴展的協商。在JDK中使用ExtendedMasterSecret爲false

相關問題