http://redtiger.tistory.com/8


http://neokido.tistory.com/548


http://stilius.net/java/java_ssl.php


http://download.java.net/jdk8/docs/technotes/guides/security/jsse/JSSERefGuide.html


http://www.herongyang.com/JDK/SSL-Socket-Client-Example-SslSocketClient.html



개요

본 문서는 SSL 소켓통신을 위한 서버 및 클라이언트의 구성과 실행방법을 실제 코드를 기준으로 정리한 것이다.
SSL 소켓통신을 위한 다양한 툴이 존재하겠지만 본 문서가 기준으로 하는 툴은 JSSE(Java Secure Sockets Extension)이다.


SSLServerSocekt

일반적인 ServerSocket과 동일한 방식으로 ServerSocket을 생성하고 클라이언트의 접근을 유지하며 Stream을 통해 자료를 주고 받는다.
샘플 코드는 다음과 같다.

package server;
import javax.net.ssl.*;
import javax.net.*;
import java.io.*;
import java.net.*;

public class SSLServer {
private static final int PORT_NUM = 10443;

public SSLServer() {
super();
}

public static void main(String args[]) {
ServerSocketFactory serverSocketFactory = SSLServerSocketFactory.getDefault();
ServerSocket serverSocket = null;

try {
serverSocket = serverSocketFactory.createServerSocket(PORT_NUM);
} catch (IOException ignored) {
ignored.printStackTrace();
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));
OutputStream os = socket.getOutputStream();
Writer writer = new OutputStreamWriter(os);
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) {
}
}
}
}
}
}

listen 포트는 10443이며 클라이언트에서 보내오는 자료를 그대로 echo하는 기능을 한다.
일반 ServerSocket과 다른 점은 SSLServerSocketFactory로부터 ServerSocket을 생산하고 있는 부분이라고 볼 수 있다. 물론 그 내부에서는 인증서를 이용하여 상호 HandShake을 하지만 말이다.


keytool 및 인증서

SSL 통신을 위해서 인증서를 기반으로 하는데 JSSE에서 사용하는 개인키/공개키는 keytool을 이용해서 쉽게 만들 수 있다.
keytool을 통해 개인키/공개키를 만들어 키 저장소(JSSE에서는 기본으로는 JKS 키 저장소를 사용)고 이를 서버 실행시 지정해 주어야 한다.
먼저 keytool을 이용하여 특정 키 저장소를 만들고 그곳에 개인키/공개키쌍을 저장하는 방법을 살펴보자.

jdk폴더의 하위 폴더인 bin 폴더에 보면 keytool이라는 실행파일이 존재한다.
이 툴을 이용하여

keytool -genkey -keystore testStore -keyalg RSA

명령을 통해 새로운 키를 생성한다. 이때 testStore라는 부분에는 파일명이 들어가는데 절대경로를 통해 특정 위치까지 지정할 수 있다.
예를 들면
keytool -genkey -keystore d:myKeyStoremyKey -keyalg RSA
이런 방식이다.


SSLServer의 시작

SSLServer를 시작할때는 키저장소를 지정하여 시작하여야 한다. 그렇지 않으면 클라이언트와 HandShake할 때 인증서를 찾을 수 없어 오류가 발생한다.

java -Djavax.net.ssl.keyStore=d:myKeyStoremyKey
-Djavax.net.ssl.keyStorePassword=tutorial server.SSLServer

이떄 keyStorePassword는 새로운 키 저장소 및 키를 생성할때 키 저장소 비밀번호로 지정한 것을 입력하여야 한다.
이제 SSLServer가 정상적으로 구동된다.


SSLClient

SSLClient는 역시 java로 구성한 SSLSocket을 이용하고 있다.

package client;
import javax.net.ssl.*;
import javax.net.*;
import java.io.*;
import java.net.*;

public class SSLClient {

public SSLClient() {
super();
}

public static void main(String args[]) throws IOException {
SocketFactory socketFactory = SSLSocketFactory.getDefault();
if(args.length!=2){
System.out.println("host port_num이 각각 인자로 설정되어야 합니다.");
System.exit(-1);
}

Socket socket = socketFactory.createSocket(args[0], Integer.parseInt(args[1]));
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);

BufferedReader socketBr = new BufferedReader(new InputStreamReader(socket.getInputStream()));

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();
}
}

위 코드를 보면 ServerSocket과 마찬가지로 SSLSocketFactory로부터 SSLSocket을 생산하는 부분만 일반 Socket 생성과 다른점이라고 볼 수 있다.

이 클라이언트를 정상적으로 구동시키기 위해서는 다시 복잡한 keytool을 사용하여 키 저장소를 설정하여야 한다. SSL서버에 접속하기 위해 클라이언트는 HandShake라는 과정을 거치는데 이때 서버로부터 서버 공개키(인증서)를 받게 된다. 하지만 이 서버의 인증서가 정상적인(또는 믿음직스러운)으로 CA로부터 인증받은 것인지를 알 수 없기때문에 오류가 발생하게 된다. 이를 피하는 방법으로 해당 서버의 인증서가 정상적인 것이라고 설정하는 방법이 있는데 이는 trustStore에 서버의 인증서를 등록하여 믿음직스러운 서버라고 등록하는 것이다.

서버의 공개키(인증서)를 먼저 export한다.
keytool -export -file d: est.cer

이렇게 export한 인증서를 클라이언트 machine으로 옮겨 그곳의 keystore에 등록해야 한다.
keytool -import -keystore d: rustKeyStore estStore -file test.cer

이제 testStore에 서버 인증서가 등록되었다.

클라이언트 실행시에는 trust store에 대한 설정을 포함하여 구동시켜야 하므로
java -Djavax.net.ssl.trustStore=d: rustKeyStore estStore
-Djavax.net.ssl.trustStorePassword=tutorial client.SSLClient

와 같이 실행한다.
역시 trustStorePassword는 trustKeyStore를 만들때 넣었던 store 비밀번호이다.


기타

위에서는 SSLServer와 SSLClient를 모두 java로 구성하여 실행하는 방법을 살펴보았는데 서버의 인증서를 신뢰할 수 있도록 인증서를 먼저 등록해 주는것이 문제가 있었다. 하지만, https를 통해서 서버에 접속하는 경우 브라우저는 expolorer 또는 firefox라면 인증서를 인뢰사이트에 따로 등록하지 않아도 브라우저에서 사용자에게 직접 질의를 하여 신뢰여부를 묻고 신뢰하여 작업을 지속하게 되면 정상적으로 통신이 이루어진다. 즉, SSLClient와 같이 java의 JSSE를 통해 SSLServer에 접속하는 방법인 경우는 반드시 서버 인증서를 클라이언트가 신뢰할 수 있도록 설정하는 작업이 필요하나 브라우저로 접근하는 경우는 브라우저에서 신뢰여부를 사용자에게 확인하고 따로 서버 인증서를 등록하지 않는다.

만일 소켓통신을 위한 클라이언트는 JSSE로 구성하지 않고 다른 언어나 java의 경우라도 다른 툴을 사용하는 경우라면 서버인증서의 신뢰여부를 굳이 인증서를 등록하지 않는 방법으로 구성할 수 있을 것으로 보인다.(브라우저가 이미 성공적으로 그 일을 하고 있으므로)