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 입니다.
코드 흐름은
-
인증서를 로드 (LetEncryption에서 fullchain.pem을 사용)
-
신뢰할 수있는 CA를 포함하는 키 스토어 생성
-
CA 입력을 신뢰하는 TrustManager 생성
-
TrustManager를 사용하는 SSLContext 생성
-
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
}
}