Last active
November 19, 2025 16:36
-
-
Save yaroslav-shlapak/046f3e100372452f34cc19453848f0d3 to your computer and use it in GitHub Desktop.
How to use certificates installed by user to Android system with OkHttp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import java.security.KeyStore | |
| import java.security.KeyStoreException | |
| import java.security.NoSuchAlgorithmException | |
| import java.security.cert.CertificateException | |
| import java.security.cert.X509Certificate | |
| import javax.net.ssl.SSLContext | |
| import javax.net.ssl.TrustManager | |
| import javax.net.ssl.TrustManagerFactory | |
| import javax.net.ssl.X509TrustManager | |
| /** | |
| * Represents an ordered list of [X509TrustManager]s with additive trust. If any one of the composed managers | |
| * trusts a certificate chain, then it is trusted by the composite manager. | |
| * | |
| * This is necessary because of the fine-print on [SSLContext.init]: Only the first instance of a particular key | |
| * and/or trust manager implementation type in the array is used. (For example, only the first | |
| * javax.net.ssl.X509KeyManager in the array will be used.) | |
| * | |
| */ | |
| class CompositeX509TrustManager : X509TrustManager { | |
| private val trustManagers: List<X509TrustManager?> | |
| constructor(trustManagers: List<X509TrustManager?>) { | |
| this.trustManagers = trustManagers.toList() | |
| } | |
| constructor(keystore: KeyStore?) { | |
| this.trustManagers = listOf(defaultTrustManager, getTrustManager(keystore)) | |
| } | |
| @Throws(CertificateException::class) | |
| override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) { | |
| for (trustManager in trustManagers) { | |
| try { | |
| trustManager?.checkClientTrusted(chain, authType) | |
| return // someone trusts them. success! | |
| } catch (e: CertificateException) { | |
| // maybe someone else will trust them | |
| } | |
| } | |
| throw CertificateException("None of the TrustManagers trust this certificate chain") | |
| } | |
| @Throws(CertificateException::class) | |
| override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) { | |
| for (trustManager in trustManagers) { | |
| try { | |
| trustManager?.checkServerTrusted(chain, authType) | |
| return // someone trusts them. success! | |
| } catch (e: CertificateException) { | |
| // maybe someone else will trust them | |
| } | |
| } | |
| throw CertificateException("None of the TrustManagers trust this certificate chain") | |
| } | |
| override fun getAcceptedIssuers(): Array<X509Certificate> { | |
| val certificates = mutableListOf<X509Certificate>() | |
| for (trustManager in trustManagers) { | |
| if (trustManager != null) { | |
| for (cert in trustManager.acceptedIssuers) { | |
| certificates.add(cert) | |
| } | |
| } | |
| } | |
| return certificates.toTypedArray() | |
| } | |
| companion object { | |
| fun getTrustManagers(keyStore: KeyStore?): Array<TrustManager> { | |
| return arrayOf(CompositeX509TrustManager(keyStore)) | |
| } | |
| val defaultTrustManager: X509TrustManager? | |
| get() = getTrustManager(null) | |
| fun getTrustManager(keystore: KeyStore?): X509TrustManager? { | |
| return getTrustManager(TrustManagerFactory.getDefaultAlgorithm(), keystore) | |
| } | |
| fun getTrustManager(algorithm: String, keystore: KeyStore?): X509TrustManager? { | |
| val factory: TrustManagerFactory | |
| try { | |
| factory = TrustManagerFactory.getInstance(algorithm) | |
| factory.init(keystore) | |
| return factory.trustManagers.first { it is X509TrustManager? } as X509TrustManager? | |
| } catch (e: NoSuchAlgorithmException) { | |
| e.printStackTrace() | |
| } catch (e: KeyStoreException) { | |
| e.printStackTrace() | |
| } | |
| return null | |
| } | |
| } | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| val ALLOW_SSL_ERRORS_CERTIFICATES_FOR_HTTP_CLIENT = SslErrorsStateForHttpClient.ALLOW_DEFAULT_AND_SYSTEM_CA | |
| enum class SslErrorsStateForHttpClient { | |
| ALLOW_ALL, | |
| ALLOW_NOTHING, | |
| ALLOW_DEFAULT_AND_CUSTOM, | |
| ALLOW_DEFAULT_AND_SYSTEM_CA | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import okhttp3.OkHttpClient | |
| import timber.log.Timber | |
| import java.io.ByteArrayInputStream | |
| import java.io.InputStream | |
| import java.security.KeyManagementException | |
| import java.security.KeyStore | |
| import java.security.NoSuchAlgorithmException | |
| import java.security.SecureRandom | |
| import java.security.cert.CertificateException | |
| import java.security.cert.CertificateFactory | |
| import java.security.cert.X509Certificate | |
| import java.util.* | |
| import javax.net.ssl.* | |
| /** | |
| * Created on 2019/06/26. | |
| */ | |
| object SSLConfigFactory { | |
| const val TAG = "SSLConfigFactory" | |
| fun provideSSLContext(keystore: KeyStore, password: CharArray): SSLContext { | |
| val defaultAlgorithm = KeyManagerFactory.getDefaultAlgorithm() | |
| val customKeyManager = getKeyManager("SunX509", keystore, password) | |
| val jvmKeyManager = getKeyManager(defaultAlgorithm, null, null) | |
| val customTrustManager = getTrustManager("SunX509", keystore) | |
| val jvmTrustManager = getTrustManager(defaultAlgorithm, null) | |
| val keyManagers = arrayOf<KeyManager>( | |
| CompositeX509KeyManager(listOf(jvmKeyManager, customKeyManager)) | |
| ) | |
| val trustManagers = arrayOf<TrustManager>( | |
| CompositeX509TrustManager(listOf(jvmTrustManager, customTrustManager)) | |
| ) | |
| val context = SSLContext.getInstance("SSL") | |
| context.init(keyManagers, trustManagers, null) | |
| return context | |
| } | |
| private fun getKeyManager(algorithm: String, keystore: KeyStore?, password: CharArray?): X509KeyManager? { | |
| val factory = KeyManagerFactory.getInstance(algorithm) | |
| factory.init(keystore, password) | |
| return factory.keyManagers.first { it is X509KeyManager? } as X509KeyManager? | |
| } | |
| fun trustDefaultAndCustomCertificates(httpClient: OkHttpClient.Builder) { | |
| try { | |
| val cf: CertificateFactory = CertificateFactory.getInstance("X.509") | |
| val cert = | |
| "-----BEGIN CERTIFICATE-----\n" + | |
| "MIIEbTCCAlWgAwIBAgIUBmEymduV1Jbp8t97Lf9kDoFFN+EwDQYJKoZIhvcNAQEL\n" + | |
| "BQAwNDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRgwFgYDVQQKDA9NZXRyaWNJ\n" + | |
| "bnNpZ2h0cy4wHhcNMTkwNjI1MTUxMzIyWhcNMjIwNDE0MTUxMzIyWjBcMQswCQYD\n" + | |
| "VQQGEwJVUzELMAkGA1UECAwCQ0ExGDAWBgNVBAoMD01ldHJpY0luc2lnaHRzLjEm\n" + | |
| "MCQGA1UEAwwdc2VsZnNpZ25lZC5tZXRyaWNpbnNpZ2h0cy5jb20wggEiMA0GCSqG\n" + | |
| "SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDG/HCCj/TZW74nsaUVXs/qWZWCvK5MKz3d\n" + | |
| "5xlwpL0r5vdCkBQIv4PVHdN5WD1J8+esdQK0XvemCPgA0wUTC8N3zGcVeQo8yZPz\n" + | |
| "K6jxbQKceduFSWvCYx0sqO3xh/VhWOI7lYmJvFmMguMPl+pC8YNFAwIr/O10mkif\n" + | |
| "CLDC8YlssanbVWXzi1HtobmPpIUEyONT1LaSqW9AOho0W99GdwBNbsx/Dy92zoFk\n" + | |
| "4PTsvfEbl6o/PpGfXMdoEK3ZMtrr9D3DRAss/d+KDgxFYl5hfSM/7AOXQhUj6JCk\n" + | |
| "D6w94m298c19Q+NlZTPhoxOicO2YuL4Xf+RUDvJZcocM+pXKOy6vAgMBAAGjTzBN\n" + | |
| "MEsGA1UdEQREMEKCHXNlbGZzaWduZWQubWV0cmljaW5zaWdodHMuY29tgiF3d3cu\n" + | |
| "c2VsZnNpZ25lZC5tZXRyaWNpbnNpZ2h0cy5jb20wDQYJKoZIhvcNAQELBQADggIB\n" + | |
| "ABbrIz2HXi5Pux+T8hrrIY0VZckcQ8c2u1/VK+Tqd8UayydkRlgfhEWWrr8gCJ+s\n" + | |
| "uji4jBXHBUMm7Qk4O1VjfgasE1ojKWKoWKnDiaWcJ7d8WLPEh+EO27URklqvcZUy\n" + | |
| "HqnukJMEFImn9RZqo+RcQ5huT2KcUhtzYxLhpuFh4lKv9tId7+3rQgAAykdHMZPf\n" + | |
| "fLfkiQbg6rEUJtAkN6kl67qECFRUhrD2Z8Osn1OkIYgq0yEs6Vc3Bc2GbKRejtrQ\n" + | |
| "M0jpApXCg6XUioPacdclzq2wtWCyh+CCBDnZr5jjYABN4N3X8s6HORkU7wSx2NFv\n" + | |
| "UApizWmE+xxzCnz3JMBeMiA58z9lYQtF1c8dac6A2gdlqA5I8eLQo+0YgLo7rkXG\n" + | |
| "CE5IlFxFLg3h+yCxXNge21TPALOmb6LrEjRfNCHa6CgP6ydkElVmAhEE9uhDPZFQ\n" + | |
| "XnPeaRPG0dhqORDzx1NLIzxhYaiGatKGPv6QYTGExTHSbRj5pbjVETJI17DWmsQx\n" + | |
| "sl4FCWUZmwKNGfpLLsdBFpgsSgb9MBygqhQP+P8Wn1ZBDAezTePPLwpE0hhdCcR5\n" + | |
| "OWiknKDgdtHPzgI0xAtIxK5VdjSEueJIJxpjF85/n03b+iu7bHQmnZBOohun50rO\n" + | |
| "XPDaCT3nAWRvph7U+FwJG8b5ZNXt76FQO+ORIfRIQ8N0\n" + | |
| "-----END CERTIFICATE-----" | |
| val caInput: InputStream = ByteArrayInputStream(cert.toByteArray(Charsets.UTF_8)) | |
| val ca: X509Certificate = caInput.use { | |
| cf.generateCertificate(it) as X509Certificate | |
| } | |
| // Create a KeyStore containing our trusted CAs | |
| val keyStoreType = KeyStore.getDefaultType() | |
| val keyStore = KeyStore.getInstance(keyStoreType).apply { | |
| load(null, null) | |
| setCertificateEntry("ca", ca) | |
| } | |
| val sslContext = SSLContext.getInstance("TLS") | |
| val tm = CompositeX509TrustManager.getTrustManagers(keyStore) | |
| sslContext.init(null, tm, null) | |
| httpClient | |
| .sslSocketFactory(sslContext.socketFactory) | |
| } catch (e: NoSuchAlgorithmException) { | |
| e.printStackTrace() | |
| } catch (e: KeyManagementException) { | |
| e.printStackTrace() | |
| } | |
| } | |
| fun trustDefaultAndSystemCertificates(httpClient: OkHttpClient.Builder) { | |
| try { | |
| val keyStore: KeyStore? = KeyStore.getInstance("AndroidCAStore").apply { | |
| load(null, null) | |
| } | |
| val sslContext = SSLContext.getInstance("TLS") | |
| val tm = CompositeX509TrustManager.getTrustManagers(keyStore) | |
| sslContext.init(null, tm, null) | |
| httpClient | |
| .sslSocketFactory(sslContext.socketFactory) | |
| } catch (e: NoSuchAlgorithmException) { | |
| e.printStackTrace() | |
| } catch (e: KeyManagementException) { | |
| e.printStackTrace() | |
| } | |
| } | |
| fun trustAllCertificates(endpoint: String, httpClient: OkHttpClient.Builder) { | |
| val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager { | |
| override fun getAcceptedIssuers(): Array<X509Certificate> { | |
| return emptyArray() | |
| } | |
| @Throws(CertificateException::class) | |
| override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) { | |
| } | |
| @Throws(CertificateException::class) | |
| override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) { | |
| } | |
| }) | |
| val sslContext = SSLContext.getInstance("SSL") | |
| sslContext.init(null, trustAllCerts, SecureRandom()) | |
| val hostnameVerifier = object : HostnameVerifier { | |
| override fun verify(hostname: String, session: SSLSession): Boolean { | |
| Timber.tag(TAG).i("input host: $hostname, endpoint: $endpoint") | |
| return endpoint.toLowerCase(Locale.getDefault()) | |
| .contains(hostname.toLowerCase(Locale.getDefault())) | |
| } | |
| } | |
| httpClient | |
| .sslSocketFactory(sslContext.socketFactory) | |
| .hostnameVerifier(hostnameVerifier) | |
| } | |
| fun printInstalledCertificates() { | |
| try { | |
| val ks = KeyStore.getInstance("AndroidCAStore") | |
| if (ks != null) { | |
| ks.load(null, null) | |
| val aliases = ks.aliases() | |
| while (aliases.hasMoreElements()) { | |
| val alias = aliases.nextElement() | |
| val cert = ks.getCertificate(alias) as X509Certificate | |
| //To print System Certs only | |
| if (cert.issuerDN.name.contains("system")) { | |
| Timber.d("system: ${cert.issuerDN.name}") | |
| } else | |
| //To print User Certs only | |
| if (cert.issuerDN.name.contains("user")) { | |
| Timber.d("user: ${cert.issuerDN.name}") | |
| } else { | |
| Timber.d("other: ${cert.issuerDN.name}") | |
| } | |
| //To print all certs | |
| } | |
| } | |
| } catch (e: Exception) { | |
| e.printStackTrace() | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| private fun applySslHandlingStrategy(endpoint: String, httpClient: OkHttpClient.Builder) { | |
| when (ALLOW_SSL_ERRORS_CERTIFICATES_FOR_HTTP_CLIENT) { | |
| SslErrorsStateForHttpClient.ALLOW_ALL -> { | |
| SSLConfigFactory.trustAllCertificates(endpoint, httpClient) | |
| } | |
| SslErrorsStateForHttpClient.ALLOW_DEFAULT_AND_CUSTOM -> { | |
| SSLConfigFactory.trustDefaultAndCustomCertificates(httpClient) | |
| } | |
| SslErrorsStateForHttpClient.ALLOW_DEFAULT_AND_SYSTEM_CA -> { | |
| SSLConfigFactory.trustDefaultAndSystemCertificates(httpClient) | |
| } | |
| SslErrorsStateForHttpClient.ALLOW_NOTHING -> { | |
| // do nothing | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment