How to establish a FTPS data connection to a FileZilla Server 1.2.0
Asked Answered
R

1

6

It is a known problem to use the Java FTPSClient of Apache commons-net with session resumption. Session resumption is a security feature which a FTPS server can require for data connections. The Apache FTPSClient does not support session resumption, and the JDK APIs make it hard to build a custom implementation. There are a couple of workarounds using reflection, see e.g. this answer and this commons-net bug entry.

I use such a workaround (see snipped below) in JDK 11 and tested it against a local FileZilla Server. It works with FileZilla Server 0.9.6, but it doesn't with FileZilla Server 1.2.0, which is the latest version at the time of writing. With that version, when trying to establish a data connection, the server responds with:

425 Unable to build data connection: TLS session of data connection not resumed.

As I said, FileZilla Server 0.9.6 is fine with how I do session resumption, and I made sure that the setting for requiring session resumption is activated.

In FileZilla Server 1.2.0, such settings are now set implicitly and cannot be changed via the GUI, maybe not at all. Are there some server settings that I can tweak for this to work? Or is it an issue with how I implemented the workaround? Does anyone experience similar issues?

This is the workaround I am using:

public class FTPSClientWithSessionResumption extends FTPSClient {

    static {
        System.setProperty("jdk.tls.useExtendedMasterSecret", "false");
        System.setProperty("jdk.tls.client.enableSessionTicketExtension", "false");
    }

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

    @Override
    protected void _prepareDataSocket_(Socket socket) throws IOException {
        if (useSessionResumption && 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 (NoSuchMethodException e) {
                        // Running in IKVM
                        getHostMethod = socket.getClass().getDeclaredMethod("getHost");
                    }
                    getHostMethod.setAccessible(true);
                    Object peerHost = getHostMethod.invoke(socket);
                    InetAddress iAddr = socket.getInetAddress();
                    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 (Exception e) {
                    throw new IOException(e);
                }
            }
            else {
                throw new IOException("Invalid SSL Session");
            }
        }
    }
}

The address under which the socket is cached is determined using getPeerHost, getInetAddress().getHostName(), and getInetAddress().getHostAddress(). I tried several combinations of doing or not doing these three, but I always get the same result.

Edit:

Here is a screenshot of the server logs of the full session:

Screenshot of FileZilla Server 1.2.0 logs

Reubenreuchlin answered 29/1, 2022 at 9:25 Comment(3)
Does the very first data connection fail already (including a directory listing)? Or subsequent data connections only? FileZilla Server 1.x has peculiarity discussed here: forum.filezilla-project.org/viewtopic.php?t=54027 + To narrow down the problem, try disabling TLS 1.3 on Java side (after allowing TLS 1.2 on FileZilla server), to check if this is TLS 1.3 issue (afaik, FileZilla server 0.x does not support TLS 1.3). TLS 1.3 is way more complicated and may require TLS session restart later.Semblance
Afaik, FileZilla Server 1.x has completely different TLS implementation (libssl?) than 0.x (OpenSSL). There seems to be an interoperability issue between libssl and other TLS implementations (OpenSSL particularly). Add new support of TLS 1.3 to that and you have lot of troubles :)Semblance
Good points! AFAICT, the first data connection fails already. I attached a screenshot of the server logs. With TLS 1.2, everything works fine, so really the workaround doesn't apply to TLS 1.3. Is there some known workaround that does work?Reubenreuchlin
H
4

As stated in this StackOverflow post it is possible to tell the JVM that only TLS 1.2 should be used.
Here is the link to the original answer which worked for me: command for java to use TLS1.2 only

You have to add a command line parameter at the start of the JVM in this case this is: java -Djdk.tls.client.protocols=TLSv1.2 -jar ... <rest of command line here>

This simple parameter worked for me, now I can connect and transfer data from a FTP-Server wich runs FileZilla FTP-Server 1.3.0

Heaton answered 31/3, 2022 at 14:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.