Серверы SSL
Содержание
Введение
Код для сервера
Получение сертификата
Код для клиента
Введение
В "Технических Советах" "Засекреченная связь с помощью JSSE" вы научились пользоваться безопасными HTTP запросами и ответами (известными как HTTPS или HTTP через SSL) со стороны клиента. В этой статье вы узнаете о SSL соединение со стороны сервера.
Код для сервера
Как и в предыдущей статье, "Работа с Selectors", сервер здесь будет эхо-сервером, т.е. будет просто возвращать то, что отправил клиент.
Также как и в статье, где описывалась работа со стороны клиента, чтобы создать сервер, вы сначала должны получить фабрику сокетов. Для SSL серверов – фабрика класса SSLServerSocketFactory. SSLServerSocketFactory входит в пакет javax.net.ssl.
ServerSocketFactory sslserversocketfactory =
SSLServerSocketFactory.getDefault();
Затем вы получаете серверный сокет и ждете соединение с подтверждением:
ServerSocket serverSocket =
serverSocketFactory.createServerSocket(PORT_NUM);
Socket socket = serverSocket.accept();
Оставшийся код для сервера довольно простой. По аналогии со статьей "SelectorTest" вы просто читаете то, что прислал вам клиент, и отправляете обратно. Вот законченный код для сервера:
import javax.net.ssl.*;
import javax.net.*;
import java.io.*;
import java.net.*;
public class EchoServer {
private static final int PORT_NUM = 6789;
public static void main(String args[]) {
ServerSocketFactory serverSocketFactory = SSLServerSocketFactory
.getDefault();
ServerSocket serverSocket = null;
try {
serverSocket = serverSocketFactory.createServerSocket(PORT_NUM);
} catch (IOException ignored) {
System.err.println("Unable to create server");
System.exit(-1);
}
while (true) {
Socket socket = null;
try {
socket = serverSocket.accept();
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(
is, "US-ASCII"));
OutputStream os = socket.getOutputStream();
Writer writer = new OutputStreamWriter(os, "US-ASCII");
PrintWriter out = new PrintWriter(writer, true);
String line = null;
while ((line = br.readLine()) != null) {
out.println(line);
}
} catch (IOException exception) {
exception.printStackTrace();
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException ignored) {
}
}
}
}
}
}
Получение сертификата
Запуская эту программу, возникает небольшая проблема. У вас нет сертификата. Если вы запустите программу без подтверждения сертификата, вы получите SSLException:
javax.net.ssl.SSLException: No available certificate
corresponds to the SSL cipher suites which are enabled.
at com.sun.net.ssl.internal.ssl.SSLServerSocketImpl.a(DashoA6275)
at com.sun.net.ssl.internal.ssl.SSLServerSocketImpl.accept(DashoA6275)
at EchoServer.main(EchoServer.java:21)
Вы можете сгенерировать сертификат с помощью программы keytool, которая поставляется с SDK. Выберете в keytool опцию -genkey, чтобы создать keypair (пару ключей), затем опцию -keystore, чтобы определить место файла с ключом и опцию -keyalg, чтобы определить алгоритм шифрования:
keytool -genkey -keystore testStore -keyalg RSA
В ответ вы получите информацию. Применяйте информацию по обстановке. Вот как может выглядеть диалог:
Enter keystore password: tutorial
What is your first and last name?
[Unknown]: Sun Tutorial
What is the name of your organizational unit?
[Unknown]: Sun
What is the name of your organization?
[Unknown]: Sun
What is the name of your City or Locality?
[Unknown]: Santa Clara
What is the name of your State or Province?
[Unknown]: CA
What is the two-letter country code for this unit?
[Unknown]: US
Is CN=Sun Tutorial, OU=Sun, O=Sun, L=Santa Clara,
ST=CA, C=US correct?
[no]: yes
Enter key password for <mykey>
(RETURN if same as keystore password):
Обычно в CN (имя и фамилия) пишут имя хоста сервера, хотя это и не обязательное требование. В этом примере это имя используется, потому что клиент имеет доступ к серверу по локальному хосту.
После запуска команды у вас появится новый файл с названием testStore в вашей рабочей директории. Теперь вы можете запустить сервер с помощью SSL. При запуске сервера, вы указываете хранилище ключа с помощью свойства javax.net.ssl.keyStore, а его пароль с помощью свойства javax.net.ssl.keyStorePassword.
java -Djavax.net.ssl.keyStore=testStore
-Djavax.net.ssl.keyStorePassword=tutorial EchoServer
Код для клиента
Конечно, нужен клиент, который будет общаться с сервером. Ниже приведен клиент, который читает входные данные из командной строки, посылает их серверу и пишет ответ сервера:
import javax.net.ssl.*;
import javax.net.*;
import java.io.*;
import java.net.*;
public class EchoClient {
private static int PORT_NUM = 6789;
private static String host = "localhost";
public static void main(String args[]) throws IOException {
SocketFactory socketFactory = SSLSocketFactory.getDefault();
Socket socket = socketFactory.createSocket(host, PORT_NUM);
BufferedReader br = new BufferedReader(new InputStreamReader(System.in,
"US-ASCII"));
PrintWriter out = new PrintWriter(new OutputStreamWriter(socket
.getOutputStream(), "US-ASCII"), true);
BufferedReader socketBr = new BufferedReader(new InputStreamReader(
socket.getInputStream(), "US-ASCII"));
String string = null;
System.out.print("First line: ");
while (!(string = br.readLine()).equals("")) {
out.println(string);
String line = socketBr.readLine();
System.out.println("Got Back: " + line);
System.out.print("Next line: ");
}
socket.close();
}
}
Скомпилируйте клиент. Для запуска клиента вам нужно указать тот же сертификат, который вы указывали, когда запускали вервер. С другой стороны, вы получаете исключение SSLHandshakeException на сервере, когда клиент пытается подключиться к серверу:
javax.net.ssl.SSLHandshakeException: Remote host closed
connection during handshake
Когда вы запускаете клиента, вы представляете сертификат как доверительное хранилище (trust store), в отличие от хранилища ключа:
java -Djavax.net.ssl.trustStore=testStore
-Djavax.net.ssl.trustStorePassword=tutorial EchoClient
Теперь клиент будет общаться с вервером через SSL.
First line: Hello | Первая строка: Hello |
Got Back: Hello | Ответ: Hello |
Next line: There | Следующая строка: There |
Got Back: There | Ответ: There |
Next line: Server | Следующая строка: Server |
Got Back: Server | Ответ: Server |
На одной машине разделить сертификат просто. Однако, разделение сертификата на нескольких машинах требует получение сертификата для другой (клиетнской) машины. Вы можете просто скопировать хранилище ключа "testStore", одноко, это раскроет ваши приватные ключи. Потом кто-нибудь может выдать себя за вас. В место этого используйте опции -export и -import программы keytool. Вы экспортируете сертификат, который можно публиковать. Затем кто-то, кто доверяет вам, импортирует его на свою машину. Это можно сделать несколько раз для нескольких машин. После импорта сертификата на другую машину, клиент на этой машине может общаться с вами через SSL.
За дополнительной информацией по использованию SSL, смотрите "Java Secure Socket Extension (JSSE) Reference Guide".