javax.net.ssl.SSLHandshakeException:握手FTPS期间远程主机关闭连接

时间:2018-02-19 17:00:07

标签: java ftp ftp-client ftps sslhandshakeexception

我正在尝试连接到需要'显式FTP over TLS'的FTP。我试图从我的本地机器上做到这一点。

以下是我使用的代码

FTPSClient ftpClient = new FTPSClient(false);
      ftpClient.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out)));
      ftpClient.setAuthValue("TLS");
      ftpClient.connect(host, port);
      int reply = ftpClient.getReplyCode();
      if (FTPReply.isPositiveCompletion(reply)) {

        if (ftpClient.login(username, password)) {

          ftpClient.execPBSZ(0);
          ftpClient.execPROT("P");
          ftpClient.enterLocalPassiveMode();

      InputStream is = new FileInputStream(localFilename);
      if (ftpClient.storeFile(remoteFilename, is)) {
        is.close();
      } else {
        System.out.println("Could not store file");
      }
      ftpClient.logout();

        } else {
          System.out.println("FTP login failed");
        }

        // Disconnect
        ftpClient.disconnect();

      } else {
        System.out.println("FTP connect to host failed");
      }

当我跑步时,我得到以下异常

    Could not connect to server.
javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake
    at sun.security.ssl.SSLSocketImpl.readRecord(Unknown Source)
    at sun.security.ssl.SSLSocketImpl.performInitialHandshake(Unknown Source)
    at sun.security.ssl.SSLSocketImpl.startHandshake(Unknown Source)
    at sun.security.ssl.SSLSocketImpl.startHandshake(Unknown Source)
    at org.apache.commons.net.ftp.FTPSClient.sslNegotiation(FTPSClient.java:240)
    at org.apache.commons.net.ftp.FTPSClient._connectAction_(FTPSClient.java:171)
    at org.apache.commons.net.SocketClient.connect(SocketClient.java:178)
    at TestFTP.main(TestFTP.java:64)
Caused by: java.io.EOFException: SSL peer shut down incorrectly
    at sun.security.ssl.InputRecord.read(Unknown Source)
    ... 8 more

以下是我从Java CommandListener获取的日志

220-FileZilla Server 0.9.60 beta
220-written by Tim Kosse (tim.kosse@filezilla-project.org)
220 Please visit https://filezilla-project.org/
AUTH TLS
234 Using authentication type TLS
FTP client received network error javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake

以下是我尝试使用FileZilla进行连接时的日志。它连接正常,我可以传输文件。但是当我尝试使用Java时,它并没有连接

Response:   220-FileZilla Server 0.9.60 beta
Response:   220-written by Tim Kosse (tim.kosse@filezilla-project.org)
Response:   220 Please visit https://filezilla-project.org/
Command:    AUTH TLS
Response:   234 Using authentication type TLS
Status: Initializing TLS...
Status: Verifying certificate...
Command:    USER 
Status: TLS/SSL connection established.
Response:   331 Password required
Command:    PASS xxxx
Response:   230 Logged on
Command:    PBSZ 0
Response:   200 PBSZ=0
Command:    PROT P
Response:   200 Protection level set to P
Status: Connected
Status: Retrieving directory listing...
Command:    PWD
Response:   257 "/" is current directory.
Command:    TYPE I
Response:   200 Type set to I
Command:    PASV
Response:   227 Entering Passive Mode ()
Command:    MLSD
Response:   150 Opening data channel for directory listing of "/"
Response:   226 Successfully transferred "/"
Status: Directory listing successful
Status: Retrieving directory listing...
Command:    PASV
Response:   227 Entering Passive Mode ()
Command:    MLSD
Response:   150 Opening data channel for directory listing of "/"
Response:   226 Successfully transferred "/"
Status: Directory listing successful

你能帮忙吗?

亲切的问候 乔恩

3 个答案:

答案 0 :(得分:1)

首先,请确保连接到FTP服务器的普通端口,因为您要在FTPS-explict-mode中实例化FTPSClient。

您使用Java 7,直到更新75仍使用SSLv2Hello启动SSL会话。在Heartbleed,BEAST以及过去几年中在SSL中发现的所有其他漏洞之后,大多数基于SSL的服务器现在拒绝使用版本< TLS-packets的TLS包进行握手尝试。 TLS 1.0。我想这也是在这里发生的。

找出我是否正确的最简单方法是尝试使用Java 8(切换到TLS 1.2)启动应用程序。如果可以,您需要确保停用SSLv2Hello。这可以通过使用以下系统属性调用JVM来全局完成:

java -Dhttps.protocols="TLSv1,TLSv1.1,TLSv1.2" -Djdk.tls.client.protocols="TLSv1,TLSv1.1,TLSv1.2" <MyApp>

或在执行握手之前在Socket上设置允许的协议,如下所示:

sslSocket.setEnabledProtocols(new String[] {"TLSv1", "TLSv1.1", "TLSv1.2"});

或设置相应的SSLSocketFactoryFTPClient,以便在FTPClient需要SSLSocket时立即设置协议,如上所示。我不知道Apache的FTPClient所以我不能告诉你如何做到这一点,但文档或谷歌搜索应该可以帮助你。

但无论如何,停用SSLv2Hello是一件好事[TM],所以将JVM更新到最新版本(无论是Java 7 Update 80还是Java 8 Update 161)绝不是一个坏主意,也是为了获得所有其他修复与SSL无关。

如果我关于SSLv2Hello的理论不正确,那么唯一可以帮助您解决此问题的下一步就是使用像Wireshark这样的TCP记录器,它可以向您显示实际的SSL握手以及对等服务器在什么时候关闭连接。通常,您可以在该跟踪中看到SSL警报,解释在Java中收到的异常中未显示的实际问题。

答案 1 :(得分:0)

该解决方案很简单,并且在技术上有些棘手,根据已建立连接的SSL的 Session 假设,也应该将其用于数据,但是对于FTPS,库会尝试 FTPS类中未定义数据会话失败尝试,并且处理此操作的方法未定义。因此,解决方案是简单地扩展 FTPS类并提供该方法的实现。

以下是示例

public class ModifiedFTPSClient extends FTPSClient {

public ModifiedFTPSClient() {
    super("TLS", false);
}

public ModifiedFTPSClient(boolean isImplicit) {
    super("TLS", isImplicit);
}

@Override
protected void _prepareDataSocket_(final Socket socket) throws IOException {
    if (socket instanceof SSLSocket) {
        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");
        }
    }
}}

现在,如果我尝试使用扩展类,它将在同一会话中运行。即

 ModifiedFTPSClient ftpClient = new ModifiedFTPSClient(true);
    ftpClient.addProtocolCommandListener(new PrintCommandListener(System.out));
    ftpClient.setTrustManager(TrustManagerUtils.getAcceptAllTrustManager());... and so on.

答案 2 :(得分:-1)

我使用Cyber​​duck库解决了这个问题。 https://github.com/iterate-ch/cyberduck
首先是为什么会出现握手问题, 因为一些进步FTP服务器只允许一个会话进行连接和数据传输。

1)控制会话 - &gt;用于连接
 2)数据会话 - &gt;数据存储/下载/等

那么如何解决这个问题。

第1步 -

首先你需要从GitHub查看cyberduck repo。 从这里 - :https://github.com/iterate-ch/cyberduck

第2步 - 您将在结帐回购中看到FTP和核心模块 ftp and core modules

第3步 - 将此模块转换为maven并为其创建jar。

第4步 - 将此jar添加到您的项目中,但此jar还需要其他依赖项,因此也将相应的内容添加到项目构建路径中。

第5步 - 代码段。

public class TestFTPS {
    public static void main(String[] args) throws SocketException, IOException {
        TrustManager[] trustManagers = new TrustManager[] { new X509TrustManager() {
            @Override
            public X509Certificate[] getAcceptedIssuers() {
                // TODO Auto-generated method stub
                return null;
            }
            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                // TODO Auto-generated method stub
            }

            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                // TODO Auto-generated method stub
            }
        } };
        SSLContext sslContext = null;
        try {
            sslContext = SSLContext.getInstance("SSL");
            sslContext.init(null, trustManagers, new SecureRandom());
        } catch (Exception e) {
            e.printStackTrace();
        }
        SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
        Protocol protocol = new FTPTLSProtocol();
        FTPClient client = new FTPClient(protocol, sslSocketFactory, sslContext);
        client.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out)));
        client.connect(ftphostname, port);
        client.execAUTH("TLS"); //SSL
        System.out.println(client.getReplyCode());
        if (client.login(username, password)) {
            client.execPBSZ(0);
            client.execPROT("P");
            String date = "Testing Data Send to provide FTP location";
            client.setFileType(FTP.BINARY_FILE_TYPE);
            client.enterLocalPassiveMode();
            InputStream is = new ByteArrayInputStream(date.getBytes());
            client.storeFile("test.txt", is);
            System.out.print(client.getReplyCode());
        }
    }
}

步骤6 - :运行此代码后,您的文件将成功传输到ftp位置。

注意 - :请在项目中添加所需的jar

希望这会对你有帮助。