如何使用相同的 TLS 会话通过数据连接连接到 FTPS 服务器?

2024-04-02

环境:我在 64 位 Windows 7 上使用 Sun Java JDK 1.8.0_60,使用 Spring Integration 4.1.6(内部似乎使用 Apache Commons Net 3.3 进行 FTPS 访问)。

我正在尝试将来自客户 FTPS 服务器的自动下载与我们的应用程序集成。我已经使用 Spring Integration 成功地使用了 SFTP 服务器,没有给其他客户端带来任何麻烦,也没有出现任何问题,但这是客户端第一次要求我们使用 FTPS,并且让它连接起来非常令人费解。在我的实际应用程序中,我使用 XML bean 配置 Spring Integration,为了尝试了解什么不起作用,我使用以下测试代码(尽管我在此处匿名化了实际的主机/用户名/密码):

final DefaultFtpsSessionFactory sessionFactory = new DefaultFtpsSessionFactory();
sessionFactory.setHost("XXXXXXXXX");
sessionFactory.setPort(990);
sessionFactory.setUsername("XXXXXXX");
sessionFactory.setPassword("XXXXXXX");
sessionFactory.setClientMode(2);
sessionFactory.setFileType(2);
sessionFactory.setUseClientMode(true);
sessionFactory.setImplicit(true);
sessionFactory.setTrustManager(TrustManagerUtils.getAcceptAllTrustManager());
sessionFactory.setProt("P");
sessionFactory.setProtocol("TLSv1.2");
sessionFactory.setProtocols(new String[]{"TLSv1.2"});
sessionFactory.setSessionCreation(true);
sessionFactory.setCipherSuites(new String[]{"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"});

final FtpSession session = sessionFactory.getSession();
//try {
    final FTPFile[] ftpFiles = session.list("/");
    logger.debug("FtpFiles: {}", (Object[]) ftpFiles);
//} catch (Exception ignored ) {}
session.close();

我正在运行这段代码-Djavax.net.debug=all打印所有 TLS 调试信息。

与 FTPS 服务器的主要“控制”连接工作正常,但是当它尝试打开列表的数据连接(或我尝试过的任何其他数据连接)时,我得到一个javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake, 引起的java.io.EOFException: SSL peer shut down incorrectly。如果我取消注释周围的吞咽异常捕获块session.list命令,然后我可以看到(通过 javax.net.debug 输出)服务器在拒绝数据连接 SSL 握手后发送了以下消息:

main, READ: TLSv1.2 Application Data, length = 129
Padded plaintext after DECRYPTION:  len = 105
0000: 34 35 30 20 54 4C 53 20   73 65 73 73 69 6F 6E 20  450 TLS session 
0010: 6F 66 20 64 61 74 61 20   63 6F 6E 6E 65 63 74 69  of data connecti
0020: 6F 6E 20 68 61 73 20 6E   6F 74 20 72 65 73 75 6D  on has not resum
0030: 65 64 20 6F 72 20 74 68   65 20 73 65 73 73 69 6F  ed or the sessio
0040: 6E 20 64 6F 65 73 20 6E   6F 74 20 6D 61 74 63 68  n does not match
0050: 20 74 68 65 20 63 6F 6E   74 72 6F 6C 20 63 6F 6E   the control con
0060: 6E 65 63 74 69 6F 6E 0D   0A                       nection..

似乎正在发生的事情(这是我第一次处理 FTPS,尽管我以前处理过普通 FTP)是服务器确保控制和数据连接上的身份验证和加密的方式是在“正常”之后TLS 连接建立控制连接和身份验证发生在那里,每个数据连接都要求客户端使用相同的 TLS 会话进行连接。这对我来说是有意义的,因为它应该如何工作,但 Apache Commons Net FTPS 实现似乎并没有这样做。它似乎正在尝试建立新的 TLS 会话,因此服务器拒绝该尝试。

基于关于在 JSSE 中恢复 SSL 会话的问题 https://stackoverflow.com/q/10605062/65839,看来 Java 假设或要求每个主机/帖子组合有不同的会话。我的假设是,由于 FTPS 数据连接与控制连接位于不同的端口,因此它找不到现有会话并尝试建立新会话,因此连接失败。

我看到三种主要的可能性:

  1. 服务器不遵循 FTPS 标准,要求数据端口上的 TLS 会话与控制端口上的相同。我可以使用 FileZilla 3.13.1 很好地连接到服务器(使用与我在代码中尝试使用的相同的主机/用户/密码)。服务器在登录时将自身标识为“FileZilla Server 0.9.53 beta”,因此这可能是某种专有的 FileZilla 处理方式,并且我需要做一些奇怪的事情来说服 Java 使用相同的 TLS 会话。
  2. Apache Commons Net 客户端实际上并不遵循 FTPS 标准,并且仅允许某些不允许保护数据连接的子集。这看起来很奇怪,因为它似乎是从 Java 内部连接到 FTPS 的标准方法。
  3. 我完全错过了一些东西并误诊了这一点。

如果您能提供有关如何连接到此类 FTPS 服务器的任何指导,我将不胜感激。谢谢。


事实上,某些 FTP(S) 服务器确实要求将 TLS/SSL 会话重新用于数据连接。这是一种安全措施,服务器可以通过该措施验证数据连接是否由与控制连接相同的客户端使用。

常见FTP服务器的一些参考:

  • vsftpd: https://scarybeastsecurity.blogspot.com/2009/02/vsftpd-210-released.html https://scarybeastsecurity.blogspot.com/2009/02/vsftpd-210-released.html
  • FileZilla 服务器:
  • ProFTPD:http://www.proftpd.org/docs/contrib/mod_tls.html#TLSOptions http://www.proftpd.org/docs/contrib/mod_tls.html#TLSOptions (NoSessionReuseRequired指示)

可能对您的实施有所帮助的是,Cyber​​duck FTP(S) 客户端确实支持 TLS/SSL 会话重用,并且它使用 Apache Commons Net 库:

  • https://github.com/iterate-ch/cyberduck/issues/5087 https://github.com/iterate-ch/cyberduck/issues/5087– 在数据连接上重用会话密钥

  • See its FTPClient.java https://github.com/iterate-ch/cyberduck/blob/master/ftp/src/main/java/ch/cyberduck/core/ftp/FTPClient.java代码(扩展 Commons NetFTPSClient),特别是其覆盖_prepareDataSocket_ method https://github.com/iterate-ch/cyberduck/blob/d4bd187f89ff7c0a2966a7eef67ca6906c4f2b05/ftp/src/main/java/ch/cyberduck/core/ftp/FTPClient.java#L89:

     @Override
     protected void _prepareDataSocket_(final Socket socket) {
         if(preferences.getBoolean("ftp.tls.session.requirereuse")) {
             if(socket instanceof SSLSocket) {
                 // Control socket is SSL
                 final SSLSession session = ((SSLSocket) _socket_).getSession();
                 if(session.isValid()) {
                     final SSLSessionContext context = session.getSessionContext();
                     context.setSessionCacheSize(preferences.getInteger("ftp.ssl.session.cache.size"));
                     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);
                         putMethod.invoke(cache, String.format("%s:%s", peerHost, socket.getPort()).toLowerCase(Locale.ROOT), session);
                     }
                     catch(NoSuchFieldException e) {
                         // Not running in expected JRE
                         log.warn("No field sessionHostPortCache in SSLSessionContext", e);
                     }
                     catch(Exception e) {
                         // Not running in expected JRE
                         log.warn(e.getMessage());
                     }
                 }
                 else {
                     log.warn(String.format("SSL session %s for socket %s is not rejoinable", session, socket));
                 }
             }
         }
     }
    
  • 看来_prepareDataSocket_方法已添加到 Commons NetFTPSClient特别是为了允许 TLS/SSL 会话重用实现:
    https://issues.apache.org/jira/browse/NET-426 https://issues.apache.org/jira/browse/NET-426

    对重用的本机支持仍然悬而未决:
    https://issues.apache.org/jira/browse/NET-408 https://issues.apache.org/jira/browse/NET-408

  • 显然你需要重写 Spring IntegrationDefaultFtpsSessionFactory.createClientInstance()返回您的定制FTPSClient具有会话重用支持的实现。


自 JDK 8u161 起,上述解决方案不再单独工作。

根据JDK 8u161 更新发行说明 https://www.oracle.com/java/technologies/javase/8u161-relnotes.html(以及@Laurent 的回答 https://stackoverflow.com/q/32398754/850848#49132702):

添加了 TLS 会话哈希和扩展主密钥扩展支持
...
如果出现兼容性问题,应用程序可以通过设置系统属性来禁用此扩展的协商jdk.tls.useExtendedMasterSecret to false在JDK中

即,您可以调用它来解决问题(您仍然需要覆盖_prepareDataSocket_):

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

尽管这应该被认为只是一种解决方法。我不知道正确的解决方案。


另一种实现在这里:
https://issues.apache.org/jira/browse/NET-408 https://issues.apache.org/jira/browse/NET-408


关于 1.8.0_161 中的问题还有一个单独的问题:
JDK 8u161 中的 Apache FTPS 客户端中的 SSL 会话重用 https://stackoverflow.com/q/49257998/850848


我其实有过一样的问题 https://stackoverflow.com/q/7786352/850848过去(只是在 C++/OpenSSL 中,我不做 Java),所以我知道要谷歌搜索什么。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何使用相同的 TLS 会话通过数据连接连接到 FTPS 服务器? 的相关文章

  • Android 2.2 SDK - Droid X 相机活动无法正常完成

    我注意到我在 Droid X 上调用的默认相机活动与我的 Droid 和 Nexus One 上的默认相机活动看起来不同 在 Droid 和 Nexus One 上选择 确定 后 活动将完成 Droid X 有一个 完成 按钮 它将带您返回
  • 如何从秘密字符串中制作 HMAC_SHA256 密钥以在 jose4j 中与 JWT 一起使用?

    我想生成 JWT 并使用 HMAC SHA256 对其进行签名 对于该任务我必须使用jose4j https bitbucket org b c jose4j wiki Home 我尝试根据秘密生成密钥 SecretKeySpec key
  • 来自数据库的 jfreechart 散点图

    如何使用java中的jfreechart绘制mysql数据库表中数据的散点图 我使用过 Swing 库 任何链接都会有帮助 我搜索了谷歌但找不到理解的解决方案 如果您有代码 请提供给我 实际上我确实做了条形图并使用 jfreechart 绘
  • 使用 RecyclerView 适配器在运行时更改布局屏幕

    我有两个布局文件 如下所示 如果列表中存在数据 则我显示此布局 当列表为空时 我会显示此布局 现在我想在运行时更改布局 当用户从列表中删除最后一项时 我想将布局更改为第二张图片中显示的 空购物车布局 In getItemCount Recy
  • 无法在 Spring Boot 测试中模拟 persistenceContext

    我正在使用带有 Mockito 框架的 spring boot 测试来测试我的应用程序 存储库类 EntityManager 之一作为参考 我的班级如下所示 Repository Transactional Slf4j public cla
  • 主线程如何在该线程之前运行?

    我有以下代码 public class Derived implements Runnable private int num public synchronized void setA int num try Thread sleep 1
  • 列表应该如何转换为具体的实现?

    假设我正在使用一个我不知道源代码的库 它有一个返回列表的方法 如下所示 public List
  • Java 数组的最大维数

    出于好奇 在 Java 中数组可以有多少维 爪哇language不限制维数 但是JavaVM规范将维度数限制为 255 例如 以下代码将无法编译 class Main public static void main String args
  • 无法加载或查找主类,可以在命令行中使用,但不能在 IDE 中使用[重复]

    这个问题在这里已经有答案了 在将其标记为重复之前 请先听我说完 我正在尝试使用 gradle 导入一个 java 项目 功能齐全 适用于所有其他笔记本电脑 没有问题 我的项目 100 正常运行 适用于所有其他笔记本电脑 当我的笔记本电脑被重
  • Java - 返回值是否会中断循环?

    我正在编写一些基本上遵循以下格式的代码 public static boolean isIncluded E element Node
  • 在 Spring Boot Actuator 健康检查 API 中启用日志记录

    我正在使用 Spring boot Actuator APIproject https imobilenumbertracker com 拥有一个健康检查端点 并通过以下方式启用它 management endpoints web base
  • 如何配置 WebService 返回 ArrayList 而不是 Array?

    我有一个在 jax ws 上实现的 java Web 服务 此 Web 服务返回用户的通用列表 它运行得很好 Stateless name AdminToolSessionEJB RemoteBinding jndiBinding Admi
  • 如何在 Eclipse Java 动态 Web 项目中使用 .properties 文件?

    我正在 Eclipse 中开发动态 Web 项目 我创建了一个 properties 文件来存储数据库详细信息 用户名 密码等 我通过右键单击项目和 New gt File 添加它 我使用了Java util包Properties类 但它不
  • 逃离的正确方法是什么?使用 Oracle 12c MATCH_RECOGNIZE 时 JDBCPreparedStatement 中的字符?

    以下查询在 Oracle 12c 中是正确的 SELECT FROM dual MATCH RECOGNIZE MEASURES a dummy AS dummy PATTERN a DEFINE a AS 1 1 但它不能通过 JDBC
  • JVM:是否可以操作帧堆栈?

    假设我需要执行N同一线程中的任务 这些任务有时可能需要来自外部存储的一些值 我事先不知道哪个任务可能需要这样的值以及何时 获取速度要快得多M价值观是一次性的而不是相同的M值在M查询外部存储 注意我不能指望任务本身进行合作 它们只不过是 ja
  • 挂钩 Eclipse 构建过程吗?

    我希望在 Eclipse 中按下构建按钮时能够运行一个简单的 Java 程序 目前 当我单击 构建 时 它会运行一些 JRebel 日志记录代码 我有一个程序可以解析 JRebel 日志文件并将统计信息存储在数据库中 是否可以编写一个插件或
  • Java的-XX:+UseMembar参数是什么

    我在各种地方 论坛等 看到这个参数 并且常见的答案是它有助于高并发服务器 尽管如此 我还是找不到 sun 的官方文档来解释它的作用 另外 它是Java 6中添加的还是Java 5中存在的 顺便说一句 许多热点虚拟机参数的好地方是这一页 ht
  • Android - 9 补丁

    我正在尝试使用 9 块图片创建一个新的微调器背景 我尝试了很多方法来获得完美的图像 但都失败了 s Here is my 9 patch 当我用Draw 9 patch模拟时 内容看起来不错 但是带有箭头的部分没有显示 或者当它显示时 这部
  • Android AutoCompleteTextView 带芯片

    我不确定我是否使用了正确的词语来描述此 UI 功能 但我已附上我希望在我的应用程序中实现的目标的快照 它由 Go SMS 使用 用户在编辑文本中键入联系人 在用户从完成下拉列表中选择联系人后 该联系人将被插入到编辑文本中 如附图所示 编辑文
  • Android 和 Java 中绘制椭圆的区别

    在Java中由于某种原因Ellipse2D Double使用参数 height width x y 当我创建一个RectF在Android中参数是 left top right bottom 所以我对适应差异有点困惑 如果在 Java 中创

随机推荐