Как установить подключение для передачи данных 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.