如何使用相同的 TLS 会话通过数据连接连接到 FTPS 服务器? [英] How to connect to FTPS server with data connection using same TLS session?

查看:37
本文介绍了如何使用相同的 TLS 会话通过数据连接连接到 FTPS 服务器?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Environment: I'm using Sun Java JDK 1.8.0_60 on 64-bit Windows 7, using Spring Integration 4.1.6 (which internally appears to use Apache Commons Net 3.3 for FTPS access).

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

I'm attempting to integrate with our application an automatic download from our client's FTPS server. I've done so successfully with SFTP servers using Spring Integration without any trouble for other clients without issues, but this is the first time a client has required us to use FTPS, and getting it to connect has been very puzzling. While in my real application I'm configuring Spring Integration using XML beans, to try to understand what's not working I'm using the following test code (though I'm anonymizing the actual host/username/password here):

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 调试信息.

I'm running this code with -Djavax.net.debug=all to get all the TLS debugging information printed.

到 FTPS 服务器的主要控制"连接工作正常,但是当它尝试打开列表的数据连接(或我尝试过的任何其他数据连接)时,我得到一个 javax.net.ssl.SSLHandshakeException: 远程主机在握手期间关闭连接,由 java.io.EOFException: SSL peer 关闭不正确 引起.如果我取消注释 session.list 命令周围的 Swinging-exceptions catch 块,那么我可以看到(通过 javax.net.debug 输出)服务器在拒绝数据连接 SSL 后发送了以下消息握手:

The main "control" connection to the FTPS server works fine, but when it tries to open the data connection for the list (or any other data connection I've tried), I get a javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake, caused by java.io.EOFException: SSL peer shut down incorrectly. If I uncomment the swallowing-exceptions catch block around the session.list command, then I can see (though the javax.net.debug output) that the server sent the following message after rejecting the data connection SSL handshake:

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 会话,因此服务器拒绝了该尝试.

What appears to be happening (and this is my first time dealing with FTPS, though I've dealt with plain FTP before) is that the way the server ensures authentication and encryption over both the control and data connections is that after a "normal" TLS connection to establish the control connection and authentication happens there, each data connection requires the client to connect with the same TLS session. This makes sense to me as how it's supposed to be work, but the Apache Commons Net FTPS implementation doesn't seem to be doing that. It seems to be trying to establish a new TLS session, and so the server is rejecting the attempt.

基于这个关于在 JSSE 中恢复 SSL 会话的问题,看来 Java 假定或要求每个会话都有不同的会话主机/帖子组合.我的假设是,由于 FTPS 数据连接位于与控制连接不同的端口上,因此它没有找到现有会话并试图建立一个新会话,因此连接失败.

Based on this question about resuming SSL sessions in JSSE, it appears that Java assumes or requires a different session for each host/post combination. My hypothesis is that since the FTPS data connection is on a different port than the control connection, it's not finding the existing session and is trying to establish a new one, so the connection fails.

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

I see three main possibilities:

  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 服务器的任何指导,我将不胜感激.谢谢.

I'd appreciate any direction you can provide as to how to connect to this kind of FTPS server. Thank you.

推荐答案

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

Indeed some FTP(S) servers do require that the TLS/SSL session is reused for the data connection. This is a security measure by which the server can verify that the data connection is used by the same client as the control connection.

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

Some references for common FTP servers:

Cyber​​duck FTP(S) 客户端确实支持 TLS/SSL 会话重用,并且它使用 Apache Commons Net 库:

What may help you with the implementation is that Cyberduck FTP(S) client does support TLS/SSL session reuse and it uses Apache Commons Net library:

  • https://trac.cyberduck.io/ticket/5087 - Reuse Session key on data connection

查看其FTPClient.java 代码(扩展 Commons Net FTPSClient),特别是它的 覆盖_prepareDataSocket_方法:

See its FTPClient.java code (extends Commons Net FTPSClient), particularly its override of _prepareDataSocket_ method:

  @Override
  protected void _prepareDataSocket_(final Socket socket) throws IOException {
      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 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) {
                      // 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 Net FTPSClient 的,以允许 TLS/SSL 会话重用实现:
    https://issues.apache.org/jira/browse/NET-426

  • It seems that the _prepareDataSocket_ method was added to Commons Net FTPSClient specifically to allow the TLS/SSL session reuse implementation:
    https://issues.apache.org/jira/browse/NET-426

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

    A native support for the reuse is still pending:
    https://issues.apache.org/jira/browse/NET-408

    您显然需要覆盖 Spring Integration DefaultFtpsSessionFactory.createClientInstance() 以返回具有会话重用支持的自定义 FTPSClient 实现.

    You will obviously need to override the Spring Integration DefaultFtpsSessionFactory.createClientInstance() to return your custom FTPSClient implementation with the session reuse support.

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

    The above solution does not work on its own anymore since JDK 8u161.

    根据 JDK 8u161 更新发行说明(和@Laurent 的回答):

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

    Added TLS session hash and extended master secret extension support
    ...
    In case of compatibility issues, an application may disable negotiation of this extension by setting the System Property jdk.tls.useExtendedMasterSecret to false in the JDK

    也就是说,您可以调用它来解决问题:

    I.e., you can call this to fix the problem:

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

    尽管这应该仅被视为一种解决方法.我不知道合适的解决方案.

    Though this should be a considered a workaround only. I do not know a proper solution.

    这里有一个替代实现:
    https://issues.apache.org/jira/browse/NET-408

    关于1.8.0_161中的问题有一个单独的问题:
    JDK 8u161 中 Apache FTPS 客户端中的 SSL 会话重用

    There's a separate question about problems in 1.8.0_161:
    SSL Session reuse in Apache FTPS client in JDK 8u161

    我过去确实遇到过同样的问题(仅在 C++/OpenSSL 中,我不这样做Java),所以我知道要谷歌搜索什么.

    I actually had the same problem in the past (just in C++/OpenSSL, I do not do Java), so I knew what to google for.

    这篇关于如何使用相同的 TLS 会话通过数据连接连接到 FTPS 服务器?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆