Android

[Android] SSL 인증서 적용해보기

Xmobile 2020. 12. 17. 22:04

서버에 https로 인증서를 적용하게되면 안드로이드에서 에러가 발생하게 됩니다.

   javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
            at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:374)
            at libcore.net.http.HttpConnection.setupSecureSocket(HttpConnection.java:209)
            at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.makeSslConnection(HttpsURLConnectionImpl.java:478)
            at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.connect(HttpsURLConnectionImpl.java:433)
            at libcore.net.http.HttpEngine.sendSocketRequest(HttpEngine.java:290)
            at libcore.net.http.HttpEngine.sendRequest(HttpEngine.java:240)
            at libcore.net.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:282)
            at libcore.net.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:177)
            at libcore.net.http.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:271)
    

 

정상적으로 접속하기 위해서 발급받은 인증서를 적용하도록 합니다.

자세한 내용은 https://developer.android.com/training/articles/security-ssl?hl=ko#kotlin 입니다.

 

코드 흐름은

  1. 인증서를 로드 (LetEncryption에서 fullchain.pem을 사용)

  2. 신뢰할 수있는 CA를 포함하는 키 스토어 생성

  3. CA 입력을 신뢰하는 TrustManager 생성

  4. TrustManager를 사용하는 SSLContext 생성

  5. OkHttpcliennt 적용

 

으로 전체 소스는 하기와 같습니다.

companion object {
        private const val BASE_URL = SERVER_API
        var VERIFY_DOMAIN: String = "*.lottois.info"

        fun create(context: Context): ApiService {
            val logger =
                HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY }

            val tmf: TrustManagerFactory? = getTrustManagerFactory(context)
            var sslsocket = tmf?.let { getSSLSocketFactory(it) }
            val hostnameVerifier = HostnameVerifier { _, session ->
                HttpsURLConnection.getDefaultHostnameVerifier().run {
                    verify(VERIFY_DOMAIN, session)
                }
            }

            val client = OkHttpClient.Builder()
                .addInterceptor(logger)
                .sslSocketFactory(sslsocket, tmf?.trustManagers?.get(0) as X509TrustManager)
                .hostnameVerifier(hostnameVerifier)
                .build()

            return Retrofit.Builder()
                .baseUrl(BASE_URL)
                .client(client)
                .addConverterFactory(GsonConverterFactory.create())
                .build()
                .create(ApiService::class.java)
        }

        private fun getTrustManagerFactory(context: Context): TrustManagerFactory? {
            // 1. CA 로드 
            val cf: CertificateFactory = CertificateFactory.getInstance("X.509")
            val caInput: InputStream = context.resources.openRawResource(R.raw.fullchain)
            val ca: X509Certificate = caInput.use {
                cf.generateCertificate(it) as X509Certificate
            }

            // 2. 신뢰할 수있는 CA를 포함하는 키 스토어 생성
            val keyStoreType = KeyStore.getDefaultType()
            val keyStore = KeyStore.getInstance(keyStoreType).apply {
                load(null, null)
                setCertificateEntry("ca", ca)
            }

            // 3. CA 입력을 신뢰하는 TrustManager 생성
            val tmfAlgorithm: String = TrustManagerFactory.getDefaultAlgorithm()
            val tmf: TrustManagerFactory = TrustManagerFactory.getInstance(tmfAlgorithm).apply {
                init(keyStore)
            }
            return tmf
        }

        private fun getSSLSocketFactory(
            tmf: TrustManagerFactory
        ): SSLSocketFactory? {
            //4. TrustManager를 사용하는 SSLContext 생성
            val sslContext: SSLContext = SSLContext.getInstance("TLS")
            sslContext.init(null, tmf.trustManagers, null)
            return sslContext.socketFactory
        }
    }