如何使用相同的TLS会话连接到具有数据连接的FTPS服务器? [英] How to connect to FTPS server with data connection using same TLS session?
问题描述
环境:我在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();
//尝试{
final FTPFile [] ftpFiles = session.list(/);
logger.debug(FtpFiles:{},(Object [])ftpFiles);
//} catch(忽略异常){}
session.close();
我使用 -Djavax.net.debug = all打印所有TLS调试信息。
到FTPS服务器的主要控制连接工作正常,但是当它试图打开列表的数据连接(或者我尝试过的任何其他数据连接),我得到一个 javax.net.ssl.SSLHandshakeException:握手过程中远程主机关闭连接
,引起的由 java.io.EOFException:SSL对等关闭不正确
。如果我取消注释 session.list
命令中的swallowing-exceptions catch块,则可以看到(尽管javax.net.debug输出)服务器发送了以下消息在拒绝数据连接SSL握手之后:
main,READ:TLSv1.2 Application Data,length = 129
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数据连接
0020:6F 6E 20 68 61 73 20 6E 6F 74 20 72 65 73 75 6D没有资格
0030:65 64 20 6F 72 20 74 68 65 20 73 65 73 73 69 6F ed或sessio
0040:6E 20 64 6F 65 73 20 6E 6F 74 20 6D 61 74 63 68 n不符合
0050:20 74 68 65 20 63 6F 6E 74 72 6F 6C 20 63 6F 6E控制权
0060:6E 65 63 74 69 6F 6E 0D 0A nection ..
什么应用程序耳朵将会发生(这是我第一次处理FTPS,虽然我之前已经处理过普通的FTP)是因为服务器通过控制和数据连接确保身份验证和加密的方式是在正常TLS连接建立控制连接和身份验证发生在那里,每个数据连接都要求客户端连接相同的TLS会话。这对我来说很有意义,因为它应该如何工作,但Apache Commons Net FTPS实现似乎没有这样做。它似乎试图建立一个新的TLS会话,所以服务器拒绝尝试。
基于有关在JSSE中恢复SSL会话的这个问题,看起来Java对于每个主机/帖子组合都假定或需要不同的会话。我的假设是,由于FTPS数据连接位于控制连接的不同端口上,因此它不会找到现有会话并尝试建立新连接,因此连接失败。
我看到三种主要的可能性:
- 服务器没有遵循FTPS标准要求在数据端口上使用相同的TLS会话如在控制端口上。我可以使用FileZilla 3.13.1连接到服务器(使用与我在代码中使用的主机/用户/密码相同的服务器)。服务器在登录时将自己标识为FileZilla Server 0.9.53 beta,因此这可能是某种专有的FileZilla做事方式,我需要做一些奇怪的事情来说服Java使用相同的TLS会话。 li>
- Apache Commons Net客户端实际上并不遵循FTPS标准,只允许一些不允许保护数据连接的子集。这看起来很奇怪,因为它似乎是从Java内部连接到FTPS的标准方式。
- 我完全错过了某些内容并对此进行了错误诊断。
我很感谢您提供关于如何连接到这种FTPS服务器的任何方向。谢谢。
确实,某些FTP(S)服务器确实需要TLS / SSL会话重用于数据连接。这是一种安全措施,通过该措施,服务器可以验证数据连接是否被同一客户端用作控制连接。
常见FTP服务器的一些参考: p>
- vsftpd: https://scarybeastsecurity.blogspot.com/2009/02/vsftpd-210-released.html
- FileZilla服务器: https://svn.filezilla-project.org/filezilla?view=revision&revision=6661
- ProFTPD: http ://www.proftpd.org/docs/contrib/mod_tls.html#TLSOptions (
NoSessionReuseRequired
指令)
有什么可以帮助您执行的是Cyberduck FTP(S)客户端会执行su pport TLS / SSL会话重用,它使用Apache Commons Net库:
-
https://trac.cyberduck.io/ticket/5087 - 在数据连接上重复使用会话密钥
-
请参阅
FTPClient.java
代码(扩展Commons NetFTPSClient
),特别是它的覆盖<$ c $
$ b $ p $@Override
保护无效_prepareDataSocket_(最终套接字套接字)抛出IOException {
if(preferences.getBoolean(ftp.tls.session.requirereuse)){
if(socket instanceof SSLSocket){
//控制袜子et是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 = 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){
//未在期望的JRE中运行
log.warn(SSLSessionContext中没有字段sessionHostPortCache,e);
}
catch(Exception e){
//未在期望的JRE中运行
log.warn(e.getMessage());
$ b else {
log.warn(String.format(SSL session%s for socket%s is not rejoinable,session,socket));
}
}
}
}
-
似乎专门为Commons Net
FTPSClient
添加了_prepareDataSocket _
方法,以允许重新实现TLS / SSL会话:
https://issues.apache.org/jira/浏览/ NET-426
原生支持重用仍在等待中:
https://issues.apache.org/jira/browse/NET-408 -
显然需要重写Spring集成
DefaultFtpsSessionFactory.createClientInstance()
来返回您的自定义FTPSClient <
$ b $自JDK 8u161以来,上述解决方案无法自行工作。
根据 JDK 8u161更新发行说明(和通过@Laurent ):
新增TLS会话哈希和扩展主密钥扩展支持
...
如果出现兼容性问题,应用程序可能会通过将系统属性
jdk.tls.useExtendedMasterSecret
设置为false
在JDK中
即,您可以调用它来解决问题:
System.setProperty(jdk.tls.useExtendedMasterSecret,false);
尽管这应该只是一种解决方法。我不知道一个合适的解决方案。
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).
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();
I'm running this code with
-Djavax.net.debug=all
to get all the TLS debugging information printed.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 byjava.io.EOFException: SSL peer shut down incorrectly
. If I uncomment the swallowing-exceptions catch block around thesession.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..
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.
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:
- The server is not following the FTPS standard in requiring the same TLS session on the data port as on the control port. I can connect to the server fine (using the same host/user/password as I'm trying to use in my code) using FileZilla 3.13.1. The server identifies itself as "FileZilla Server 0.9.53 beta" upon login, so perhaps this is some sort of proprietary FileZilla way of doing things, and there's something odd I need to do to convince Java to use the same TLS session.
- The Apache Commons Net client doesn't actually follow the FTPS standard, and only allows some subset that doesn't allow for securing the data connections. This would seem odd, as it appears to be the standard way of connecting to FTPS from within Java.
- I'm completely missing something and misdiagnosing this.
I'd appreciate any direction you can provide as to how to connect to this kind of FTPS server. Thank you.
解决方案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.
Some references for common FTP servers:
- vsftpd: https://scarybeastsecurity.blogspot.com/2009/02/vsftpd-210-released.html
- FileZilla server: https://svn.filezilla-project.org/filezilla?view=revision&revision=6661
- ProFTPD: http://www.proftpd.org/docs/contrib/mod_tls.html#TLSOptions (
NoSessionReuseRequired
directive)
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
See its
FTPClient.java
code (extends Commons NetFTPSClient
), 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)); } } } }
It seems that the
_prepareDataSocket_
method was added to Commons NetFTPSClient
specifically to allow the TLS/SSL session reuse implementation:
https://issues.apache.org/jira/browse/NET-426A native support for the reuse is still pending:
https://issues.apache.org/jira/browse/NET-408You will obviously need to override the Spring Integration
DefaultFtpsSessionFactory.createClientInstance()
to return your customFTPSClient
implementation with the session reuse support.
The above solution does not work on its own anymore since JDK 8u161.
According to JDK 8u161 Update Release Notes (and the answer by @Laurent):
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
tofalse
in the JDKI.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.
这篇关于如何使用相同的TLS会话连接到具有数据连接的FTPS服务器?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!