在上一篇文章【使用自签证书利用Okhttp进行HTTPS接口的安全连接】中,我们自己生成了证书,创建了最简单的接口,实现了让浏览器信任了我们的证书,那么,在Android上要怎么做呢?在android中,其实也是差不多的概念,android的app都会默认使用系统的受信任证书列表,
我们可以参考android官网https://developer.android.com/training/articles/security-config?hl=zh-cn#certificates里面的说明来学习,这个受信任列表是可以切换的,并且都有默认值
系统的受信任列表我们也可以在手机的设置里面找到,并且我们可以选择把一些证书让他失效,大家可以试试将上面的百度的根证书找到它让它失效后
再来看看手机浏览器里访问百度,你会发现百度也不信任了哈哈。
所以在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()
由于我们的证书手机上的受信任列表中肯定是不存在的,所以肯定会报错
这时候我们给okhttp配置ssl,来让okhttp使用我们自己的ssl规则和证书进行https连线,而不是使用默认的系统的受信任列表
这边我们将CA根证书放在了assets目录下,方便取出
模仿官网: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方法进行验证
hostnameVerifier有默认的实现类实现了默认的verify方法,其有一套默认的验证规则,会根据ip和hostname分别验证,
会使用getSubjectAltNames来获取证书中的Subject Alternative Name字段
所以,我们只要用上一篇文章的方法在证书中配置了正确的IP或者Hostname,那么就可以直接访问了,或者我们可以通过把默认的实现类进行覆盖,
val client = OkHttpClient.Builder()
.sslSocketFactory(sslSocketFactory, trustManager)
.hostnameVerifier { hostname, _ ->
true
}
.build()
当然,这样做还是有一定风险的,所以还是推荐证书中写入Subject Alternative Name字段。
另外补充下,我们也是可以无条件的让okhttp信任所有证书来进行https连接,但是这样是非常危险的,所以不推荐这样处理。