0

I'm trying to upload a file using apache's FTPSClient. Login is fine, server always replies with 2xx codes. But i get the following error when using FTPClient.storeFile: Remote host closed connection during handshake

    javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake
    at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:994)
    at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1367)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1395)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1379)
    at org.apache.commons.net.ftp.FTPSClient._openDataConnection_(FTPSClient.java:646)
    at org.apache.commons.net.ftp.FTPClient._storeFile(FTPClient.java:653)
    at org.apache.commons.net.ftp.FTPClient.__storeFile(FTPClient.java:639)
    at org.apache.commons.net.ftp.FTPClient.storeFile(FTPClient.java:2030)
   ...
Caused by: java.io.EOFException: SSL peer shut down incorrectly
    at sun.security.ssl.InputRecord.read(InputRecord.java:505)
    at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:975)

Obviously with an external ftp client like WinSCP everything works fine, and i'm using the same port and same TLS version (1.2). I even tried adding the following arguments to the VM.

-Djavax.net.ssl.trustStore=trustStore
-Dhttps.protocols=TLSv1.2

What's wrong?

public class FTPS {
    private final FTPSClient ftpClient;

   ...

    protected void startClient() throws NoSuchAlgorithmException, IOException, KeyManagementException {
        if(this.acceptAllCertificates) {
            this.ftpClient.setTrustManager(TrustManagerUtils.getAcceptAllTrustManager());
        }
        
        // The FTPSClient tries to manage the following SSLSocket related configuration options
        // on its own based on internal configuration options.  FTPSClient does not lend itself
        // to subclassing for the purpose of overriding this behavior (private methods, fields, etc.).
        // As such, we create a socket (preconfigured by SSLContextParameters) from the context
        // we gave to FTPSClient and then setup FTPSClient to reuse the already configured configuration
        // from the socket for all future sockets it creates.  Not sexy and a little brittle, but it works.
        SSLContext context = SSLContext.getInstance("TLSv1.2");
        context.init(null, null, null);
        SSLSocket socket = (SSLSocket)context.getSocketFactory().createSocket();
        this.ftpClient.setEnabledCipherSuites(socket.getEnabledCipherSuites());
        this.ftpClient.setEnabledProtocols(new String[] {"TLSv1.2"});

        this.ftpClient.setEnabledSessionCreation(socket.getEnableSessionCreation());
        
        this.clientStarted = true;
        
        ftpClient.setDefaultTimeout(30000);
        ftpClient.setDataTimeout(30000);
        ftpClient.setConnectTimeout(30000);
        ftpClient.setControlKeepAliveReplyTimeout(30000);
        
    }

   ...
   
   public boolean upload(String localPath, String remoteRelativePath) {
        boolean upload;
        
        if(!this.clientStarted) {
            try {
                this.startClient();
            } catch (NoSuchAlgorithmException | IOException | KeyManagementException e) {
                logger.error(e.getMessage(), e);
                return false;
            }
        }
        
        
        if(!this.ftpClient.isConnected()) {
            try {
                this.ftpClient.connect(host, 990);
                
                this.showServerReply();
                
                this.ftpClient.setSendDataSocketBufferSize(1024 * 1024);
                this.ftpClient.setReceieveDataSocketBufferSize(1024 * 1024);
                this.ftpClient.setKeepAlive(true);
                this.ftpClient.execPBSZ(0);
                this.ftpClient.execPROT("P");
                
                
            } catch (SocketException e) {
                logger.error(e.getMessage(), e);
                return false;
            } catch (IOException e) {
                logger.error(e.getMessage(), e);
                return false;
            }
            
        }
            
        boolean login = login(); // basically calls -> this.ftpClient.login(username, password);
        this.showServerReply();
        if(!login) {
            logger.error("... some debug data");
            return false;
        }
        
        try {
            this.ftpClient.enterLocalPassiveMode();
            this.ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
            
            this.showServerReply();
            
            FileInputStream inputStream = new FileInputStream(localPath);
            upload = ftpClient.storeFile(remoteRelativePath, inputStream);
            inputStream.close();
            this.showServerReply();
            
        } catch (IOException e) {
            upload = false;
            logger.error(e.getMessage(), e);
            this.showServerReply();
        }finally {
            logout();
            
            try {
                this.ftpClient.disconnect();
            } catch (IOException e) {
                logger.error(e.getMessage(), e);
            }
        }
        
            
        return upload;
    } 
}


I even tried extending FTPSClient and using this code to be able to use a single session

public class SSLSessionReuseFTPSClient extends FTPSClient {
   ...
// adapted from: https://trac.cyberduck.io/changeset/10760
  @Override
  protected void _prepareDataSocket_(final Socket socket) throws IOException {
    if(socket instanceof SSLSocket) {
      final SSLSession session = ((SSLSocket) _socket_).getSession();
      final SSLSessionContext context = session.getSessionContext();
      try {
        final Field sessionHostPortCache = context.getClass().getDeclaredField("sessionHostPortCache");
        sessionHostPortCache.setAccessible(true);
        final Object cache = sessionHostPortCache.get(context);
        final Method putMethod = cache.getClass().getDeclaredMethod("put", Object.class, Object.class);
        putMethod.setAccessible(true);
        final Method getHostMethod = socket.getClass().getDeclaredMethod("getHost");
        getHostMethod.setAccessible(true);
        Object host = getHostMethod.invoke(socket);
        final String key = String.format("%s:%s", host, String.valueOf(socket.getPort())).toLowerCase(Locale.ROOT);
        putMethod.invoke(cache, key, session);
      } catch(Exception e) {
        throw new IOException(e.getMessage());
      }
    }
  }

No luck with that, either.

dgtal
  • 193
  • 16
  • Can you check that you are configuring the FTPSClient TLS correctly? https://stackoverflow.com/questions/13471015/how-to-configure-client-authentication-with-apache-commons-net-ftps – JCompetence Feb 22 '21 at 09:58
  • Thanks! The code System.setProperty("jdk.tls.useExtendedMasterSecret", "false"); solved it (if you have the same problem and you read this, it's a workaround, use with caution!) – dgtal Feb 22 '21 at 15:44

0 Answers0