목차
- 자바 소스코드 컴파일 및 실행 예제 이전 포스팅
- 자바 HTTPS 통신 예제 이전 포스팅
- 자바에서 서버 CA 인증서 피닝 예제(CA Pinning)
- 잘못된 CA 인증서 피닝 후 HTTPS 서버 요청 예제
- 정상 CA 인증서 피닝 후 HTTPS 서버 요청 예제
자바 소스코드 컴파일 및 실행 예제 이전 포스팅
오늘 포스팅을 따라하기에 앞서 리눅스 환경에서 자바 소스코드 컴파일 및 실행 방법을 아직 모르신다면 예제를 따라하기 어렵습니다. 우선 아래 이전 포스팅 링크를 참조하여 자바 소스코드 컴파일 방법을 배운 후 진행해주세요.
2023.08.10 - [Linux] - [Linux/Java] 리눅스 우분투에서 java 컴파일 및 실행 예제(javac)
자바 HTTPS 통신 예제 이전 포스팅
그리고 SSLContext를 활용한 CA 인증서 피닝 후에 이전 포스팅에서 다뤘던 HTTP 커넥션을 이용하여 HTTPS 암호화 통신 예제를 진행하려합니다. 따라서 커넥션 생성 및 HTTPS 통신 방법에 대해 학습해주세요.
2023.08.10 - [Java] - [Java] HTTPS 클라이언트 암호화 통신 예제(HttpURLConnection)
자바에서 서버 CA 인증서 피닝 예제(CA Pinning)
아래는 자바에서 내가 요청하려는 서버의 CA 인증서를 미리 설정해놓는 예제입니다. 각 서버들은 사전에 신뢰할 수 있는 인증기관(CA)에게 자신들의 인증서를 서명 받습니다. 간혹 fake 웹 서버들이 다른 서버의 인증서를 자기들의 서명으로 위장하여 통신하는 경우가 있습니다. 이를 MITM 공격이라 합니다.
이 공격에 당하면 서버와 통신하는 내용이 유출될 수 있습니다. 따라서 클라이언트에 서버의 CA 인증서를 미리 저장하여 비정상적인 CA로 서명된 인증서가 오면 통신을 하지 않도록 설계합니다. 이러한 보안 기술을 SSL CA 피닝이라 합니다.
뒤에 나올 예제에서는 "example.com" 이라는 https 웹사이트에 접속합니다. 이 사이트의 CA 인증서는 아래와 같습니다.
저 인증서 파일을 "/tmp/pinning_ca.cert" 파일에 저장했습니다. 이제 아래 코드를 진행합니다.
import java.io.FileInputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
public class Example {
public static void main(String[] args) {
try {
// Load the CA certificate
String caCertificatePath = "/tmp/pinning_ca.cert"; // Specify the path to your CA certificate
FileInputStream caInputStream = new FileInputStream(caCertificatePath);
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
X509Certificate caCertificate = (X509Certificate) certificateFactory.generateCertificate(caInputStream);
caInputStream.close();
// Create a trust manager that checks against the CA certificate
TrustManager[] trustManager = new TrustManager[] {
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[] { caCertificate };
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
for (X509Certificate cert : certs) {
try {
cert.verify(caCertificate.getPublicKey());
} catch (Exception e) {
throw new SecurityException("Certificate verification failed: " + e.getMessage());
}
}
}
}
};
// Install the trust manager to the SSL context
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManager, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
} catch (Exception e) {
e.printStackTrace();
}
}
}
잘못된 CA 인증서 피닝 후 HTTPS 서버 요청 예제
그러면 위의 pinning_ca.cert는 정상 CA인증서이지만 잘못된 다른 서버 인증서, 즉 네이버 인증서(pinning_wrong_ca.cert ) 파일을 로드한 후 HTTP 커넥션을 하고 example.com 서버에 요청을 해보겠습니다.
import java.io.FileInputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
public class Example {
public static void main(String[] args) {
try {
// Load the CA certificate
String caCertificatePath = "/tmp/pinning_wrong_ca.cert"; // Specify the path to your CA certificate
FileInputStream caInputStream = new FileInputStream(caCertificatePath);
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
X509Certificate caCertificate = (X509Certificate) certificateFactory.generateCertificate(caInputStream);
caInputStream.close();
// Create a trust manager that checks against the CA certificate
TrustManager[] trustManager = new TrustManager[] {
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[] { caCertificate };
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
for (X509Certificate cert : certs) {
try {
cert.verify(caCertificate.getPublicKey());
break;
} catch (Exception e) {
throw new SecurityException("Certificate verification failed: " + e.getMessage());
}
}
}
}
};
// Install the trust manager to the SSL context
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManager, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
// Create a URL for the HTTPS request
URL url = new URL("https://example.com");
// Open a connection to the URL
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// Send a GET request
connection.setRequestMethod("GET");
// Read the response
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
StringBuilder response = new StringBuilder();
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
// Print the response
System.out.println("Response:\n" + response.toString());
// Close the connection
connection.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
정상 CA 인증서 피닝 후 HTTPS 서버 요청 예제
아래는 정상적인 CA 인증서 파일인 pinning_ca.cert 파일로 다시 지정하여 example.com 서버와 HTTPS 통신을 진행한 예제 결과입니다.