본문 바로가기

Java

SSL 서버

SSL 서버
지난 테크팁 'JSSE를 이용한 안전한 커뮤니케이션'에서 클라이언트측으로부터의 보안 HTTP 요청과 반응(HTTPS또는 HTTP over SSL)을 어떻게 핸들링하는지 배웠다. 이번 팁에서는 서버측 부분의 SSL 커뮤니케이션에 대해서 알아보기로 한다.

첫번째 팁 Working with Selectors에서와 같이 이번 팁의 서버는 에코 서버이다. 즉 서버는 클라이언트로부터 수신한 것을 단순히 클라이언트에게 재송신한다.

Again, 클라이언트 측 테크팁에서의 경우과 같이 서버를 생성하기 위해 먼저 소켓 팩토리를 얻는다. SSL 서버용 소켓을 위해서 SSLServerSocketFactory 타입 팩토리를 이용한다. SSLServerSocketFactory는 javax.net.ssl 패키지에 있다.

   ServerSocketFactory sslserversocketfactory =
     SSLServerSocketFactory.getDefault();

그 후 서버 소켓을 얻고 accept와 연결되기를 기다린다.

   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)

SDK와 함께 제공되는 키툴(keytool) 프로그램으로 인증서를 생성할 수 있다. 키페어(keypair)를 생성하는 -genkey 옵션, 키 스토어 파일(key store file)을 지정하는 -keystore옵션, 암호 알고리즘을 지정하는 -keyalg 옵션이 포함된 keytool 커멘드를 실행하자.

   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 엔트리(이름)는 필수적인 요청사항은 아니지만, 서버의 호스트명이 되어야한다. 위의 예에서 클라이언트가 localhost로 서버에 접근하기 때문에, 그 이름이 필히 사용될 것이다.

커멘드를 구동한 후에는, 작업하고 있는 디렉토리에서 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

클라이언트를 시작할 때 키 스토어 대신 트러스트 스토어로서 인증서를 참조해보자.

   java -Djavax.net.ssl.trustStore=testStore
     -Djavax.net.ssl.trustStorePassword=tutorial EchoClient

그러면 클라이언트가 SSL을 통해 서버와 커뮤니케이션 하게 될 것이다.

   First line: Hello
   Got Back: Hello
   Next line: There
   Got Back: There
   Next line: Server
   Got Back: Server

한 머신 안에서 인증서를 공유하는 것은 쉽다. 그러나 여러 머신에서 인증서를 공유하기 위해서는 외부 클라이언트 머신에 인증서를 발행하는 것이 필요하다. 단순히 "testStore" 키스토어를 복사할 수 있지만, 그렇게 되면 사용자의 개인 키를 노출하게 되어 아무나 그 사용자인양 위장할 수 있게 된다. 따라서 그 대신에 키툴 프로그램의 -export 와 -import옵션을 사용할 수 있다. 키스토어의 공개 가능한 인증서를 익스포트하자. 그러면 당신을 신뢰하는 누군가가 그들의 머신으로 인증서를 임포트할 것이다. 이 작업은 여러 머신에 여러 번 가능하다. 외부 머신이 인증서를 임포트한 후에는 그 머신의 클라이언트와 SSL을 통해 커뮤니케이션 할 수 있다.

SSL 사용에 대한 좀 더 자세한 정보를 원한다면 Java Secure Socket Extension (JSSE) Reference Guide를 참고하기 바란다.

[출처] SSL 서버|작성자 선성환


'Java' 카테고리의 다른 글

자바 중복 제거  (0) 2010.03.04
[자바] 현재 시간을 출력하는 함수  (0) 2010.03.03
컬렉션  (0) 2010.02.12
[JAVA] 컴파일러 들여다보기  (0) 2009.12.24
자바에서 구조체사용..  (0) 2009.12.23