Как установить подключение для передачи данных FTPS к серверу FileZilla 1.2.0

Известной проблемой является использование Java FTPSClient Apache commons-net с возобновлением сеанса. Возобновление сеанса — это функция безопасности, которая может потребоваться серверу FTPS для подключения к данным. Apache FTPSClient не поддерживает возобновление сеанса, а API-интерфейсы JDK затрудняют создание пользовательской реализации. Есть несколько обходных путей с использованием отражения, см., например, этот ответ и эту запись об ошибке в Commons-net.

Я использую такой обходной путь (см. фрагмент ниже) в JDK 11 и тестировал его на локальном сервере FileZilla. Он работает с FileZilla Server 0.9.6, но не с FileZilla Server 1.2.0, последней версией на момент написания. В этой версии при попытке установить соединение для передачи данных сервер отвечает:

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

Как я уже сказал, FileZilla Server 0.9.6 отлично подходит для возобновления сеанса, и я убедился, что параметр, требующий возобновления сеанса, активирован.

В FileZilla Server 1.2.0 такие настройки теперь задаются неявно и не могут быть изменены через графический интерфейс, а может быть и вообще. Есть ли какие-то настройки сервера, которые я могу настроить, чтобы это работало? Или это проблема с тем, как я реализовал обходной путь? Кто-нибудь испытывает подобные проблемы?

Это обходной путь, который я использую:

      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");
            }
        }
    }
}

Адрес, под которым кэшируется сокет, определяется с помощью getPeerHost, getInetAddress().getHostName(), и getInetAddress().getHostAddress(). Я пробовал несколько комбинаций выполнения или не выполнения этих трех действий, но всегда получаю один и тот же результат.

1 ответ

Как указано в этом сообщении StackOverflow, можно указать JVM, что следует использовать только TLS 1.2.
Вот ссылка на исходный ответ, который сработал для меня: команда для java использовать только TLS1.2

Вы должны добавить параметр командной строки в начале JVM, в этом случае это: java -Djdk.tls.client.protocols=TLSv1.2 -jar ... <rest of command line here>

Этот простой параметр у меня сработал, теперь я могу подключаться и передавать данные с FTP-сервера, на котором работает FileZilla FTP-Server 1.3.0.

Другие вопросы по тегам