How to connect to FTPS server (TLS-enabled secure FTP connection) on Java 17

941 Views Asked by At

We went through couple of solutions provided on tranferring files using FTPS. They are working fine with lower versions of Java like Java 11 etc. But are facing the same "SSL peer shut down incorrectly" issue in Java 17. Please let us know if there is any solution for Java 17.

Reffered Links :

How to connect to FTPS server with data connection using same TLS session? (Solution working fine in Java 11)

Stack trace:

Caused by: java.io.EOFException: SSL peer shut down incorrectly at java.base/sun.security.ssl.SSLSocketInputRecord.read(SSLSocketInputRecord.java:483) at java.base/sun.security.ssl.SSLSocketInputRecord.readHeader(SSLSocketInputRecord.java:472) at java.base/sun.security.ssl.SSLSocketInputRecord.decode(SSLSocketInputRecord.java:160) at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:111) at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1505) ... 9 more

Already tried below links:

FTPSClient throw exception javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake

Solution:

Adding System.setProperty("jdk.tls.client.enableSessionTicketExtension", "false") fixed the issue!

1

There are 1 best solutions below

0
Rguihard On

I encountered the same issue attempting connect to a FileZilla server with apache FTPSClient on Java 17.
Finally, the connection worked with this configuration :

FTPClient client = new FTPSClientWithSessionResumption("TLSv1.2");

Downgrading TLS version (TLSv1.2 instead of TLSv1.3)

According this link : https://gitlab.com/gnutls/gnutls/-/issues/1451
JDK has a compatibility issues with TLSv1.3 caused by a non-standard use of 'user_canceled' message.

Using a custom implementation of FTPSClient that permits SSL Session reuse

In other words, when you want to transfer a file or read the content of a directory and must thus open a data socket, if the control connection is secure (TLS), then both the following conditions must be met:

  • the data connection must be secure too (TLS);
  • the data connection must share the TLS session with the control connection to which it pertains

(From Filezilla-forum)

According this link : How to establish a FTPS data connection to a FileZilla Server 1.2.0

Here is the copied/pasted implementation that meets the prerequisites

public class FTPSClientWithSessionResumption extends FTPSClient {
    static {
        System.setProperty("jdk.tls.useExtendedMasterSecret", "false");
        System.setProperty("jdk.tls.client.enableSessionTicketExtension", "false");
    }

    public FTPSClientWithSessionResumption() {
        super();
    }

    public FTPSClientWithSessionResumption(String protocol) {
        super(protocol);
    }

    @Override
    protected void _connectAction_() throws IOException {
        super._connectAction_();
        execPBSZ(0);
        execPROT("P");
    }

    @Override
    protected void _prepareDataSocket_(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 putMethod = cache.getClass()
                        .getDeclaredMethod("put", Object.class, Object.class);
                    putMethod.setAccessible(true);
                    Method getHostMethod;
                    try {
                        getHostMethod = socket.getClass()
                            .getMethod("getPeerHost");
                    } catch (final NoSuchMethodException e) {
                        // Running in IKVM
                        getHostMethod = socket.getClass()
                            .getDeclaredMethod("getHost");
                    }
                    getHostMethod.setAccessible(true);
                    final Object peerHost = getHostMethod.invoke(socket);
                    final InetAddress iAddr = socket.getInetAddress();
                    final int port = socket.getPort();
                    putMethod.invoke(cache, String.format("%s:%s", peerHost, port)
                        .toLowerCase(Locale.ROOT), session);
                    putMethod.invoke(cache, String.format("%s:%s", iAddr.getHostName(), port)
                        .toLowerCase(Locale.ROOT), session);
                    putMethod.invoke(cache, String.format("%s:%s", iAddr.getHostAddress(), port)
                        .toLowerCase(Locale.ROOT), session);
                } catch (final Exception e) {
                    throw new IOException(e);
                }
            } else {
                throw new IOException("Invalid SSL Session");
            }
        }
    }
}

You must open this 2 modules to allow reflection on SSLSession : --add-opens=java.base/sun.security.ssl=ALL-UNNAMED --add-opens=java.base/sun.security.util=ALL-UNNAMED