프로젝트명: 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)
서버는 지정된 인증키 저장소를 이용해 클라이언트에 자신을 인증하는 키를 보내게 된다. 인증과정이 성공하면 클라이언트에 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"); } }결과: