본문 바로가기

개발/Java

Java 7에서 HTTPS 호출이 안 된다... TLS 1.2만의 문제가 아니었다

기존에 외부 통계 API를 호출해서 데이터를 받아오는 기능이 있었다.

오랫동안 별문제 없이 사용하던 기능이었는데, 어느 날부터 개발 서버에서 API 호출이 정상적으로 되지 않았다.

확인해보니 기존에는 HTTP로도 요청할 수 있었지만 이제 HTTPS를 사용해야 하는 상황이었다.

처음에는 단순하게 생각했다.

URL이 http에서 https로 바뀌어서 그런가?

 

그런데 URL만 HTTPS로 변경한다고 끝나는 문제가 아니었다.

개발 서버는 Java 7을 사용하고 있었다

문제가 발생한 개발 서버의 Java 버전을 확인해보니 Java 1.7.0_80이었다.

여기서 가장 먼저 의심했던 게 TLS였다.

HTTPS 통신을 하려면 클라이언트와 서버가 서로 사용할 수 있는 TLS 버전이 맞아야 하는데, Java 7은 꽤 오래된 버전이다.

그래서 Java 7이 TLS 1.2를 아예 지원하지 않는 건가 싶었다.

확인해보니 그건 아니었다.

Supported Protocols:
[SSLv2Hello, SSLv3, TLSv1, TLSv1.1, TLSv1.2]

Enabled Protocols:
[TLSv1]
 

TLS 1.2 자체는 지원하고 있었다.

그런데 지원하는 프로토콜과 기본으로 활성화되어 있는 프로토콜은 달랐다.

현재 환경에서는 TLS 1.2까지 지원하지만 기본 HTTPS 연결에서는 TLS 1.0이 활성화되어 있었다.


TLS 1.2를 명시해서 다시 호출해봤다

그렇다면 HTTPS 연결에서 TLS 1.2를 사용하도록 명시하면 되지 않을까 싶었다.

 
System.setProperty("https.protocols", "TLSv1.2");
 

TLS 1.2를 사용하도록 설정한 뒤 다시 요청해봤다.

그런데 이번에는 다른 오류가 발생했다.

javax.net.ssl.SSLHandshakeException:
sun.security.validator.ValidatorException:
PKIX path building failed

SunCertPathBuilderException:
unable to find valid certification path to requested target
 

처음에는 TLS 문제 하나만 해결하면 끝날 줄 알았는데 아니었다.

오히려 TLS 1.2로 통신할 수 있게 되면서 그다음 단계인 서버 인증서 검증에서 문제가 드러난 것이었다.


PKIX path building failed는 무슨 뜻일까?

HTTPS 연결에서는 상대 서버가 보내주는 인증서를 그냥 믿는 게 아니다.

Java가 가지고 있는 신뢰할 수 있는 인증서 목록을 기준으로

서버 인증서
    ↓
중간 인증서
    ↓
루트 인증서
 

이 연결을 따라가면서 이 서버 인증서를 믿어도 되는지 확인한다.

Java에서는 기본적으로 cacerts라는 truststore에 신뢰할 인증서들이 들어 있다.

그런데 개발 서버에서 사용하는 Java 7의 cacerts에는 현재 API 서버의 인증서 체인을 검증하는 데 필요한 인증서가 없었다.

그래서 Java 입장에서는 대략 이런 상태였던 셈이다.

TLS 연결 자체는 할 수 있는데,
이 서버가 보내준 인증서를 내가 믿어도 되는지 확인할 수가 없는데?

 

그 결과가 PKIX path building failed였다.

Java 7이면 무조건 발생하는 문제는 아니었다

처음에는 이것도 Java 7이라서 발생한 문제라고 생각하기 쉬웠다.

하지만 정확히는 그렇지 않았다.

같은 Java 7을 사용한다고 해도 각 서버의 cacerts에 어떤 인증서가 등록되어 있는지에 따라 결과가 달라질 수 있다.

예를 들어 다른 서버에서는 예전에 필요한 인증서를 등록했을 수도 있고, 사용하는 JDK 배포판이나 관리 상태가 다를 수도 있다.

그래서

Java 7 = 해당 HTTPS 사이트 접속 불가

 

라고 보기보다는,

현재 개발 서버의 Java truststore에서 해당 인증서 체인을 신뢰할 수 없는 상태

라고 보는 게 더 정확했다.

cacerts를 직접 확인해봤다

Java의 keytool을 이용하면 truststore에 어떤 인증서가 들어 있는지 확인할 수 있다.

내 환경에서는 다음과 같이 확인했다.

 
keytool -list -keystore "%JAVA_HOME%\jre\lib\security\cacerts"
 

특정 인증서를 확인할 때는 alias를 지정할 수도 있다.

 
keytool -list ^
  -keystore "%JAVA_HOME%\jre\lib\security\cacerts" ^
  -alias <인증서-alias>
 

필요한 인증서를 등록한 후에는 다시 keytool -list로 실제 등록 여부를 확인하는 게 좋다.


이번에 헷갈렸던 부분

처음에는 문제가 하나라고 생각했다.

HTTPS로 변경됨
       ↓
Java 7이라 TLS 문제인가?
 

그런데 확인해보니 실제로는 단계를 나눠서 봐야 했다.

Java 7
 ↓
TLS 1.2 지원 여부 확인
 ↓
지원은 함
 ↓
하지만 기본 활성화는 TLS 1.0
 ↓
TLS 1.2 명시
 ↓
SSL Handshake 진행
 ↓
PKIX path building failed
 ↓
인증서 신뢰 문제 확인
 

특히 TLS 1.2를 지원한다와 TLS 1.2가 기본으로 사용된다는 같은 말이 아니었다.

그리고 PKIX path building failed도 TLS 버전 자체의 오류가 아니라 TLS 연결 과정에서 인증서를 신뢰하지 못해서 발생하는 오류였다.

이번에는 HTTPS 하나 때문에 시작했는데 생각보다 확인할 게 많았다.

다음에 오래된 Java 환경에서 HTTPS 호출이 갑자기 안 된다면 URL만 볼 게 아니라 TLS 버전과 truststore를 따로 확인해봐야겠다.