Apache 在 Java11+ 上嵌入 FTPS (Mina) 问题

问题描述

我有一个非常简单的 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>

简单地创建一个自签名的密钥库:

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,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,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+ 上遇到过类似的问题吗?如果是,您是如何找到解决方案的?

解决方法

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

FileZilla 似乎对 TLSv1.3 的 jdk 实现有问题。 Filezilla 的错误跟踪器 [1] 中有一张关于此问题的已关闭(已拒绝)票证。

此外,我可以在使用 jdk 8 时重现该问题。自 8u261-b12 [2] 起,在 jdk 8 中添加并启用了 TLSv1.3。

作为一种解决方法,您可以使用安全属性 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

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

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

[2]:https://www.oracle.com/java/technologies/javase/8u261-relnotes.html

[3]:https://docs.oracle.com/en/java/javase/11/security/java-secure-socket-extension-jsse-reference-guide.html#GUID-0A438179-32A7-4900-A81C-29E3073E1E90

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...