카테고리 없음

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

No_Se_Can 2022. 6. 7. 08:22

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를 설정한다.

 

자 해결하기 위해 우선 자신의 api 웹사이트의 인증서를 확인해보자!

 

자신의 api 주소를 크롬으로 타고 들어가보자!

자신의 api 주소를 인터넷에 치면 response 값이 나올것이다.

 

다만 여기서 주목해야할점은 저위 자물쇠 표시다!

해당 자물쇠를 타고 들어가면 다음과 같이 인증서 정보를 볼 수 있는데, 

나같은경우 GlobalSign RSA OV SSL CA 2018 이라는 인증서를 발견할 수 있었다.

 

 

이제 인터넷에 해당 인증서를 검색해 다운로드하자.

나의경우 밑의 웹사이트에서 인증서를 다운받았다.

 

https://support.globalsign.com/ca-certificates/intermediate-certificates/organizationssl-intermediate-certificates

 

OrganizationSSL Intermediate Certificates :: GlobalSign Support

Intermediate Certificates help complete a "Chain of Trust" from your SSL or Client Certificate to GlobalSign's Root Certificate.

support.globalsign.com

 

이제 준비는 끝났다! 안드로이드 스튜디오로 돌아가도록 하자.

 

우선, ras 폴더 밑에 raw 폴더를 생성하고 raw 폴더에 다운받은 인증서를 넣어주자!

 

ras > raw

그리고 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
    }
}

 

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

 

@Provides
@Singleton
fun provideHttpClient(selfSigningHelper: SelfSigningHelper): OkHttpClient {
    return OkHttpClient.Builder()
        .readTimeout(10, TimeUnit.SECONDS)
        .connectTimeout(10, TimeUnit.SECONDS)
        .writeTimeout(15, TimeUnit.SECONDS)
        .addInterceptor(getLoggingInterceptor())
        .sslSocketFactory(
            selfSigningHelper.sslContext.socketFactory,
            selfSigningHelper.tmf.trustManagers[0] as X509TrustManager
        )
        .build()
}