2015-09-04 21 views
8

環境:Spring Integration 4.1.6(内部ではApache Commons Net 3.3をFTPSアクセス用に使用しているようです)を使用して、64ビットWindows 7上でSun Java JDK 1.8.0_60を使用しています。同じ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サーバーへの主な「制御」接続はうまくいきますが、リスト(または私が試した他のデータ接続)のデータ接続を開こうとするととなり、java.io.EOFException: SSL peer shut down incorrectlyが発生します。

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.. 

:私は嚥下例外がsession.listコマンド周りのブロックをキャッチコメントを解除した場合、私は、サーバーがデータ接続SSLハンドシェイクを拒否した後、次のメッセージを送っていること(javax.net.debug出力が)見ることができます何が起きているように見えますか(これは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)クライアントtのTLS/SSLセッションの再利用をサポートし、それは、Apache Commonsのネットライブラリを使用しない:

  • 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_方法が具体的にTLS/SSLセッションの再利用の実装を可能にするためにコモンズネットFTPSClientに追加されたようです:
    https://issues.apache.org/jira/browse/NET-426

    再利用のためのネイティブサポートがまだ保留されています
    https://issues.apache.org/jira/browse/NET-408

  • あなたのカスタムFTPSClient実装をセッション再利用サポートで返すには、Spring Integration DefaultFtpsSessionFactory.createClientInstance()をオーバーライドする必要があります。

+0

ありがとうございます。私はApache Commons Net JIRAを見ていましたが、明らかにあなたが行ったように発掘しませんでした。そして、作業中のプロジェクトのサンプルコードはうまく動作しているようです。 –

+0

あなたは大歓迎です。私は実際には(同じ問題(http://stackoverflow.com/q/7786352/850848)(C++/OpenSSLだけで、私はJavaをやっていない)ので、私は何のためにGoogleを知っていた。 –

+0

理由はわかりませんが、これはopenjdkバージョン "1.8.0_151" で動作しており、openjdkバージョン "1.8.0_161" –

2

私はsocket.getInetAddress().getHostName()の下でもsocket.getInetAddress().getHostAddress()下だけでなく、キーを保存しなければならなかった私にとってマーティンPrikrylの提案を動作させるために。 (hereから盗まれたソリューション。)

0

あなたはこのSSLSessionReuseFTPSClientクラスを使用することができます:我々は、設定しなければなりません

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

に応じ

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付きを http://www.oracle.com/technetwork/java/javase/8u161-relnotes-4021379.html

TLSセッションハッシュと拡張マスターシークレット拡張サポートの追加

互換性の問題が発生した場合、アプリケーションはシステムプロパティjdk.tlsを設定してこの拡張機能のネゴシエーションを無効にすることがあります。JDKでuseExtendedMasterSecretをfalseにする

関連する問題