我们认识到在移动端开发中安全性设置非常重要,尤其是目前非常流程H5混合式开发APP,在Android开发中,我们可以通过证书锁定的方式来增加客户端与服务端的安全保障《证书锁定SSL Pinning简介及用途》,本文主要介绍 SSL数字证书在Android开发中的证书锁定(SSL/TLS Pinning)

4c1627d2d664f5702061f61237859f5e.png

我们认识到在移动端开发中安全性设置非常重要,尤其是目前非常流程H5混合式开发APP,在Android开发中,我们可以通过证书锁定的方式来增加客户端与服务端的安全保障《证书锁定SSL Pinning简介及用途》,本文主要介绍 SSL数字证书在Android开发中的证书锁定(SSL/TLS Pinning)

1. 常规SSL证书设置

通常由CA权威机构签发的证书,其根证书都内置在最新的Android操作系统中,因此默认情况下可不进行SSL证书锁定,开发APP时也就变得非常简单,以infinisign.com为例,摘自App security best practices

1.1 JAVA方法

URL url = new URL("https://infinisign.com");
HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
urlConnection.connect();
InputStream in = urlConnection.getInputStream();

1.2 KOTLIN方法

val url = URL("https://infinisign.com")
val urlConnection = url.openConnection() as HttpsURLConnection
urlConnection.connect()
urlConnection.inputStream.use {
    ...
}

2. 网络安全性设置

本方案是官方提供,但需要依赖Android N(Android 7.0 API 24)及以后版本,可在APP开发阶段在APP中内置安全性设置,以达到防止中间人攻击(MITM)的目的,此方法只限制在Android 7.0 API 24以后版本,因此该版本之前的安全性设置仍然需要使用证书锁定方法,本文以infinisign.com为例。

2.1 创建配置文件

创建文件res/xml/network_security_config.xml,需要注意的是,使用证书锁定,需要配置一个备份密钥,假如证书到期或更换了CA品牌后,不至于重新发行APP,这个备份密码可以是中级证书或根证书。

通俗的说,如果系统检测到签发证书过期了,则自动使用其中级或才根级证书作为验证,因为通常中级机构、根机构的证书到期时间非常长。

但实际情况是,infinisign.com所售的CA签发证书有效期都是一年,而现在发行APP或更新APP通常在一年都会有更新重新上架操作。

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config>
        <domain includeSubdomains="true">infinisign.com</domain>
        <pin digest="SHA-256">wLgBEAGmLltnXbK6pzpvPMeOCTKZ0QwrWGem6DkNf6o</pin>
        <!-- 备份密钥,比如infinisign.com的中级机构是geotrust -->
        <pin digest="SHA-256">wLgBEAGmLltnXbK6pzpvPMeOCTKZ0QwrWGem6DkNf6o</pin>
    </domain-config>
</network-security-config>

2.2 引入配置文件

Androidmanifest.xml引入配置文件android:networkSecurityConfig="@xml/network_security_config"

<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
    <application
        android:networkSecurityConfig="@xml/network_security_config">
        <!-- application 其它子元素 -->
    </application>
</manifest>

3. OkHttp锁定

OkHttp是一个用于Android处理网络请求的开源项目,是安卓端最流行的轻量级的网络框架,其主要用来替代HttpUrlConnection处理方式

client = new OkHttpClient.Builder()
    .certificatePinner(new CertificatePinner.Builder()
      .add("infinisign.com", "sha256/S8Ff3JCaO4V...")
      .build())
    .build();

不过需要注意的是,OkHttp锁定证书方式不适用于公钥锁定方式,必须以证书锁定方式内置SSL数字证书。

4. TrustManager锁定

TrustManager是一个比较老的证书锁定方法,主要用于早期的Android版本或者用于一些CA根机构在Android系统中缺失根证书的情形下,当然也适用于自签名证书的锁定,通常我们不建议这样做,因为TrustManager的缺点是中间人仍然可以使用成熟的绕过方案来实现截持。

在SSL普及的今天,let's encrypt的开源免费解决方案,和一些入门的便宜DV域名型SSL证书(见PositiveSSL ¥39/年)足以媲美自签名方案。

详细请参考javax.net.ssl.TrustManager接口实现,简单步骤如下

  1. 在APP源码中内置证书

    /res/raw
    
  2. 使用 KeyStore加载证书

    val resourceStream = resources.openRawResource(R.raw.infinisign_cert)
    val keyStoreType = KeyStore.getDefaultType()
    val keyStore = KeyStore.getInstance(keyStoreType)
    keyStore.load(resourceStream, null)
    
  3. TrustManagerFactory实例化证书

    val trustManagerAlgorithm = TrustManagerFactory.getDefaultAlgorithm()
    val trustManagerFactory = TrustManagerFactory.getInstance(trustManagerAlgorithm)
    trustManagerFactory.init(keyStore)
    
  4. 创建SSLContext实例,与TrustManager进行绑定。

    val sslContext = SSLContext.getInstance("TLS")
    sslContext.init(null, trustManagerFactory.trustManagers, null)
    val url = URL("http://infinisign.com/")
    val urlConnection = url.openConnection() as HttpsURLConnection
    urlConnection.sslSocketFactory = sslContext.socketFactory
    

总结

在多数移动操作系统中有大大小小几十个CA机构内置的根证书,但也不排除已经不被信任的CA机构存在的旧根证书,还有一些例如国内的一些基于Android老版本的操作系统仍然面临着安全风险,所以使用SSL数字证书锁定(SSL/TLS Pinning)的目标是缩小可信CA的范围,让APP客户端传送数据更安全,更切实地保障用户数据。

关于IOS端的证书锁定,请参考:IOS SSL证书设置和锁定(SSL/TLS Pinning)

参考文章:

相关文章