【发布时间】:2022-01-29 09:25:21
【问题描述】:
使用 Apache commons-net 的 Java FTPSClient 进行会话恢复是一个已知问题。会话恢复是 FTPS 服务器数据连接可能需要的一项安全功能。 Apache FTPSClient 不支持会话恢复,JDK API 使得构建自定义实现变得困难。有几个使用反射的解决方法,请参见例如this answer 和这个commons-net bug entry。
我在 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 中,这些设置现在是隐式设置的,不能通过 GUI 更改,也许根本不能更改。我可以调整一些服务器设置以使其正常工作吗?还是我如何实施解决方法有问题?有没有人遇到过类似的问题?
这是我正在使用的解决方法:
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() 确定缓存套接字的地址。我尝试了几种做或不做这三个的组合,但我总是得到相同的结果。
编辑:
这是完整会话的服务器日志截图:
【问题讨论】:
-
第一个数据连接是否已经失败(包括目录列表)?还是只有后续的数据连接? FileZilla Server 1.x 具有此处讨论的特性:forum.filezilla-project.org/viewtopic.php?t=54027 + 要缩小问题范围,请尝试在 Java 端禁用 TLS 1.3(在 FileZilla 服务器上允许 TLS 1.2 之后),检查这是否是 TLS 1.3 问题(afaik,FileZilla 服务器0.x 不支持 TLS 1.3)。 TLS 1.3 要复杂得多,可能需要稍后重新启动 TLS 会话。
-
Afaik,FileZilla Server 1.x 的 TLS 实现(libssl?)与 0.x(OpenSSL)完全不同。 libssl 和其他 TLS 实现(尤其是 OpenSSL)之间似乎存在互操作性问题。添加对 TLS 1.3 的新支持,你会遇到很多麻烦 :)
-
好点! AFAICT,第一个数据连接已经失败。我附上了服务器日志的屏幕截图。使用 TLS 1.2,一切正常,所以真正的解决方法不适用于 TLS 1.3。是否有一些已知的解决方法有效?
标签: java filezilla ftps apache-commons-net session-reuse