문제 분석
앱에서 로그인이 실패한다는 문의를 받았습니다.
앱의 로그를 확인해 보니 다음과 같은 오류가 발생하고 있었습니다.
javax.net.ssl.SSLHandshakeException:
java.security.cert.CertPathValidatorException:
Trust anchor for certification path not found.
SSL Handshake 과정에서 인증서 검증에 실패한 것입니다.
조금 더 아래의 로그를 살펴보면 다음과 같은 원인도 함께 확인할 수 있었습니다.
Flattened final assist data: 4868 bytes, containing 1 windows, 16 views
2026-07-08 17:33:46.768 25909-26119 ==========...========== E ================HttpConnect=====================
2026-07-08 17:33:50.033 25909-26114 nkiosk.app.tes W Accessing hidden method Ljava/lang/invoke/MethodHandles$Lookup;-><init>(Ljava/lang/Class;I)V (light greylist, reflection)
2026-07-08 17:33:50.125 25909-26114 System.err W **javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.**
2026-07-08 17:33:50.129 25909-26114 System.err W **Caused by: java.security.cert.CertificateException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.**
2026-07-08 17:33:50.129 25909-26114 System.err W at com.android.org.conscrypt.TrustManagerImpl.checkTrustedRecursive(TrustManagerImpl.java:646)
2026-07-08 17:33:50.129 25909-26114 System.err W at com.android.org.conscrypt.TrustManagerImpl.checkTrusted(TrustManagerImpl.java:495)
2026-07-08 17:33:50.129 25909-26114 System.err W at com.android.org.conscrypt.TrustManagerImpl.checkTrusted(TrustManagerImpl.java:418)
2026-07-08 17:33:50.129 25909-26114 System.err W at com.android.org.conscrypt.TrustManagerImpl.getTrustedChainForServer(TrustManagerImpl.java:339)
2026-07-08 17:33:50.129 25909-26114 System.err W at android.security.net.config.NetworkSecurityTrustManager.checkServerTrusted(NetworkSecurityTrustManager.java:94)
2026-07-08 17:33:50.130 25909-26114 System.err W at android.security.net.config.RootTrustManager.checkServerTrusted(RootTrustManager.java:88)
2026-07-08 17:33:50.130 25909-26114 System.err W at com.android.org.conscrypt.Platform.checkServerTrusted(Platform.java:208)
2026-07-08 17:33:50.130 25909-26114 System.err W at com.android.org.conscrypt.ConscryptFileDescriptorSocket.verifyCertificateChain(ConscryptFileDescriptorSocket.java:404)
2026-07-08 17:33:50.130 25909-26114 System.err W at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
2026-07-08 17:33:50.130 25909-26114 System.err W at com.android.org.conscrypt.NativeSsl.doHandshake(NativeSsl.java:375)
2026-07-08 17:33:50.130 25909-26114 System.err W at com.android.org.conscrypt.ConscryptFileDescriptorSocket.startHandshake(ConscryptFileDescriptorSocket.java:224)
2026-07-08 17:33:50.130 25909-26114 System.err W ... 30 more
2026-07-08 17:33:50.131 25909-26114 System.err W **Caused by: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.**
2026-07-08 17:33:50.131 25909-26114 System.err W ... 41 more
2026-07-08 17:33:50.194 25909-25909 kr.co.sol....SOL_Logger E [[LoginActivity]] 로그인 페이지 - 로그인 실패 , result = null
2026-07-08 17:33:50.202 25909-25909 System.err W java.lang.Exception: 사용자 네트워크 연결이 불안정 하거나
2026-07-08 17:33:50.202 25909-25909 System.err W 인터넷 서비스 문제 일 수 있습니다.
2026-07-08 17:33:50.202 25909-25909 System.err W 인터넷 연결 확인 후 다시 시도해주세요.
2026-07-08 17:33:50.203 25909-25909 System.err W at $4$1.success(LoginActivity.java:189)
2026-07-08 17:33:50.203 25909-25909 System.err W at $8.completedConnection(ApiRequest.java:425)
2026-07-08 17:33:50.203 25909-25909 System.err W at com.w.base.lib.socket.ConnectionManager$ConnectionProcess.onPostExecute(ConnectionManager.java:284)
2026-07-08 17:33:50.203 25909-25909 System.err W at com.w.base.lib.socket.ConnectionManager$ConnectionProcess.onPostExecute(ConnectionManager.java:91)
2026-07-08 17:33:50.204 25909-25909 System.err W at android.os.AsyncTask.finish(AsyncTask.java:695)
2026-07-08 17:33:50.204 25909-25909 System.err W at android.os.AsyncTask.access$600(AsyncTask.java:180)
2026-07-08 17:33:50.204 25909-25909 System.err W at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:712)
2026-07-08 17:33:50.204 25909-25909 System.err W at android.os.Handler.dispatchMessage(Handler.java:106)
2026-07-08 17:33:50.204 25909-25909 System.err W at android.os.Looper.loop(Looper.java:193)
2026-07-08 17:33:50.204 25909-25909 System.err W at android.app.ActivityThread.main(ActivityThread.java:6718)
2026-07-08 17:33:50.204 25909-25909 System.err W at java.lang.reflect.Method.invoke(Native Method)
2026-07-08 17:33:50.205 25909-25909 System.err W at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
2026-07-08 17:33:50.205 25909-25909 System.err W at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
2026-07-08 17:33:50.213 25909-25909 kr.co.sol....SOL_Logger E [[LoginActivity]] 사용자 네트워크 연결이 불안정 하거나
**Trust anchor for certification path not found.**는 인증서 체인을 따라 최종적으로 신뢰할 수 있는 루트 인증기관(Root CA)까지 도달하지 못했다는 의미입니다.
즉, 클라이언트 입장에서는 이 서버가 정말 신뢰할 수 있는 기관에서 발급받은 인증서인지 끝까지 증명하지 못했다 라고 판단한 것입니다.
문제가 발생하기 전, SSL 인증서를 새롭게 발급하여 서버에 적용했던 것이 떠올랐습니다. 혹시 인증서 교체 과정에서 문제가 발생했던 것은 아닌지 웹 호스팅 업체에 문의를 진행했습니다.
서버에 등록된 SSL 인증서 체인의 순서가 올바르지 않은 것 같다
라고 답변이 와 우선 기존 인증서로 원복을 진행했고, 이후 로그인 다시 정상적으로 동작했습니다.
하지만, 한 가지 의문이 생겼습니다.
인증서 순서가 정말 이렇게까지 중요한 걸까?
제가 인증서를 적용했을 때는 인증서 순서와 상관없이 적용이 되었던 것을 기억했습니다. 그렇다면 무슨 차이가 있길래 또 인증서 순서가 왜 중요한지에 대해 알기 위해 다시 공부해 보기로 했습니다.
SSL 인증서는 어떻게 신뢰를 증명할까?
SSL 인증서의 구조를 이해하기 위해서는 인증기관(CA)이 왜 여러 단계로 나누어져 있는지부터 알아야합니다.
저희가 해외여행을 간다고 생각 해봅시다.
입국 심사관들은 “대한민국은 입국 허가할 정도로 믿을 만한 나라다.”는 사실을 미리 알고 있습니다.
이것이 Root CA 입니다.
하지만, 각 정부에서 직접 국민 한 명씩에게 여권을 발급하기 어렵습니다.
그래서 정부는 “이 관청은 대한민국이라는 이름으로 여권을 발급할 권한이 있다”라는 위임장을 각 구청과 영사관에 줍니다. 이 **위임장이 중간 인증서(Intermediate CA)**입니다.
실제로 국민들이 받는 여권은 이 구청이나 영사관에서 발급합니다.
이 여권이 서버 인증서(Server Certificate) 입니다.
이렇게 계층을 나누는 이유도 있습니다.
만약 어느 구청의 위임장이 도난 당하더라도 그 구청의 위임장만 취소하면 됩니다.
정부 본청의 도장(Root CA)은 안전하게 남습니다.
실제 SSL 인증서도 같은 이유로 Root CA가 Intermediate CA를 발급하고, Intermediate CA가 다시 Server Certificate를 발급하는 계층 구조를 사용합니다.
인증서는 어떻게 검증될까?
여권에는 이런 내용이 적혀 있습니다.
“이 여권은 강남구청에서 발급했습니다.”
하지만 입국 심사관은 강남구청을 모릅니다.
심사관이 신뢰하는 건 오직 대한민국 정부뿐입니다.
그래서 당신은 여권 뿐만 아니라 강남구청이 정부로부터 권한을 위임 받았다는 위임장(중간 인증서)까지 함께 제출해야 합니다.
그러면 심사관은 다음과 같이 확인합니다.
여권 확인
↓
강남구청이 발급했네
↓
강남구청 위임장을 확인
↓
대한민국 정부가 서명했네
↓
대한민국 정부는 내가 이미 가지고 있는 신뢰 목록(Trust Store)에 있어
↓
통과
이 과정은 인증서 체인(Certificate Chain) 검증이라고 합니다.
중간 인증서가 없다면?
만약 여권만 제출하고 위임장을 빠뜨리면 어떤 일이 발생할까요?
심사관은 이렇게 생각할 것입니다.
강남구청? 진짜 권한이 있는 기관인지 내가 어떻게 알아?
연결고리(chain)가 끊어져 입국 거부가 됩니다.
이는 불완전한 인증서 체인(Incomplete Certificate Chain)입니다.
이때 다음과 같은 오류를 자주 볼 수 있습니다.
"Certificate chain incomplete"
"Unable to verify the first certificate"
"Certificate not trusted"
"SSL certificate problem: unable to get local issuer certificate"
즉, 서버 인증서만 전달하고 중간 인증서를 전달하지 않으면 클라이언트는 루트 인증기관까지 연결되는 체인을 만들 수 없기 때문에 인증서를 신뢰하지 못하게 됩니다.
그렇다면 인증서 순서는 왜 중요할까?
심사관은 다음 순서로 확인 합니다.
여권
↓
위임장
↓
정부
그런데,
위임장
↓
여권
순으로 제출하게 되면 어떻게 될까요?
유능한 심사관(관대한 클라이언트)이라면, 서류가 뒤섞여 있어도 알아서 정리해서 읽어줍니다.
실제로 최신 브라우저나 OpenSSL, 일부 Android 환경은 인증서를 재조합하여 올바른 체인을 찾아주는 경우가 있습니다.
하지만, 원칙대로 검증하는 심사관(엄격한 클라이언트)은 순서가 올바르지 않으면 반려할 수도 있습니다.
일부 오래된 Android나 Java환경은 서버가 전달한 인증서 순서를 그대로 검증하기 때문에 체인 순서가 올바르지 않으면 인증서를 신뢰하지 못하는 경우가 있습니다.
그래서 대부분의 서버는 다음 순서대로 인증서를 구성하는 것이 권장됩니다.
Server Certificate
↓
Intermediate Certificate
↓
(Root CA는 일반적으로 포함하지 않음)
오래된 기기에서는 왜 더 자주 발생할까?
가끔 오래된 공항은 최신 정부 목록을 가지고 있지 않습니다.
새롭게 신뢰받기 시작한 정부가 있다면 그 정부를 아직 모를 수도 있죠.
그래서 구청은
새로운 정부의 위임장뿐만 아니라, 오래전부터 모두가 신뢰하던 정부의 보증도 함께 받아 둡니다.
최신 공항은 새로운 정부를 보고 통과시키고,
오래된 공항은 예전 정부를 보고 통과시킵니다.
SSL에서는 이러한 방식을 교차 서명(Cross Signing)이라고 합니다.
덕분에 오래된 운영체제나 브라우저에서도 동일한 인증서를 계속 사용할 수 있습니다.
이번 장애를 통해 배운 점
이번 일을 겪으면서 인증서는 단순히 “발급만 받으면 끝”인 것이 아니라는 사실을 다시 한번 느끼게 되었습니다.
인증서 자체가 정상이라 하더라도 인증서 체인이 올바르게 구성되어 있는지, 중간 인증서가 함께 전달되는지, 체인의 순서가 적절한지에 따라 일부 환경에서는 SSLHandshake가 실패할 수 있습니다.
또, 모든 클라이언트가 동일한 방식으로 인증서를 검증하는 것은 아니었습니다.
앞으로 비슷한 오류를 만나게 된다면 인증서 자체만 확인하는 것이 아니라 인증서 체인과 전달 순서까지 함께 점검해야 한다는 것을 이번 장애를 통해 배울 수 있었습니다.
'코딩 > ☠ Error' 카테고리의 다른 글
| 서버 오류(Too many connections) (1) | 2026.06.04 |
|---|---|
| [solidity] Error -32000 (0) | 2022.04.19 |
| [Solidity] TypeError: Contract "" should be marked as abstract. (0) | 2022.03.30 |
| invalid BigNumber value (argument="value", value=undefined, code=INVALID_ARGUMENT, version=bignumber/5.5.0) (0) | 2022.03.04 |
| [Next] Uncaught Error : Invalid src prop (0) | 2022.02.18 |