【发布时间】: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