Серверы 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 &ltmykey&gt
           (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".

Теги: j2ee servers SSL