【问题标题】:Android - FTPS Session Reuse - No field sessionHostPortCacheAndroid - FTPS 会话重用 - 无字段 sessionHostPortCache
【发布时间】:2020-08-04 12:28:39
【问题描述】:

我正在使用 Android Studio 在 Android 上开发应用程序,我想使用 FTP 将文件发送到服务器。我需要支持会话重用,因为服务器由托管服务提供商托管,并且他们显然启用了会话重用。


我发现许多人使用这种反射黑客in this post 来实现这一点:

// adapted from:
// https://trac.cyberduck.io/browser/trunk/ftp/src/main/java/ch/cyberduck/core/ftp/FTPClient.java
@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");
        }
    }
}

这是使用 SSLSessionReuseFTPSClient 的代码:

System.setProperty("jdk.tls.useExtendedMasterSecret", "false");

String host = "xxxxxxxx";
String user = "xxxxxxxx";
String password = "xxxxxxxx";
String directory = "xxxxxxxx";

ProtocolCommandListener listener = new MyProtocolCommandListener(host);

SSLSessionReuseFTPSClient client = new SSLSessionReuseFTPSClient("TLS", false);
client.addProtocolCommandListener(listener);

try {
    client.connect(host);
    client.execPBSZ(0);
    client.execPROT("P");

    if (client.login(user, password)) {
        Log.w("myApp", "Logged in as " + user + " on " + host + ".");
    }

    if (client.changeWorkingDirectory(directory)) {
        Log.w("myApp", "Working directory changed to " + directory + ".");
    }

    client.enterLocalPassiveMode();

    InputStream input = new FileInputStream(file);

    if (client.storeFile(file.getName(), input)) {
        Log.w("myApp", "File " + file.getName() + " sent to " + host + ".");
    } else {
        Log.w("myApp", "Couldn't send file " + file.getName() + " to " + host + ".");
        Log.w("myApp", "Reply: " + client.getReplyString());
    }

    client.logout();
    client.disconnect();
} catch (Exception e) {
    e.printStackTrace();
}

我首先在 Eclipse 中进行了尝试,并且成功了。然后我尝试在我的 Android 应用程序中实现它,但我得到了这个错误:

java.io.IOException: java.lang.NoSuchFieldException: No field sessionHostPortCache in class Lcom/android/org/conscrypt/ClientSessionContext; (declaration of 'com.android.org.conscrypt.ClientSessionContext' appears in /system/framework/conscrypt.jar)

我注意到,当我在 Eclipse 中执行代码并打印 context 类名时,我得到:sun.security.ssl.SSLSessionContextImpl,但在 Android Studio 中,我得到:com.android.org.conscrypt.ClientSessionContext


我已经连续搜索了将近两天,只是经验不足,无法知道发生了什么。为什么使用 com.android.org.conscrypt.ClientSessionContext 而不是 sun.security.ssl.SSLSessionContextImpl ?我检查了 java.security 文件,据我所见,应该使用sun.security.ssl.SSLSessionContextImpl

如果有人可以帮助我,我将非常感激。

最后,这里有一些有用的信息:

Android Studio 3.6.2
commons-net-3.6
openjdk version "1.8.0_212-release"
OpenJDK Runtime Environment (build 1.8.0_212-release-1586-b04)
OpenJDK 64-Bit Server VM (build 25.212-b04, mixed mode)

谢谢!

【问题讨论】:

    标签: java android-studio ftp apache-commons-net session-reuse


    【解决方案1】:

    基于 Java cyberduck 解决方案的相同思路,我重写了 FTPSClient 的“prepareDataSocket”方法,使其在 Android 上运行。 我在 Android 9.0 和 Android 5.1.1 中对其进行了测试,它运行良好。 代码是:

    import javax.net.ssl.SSLSession;
    import javax.net.ssl.SSLSessionContext;
    import javax.net.ssl.SSLSocket;
    
    import org.apache.commons.net.ftp.FTPSClient;
    
    public class TLSAndroidFTPSClient extends FTPSClient
    {
        @Override
        protected void _prepareDataSocket_(final Socket socket) throws IOException
        {
            if (socket instanceof SSLSocket)
            {
                final SSLSession sessionAux = ((SSLSocket) _socket_).getSession();
                if(sessionAux.isValid())
                {
                    final SSLSessionContext sessionsContext = sessionAux.getSessionContext();
                    try
                    {
                        // lets find the sessions in the context' cache
                        final Field fieldSessionsInContext =sessionsContext.getClass().getDeclaredField("sessionsByHostAndPort");
                        fieldSessionsInContext.setAccessible(true);
                        final Object sessionsInContext = fieldSessionsInContext.get(sessionsContext);
    
                        // lets find the session of our conexion
                        int portNumb=sessionAux.getPeerPort();
                        Set keys=((HashMap)sessionsInContext).keySet();
                        if(keys.size()==0)
                            throw new IOException("Invalid SSL Session");
                        final Field fieldPort=((keys.toArray())[0]).getClass().getDeclaredField("port");
                        fieldPort.setAccessible(true);
                        int i=0;
                        while(i<keys.size() && ((int)fieldPort.get((keys.toArray())[i]))!=portNumb)
                            i++;
    
                        if(i<keys.size())   // it was found
                        {
                            Object ourKey=(keys.toArray())[i];
                            // building two objects like our key but with the new port and the host Name and host address
                            final Constructor construc =ourKey.getClass().getDeclaredConstructor(String.class, int.class);
                            construc.setAccessible(true);
                            Object copy1Key=construc.newInstance(socket.getInetAddress().getHostName(),socket.getPort());
                            Object copy2Key=construc.newInstance(socket.getInetAddress().getHostAddress(),socket.getPort());
    
                            // getting our session
                            Object ourSession=((HashMap)sessionsInContext).get(ourKey);
    
                            // Lets add the pairs copy1Key-ourSession & copy2Key-ourSession to the context'cache
                            final Method method = sessionsInContext.getClass().getDeclaredMethod("put", Object.class, Object.class);
                            method.setAccessible(true);
                            method.invoke(sessionsInContext,copy1Key,ourSession);
                            method.invoke(sessionsInContext,copy2Key,ourSession);
                        }
                        else
                            throw new IOException("Invalid SSL Session");
    
                    } catch (NoSuchFieldException e) {
                        throw new IOException(e);
                    } catch (Exception e) {
                        throw new IOException(e);
                    }
                } else {
                    throw new IOException("Invalid SSL Session");
                }
            }
        }
    }
    

    【讨论】:

    • 有什么问题比有人发现或取消投票的答案?如果不够清楚,我认为最好问一下。
    • 我已经测试了这段代码。它适用于 Android 4.1 到 Android 9.0。但它不适用于 Android 10。你有任何适用于 android 10 的解决方案吗?
    • 我已将 targetSdkVersion 放入应用程序中的 api 28,然后它仍然可以在 Android 10 设备上运行。我会在针对更高的api时尝试解决它。​​
    • 在 Android 11 上再次遇到问题。我得到:java.lang.NoSuchFieldException: No field sessionsByHostAndPort in class Lcom/android/org/conscrypt/ClientSessionContext;即使在调试器中我可以看到地图并对其进行检查,这怎么可能?
    猜你喜欢
    • 2021-04-02
    • 2013-01-17
    • 2021-01-06
    • 1970-01-01
    • 2018-08-21
    • 2020-05-14
    • 1970-01-01
    • 1970-01-01
    • 2012-07-01
    相关资源
    最近更新 更多