【问题标题】:How does FTP protocol negotiate SSL connection? [closed]FTP 协议如何协商 SSL 连接? [关闭]
【发布时间】:2016-02-17 05:34:35
【问题描述】:

我已阅读 RFC 并尝试在我的 FTP 服务器中实现 SSL 连接。

我已使用 javax.net.ssl.SSLSocket 将我的所有 Sockets 切换为 SSLServer Sockets 和 SSLsockets; javax.net.ssl.SSLSocketFactory;

当使用 FileZilla 使用 SSL 显式加密时,我收到一条消息说, "已与 127.0.0.1 连接,正在协商 SSL 连接"

它会卡在那里。

如何对我的服务器进行编程以协商 SSL 连接?

下面这段代码是 ServerSocket 启动并等待连接的地方。

// Starts a server Socket to listen to new clients
        System.setProperty("javax.net.ssl.trustStore","ServerFiles\\default_KeyStore\\server.keystore");
        SSLServerSocketFactory sslserversocketfactory = (SSLServerSocketFactory)SSLServerSocketFactory.getDefault();
        this.entryPortSock = (SSLServerSocket)sslserversocketfactory.createServerSocket((Integer) st.getValue("CMDPort"));
        SSLSocket scCMDPort;

        // Initiates the Rolling Log
        RollingLog rollingLog = new RollingLog((String) st.getValue("LOGfiledirectory"), st);
        Runnable RunnableRollingLog = rollingLog;
        Thread logger = new Thread(RunnableRollingLog);
        logger.start();

        // Continues to listen to new clients and connect to them when
        // available.
        while (true) {
            scCMDPort =(SSLSocket) this.entryPortSock.accept();
            System.out.println("System accepted a new friend!");

            // Starting the service for the client
            Runnable ftpService = new FTPService(rollingLog, (String) st.getValue("ROOT"), scCMDPort, st);
            Thread service = new Thread(ftpService);
            service.start();
        }

以下是我的服务器支持的命令,它可以从客户端接受。

 private void action(ServerCmd command) {
    // Process of logging in, Must pass this boundary before any other
    // command can be given.

    if (DEBUG_INCOMING) {
        System.out.println("Client: " + command.cmdType + " " + reJoinString(command.args));
    }
    printLog("Client: " + command.cmdType + " " + reJoinString(command.args));

    if (command.cmdType.equals(ServerCommands.AUTH)) {
        if(command.args.get(0).equalsIgnoreCase("SSL")){


            this.sendOut("334 Yo homie we can start that SSL connection!");
        }
    }
    else if (command.cmdType.equals(ServerCommands.USER)) {
        this.User(command);
    }

    else if (command.cmdType.equals(ServerCommands.PASS)) {
        this.Pass(command.args.get(0));
    }

    // After login in, all of these commands can be read.
    else if (this.Status == 1) {
        if (command.cmdType.equals(ServerCommands.SYST)) {
            // Reads a request for the operating system of the Server.
            this.Syst();
        }

        else if (command.cmdType.equals(ServerCommands.FEAT)) {
            // Reads a request FEAT
            this.Feat();
        } else if (command.cmdType.equals(ServerCommands.PWD)) {
            // Reads a request to print the working directory
            this.Pwd();
        }

        else if (command.cmdType.equals(ServerCommands.TYPE)) {
            // Read a request Type
            this.Type(command.args.get(0));
        }

        else if (command.cmdType.equals(ServerCommands.PASV)) {
            // Request PASV connection
            this.Pasv();
        } else if (command.cmdType.equals(ServerCommands.LIST)) {
            // Request directory listing
            this.List();
        }

        else if (command.cmdType.equals(ServerCommands.CWD)) {
            //
            String temp = this.reJoinString((command.args));
            this.Cwd(temp);
        }

        else if (command.cmdType.equals(ServerCommands.CDUP)) {
            this.Cdup();
        }

        else if (command.cmdType.equals(ServerCommands.STOR)) {
            this.Stor(this.reJoinString(command.args));
        } else if (command.cmdType.equals(ServerCommands.RETR)) {
            this.Retr(this.reJoinString(command.args));
        } else if (command.cmdType.equals(ServerCommands.QUIT)) {
            System.exit(0);
        } else if (command.cmdType.equals(ServerCommands.HELP)) {
            sendOut("214 Help: Connect to FileZilla, the older version without caching to use this FTP Server. Or, a command prompt client would work as well.\n"
                    + "Accepted FTP Commands: USER, PASS, SYST, FEAT, PWD, TYPE, PASV, LIST, CWD, CDUP, EPSV, STOR, RETR, QUIT");
        } else if (command.cmdType.equals((ServerCommands.PORT))) {
            if (((String) (this.settings.getValue("PORTmode"))).equalsIgnoreCase("YES")) {
                this.Port(this.reJoinString(command.args));
            } else {
                sendOut("522 This command is not supported");
            }
        } else if (command.cmdType.equals(ServerCommands.EPRT)) {
            if (((String) (this.settings.getValue("PORTmode"))).equalsIgnoreCase("YES")) {
                this.EPRT(this.reJoinString(command.args));
            } else {
                sendOut("522 This command is not supported");
            }
        } else if (command.cmdType.equals((ServerCommands.EPSV))) {
            // Turn true to support EPSV
            if (((String) (this.settings.getValue("PASVmode"))).equalsIgnoreCase("YES")) {
                this.EPSV();
            } else {
                sendOut("522 This command is not supported");
            }
        }
    }

更新: 一旦从客户端发送了 AUTH SSL 命令,我就实现了从 Socket 到 SSL 套接字的升级。这是我的代码:

SSLSocketFactory sslsocketfactory = (SSLSocketFactory)SSLSocketFactory.getDefault();
            this.clientSocket1 = sslsocketfactory.createSocket(serverSocket.getInetAddress().getHostAddress(), serverSocket.getLocalPort());
            ((SSLSocket)this.clientSocket1).setUseClientMode(false);
            this.sendOut("334 Yo homie we can start that SSL connection!");

客户端响应如下:客户端:€j Q [] 9 8 5 [] [] 类似的东西。如何解密消息?

为了尝试解决上述问题,我使用以下代码重新实例化了我的缓冲区读取器,该读取器正在从常规 Socket 读取到来自 SSLSocket 的流:

SSLSocketFactory sslsocketfactory = (SSLSocketFactory)SSLSocketFactory.getDefault();
            this.clientSocket1 = sslsocketfactory.createSocket(serverSocket.getInetAddress().getHostAddress(), serverSocket.getLocalPort());
            ((SSLSocket)this.clientSocket1).setUseClientMode(false);
            ((SSLSocket)this.clientSocket1).startHandshake();
            cipher  =sslsocketfactory.getDefaultCipherSuites();
            this.encrypted = true;
            this.sendOut("334 Yo homie we can start that SSL connection!");
            this.in = new BufferedReader(new InputStreamReader(((SSLSocket)this.clientSocket1).getInputStream()));
            this.out = new PrintWriter(((SSLSocket)this.clientSocket1).getOutputStream());

可能的握手崩溃的整个堆栈跟踪。

javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?
at sun.security.ssl.InputRecord.handleUnknownRecord(Unknown Source)
at sun.security.ssl.InputRecord.read(Unknown Source)
at sun.security.ssl.SSLSocketImpl.readRecord(Unknown Source)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(Unknown Source)
at sun.security.ssl.SSLSocketImpl.readDataRecord(Unknown Source)
at sun.security.ssl.AppInputStream.read(Unknown Source)
at sun.nio.cs.StreamDecoder.readBytes(Unknown Source)
at sun.nio.cs.StreamDecoder.implRead(Unknown Source)
at sun.nio.cs.StreamDecoder.read(Unknown Source)
at java.io.InputStreamReader.read(Unknown Source)
at java.io.BufferedReader.fill(Unknown Source)
at java.io.BufferedReader.readLine(Unknown Source)
at java.io.BufferedReader.readLine(Unknown Source)
at FTPServer.FTPService.run(FTPService.java:100)
at java.lang.Thread.run(Unknown Source)

客户日志:

服务器的日志

javax.net.ssl.SSLException: Connection has been shutdown: javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?
at sun.security.ssl.SSLSocketImpl.checkEOF(Unknown Source)
at sun.security.ssl.AppInputStream.read(Unknown Source)
at sun.nio.cs.StreamDecoder.readBytes(Unknown Source)
at sun.nio.cs.StreamDecoder.implRead(Unknown Source)
at sun.nio.cs.StreamDecoder.read(Unknown Source)
at java.io.InputStreamReader.read(Unknown Source)
at java.io.BufferedReader.fill(Unknown Source)
at java.io.BufferedReader.readLine(Unknown Source)
at java.io.BufferedReader.readLine(Unknown Source)
at FTPServer.FTPService.run(FTPService.java:100)
at java.lang.Thread.run(Unknown Source)
Caused by: javax.net.ssl.SSLException: Unrecognized SSL message, plaintext     connection?
at sun.security.ssl.InputRecord.handleUnknownRecord(Unknown Source)
at sun.security.ssl.InputRecord.read(Unknown Source)
at sun.security.ssl.SSLSocketImpl.readRecord(Unknown Source)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(Unknown Source)
at sun.security.ssl.SSLSocketImpl.startHandshake(Unknown Source)
at sun.security.ssl.SSLSocketImpl.getSession(Unknown Source)
at FTPServer.FTPService.action(FTPService.java:143)
at FTPServer.FTPService.run(FTPService.java:102)
... 1 more

服务线程已停止?!

短版

package testServer;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;

public class driverServerTest {
public static void main(String args[]) {
    try {
        ServerSocket Ssc = new ServerSocket(21);
        Socket sc;
        sc = Ssc.accept();
        PrintWriter out = new PrintWriter(sc.getOutputStream());
        BufferedReader in = new BufferedReader(new InputStreamReader(sc.getInputStream()));
        out.println("220 The connection succeeded");
        out.flush();
        String temp;
        while (true) {
            temp = in.readLine();
            System.out.println(temp);
            if (temp.equalsIgnoreCase("AUTH SSL")) {
                out.println("334 Yo homie we can start that SSL connection!");
                out.flush();
                SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
                sc = sslsocketfactory.createSocket(Ssc.getInetAddress().getHostAddress(), Ssc.getLocalPort());
                ((SSLSocket) sc).setUseClientMode(false);

                in = new BufferedReader(new InputStreamReader(((SSLSocket) sc).getInputStream()));
                out = new PrintWriter(((SSLSocket) sc).getOutputStream());
                out.println("234 request login information");
                out.flush();
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}
}

客户响应:

Status: Connecting to 127.0.0.1 ...
Status: Connected with 127.0.0.1, negotiating SSL connection...
Response:   220 The connection succeeded
Command:    AUTH SSL
Response:   334 Yo homie we can start that SSL connection!

【问题讨论】:

  • 要么您没有正确阅读 RFC,要么您执行错误。由于您没有显示任何代码,也没有显示任何有关您对 RFC 的理解的信息,因此不清楚您的问题是什么。
  • 不幸的是,代码很长。独立它是一个没有安全连接的 FTP 服务器。我在 RFC 中读到客户端应该发送一个 AUTH SSL 来询问服务器是否使用该协议。并且回复应该是 334。然后客户端将开始通过套接字发送 SSL 消息。我只是不明白客户端如何开始发送​​这些消息,除非它有某种密钥分配给它。除了其他模糊的地方。
  • AUTH TLS 的机制与 SMTP 中的 STARTTLS、HTTP 代理中的 CONNECT 等相同:客户端发送将普通连接升级到 TLS 的请求,服务器确认此请求。在此之后,客户端开始正常的 TLS 握手,也就是说,如果你不先说清楚的话,就像你做的一样。当然,服务器需要为此提供证书和密钥,与您在 Web 服务器中找到的“普通”TLS 相同。
  • @close-voters 请停止。这个问题没有任何离题或不清楚的地方。如果关闭,我肯定会投票重新开放。

标签: java sockets ssl ftp network-programming


【解决方案1】:

如果收到 AUTH TLS 命令,您需要从纯 ServerSocket 开始,然后使用 SSLSocketFactory.createSocket(Socket, ...) 将纯 Socket 升级到 SSL。

在对它进行任何 I/O 之前,不要忘记调用 SSLSocket.setUseClientMode(false);

【讨论】:

  • 我会试试的。非常感谢。
  • 我在刚刚添加到原始问题的更新部分下实现了这一点,现在从客户端获得了一个奇怪的字符串。那是加密的消息吗?我该如何解密?我知道我需要一个特殊的密钥库,但如何获取和生成类似的东西?
  • 您是如何阅读客户回复的?您需要使用SSLSocket, 及其输入和输出流,以进行所有未来的通信。我不清楚 334 应该以明文还是通过 SSL 发送:可能是前者。
  • 我用新声明的 SSL 套接字声明了我的缓冲区阅读器。不确定我是否做得对。会多修修补补。但是,我收到一条错误消息: javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?我在上面的原始问题中包含了我的新更新代码。
  • 我无法确定您第一句话的开头或结尾,并且您的问题中没有足够的代码来验证您是否正确地遵循了我的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-12
  • 1970-01-01
  • 2017-04-28
  • 1970-01-01
  • 2013-05-20
相关资源
最近更新 更多