본문 바로가기

Dev Log

IntelliJ에서는 안 되고, Gradle에서는 되는 이유 (PKIX 에러 해결기)

728x90

프로젝트를 실행할 때 항상 IntelliJ 상단 Run 버튼으로 실행해왔다. 그런데 메인 브랜치를 pull 받은 후 실행했더니 아래와 같은 오류가 발생했다.

`unable to find valid certification path to requested target
  at java.base/sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:148)
  at java.base/sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:129)

 

로그를 따라가보니 다음 부분에서 터지고 있었다.

PKIX path building failed
unable to find valid certification path to requested target 

executing POST <https://xxxxxxx/auth-api/petra/decrypt>

PKIX = Public Key Infrastructure (X.509) : 공개키 기반 인증 시스템를 의미

🤔 이상한 점

같은 프로젝트인데:

  • ❌ IntelliJ Run → 실패
  • ✅ ./gradlew clean bootRun 명령어 실행→ 성공

“도대체 뭐가 다른 거지?”라는 의문이 생겼다.

문제 발생 지점 추적

에러는 Spring Boot 시작 과정에서 발생했다.

MainApiApplication.main()
        ↓
SpringApplication.run()
        ↓
ApplicationContext 생성
        ↓
Bean 생성 시작
        ↓
DatabaseConfig
        ↓
primaryDataSource()
        ↓
🔴 decrypt(password) 실행
        ↓
Feign → HTTPS 요청
        ↓
🔥 SSL Handshake 발생
        ↓
❌ PKIX 에러

 

정리하자면 핵심은 이것이였다:

DB 비밀번호를 복호화하기 위해 외부 API를 호출하는 순간 SSL handshake가 발생했고, 여기서 실패했다.

 

이쯤에서 정리해보는 SSL 단계

ClientHello (TLS 버전, 지원 알고리즘, 랜덤값)  ---->
                              <---- ServerHello (선택된 cipher, 서버 랜덤값)
                              <---- Server 인증서 전달 (도메인 인증서, 중간 인증서, 루트 체인 일부)

[클라이언트(JVM) 내부]
인증서 검증 수행
- CA가 truststore에 존재하는가?
- 인증서 체인이 유효한가?
- 도메인(hostname) 일치하는가?

❌ (현재 경우)
검증 실패 → PKIX path building failed → handshake 종료

✔ (정상일 경우)
ClientKeyExchange (Pre-Master Secret 전달) ---->
→ Client Random + Server Random + Pre-Master Secret 조합
ChangeCipherSpec -------------------------->
Finished ---------------------------------->

                              <---- ChangeCipherSpec
                              <---- Finished

→ 이후 HTTPS(암호화된 HTTP) 요청 시작

왜 실패를 했을까?

SSL 검증은 JVM 내부의 Truststore를 기준으로 수행된다.

Truststore = “이 인증서는 믿어도 된다” 목록

여기서 추측할 수 있는 부분은 bootRun을 실행했을 때, intelliJ 에서 Run 헀을 때 실행 환경마다 사용하는 JDK가 다르다는 점이였다. 아래 명령어를 각각 실행하여 참조하는 JDK를 확인해봤다.

실제 Gradle 은

// ./gradlew -version 명령어 사용

------------------------------------------------------------
Gradle 8.14.2
------------------------------------------------------------

Build time:    2025-06-05 13:32:01 UTC
Revision:      30db2a3bdfffa9f8b40e798095675f9dab990a9a

Kotlin:        2.0.21
Groovy:        3.0.24
Ant:           Apache Ant(TM) version 1.10.15 compiled on August 25 2024
Launcher JVM:  21.0.7 (Eclipse Adoptium 21.0.7+6-LTS)
Daemon JVM:    /Users/xx/Library/Java/JavaVirtualMachines/temurin-21.0.7/Contents/Home (no JDK specified, using current Java home)
OS:            Mac OS X 15.5 aarch64

 

Intellij(에러 발생 환경)

//Intellij에서 사용하는 것 확인해보기
System.out.println(System.getProperty("java.home")); > 메인함수 입력후 RUN

>>> java.home: /opt/homebrew/Cellar/openjdk@21/21.0.8/libexec/openjdk.jdk/Contents/Home

 

그러면 복호화를 하기 위한 호출하는 API의 CA를 확인하고 그게 각각의 truststore에 세팅이 되어있는지 확인해보자.

 

서버 CA 확인 방법 :

이 서버는 NAVER Cloud 전용 CA로 발급된 인증서를 사용 중

//openssl s_client -connect ${domain name}.net:443 -showcerts

Server certificate
subject=CN=*.${domain}
issuer=C=KR, O=NAVER Cloud Trust Services Corp., CN=NAVER Secure Certification Authority 2

 

네이버 CA 있는지 확인

  1. Temurin JDK (bootRun 일때 실행되는것)
/Users/${user}/Library/Java/JavaVirtualMachines/temurin-21.0.7/Contents/Home/bin/keytool \\
-list \\
-keystore /Users/xxx/Library/Java/JavaVirtualMachines/temurin-21.0.7/Contents/Home/lib/security/cacerts \\
-storepass changeit | grep -i naver

 

결과 - CA 있음

Warning: use -cacerts option to access cacerts keystore
cn_naver_global_root_certification_authority,o_naver_business_platform_corp,c_kr [jdk], Apr 14, 2026, trustedCertEntry, 

  1. Homebrew OpenJDK (IntelliJ에서 쓰는 것)
/opt/homebrew/Cellar/openjdk@21/21.0.8/libexec/openjdk.jdk/Contents/Home/bin/keytool \\
-list \\
-keystore /opt/homebrew/Cellar/openjdk@21/21.0.8/libexec/openjdk.jdk/Contents/Home/lib/security/cacerts \\
-storepass changeit | grep -i naver

 

결과 - CA 없음

warning: use -cacerts option to access cacerts keystore

 

결론: 원인은 네이버 CA 가 homebrew installed JDK 21 에는 없어서 복호화 과정에서 SSL 오류가 난 것이였다.

해결책 : CA를 세팅해주면 된다!

여기서 또 이해해야하는 개념이, SSL 인증서에는 서버 인증서, 중간 인증서, 루트 인증서가 존재한다. 그래서 해당 도메인 CA 확인하는 부분에 가게되면 3개의 블록이 존재하는데 서버 인증서, 중간인증서, 루트인증서가 있다.

Certificate chain
 0 s:CN=*.${domain}
   i:C=KR, O=NAVER Cloud Trust Services Corp., CN=NAVER Secure Certification Authority 2
   a:PKEY: RSA, 2048 (bit); sigalg: sha256WithRSAEncryption
   v:NotBefore: Feb 20 00:00:00 2026 GMT; NotAfter: Sep  7 11:59:59 2026 GMT
-----BEGIN CERTIFICATE-----

-----END CERTIFICATE-----
 1 s:C=KR, O=NAVER Cloud Trust Services Corp., CN=NAVER Secure Certification Authority 2
   i:C=KR, O=NAVER BUSINESS PLATFORM Corp., CN=NAVER Global Root Certification Authority
   a:PKEY: RSA, 4096 (bit); sigalg: sha384WithRSAEncryption
   v:NotBefore: Nov 19 00:00:00 2025 GMT; NotAfter: Nov 19 11:59:59 2035 GMT
-----BEGIN CERTIFICATE-----

-----END CERTIFICATE-----
 2 s:C=KR, O=NAVER BUSINESS PLATFORM Corp., CN=NAVER Global Root Certification Authority
   i:C=KR, O=NAVER BUSINESS PLATFORM Corp., CN=NAVER Global Root Certification Authority
   a:PKEY: RSA, 4096 (bit); sigalg: sha384WithRSAEncryption
   v:NotBefore: Aug 18 08:58:42 2017 GMT; NotAfter: Aug 18 23:59:59 2037 GMT
-----BEGIN CERTIFICATE-----

-----END CERTIFICATE-----

 

참고로 SSL 인증서는 한 장짜리가 아니라 아래처럼 체인 구조인데, 이렇게 나눈 이유는 보안+ 관리 때문이다. 루트는 숨겨두고 중간 CA에게 발급 권한을 위임하는 것 이다.

[루트 인증서 (Root CA)]
        ↓
[중간 인증서 (Intermediate CA)]
        ↓
[서버 인증서 (도메인 인증서)]
  • 루트 인증서 (Root CA): 최상위, JDK truststore에 이미 들어있음 (DigiCert)
  • 중간 인증서 : 루트 CA 대신 실제 인증서를 발급하는 예
  • 서버 인증서: 우리가 접속하는 대상 (dev.xxx.net)

비유하자면

사원: 이거 제가 만든 문서입니다(서버 인증서)

팀장: 이 문서는 내가 검토했고 문제없다(중간 인증서)

CEO: 이 팀장은 내가 인증한 사람입니다(루트 인증서)

만약 루트 인증서(CEO)가 직접 모든 인증을 처리한다고 가정해보면 이 경우 CEO의 키가 유출되면, 그 CEO가 승인한 모든 인증서가 신뢰를 잃게 되고 전체 시스템의 보안이 무너진다.

그래서 실제 SSL 구조에서는 루트 인증서가 직접 서명하지 않고, 중간 인증서(팀장)에게 권한을 위임하는 구조를 사용한다.

이번 문제 상황을 비유로 보면 다음과 같다.

  • 신입사원(서버)이 문서를 제출했다
  • 팀장(중간 인증서)의 서명이 있다
  • 하지만 IntelliJ의 JVM는 그 팀장을 모른다

결과: “이 팀장 누구야? 못 믿겠는데?” → PKIX 에러 발생

이를 해결하기 위해 우리가 한 작업은 다음과 같다.

✔ JVM에 “이 팀장은 신뢰 가능한 사람이다”라고 등록

→ 즉, 중간 인증서를 truststore에 추가

자 이제 실제로 인증서를 붙여넣어 보자. 여기서 중간 인증서를 붙여넣는다

nano ~/Downloads/naver-ca.crt

-----BEGIN CERTIFICATE-----
MIIGrjCCBJagAwIBAgIUFJ5/xhegeJsX13b9RcYLaXVPUwAwDQYJKoZIhvcNAQEM
xxxx
-----END CERTIFICATE-----

아래 명렁어를 실행해서 CA import > 다시 Intellij Run 해주면 문제 없이 빌드 성공

sudo keytool -import \\
-alias naver-ca \\
-file /Users/${user}/Downloads/naver-ca.crt \\
-keystore /opt/homebrew/Cellar/openjdk@21/21.0.8/libexec/openjdk.jdk/Contents/Home/lib/security/cacerts

위 인증서 trust 할거냐 질문나오는데 Yes 해주면 됨

 

마무리

이번 이슈를 통해 SSL과 인증서 검증 과정에 대해 더 깊이 이해할 수 있었다. 특히 SSL 인증서가 단일 구조가 아니라 체인 구조로 이루어져 있다는 점은 새롭게 알게 된 부분이었다. 실제로 CA를 확인하고 중간 인증서를 직접 추출해 적용해보면서, 인증서가 어떤 방식으로 신뢰를 형성하는지 동작 원리까지 체감할 수 있었다.

728x90