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.