HTTPS 통신을 구현하다가 아래와 같은 오류를 보게 되었습니다. "CertPathValidatorException: Trust anchor for certification path not found" 이것저것 찾다보니까 몰랐던 내용...

[안드로이드] Retrofit, Trust anchor for certification path not found 해결방법

HTTPS 통신을 구현하다가 아래와 같은 오류를 보게 되었습니다.

"CertPathValidatorException: Trust anchor for certification path not found"

이것저것 찾다보니까 몰랐던 내용들도 많고 해서

해당 오류에 대한 해결 방법을 간단하게 정리해보려고 합니다.


 

 

 

 

 

 

1. 원인

HTTP와 다르게 HTTPS는 SSL(Secure Socket Layer)이 더해져서 보안이 강화된 프로토콜입니다.

SSL을 활용한 통신은 클라이언트와 서버 간의 암호화된 통신을 하면서 외부로부터 보호하는 역할을 합니다.


HTTPS 통신을 하기 위해서는 SSL 인증서에 대해 확인하는 동작이 필요하며,

이는 클라이언트측 OkHttpClient을 통해 적용이 가능합니다.

이를 별도로 구현하지 않았을 경우 위와 같은 오류를 볼 수 있습니다..!


해당 오류없이 HTTPS 통신을 구현하는 방법은 크게 두가지로 나눠서 볼 수 있습니다.

 - 인증서 없이 HTTPS 연결 (안전하지 않은 방법)

 - 인증서를 활용하여 HTTPS 연결 (권장)

 


 

 

 

 

 

2. 인증서 없이 HTTPS 연결

https:// 도메인에 접속했을 때 아래와 같은 화면을 보신적이 있을겁니다!

이때 "위험을 감수하고 계속 진행" 또는 "안전하지 않음으로 이동"으로 접속하는 방법과 비슷하게 

인증서 없이 HTTPS 연결하는 방법으로보면 좋을 것 같습니다.



Retrofit 생성

fun getInstance(context: Context): Retrofit {
if (instance == null) {
// 안전하지 않음으로 HTTPS 연결 시도
val okHttpClient = unSafeOkHttpClient()

instance = Retrofit.Builder()
.baseUrl(serverUrl)
.addConverterFactory(GsonConverterFactory.create(gson))
.client(okHttpClient)
.build()
}
return instance!!
}


별도의 SSL 인증서 없이 우회하기

private fun unSafeOkHttpClient(): OkHttpClient {
val okHttpClient = OkHttpClient.Builder()
try {
val trustAllCerts: Array<TrustManager> = arrayOf(object : X509TrustManager {
override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {}
override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {}
override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
})

val sslContext = SSLContext.getInstance("SSL")
sslContext.init(null, trustAllCerts, SecureRandom())

if (trustAllCerts.isNotEmpty() && trustAllCerts.first() is X509TrustManager) {
okHttpClient.sslSocketFactory(sslContext.socketFactory, trustAllCerts.first() as X509TrustManager)
okHttpClient.hostnameVerifier { _, _ -> true }
}
} catch (e: Exception) {
e.printStackTrace()
}
return okHttpClient.build()
}

 

 

 

 

 

 

3. 인증서를 통해 HTTPS 접속하기

먼저 연결하고자하는 도메인의 인증서를 다운받아야합니다.

아래 화면과 같이 "인증서 보기"에서 다운받을 수 있습니다.




인증서 보기가 따로 없다면 아래 그림과 같이 주소창 왼쪽에 배치된 자물쇠 또는 뭔가 누를 수 있는 버튼을 클릭해서 다운 받을 수 있습니다.




다운받은 인증서를 raw에 배치시켜줍니다.

 

 


Retrofit 생성

fun getInstance(context: Context): Retrofit {
if (instance == null) {
// SSL 인증서로 HTTPS 연결 시도
val okHttpClient = sslOkHttpClient(context)

instance = Retrofit.Builder()
.baseUrl(serverUrl)
.addConverterFactory(GsonConverterFactory.create(gson))
.client(okHttpClient)
.build()
}
return instance!!
}


SSL 인증서 확인

private fun sslOkHttpClient(context: Context): OkHttpClient {
val okHttpClient = OkHttpClient.Builder()
try {
val cf = CertificateFactory.getInstance("X.509")
val caInput = context.resources.openRawResource(R.raw.my_cert)
var ca: Certificate? = null
try {
ca = cf.generateCertificate(caInput)
Log.d("[HTTP]", "ca=" + (ca as X509Certificate?)!!.subjectDN)
} catch (e: CertificateException) {
e.printStackTrace()
} finally {
caInput.close()
}

if (ca != null) {
val keyStoreType = KeyStore.getDefaultType()
val keyStore = KeyStore.getInstance(keyStoreType)
keyStore.load(null, null)
keyStore.setCertificateEntry("ca", ca)

val tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm()
val tmf = TrustManagerFactory.getInstance(tmfAlgorithm)
tmf.init(keyStore)

val sslContext = SSLContext.getInstance("TLS")
sslContext.init(null, tmf.trustManagers, null)

if (tmf.trustManagers.isNotEmpty() && tmf.trustManagers.first() is X509TrustManager) {
okHttpClient.sslSocketFactory(sslContext.socketFactory, tmf.trustManagers.first() as X509TrustManager)
okHttpClient.hostnameVerifier { _, _ -> true }
}
}
} catch (e: Exception) {
e.printStackTrace()
}
return okHttpClient.build()
}

 

 

 

 

 

 

 

3. 끝으로...(추가내용)

찾아보다가 해당 오류가 꼭 위와 같은이유로만 출력되는 오류는 아니였네요.

위의 내용은 제가 겪고 해결한 방법을 정리한 것이니 참고만 해주시고,

자세한 내용은 아래 공식문서를 확인해주세요!

Android Developer : 서버 인증서 확인과 관련된 일반적인 문제

 

그리고..

인증서 없이 연결했을 때와 인증서를 포함하여 연결했을 때의 차이를 확인하고싶었습니다.

대부분 인증서를 포함시키는걸 권장한다고는하는데...

정확히 어떤 차이가 생기는지를 알고 싶었지만 찾지 못했네요ㅠㅠ

혹시 차이를 알고 계신분이 있다면 댓글로 공유 부탁드립니다!!!

 







[Reference]

stackOverflow

Retrofit2로 SSL을 이용한 HTTPS 통신하기(Okhttp3)

HTTPS 통신 원리 쉽게 이해하기




0 comments: