2012년 3월 19일 월요일

HTTPS 통신

참고 - http://docs.oracle.com/javase/6/docs/technotes/guides/security/jsse/JSSERefGuide.html

프로젝트명: HTTPSPractice


파일 구성:

 - HttpsClientWithCustomCert.java - 사용자가 만든 인증서를 이용한 서버 인증
 - HttpsClientWithDefaultCACert.java - 공인 인증된 인증서로 서버 인증
 - HttpsClientWithoutValidation.java - 인증 없이 서버 접속
 - SimpleHttpsServer.java - 사용자가 만든 인증키를 이용하는 간단한 HTTPS 서버
 - keystore.jks - 서버(SimpleHttpsServer) 에서 사용하는 인증키 저장소
 - truststore.jks - 클라이언트(HttpsClientWithCustomCert) 에서 서버 인증시 사용하는 인증서 저장소


다음과 같은 순서로 설명
1. HttpsURLConnection 을 이용한 접속 (인증 과정 없음)
2. 공인 인증된 인증서로 서버 인증
3. 사용자가 만든 인증서로 서버 인증


1. HttpsURLConnection 을 이용한 접속 (인증 과정 없음)

인증 과정을 결정하는 부분은 SSLContext.init() 함수이다.
아래 소스에는 init() 함수에 모두 null 을 집어 넣음으로써 인증 과정을 무시하고 서버의 내용물을 받는다.

소스:
package com.test.https.practice;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;


/**
 * 
 */
public class HttpsClientWithoutValidation {
 
 /**
  * 
  * @param urlString
  * @throws IOException
  * @throws NoSuchAlgorithmException
  * @throws KeyManagementException
  */
 public void getHttps(String urlString) throws IOException, NoSuchAlgorithmException, KeyManagementException {
  
  // Get HTTPS URL connection
  URL url = new URL(urlString);  
  HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
  
  // Set Hostname verification
  conn.setHostnameVerifier(new HostnameVerifier() {
   @Override
   public boolean verify(String hostname, SSLSession session) {
    // Ignore host name verification. It always returns true.
    return true;
   }
   
  });
  
  // SSL setting
  SSLContext context = SSLContext.getInstance("TLS");
  context.init(null, null, null);  // No validation for now
  conn.setSSLSocketFactory(context.getSocketFactory());
  
  // Connect to host
  conn.connect();
  conn.setInstanceFollowRedirects(true);
  
  // Print response from host
  InputStream in = conn.getInputStream();
  BufferedReader reader = new BufferedReader(new InputStreamReader(in));
  String line = null;
  while ((line = reader.readLine()) != null) {
   System.out.printf("%s\n", line);
  }
  
  reader.close();
 }
 
 /**
  * 
  * @param args
  * @throws Exception
  */
 public static void main(String[] args) throws Exception {
  HttpsClientWithoutValidation test = new HttpsClientWithoutValidation();
  test.getHttps("https://www.google.com");
 }
}

결과:


2. 공인 인증된 인증서로 서버 인증

SSLContext.init() 함수의 두번째 인자에 X509TrustManager 를 선언하여 집어 넣었다.
그리고 JRE 를 설치하면 기본적으로 [JRE 경로]/lib/security/cacerts 라는 파일명의 공인 인증된 인증서 저장소 파일이 있다.
해당 인증서 저장소를 이용하여 서버의 인증서를 검사하게 하였다.

소스:
package com.test.https.practice;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

/**
 *
 */
public class HttpsClientWithDefaultCACert {
 
 /**
  * 
  * @param urlString
  * @throws IOException
  * @throws NoSuchAlgorithmException
  * @throws KeyManagementException
  */
 public void getHttps(String urlString) throws IOException, NoSuchAlgorithmException, KeyManagementException {
  
  // Get HTTPS URL connection
  URL url = new URL(urlString);  
  HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
  
  // Set Hostname verification
  conn.setHostnameVerifier(new HostnameVerifier() {
   @Override
   public boolean verify(String hostname, SSLSession session) {
    // Ignore host name verification. It always returns true.
    return true;
   }
   
  });
  
  // SSL setting
  SSLContext context = SSLContext.getInstance("TLS");
  context.init(null, new TrustManager[] { new X509TrustManager() {

   @Override
   public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    // client certification check
   }

   @Override
   public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    
    // Server certification check
    
    try {
     
     // Get trust store
     KeyStore trustStore = KeyStore.getInstance("JKS");
     String cacertPath = System.getProperty("java.home") + "/lib/security/cacerts"; // Trust store path should be different by system platform.
     trustStore.load(new FileInputStream(cacertPath), "changeit".toCharArray()); // Use default certification validation
     
     // Get Trust Manager
     TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
     tmf.init(trustStore);
     TrustManager[] tms = tmf.getTrustManagers();
     ((X509TrustManager)tms[0]).checkServerTrusted(chain, authType);
     
    } catch (KeyStoreException e) {
     e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
     e.printStackTrace();
    } catch (IOException e) {
     e.printStackTrace();
    }
    
   }

   @Override
   public X509Certificate[] getAcceptedIssuers() {
    return null;
   }
  } }, null);
  conn.setSSLSocketFactory(context.getSocketFactory());
  
  // Connect to host
  conn.connect();
  conn.setInstanceFollowRedirects(true);
  
  // Print response from host
  InputStream in = conn.getInputStream();
  BufferedReader reader = new BufferedReader(new InputStreamReader(in));
  String line = null;
  while ((line = reader.readLine()) != null) {
   System.out.printf("%s\n", line);
  }
  
  reader.close();
 }
 
 /**
  * 
  * @param args
  * @throws Exception
  */
 public static void main(String[] args) throws Exception {
  HttpsClientWithDefaultCACert test = new HttpsClientWithDefaultCACert();
  test.getHttps("https://www.google.com");
 }
}

결과:


3. 사용자가 만든 인증서로 서버 인증

우선 인증키 저장소와 인증서 저장소를 만들어 보자.
Portecle 라는 툴을 사용했다.

3.1. 인증키 저장소 만들기(keystore.jks)
3.2. 인증키에서 인증서 추출
3.3. 인증서 저장소 만들기(truststore.jks)

서버는 지정된 인증키 저장소를 이용해 클라이언트에 자신을 인증하는 키를 보내게 된다. 인증과정이 성공하면 클라이언트에 html 내용물을 전달하고 종료한다.
클라이언트는 지정된 인증서 저장소를 이용해 서버를 인증한다. 인증과정이 성공하면 서버로 부터 html 내용물을 전달받는다.

서버 소스:
참고 - http://www.java2s.com/Tutorial/Java/0490__Security/SSLContextandKeymanager.htm
package com.test.https.practice;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;

public class SimpleHttpsServer {
 
 public void run(int port) throws NoSuchAlgorithmException, KeyManagementException, IOException, KeyStoreException, CertificateException, UnrecoverableKeyException {
  
  // create ssl context
  SSLContext context = SSLContext.getInstance("TLS");
  
  // set key store
  KeyStore keyStore = KeyStore.getInstance("JKS");
  keyStore.load(new FileInputStream("keystore.jks"), "changeit".toCharArray());
  KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
  kmf.init(keyStore, "changeit".toCharArray());
  context.init(kmf.getKeyManagers(), null, null);
  
  // create ssl socket
  SSLServerSocketFactory factory = context.getServerSocketFactory();
  SSLServerSocket socket = (SSLServerSocket)factory.createServerSocket(port);
  SSLSocket client = (SSLSocket)socket.accept();
  InputStream in = client.getInputStream();
  OutputStream out = client.getOutputStream();
  
  // read from client
  BufferedReader reader = new BufferedReader(new InputStreamReader(in));
  reader.readLine();
  
  // write to client
  BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out));
  writer.write("HTTP/1.0 200 OK");
  writer.newLine();
  writer.write("Content-Type: text/html");
  writer.newLine();
  writer.newLine();
  writer.write("<html><head><title>Hello</title></head><body>Hellow!</body></html>");
  writer.flush();
  
  // close
  writer.close();
  reader.close();
  client.close();
 }
 
 /**
  * 
  * @param args
  * @throws Exception
  */
 public static void main(String[] args) throws Exception {
  SimpleHttpsServer server = new SimpleHttpsServer();
  server.run(9999);
 }
}


클라이언트 소스:
package com.test.https.practice;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

public class HttpsClientWithCustomCert {
 
 /**
  * 
  * @param urlString
  * @throws IOException
  * @throws NoSuchAlgorithmException
  * @throws KeyManagementException
  */
 public void getHttps(String urlString) throws IOException, NoSuchAlgorithmException, KeyManagementException {
  
  // Get HTTPS URL connection
  URL url = new URL(urlString);  
  HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
  
  // Set Hostname verification
  conn.setHostnameVerifier(new HostnameVerifier() {
   @Override
   public boolean verify(String hostname, SSLSession session) {
    // Ignore host name verification. It always returns true.
    return true;
   }
   
  });
  
  // SSL setting
  SSLContext context = SSLContext.getInstance("TLS");
  context.init(null, new TrustManager[] { new X509TrustManager() {

   @Override
   public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    // client certification check
   }

   @Override
   public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    
    // Server certification check
    
    try {
     
     // Get trust store
     KeyStore trustStore = KeyStore.getInstance("JKS");
     trustStore.load(new FileInputStream("truststore.jks"), "changeit".toCharArray()); // Use default certification validation
     
     // Get Trust Manager
     TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
     tmf.init(trustStore);
     TrustManager[] tms = tmf.getTrustManagers();
     ((X509TrustManager)tms[0]).checkServerTrusted(chain, authType);
     
    } catch (KeyStoreException e) {
     e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
     e.printStackTrace();
    } catch (IOException e) {
     e.printStackTrace();
    }
    
   }

   @Override
   public X509Certificate[] getAcceptedIssuers() {
    return null;
   }
  } }, null);
  conn.setSSLSocketFactory(context.getSocketFactory());
  
  // Connect to host
  conn.connect();
  conn.setInstanceFollowRedirects(true);
  
  // Print response from host
  InputStream in = conn.getInputStream();
  BufferedReader reader = new BufferedReader(new InputStreamReader(in));
  String line = null;
  while ((line = reader.readLine()) != null) {
   System.out.printf("%s\n", line);
  }
  
  reader.close();
 }
 
 /**
  * 
  * @param args
  * @throws Exception
  */
 public static void main(String[] args) throws Exception {
  HttpsClientWithCustomCert test = new HttpsClientWithCustomCert();
  test.getHttps("https://127.0.0.1:9999");
 }
}

결과:

댓글 없음:

댓글 쓰기