在上一篇文章【使用自签证书利用Okhttp进行HTTPS接口的安全连接】中,我们自己生成了证书,创建了最简单的接口,实现了让浏览器信任了我们的证书,那么,在Android上要怎么做呢?在android中,其实也是差不多的概念,android的app都会默认使用系统的受信任证书列表,
我们可以参考android官网https://developer.android.com/training/articles/security-config?hl=zh-cn#certificates里面的说明来学习,这个受信任列表是可以切换的,并且都有默认值

android 手机信任证书 charles android 证书 信任 设置_android

系统的受信任列表我们也可以在手机的设置里面找到,并且我们可以选择把一些证书让他失效,大家可以试试将上面的百度的根证书找到它让它失效后

android 手机信任证书 charles android 证书 信任 设置_ca证书_02

再来看看手机浏览器里访问百度,你会发现百度也不信任了哈哈。

所以在Android上,套路是和我们Windows一样的,但是由于我们没法直接把我们的证书加入到系统里面,当然root的除外,所以我们只能使用另一种方式,即访问接口的时候,不使用系统的默认受信任列表,而是使用我们指定的证书来做验证,这边以okhttp来实现将证书植入到我们的app中,让我们的app可以使用自签证书实现https的通讯

创建okhttp client,我们还是使用以之前的go服务器为例,正常情况下的访问会是这样来使用

val client = OkHttpClient.Builder()
            .build()
val request: Request = Request.Builder()
.url("https://10.0.10.22:8081/ping")
.build()
Thread {
    client.newCall(request).execute()
    .use { response -> Log.d("MainActivity", "response str is '${response.body?.string()}'") }
}.start()

由于我们的证书手机上的受信任列表中肯定是不存在的,所以肯定会报错

android 手机信任证书 charles android 证书 信任 设置_ssl_03

这时候我们给okhttp配置ssl,来让okhttp使用我们自己的ssl规则和证书进行https连线,而不是使用默认的系统的受信任列表

这边我们将CA根证书放在了assets目录下,方便取出

android 手机信任证书 charles android 证书 信任 设置_ssl_04

模仿官网:https://developer.android.com/training/articles/security-ssl?hl=zh-cn#UnknownCa 的未知证书的用法,创建SSLSocketFactory和TrustManager

// Load CAs from an InputStream
// (could be from a resource or ByteArrayInputStream or ...)
val cf: CertificateFactory = CertificateFactory.getInstance("X.509")
val caInput: InputStream = BufferedInputStream(assets.open("rootca.crt"))
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 trustManagerFactory: TrustManagerFactory = TrustManagerFactory.getInstance(
    TrustManagerFactory.getDefaultAlgorithm()
)
trustManagerFactory.init(keyStore)
val trustManagers: Array<TrustManager> = trustManagerFactory.trustManagers
check(!(trustManagers.size != 1 || trustManagers[0] !is X509TrustManager)) {
    ("Unexpected default trust managers:"
            + trustManagers.contentToString())
}
val trustManager: X509TrustManager = trustManagers[0] as X509TrustManager

val sslContext: SSLContext = SSLContext.getInstance("TLS")
sslContext.init(null, arrayOf<TrustManager>(trustManager), null)
val sslSocketFactory: SSLSocketFactory = sslContext.socketFactory

然后在okhttp client上使用我们的SSLSocketFactory和TrustManager

val client = OkHttpClient.Builder()
            	.sslSocketFactory(sslSocketFactory, trustManager)
            	.build()

这边还需要注意的是,我们提到过证书中“Subject Alternative Name”这个字段,那么okhttp内部也会对这个字段进行验证,在connectTls方法中,会调用verify方法进行验证

android 手机信任证书 charles android 证书 信任 设置_ssl_05

hostnameVerifier有默认的实现类实现了默认的verify方法,其有一套默认的验证规则,会根据ip和hostname分别验证,

android 手机信任证书 charles android 证书 信任 设置_安全_06

会使用getSubjectAltNames来获取证书中的Subject Alternative Name字段

android 手机信任证书 charles android 证书 信任 设置_android_07

所以,我们只要用上一篇文章的方法在证书中配置了正确的IP或者Hostname,那么就可以直接访问了,或者我们可以通过把默认的实现类进行覆盖,

val client = OkHttpClient.Builder()
            .sslSocketFactory(sslSocketFactory, trustManager)
            .hostnameVerifier { hostname, _ ->
                true
            }
            .build()

当然,这样做还是有一定风险的,所以还是推荐证书中写入Subject Alternative Name字段。

另外补充下,我们也是可以无条件的让okhttp信任所有证书来进行https连接,但是这样是非常危险的,所以不推荐这样处理。