Как подключиться к серверу FTPS с повторным использованием сессий?
Я использую FTP-библиотеку Apache Commons в своем приложении для Android.
Я пытаюсь подключиться к своему FTPS-серверу из своего приложения (с помощью Android Studio), а затем загрузить несколько файлов.
Однако, когда я хочу повторно использовать сеансы, перекрывая метод _prepareDataSocket_
от FTPSClient
, У меня всегда одна и та же ошибка:java.lang.NoSuchFieldException: sessionHostPortCache
. Я пробовал использовать код из других сообщений: Как подключиться к серверу FTPS с подключением к данным, используя тот же сеанс TLS? или перенести файлы с android с FTPS на сервер Вы знаете, почему у меня такая ошибка?
Я использую студию Android с jdk 1.8.
Я ценю любую помощь, спасибо.
Основной код:
String server = "ftp.[HIDDEN]";
int port = 21;
String user = "[HIDDEN]";
String pass = "[HIDDEN]";
System.setProperty("jdk.tls.useExtendedMasterSecret", "false");
SSLSessionReuseFTPSClient ftpClient = new SSLSessionReuseFTPSClient("SSL");
ftpClient.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out), true));
ftpClient.connect(server, port);
System.out.println("Connected to " + server + " on " + port);
ftpClient.login(user, pass);
ftpClient.execPBSZ(0);
ftpClient.execPROT("P");
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
ftpClient.enterLocalPassiveMode();
// transfer files
InputStream input;
input = new FileInputStream(local);
ftpClient.storeFile(remote, input);
input.close();
ftpClient.noop(); // check that control connection is working OK
ftpClient.logout();
SSLSessionReuseFTPSClient:
public class SSLSessionReuseFTPSClient extends FTPSClient {
public SSLSessionReuseFTPSClient(String protocol) {
super(protocol);
}
// adapted from: https://trac.cyberduck.io/changeset/10760
@Override
protected void _prepareDataSocket_(final 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 method = cache.getClass().getDeclaredMethod("put", Object.class, Object.class);
method.setAccessible(true);
method.invoke(cache, String
.format("%s:%s", socket.getInetAddress().getHostName(), String.valueOf(socket.getPort()))
.toLowerCase(Locale.ROOT), session);
method.invoke(cache, String
.format("%s:%s", socket.getInetAddress().getHostAddress(), String.valueOf(socket.getPort()))
.toLowerCase(Locale.ROOT), session);
} catch (NoSuchFieldException e) {
throw new IOException(e);
} catch (Exception e) {
throw new IOException(e);
}
} else {
throw new IOException("Invalid SSL Session");
}
}
}
}
Logcat:
220---------- Welcome to Pure-FTPd [privsep] [TLS] ----------
07-22 09:53:59.516 9529-9561/com.example.test I/System.out: 220-You are user number 1 of 50 allowed.
07-22 09:53:59.516 9529-9561/com.example.test I/System.out: 220-Local time is now 11:54. Server port: 21.
07-22 09:53:59.516 9529-9561/com.example.test I/System.out: 220-This is a private system - No anonymous login
07-22 09:53:59.516 9529-9561/com.example.test I/System.out: 220-IPv6 connections are also welcome on this server.
07-22 09:53:59.516 9529-9561/com.example.test I/System.out: 220 You will be disconnected after 15 minutes of inactivity.
07-22 09:53:59.519 9529-9561/com.example.test I/System.out: AUTH TLS
07-22 09:53:59.533 9529-9561/com.example.test I/System.out: 234 AUTH TLS OK.
07-22 09:53:59.561 9529-9550/com.example.test D/EGL_emulation: eglMakeCurrent: 0xadc34d60: ver 2 0 (tinfo 0xadc394d0)
07-22 09:53:59.589 9529-9550/com.example.test D/EGL_emulation: eglMakeCurrent: 0xadc34d60: ver 2 0 (tinfo 0xadc394d0)
07-22 09:53:59.593 9529-9561/com.example.test I/System.out: Connected to ftp.[HIDDEN] on 21
07-22 09:53:59.597 9529-9561/com.example.test I/System.out: USER *******
07-22 09:53:59.609 9529-9561/com.example.test I/System.out: 331 User [HIDDEN] OK. Password required
07-22 09:53:59.610 9529-9561/com.example.test I/System.out: PASS *******
07-22 09:53:59.613 9529-9550/com.example.test D/EGL_emulation: eglMakeCurrent: 0xadc34d60: ver 2 0 (tinfo 0xadc394d0)
07-22 09:53:59.695 9529-9550/com.example.test D/EGL_emulation: eglMakeCurrent: 0xadc34d60: ver 2 0 (tinfo 0xadc394d0)
07-22 09:53:59.701 9529-9561/com.example.test I/System.out: 230 OK. Current restricted directory is /
07-22 09:53:59.702 9529-9561/com.example.test I/System.out: PBSZ 0
07-22 09:53:59.714 9529-9561/com.example.test I/System.out: 200 PBSZ=0
07-22 09:53:59.717 9529-9561/com.example.test I/System.out: PROT P
07-22 09:53:59.727 9529-9561/com.example.test I/System.out: 200 Data protection level set to "private"
07-22 09:53:59.729 9529-9561/com.example.test I/System.out: TYPE I
07-22 09:53:59.743 9529-9561/com.example.test I/System.out: 200 TYPE is now 8-bit binary
07-22 09:53:59.743 9529-9561/com.example.test I/System.out: PASV
07-22 09:53:59.757 9529-9550/com.example.test D/EGL_emulation: eglMakeCurrent: 0xadc34d60: ver 2 0 (tinfo 0xadc394d0)
07-22 09:53:59.758 9529-9561/com.example.test I/System.out: 227 Entering Passive Mode (5,134,13,241,188,161)
07-22 09:53:59.777 9529-9550/com.example.test D/EGL_emulation: eglMakeCurrent: 0xadc34d60: ver 2 0 (tinfo 0xadc394d0)
07-22 09:53:59.782 9529-9561/com.example.test I/System.out: STOR pictures/test 18.07 1431/test 18.07 1431_19.6.2019_0.32.15.667.jpg
07-22 09:53:59.792 9529-9561/com.example.test I/System.out: 150 Accepted data connection
07-22 09:53:59.793 9529-9561/com.example.test W/System.err: java.io.IOException: java.lang.NoSuchFieldException: sessionHostPortCache
07-22 09:53:59.793 9529-9561/com.example.test W/System.err: at com.example.test.functions.SSLSessionReuseFTPSClient._prepareDataSocket_(SSLSessionReuseFTPSClient.java:54)
07-22 09:53:59.793 9529-9561/com.example.test W/System.err: at org.apache.commons.net.ftp.FTPSClient._openDataConnection_(FTPSClient.java:628)
07-22 09:53:59.793 9529-9561/com.example.test W/System.err: at org.apache.commons.net.ftp.FTPClient._storeFile(FTPClient.java:653)
07-22 09:53:59.793 9529-9561/com.example.test W/System.err: at org.apache.commons.net.ftp.FTPClient.__storeFile(FTPClient.java:639)
07-22 09:53:59.793 9529-9561/com.example.test W/System.err: at org.apache.commons.net.ftp.FTPClient.storeFile(FTPClient.java:2030)
07-22 09:53:59.793 9529-9561/com.example.test W/System.err: at com.example.test.functions.FTPfunctions2$BackGroundWorker.doInBackground(FTPfunctions2.java:81)
07-22 09:53:59.793 9529-9561/com.example.test W/System.err: at com.example.test.functions.FTPfunctions2$BackGroundWorker.doInBackground(FTPfunctions2.java:41)
07-22 09:53:59.794 9529-9561/com.example.test W/System.err: at android.os.AsyncTask$2.call(AsyncTask.java:292)
07-22 09:53:59.794 9529-9561/com.example.test W/System.err: at java.util.concurrent.FutureTask.run(FutureTask.java:237)
07-22 09:53:59.794 9529-9561/com.example.test W/System.err: at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)
07-22 09:53:59.794 9529-9561/com.example.test W/System.err: at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
07-22 09:53:59.794 9529-9561/com.example.test W/System.err: at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
07-22 09:53:59.794 9529-9561/com.example.test W/System.err: at java.lang.Thread.run(Thread.java:818)
07-22 09:53:59.794 9529-9561/com.example.test W/System.err: Caused by: java.lang.NoSuchFieldException: sessionHostPortCache
07-22 09:53:59.794 9529-9561/com.example.test W/System.err: at java.lang.Class.getDeclaredField(Class.java:890)
07-22 09:53:59.794 9529-9561/com.example.test W/System.err: at com.example.test.functions.SSLSessionReuseFTPSClient._prepareDataSocket_(SSLSessionReuseFTPSClient.java:42)
07-22 09:53:59.794 9529-9561/com.example.test W/System.err: ... 12 more
2 ответа
Я столкнулся с той же ошибкой, когда меняю java.security на использование поставщика безопасности ssl, отличного от SunJSSE. В частности, в моем случае я меняю его на: security.provider.4=org.bouncycastle.jsse.provider.BouncyCastleJsseProvider вместо security.provider.4 = com.sun.net.ssl.internal.ssl.Provider
1) Проверьте свой файл java.security.
2) Я добавил в класс сообщение журнала, которое показывает, какая реализация SSLSessionContext фактически используется.
logger.debug("sessionContext calss = " + sessionContext.getClass().getCanonicalName());
final Field sessionHostPortCache = sessionContext.getClass().getDeclaredField("sessionHostPortCache");
Я действительно обнаружил, что в классе BouncyCastle org.bouncycastle.jsse.provider.ProvSSLSessionContext нет члена sessionHostPortCache. Вы можете попробовать это, чтобы обнаружить проблему.
Другая причина получения исключения java.lang.NoSuchFieldException заключается в том, что байт-код JRE подвергся обфускации. Таким образом, поле sessionHostPortCache может называться, например, "b".
Вы можете проверить это, открыв SSLSessionContext.class в своей среде IDE, и если вы установите плагин декомпилятора, он покажет исходный код.
С такими решениями для Android есть две проблемы.
Многие из них ссылаются на классы, отличные от конкретных реализаций классов контекста сеанса для Android, поэтому вам придется изменить свой код, чтобы он правильно отражал реализацию Android. Это не проблема, особенно с учетом того, что Android имеет открытый исходный код.
Android блокирует отражение многих своих системных классов, включая класс контекста сеанса. Вероятно, это невозможно обойти, и эти решения, вероятно, мертвы на Android.
Я склонен видеть в logcat ошибку, сообщающую, что отражение было заблокировано до исключения «не найдено», поэтому ваш код, вероятно, страдает от обеих проблем (не то, чтобы исправление № 1 вам в любом случае помогло).