본문 바로가기
Android/에러 & 이슈

[Android] Retrofit - Trust anchor for certification path not found 에러 해결

by Taehyung Kim, dev 2021. 2. 14.
728x90

Retrofit을 이용한 http 통신 중에

 

javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException:Trust anchor for certification path not found

 

위와 같은 이슈가 발생한다.

 

원인은 Retrofit을 요청하는 웹사이트의 Certificate 인증서가 안드로이드 단말에 존재하지 않을 때 발생한다.

 

해결하기 위해 순서는 아래와 같다.

1. 요청하고자 하는 웹사이트의 인증서를 확인한다.

2. 인증서를 다운로드 받아서 프로젝트 내부 또는 외부에서 가져올 수 있도록 한다.

3. OkHttpClient에 인증서를 내포한 client를 만들 수 있는 Helper 클래스를 생성한다.

4. Retrofit 생성 시 Client에 인증서를 갖는 client를 설정한다.

 

요청하고자 하는 웹사이트의 Certificate를 확인하기 위해서는 아래와 같이 터미널에 입력하면 사용하는 인증서를 확인할 수 있다.

 

openssl s_client -connect {웹사이트 주소:443} | openssl x509 -noout -subject -issuer

 

터미널에 요청하면 아래와 같은 응답을 받게되는데 시간이 조금 소요되었다.

위에서 주목할 부분은 물결 부분의 Thawte RSA CA 2018 이다.

 

요청한 웹사이트의 인증서이다.

 

이 인증서를 다운받아야하는데 필자는 아래 사이트에서 다운로드 받았다.

https://www.digicert.com/kb/digicert-root-certificates.htm

 

DigiCert Root Certificates - Download & Test | DigiCert.com

Download DigiCert Root and Intermediate Certificate DigiCert Root Certificates are widely trusted and are used for issuing SSL Certificates to DigiCert customers—including educational and financial institutions as well as government entities worldwide. I

www.digicert.com

 

다운로드 받은 후 안드로이드 스튜디오에

1. res - raw 폴더 생성

2. 대문자 이름을 소문자로 변경하였다.

 

 

그리고 Retrofit에 Http Client에 ssl 인증서를 함께 요청하기위해 helper를 생성하였다.

필자는 DI를 이용하여 Retrofit Module을 사용하므로 Helper 클래스를 Singleton 어노테이션과 함께 사용하였다.

아래는 코드이다.

 

import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import okhttp3.OkHttpClient
import java.io.IOException
import java.io.InputStream
import java.security.KeyManagementException
import java.security.KeyStore
import java.security.KeyStoreException
import java.security.NoSuchAlgorithmException
import java.security.cert.Certificate
import java.security.cert.CertificateException
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import javax.inject.Inject
import javax.inject.Singleton
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509TrustManager

@Singleton
class SelfSigningHelper @Inject constructor(
    @ApplicationContext context: Context
) {
    lateinit var tmf: TrustManagerFactory
    lateinit var sslContext: SSLContext

    init {
        val cf: CertificateFactory
        val ca: Certificate

        val caInput: InputStream

        try {
            cf = CertificateFactory.getInstance("X.509")

            caInput = context.resources.openRawResource(R.raw.thawte_rsa_ca_2018)

            ca = cf.generateCertificate(caInput)
            println("ca = ${(ca as X509Certificate).subjectDN}")

            // Create a KeyStore containing our trusted CAs
            val keyStoreType = KeyStore.getDefaultType()
            val keyStore = KeyStore.getInstance(keyStoreType)
            with(keyStore) {
                load(null, null)
                keyStore.setCertificateEntry("ca", ca)
            }

            // Create a TrustManager that trusts the CAs in our KeyStore
            val tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm()
            tmf = TrustManagerFactory.getInstance(tmfAlgorithm)
            tmf.init(keyStore)

            // Create an SSLContext that uses our TrustManager
            sslContext = SSLContext.getInstance("TLS")
            sslContext.init(null, tmf.trustManagers, java.security.SecureRandom())

            caInput.close()
        } catch (e: KeyStoreException) {
            e.printStackTrace()
        } catch (e: CertificateException) {
            e.printStackTrace()
        } catch (e: NoSuchAlgorithmException) {
            e.printStackTrace()
        } catch (e: KeyManagementException) {
            e.printStackTrace()
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }

// DI 를 사용하지 않을 경우 아래 함수를 OkHttpClient로 사용.
    fun setSSLOkHttp(builder: OkHttpClient.Builder): OkHttpClient.Builder {
        builder.sslSocketFactory(sslContext.socketFactory, tmf.trustManagers[0] as X509TrustManager)

        return builder
    }
}

 

위 Helper를 DI 모듈에 사용하기 위해서 아래와 같이 사용하였다.

    @Singleton
    @Provides
    fun provideOkHttpClient(selfSigningHelper: SelfSigningHelper): OkHttpClient =
        OkHttpClient.Builder()
            .run {
                addInterceptor(HttpLoggingInterceptor().apply {
                    level = HttpLoggingInterceptor.Level.BODY
                })
                sslSocketFactory(
                    selfSigningHelper.sslContext.socketFactory,
                    selfSigningHelper.tmf.trustManagers[0] as X509TrustManager
                )
                build()
            }

 

위 OkHttpClient를 Retrofit에 client로 적용시켜주면 SSLHandshakeException이 발생하지 않고 정상적으로 response를 받을 수 있다.

728x90

댓글