Java11+上使用Apache嵌入式FTPS(Mina)遇到的问题

3

我有一个非常简单的Java 8项目(FTP服务器),它使用了Apache FTPS(Mina)服务器库(版本为1.1.1)。代码如下:

    ListenerFactory factory = new ListenerFactory();
    factory.setPort(2221);
    
    // SSL config
    SslConfigurationFactory ssl = new SslConfigurationFactory();
    ssl.setKeystoreFile(new File("keystore.jks"));
    ssl.setKeystorePassword("password");
    // set the SSL configuration for the listener
    factory.setSslConfiguration(ssl.createSslConfiguration());
    factory.setImplicitSsl(true);

    FtpServerFactory serverFactory = new FtpServerFactory();
    // replace the default listener
    serverFactory.addListener("default", factory.createListener());
    
    //Configure user manager and set admin user
    PropertiesUserManagerFactory userManagerFactory = new PropertiesUserManagerFactory();
    userManagerFactory.setFile(new File("users.properties"));
    UserManager userManager = userManagerFactory.createUserManager();
    if (!userManager.doesExist("admin")) {
        BaseUser user = new BaseUser();
        user.setName("admin");
        user.setPassword("password");
        user.setEnabled(true);
        user.setHomeDirectory(USER_HOME_DIR);
        user.setAuthorities(Collections.<Authority>singletonList(new WritePermission()));
        userManager.save(user);
    }
    serverFactory.setUserManager(userManager);

   // start the server
   FtpServer server = serverFactory.createServer(); 
   server.start();

所需 Maven 依赖:

    <dependency>
        <groupId>org.apache.ftpserver</groupId>
        <artifactId>ftpserver-core</artifactId>
        <version>1.1.1</version>
    </dependency>

简单创建自签名Keystore的方法:

keytool -genkey -keyalg RSA -alias self-signed -keystore keystore.jks -validity 360 -keysize 2048

我遵循官方指南编写了这段代码:https://mina.apache.org/ftpserver-project/embedding_ftpserver.html 如果我使用Java 8编译并运行这段代码,则我的FTPS服务器可以正常工作,我可以通过本地主机的localhost:2221访问该服务器,并使用用户名“admin”和密码“password”。从我的FTP客户端(我使用Filezilla)中,我可以看到TLS连接已成功建立。
如果我使用Java 11+(我尝试过11和15)编译并运行相同的代码,则会在我的FTP客户端中看到以下消息,并且目录列表会失败:
Status:         Connecting to 127.0.0.1:2223...
Status:         Connection established, initializing TLS...
Status:         Verifying certificate...
Status:         TLS connection established, waiting for welcome message...
Status:         Logged in
Status:         Retrieving directory listing...
Command:    PWD
Response:   257 "/" is current directory.
Command:    TYPE I
Response:   200 Command TYPE okay.
Command:    PASV
Response:   227 Entering Passive Mode (127,0,0,1,225,229)
Command:    MLSD
Response:   150 File status okay; about to open data connection.
Error:          Received TLS alert from the server: User canceled (90)
Error:          Could not read from transfer socket: ECONNABORTED - Connection aborted
Response:   226 Closing data connection.
Error:          Failed to retrieve directory listing

以下是带有 VM 参数的完整应用程序日志:

2021-03-30 22:59:09.304  INFO 10557 --- [           main] com.example.ftp.demo.DemoApplication     : Starting DemoApplication using Java 11.0.7 on Kara's-MBP with PID 10557 (...)
2021-03-30 22:59:09.306  INFO 10557 --- [           main] com.example.ftp.demo.DemoApplication     : No active profile set, falling back to default profiles: default
2021-03-30 22:59:09.601  INFO 10557 --- [           main] com.example.ftp.demo.DemoApplication     : Started DemoApplication in 0.487 seconds (JVM running for 1.046)
javax.net.ssl|DEBUG|01|main|2021-03-30 22:59:09.886 CEST|SSLCipher.java:438|jdk.tls.keyLimits:  entry = AES/GCM/NoPadding KeyUpdate 2^37. AES/GCM/NOPADDING:KEYUPDATE = 137438953472
2021-03-30 22:59:09.966  INFO 10557 --- [           main] o.a.ftpserver.impl.DefaultFtpServer      : FTP server started
2021-03-30 22:59:24.393  INFO 10557 --- [ NioProcessor-3] o.a.f.listener.nio.FtpLoggingFilter      : CREATED
2021-03-30 22:59:24.395  INFO 10557 --- [pool-3-thread-1] o.a.f.listener.nio.FtpLoggingFilter      : OPENED
javax.net.ssl|DEBUG|1B|NioProcessor-3|2021-03-30 22:59:24.443 CEST|SSLCipher.java:1840|KeyLimit read side: algorithm = AES/GCM/NOPADDING:KEYUPDATE-countdown value = 137438953472
javax.net.ssl|DEBUG|1B|NioProcessor-3|2021-03-30 22:59:24.444 CEST|SSLCipher.java:1994|KeyLimit write side: algorithm = AES/GCM/NOPADDING:KEYUPDATE-countdown value = 137438953472
javax.net.ssl|DEBUG|1B|NioProcessor-3|2021-03-30 22:59:24.472 CEST|SSLCipher.java:1994|KeyLimit write side: algorithm = AES/GCM/NOPADDING:KEYUPDATE-countdown value = 137438953472
javax.net.ssl|DEBUG|1B|NioProcessor-3|2021-03-30 22:59:24.490 CEST|SSLCipher.java:1840|KeyLimit read side: algorithm = AES/GCM/NOPADDING:KEYUPDATE-countdown value = 137438953472
2021-03-30 22:59:24.493  INFO 10557 --- [pool-3-thread-1] o.a.f.listener.nio.FtpLoggingFilter      : SENT: 220 Service ready for new user.

2021-03-30 22:59:24.501  INFO 10557 --- [pool-3-thread-1] o.a.f.listener.nio.FtpLoggingFilter      : RECEIVED: USER admin
2021-03-30 22:59:24.503  INFO 10557 --- [pool-3-thread-1] o.a.f.listener.nio.FtpLoggingFilter      : SENT: 331 User name okay, need password for admin.

2021-03-30 22:59:24.503  INFO 10557 --- [pool-3-thread-1] o.a.f.listener.nio.FtpLoggingFilter      : RECEIVED: PASS *****
2021-03-30 22:59:24.505  INFO 10557 --- [pool-3-thread-1] org.apache.ftpserver.command.impl.PASS   : Login success - admin
2021-03-30 22:59:24.505  INFO 10557 --- [pool-3-thread-1] o.a.f.listener.nio.FtpLoggingFilter      : SENT: 230 User logged in, proceed.

2021-03-30 22:59:24.505  INFO 10557 --- [pool-3-thread-2] o.a.f.listener.nio.FtpLoggingFilter      : RECEIVED: OPTS UTF8 ON
2021-03-30 22:59:24.506  INFO 10557 --- [pool-3-thread-2] o.a.f.listener.nio.FtpLoggingFilter      : SENT: 200 Command OPTS okay.

2021-03-30 22:59:24.506  INFO 10557 --- [pool-3-thread-1] o.a.f.listener.nio.FtpLoggingFilter      : RECEIVED: PBSZ 0
2021-03-30 22:59:24.506  INFO 10557 --- [pool-3-thread-1] o.a.f.listener.nio.FtpLoggingFilter      : SENT: 200 Command PBSZ okay.

2021-03-30 22:59:24.507  INFO 10557 --- [pool-3-thread-2] o.a.f.listener.nio.FtpLoggingFilter      : RECEIVED: PROT P
2021-03-30 22:59:24.508  INFO 10557 --- [pool-3-thread-2] o.a.f.listener.nio.FtpLoggingFilter      : SENT: 200 Command PROT okay.

2021-03-30 22:59:24.508  INFO 10557 --- [pool-3-thread-1] o.a.f.listener.nio.FtpLoggingFilter      : RECEIVED: OPTS MLST size;modify;type;
2021-03-30 22:59:24.509  INFO 10557 --- [pool-3-thread-1] o.a.f.listener.nio.FtpLoggingFilter      : SENT: 200 Command OPTS okay.

2021-03-30 22:59:24.509  INFO 10557 --- [pool-3-thread-2] o.a.f.listener.nio.FtpLoggingFilter      : RECEIVED: CWD /
2021-03-30 22:59:24.511  INFO 10557 --- [pool-3-thread-2] o.a.f.listener.nio.FtpLoggingFilter      : SENT: 250 Directory changed to /

2021-03-30 22:59:24.511  INFO 10557 --- [pool-3-thread-2] o.a.f.listener.nio.FtpLoggingFilter      : RECEIVED: TYPE I
2021-03-30 22:59:24.512  INFO 10557 --- [pool-3-thread-2] o.a.f.listener.nio.FtpLoggingFilter      : SENT: 200 Command TYPE okay.

2021-03-30 22:59:24.512  INFO 10557 --- [pool-3-thread-1] o.a.f.listener.nio.FtpLoggingFilter      : RECEIVED: PASV
2021-03-30 22:59:24.513  INFO 10557 --- [pool-3-thread-1] o.a.f.listener.nio.FtpLoggingFilter      : SENT: 227 Entering Passive Mode (127,0,0,1,226,235)

2021-03-30 22:59:24.513  INFO 10557 --- [pool-3-thread-2] o.a.f.listener.nio.FtpLoggingFilter      : RECEIVED: MLSD
javax.net.ssl|DEBUG|1D|pool-3-thread-2|2021-03-30 22:59:24.526 CEST|SSLCipher.java:1840|KeyLimit read side: algorithm = AES/GCM/NOPADDING:KEYUPDATE-countdown value = 137438953472
javax.net.ssl|DEBUG|1D|pool-3-thread-2|2021-03-30 22:59:24.527 CEST|SSLCipher.java:1994|KeyLimit write side: algorithm = AES/GCM/NOPADDING:KEYUPDATE-countdown value = 137438953472
javax.net.ssl|DEBUG|1D|pool-3-thread-2|2021-03-30 22:59:24.528 CEST|SSLCipher.java:1994|KeyLimit write side: algorithm = AES/GCM/NOPADDING:KEYUPDATE-countdown value = 137438953472
javax.net.ssl|DEBUG|1D|pool-3-thread-2|2021-03-30 22:59:24.529 CEST|SSLCipher.java:1840|KeyLimit read side: algorithm = AES/GCM/NOPADDING:KEYUPDATE-countdown value = 137438953472
javax.net.ssl|ALL|1D|pool-3-thread-2|2021-03-30 22:59:24.533 CEST|SSLSocketImpl.java:994|Closing output stream
javax.net.ssl|DEBUG|1D|pool-3-thread-2|2021-03-30 22:59:24.533 CEST|SSLSocketImpl.java:466|duplex close of SSLSocket
javax.net.ssl|DEBUG|1D|pool-3-thread-2|2021-03-30 22:59:24.534 CEST|SSLSocketImpl.java:1372|close the SSL connection (passive)
2021-03-30 22:59:24.535  WARN 10557 --- [pool-3-thread-2] org.apache.ftpserver.impl.PassivePorts   : Releasing unreserved passive port: 58091
2021-03-30 22:59:24.535  INFO 10557 --- [pool-3-thread-2] o.a.f.listener.nio.FtpLoggingFilter      : SENT: 150 File status okay; about to open data connection.

2021-03-30 22:59:24.535  INFO 10557 --- [pool-3-thread-2] o.a.f.listener.nio.FtpLoggingFilter      : SENT: 226 Closing data connection.

此外,如果我从代码中删除SSL支持,在Java 11+的情况下,我的FTP服务器仍可以正常工作。

你们有人在使用Apache FTPS和Java 11+时遇到类似的问题吗?如果有,你们是如何找到解决方案的?


更新:Java 11 强制使用 TLS1.3,我认为这可能是导致问题的原因。 - Kara
2个回答

2

只有使用FileZilla时,我才能重现这个问题。例如,当我使用lftp时,我可以成功连接到服务器(在信任自签名证书后)。

FileZilla似乎与jdk对TLSv1.3的实现存在问题。在Filezilla的bugtracker [1]中有一个关闭的(被拒绝的)关于此问题的票据。

此外,当使用jdk 8时,我也能重现这个问题。自jdk 8以来,TLSv1.3已经被添加并启用,自8u261-b12开始[2]。

作为解决方法,您可以通过使用安全属性jdk.tls.disabledAlgorithms [3]来禁用TLSv1.3,这将强制jvm选择另一种安全握手算法(希望它将是TLSv1.2)。 (由于这是一个安全设置,最好与您公司的安全团队讨论一下)。

安全属性可以在jdk的配置文件java.security中设置或更新。其路径取决于您使用的jdk和操作系统。

通常位于$JAVA_HOME/jre/lib/security$JAVA_HOME/lib/security下。

如果找不到它,可以通过使用-Djava.security.debug=all启动jvm来打印其路径。您应该在启动日志中看到打印的路径(可能有多个文件)。查找类似于以下行的内容:

properties: reading security properties file: /usr/lib/jvm/java-11-openjdk-11.0.11.0.9-4.fc34.x86_64/conf/security/java.security
...
properties: reading system security properties file /etc/crypto-policies/back-ends/java.config

您还可以通过在ssl.createSslConfiguration()之前添加以下两行来以编程方式更新jdk.tls.disabledAlgorithms

String disabledAlgorithms = Security.getProperty("jdk.tls.disabledAlgorithms") + ", TLSv1.3";
Security.setProperty("jdk.tls.disabledAlgorithms", disabledAlgorithms);

这是完整的程序,加上了两行代码:

import org.apache.ftpserver.FtpServer;
import org.apache.ftpserver.FtpServerFactory;
import org.apache.ftpserver.ftplet.Authority;
import org.apache.ftpserver.ftplet.FtpException;
import org.apache.ftpserver.ftplet.UserManager;
import org.apache.ftpserver.listener.ListenerFactory;
import org.apache.ftpserver.ssl.SslConfigurationFactory;
import org.apache.ftpserver.usermanager.PropertiesUserManagerFactory;
import org.apache.ftpserver.usermanager.impl.BaseUser;
import org.apache.ftpserver.usermanager.impl.WritePermission;

import java.io.File;
import java.security.Security;
import java.util.Collections;

public class Main {
    public static void main(String[] args) throws FtpException {
        String disabledAlgorithms = Security.getProperty("jdk.tls.disabledAlgorithms") + ", TLSv1.3";
        Security.setProperty("jdk.tls.disabledAlgorithms", disabledAlgorithms);

        ListenerFactory factory = new ListenerFactory();
        factory.setPort(2221);

        // SSL config
        SslConfigurationFactory ssl = new SslConfigurationFactory();
        ssl.setKeystoreFile(new File("keystore.jks"));
        ssl.setKeystorePassword("password");
        // set the SSL configuration for the listener
        factory.setSslConfiguration(ssl.createSslConfiguration());
        factory.setImplicitSsl(true);

        FtpServerFactory serverFactory = new FtpServerFactory();
        // replace the default listener
        serverFactory.addListener("default", factory.createListener());

        //Configure user manager and set admin user
        PropertiesUserManagerFactory userManagerFactory = new PropertiesUserManagerFactory();
        userManagerFactory.setFile(new File("users.properties"));
        UserManager userManager = userManagerFactory.createUserManager();
        if (!userManager.doesExist("admin")) {
            BaseUser user = new BaseUser();
            user.setName("admin");
            user.setPassword("password");
            user.setEnabled(true);
            user.setHomeDirectory("/tmp/admin");
            user.setAuthorities(Collections.<Authority>singletonList(new WritePermission()));
            userManager.save(user);
        }
        serverFactory.setUserManager(userManager);

        // start the server
        FtpServer server = serverFactory.createServer();
        server.start();
    }
}

[1] : https://trac.filezilla-project.org/ticket/12099 是一个关于FileZilla客户端的问题报告,其中提到当使用SFTP协议时,如果服务器在公共网络上,则可能会受到中间人攻击。
[2] : https://www.oracle.com/java/technologies/javase/8u261-relnotes.html 是Java 8更新版本的发布说明。该版本包含了一些安全修复和增强功能。
[3] : https://docs.oracle.com/en/java/javase/11/security/java-secure-socket-extension-jsse-reference-guide.html#GUID-0A438179-32A7-4900-A81C-29E3073E1E90 是关于Java安全套接字扩展(JSSE)的参考指南。它提供了有关如何配置和使用JSSE以及如何保护Java应用程序与其他系统通信的详细信息。

1
感谢@Mohamed提供的详细信息。
我最近遇到了这个问题,想分享一下最近的测试结果。我可以使用JDK 16.0.1_64和FileZilla pro 3.57.1重现此问题;而使用JDK 16.0.1_64和winscp 5.15.5正常工作;使用JDK 17.0.1_64和FileZilla pro 3.57.1也正常工作;
这意味着使用JDK 17.0.1_64可能是一个解决方案。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接