Почему соединение сокета сервера SSL блокируется в Java, а не сокет сервера SSL - нет?
Рассмотрим следующий код для сокет-сервера и клиента без SSL-соединения в одном потоке:
import java.io.DataInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerClient {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(0); // open a random free port.
Socket c = new Socket(ss.getInetAddress(), ss.getLocalPort());
Socket s = ss.accept();
final byte[] bytes = "Hello World!".getBytes();
final OutputStream out = c.getOutputStream();
System.out.println("writing to stream");
out.write(bytes.length);
out.write(bytes);
System.out.println("reading from stream");
final DataInputStream in = new DataInputStream(s.getInputStream());
int len = in.read();
final byte[] b = new byte[len];
in.readFully(b);
System.out.println(new String(b));
c.close();
ss.close();
}
}
Это дает следующий вывод:
writing to stream
reading from stream
Hello World!
Этот процесс открыл сокет сервера - связанный с сокетом клиента. Передал данные в сокет и затем закрыл. Не было проблем с передачей данных.
Рассмотрим версию, чтобы доказать свою точку зрения с помощью SSL-сокетов:
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.logging.Logger;
public class SSLServerClient {
private static Logger log = Logger.getLogger("InfoLogging");
public static void main(String[] args) throws IOException {
System.setProperty("javax.net.ssl.keyStore", "/path/KeyStore.jks");
System.setProperty("javax.net.ssl.keyStorePassword", "password");
SSLServerSocketFactory sslserversocketfactory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
SSLServerSocket serverListeningSSLSocket = (SSLServerSocket) sslserversocketfactory.createServerSocket(4380);
log.info("Server started");
SSLSocketFactory sslSocketFactory=(SSLSocketFactory) SSLSocketFactory.getDefault();
SSLSocket clientSocket = (SSLSocket) sslSocketFactory.createSocket(serverListeningSSLSocket.getInetAddress(),
serverListeningSSLSocket.getLocalPort());
SSLSocket serverCommsSSLSocket = (SSLSocket) serverListeningSSLSocket.accept();
log.info("new client");
final byte[] bytes = "Hello World!".getBytes();
final OutputStream out = clientSocket.getOutputStream();
System.out.println("writing to stream");
out.write(bytes.length);
out.write(bytes);
System.out.println("reading from stream");
final DataInputStream in = new DataInputStream(serverCommsSSLSocket.getInputStream());
int len = in.read();
final byte[] b = new byte[len];
in.readFully(b);
System.out.println(new String(b));
clientSocket.close();
serverCommsSSLSocket.close();
serverListeningSSLSocket.close();
}
}
Это дает следующий вывод:
Nov 21, 2018 10:23:51 PM com.gamble.ssl.SSLServerClient main INFO: Server started
Nov 21, 2018 10:23:52 PM com.gamble.ssl.SSLServerClient main INFO: new client
writing to stream
т.е. он блокирует запуск сокета на сервере.
У меня такой вопрос: почему соединение с сокетом сервера SSL блокируется в Java, а без сокета сервера без SSL?
2 ответа
Простой ответ на вопрос:
Потому что SSL Handshake является асинхронным, и сокет без SSL не должен делать этот шаг.
Хорошо - было две проблемы с оригинальным кодом SSL:
Там не было TrustStore:
System.setProperty( "javax.net.ssl.trustStore", "/path/KeyStore.jks");
Поскольку рукопожатие является синхронным - вы должны запустить клиента в другом потоке. то есть:
(new Thread() { public void run() { // do stuff } }).start();
Итак, код выглядит так:
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.logging.Logger;
public class SSLServerClient {
public static void main(String[] args) throws IOException {
System.setProperty("javax.net.ssl.keyStore", "/path/KeyStore.jks");
System.setProperty( "javax.net.ssl.trustStore", "/path/KeyStore.jks");
System.setProperty("javax.net.ssl.keyStorePassword", "password");
SSLServerSocketFactory sslserversocketfactory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
SSLServerSocket serverListeningSSLSocket = (SSLServerSocket) sslserversocketfactory.createServerSocket(4380);
System.out.println("--server started");
SSLSocketFactory sslSocketFactory=(SSLSocketFactory) SSLSocketFactory.getDefault();
SSLSocket clientSocket = (SSLSocket) sslSocketFactory.createSocket(serverListeningSSLSocket.getInetAddress(),
serverListeningSSLSocket.getLocalPort());
SSLSocket serverCommsSSLSocket = (SSLSocket) serverListeningSSLSocket.accept();
System.out.println("--new client");
final byte[] bytes = "--Hello World!".getBytes();
final OutputStream out = clientSocket.getOutputStream();
System.out.println("--Gotten output stream");
final DataInputStream in = new DataInputStream(serverCommsSSLSocket.getInputStream());
(new Thread() {
public void run() {
System.out.println("--reading from stream");
int len = 0;
try {
len = in.read();
final byte[] b = new byte[len];
in.readFully(b);
System.out.println(new String(b));
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
System.out.println("--writing to stream");
out.write(bytes.length);
System.out.println("--writing to stream - length");
out.write(bytes);
clientSocket.close();
serverCommsSSLSocket.close();
serverListeningSSLSocket.close();
}
}
И вывод выглядит так
--server started
--new client
--Gotten output stream
--writing to stream
--reading from stream
--writing to stream - length
--Hello World!
Process finished with exit code 0
Готово
(Обратите внимание, я добавил ведущий --
к выводу, чтобы помочь при чтении вывода отладки SSL.
Что значит "перевернуть в открытый текст"? имею в виду?
Вот что вы делаете:
- Создание сокета сервера SSL
- Создание обычного сокета, подключенного к сокету сервера SSL
- Отправка некоторых данных со стороны клиента
- Чтение некоторых данных на стороне сервера
Теперь сокет SSL ожидает зашифрованной передачи данных. Но данные, которые вы не отправляете, не зашифрованы, поэтому они не являются действительным SSL и выдают исключение - как говорится, "Нераспознанное сообщение SSL", потому что вы не отправляете действительное сообщение SSL, а "соединение в незашифрованном виде?" намек на то, что может быть не так.
Вы должны использовать SSL на обеих сторонах соединения, иначе они не смогут нормально общаться друг с другом.