Android 4.0 之后增加了添加可信证书的 API 。但对于我们 App 开发者来说,毕竟还是要支持 4.0 之前版本的 Android 的。所以我们要想一些其它的办法来使我们的 App 信任我们自己的自签名证书。
经过在 StackOverflow 上的搜索后,我发现很多答案都是教给开发者直接信任任何证书就可以了。这当然算是一个误导了,因为这样做会使中间人攻击变为可能,也就失去了使用 Https 的意义了。甚至连我们自己的开发者写的代码刚开始也是复制的 StackOverflow 上的这种方案,还好经过 Review 我发现了这段代码的问题,于是又仔细搜索了一下,最后改成了现在所用的方案。
生成 KeyStore
文件
由于我们要用代码构造自己的 KeyStore
实例,所以我们要先准备好 Android 系统能导入的 KeyStore
文件。这里我们要用 Bouncy Castle
的格式来生成一个 KeyStore
文件。
先去下载一个 Bouncy Castle
的 jar 文件。
然后下载 这个 shell script。
把这两个文件放在同一个目录下。再把服务器的证书公钥(pem
格式)也放在这个目录下,比如我们就叫它 cacert.pem
吧。
打开终端,cd 到这个目录里。
赋予 cert2keystore_bks.sh
可执行权限。
chmod +x cert2keystore_bks.sh
然后执行命令生成 KeyStore
文件。
这里需要我们想一个密语用来加密证书,随便一个就行,因为反正我们这里存的也只是公钥,保密需求不大,而且这个密语也是要写到代码里用来读取 KeyStore
的。假设我们使用 bither
做密语。
执行如下命令。
./cert2keystore_bks.sh cacert.pem bither
现在我们有了自己的 KeyStore
文件了 bithertruststore.bks
。把它放到 Android 项目的 res/raw
里吧。
自定义 TrustManager
我们需要写一个自定义的 TrustManager
来给 Https
连接用。这个 TrustManager
首先要调用系统默认的 TrustManager
来检查 Https
证书。如果没有通过系统默认的检查,再用我们自己的 KeyStore
来进行检查。两层检查都没通过,才会认定这个证书不可信。
这段代码很简单,可以从 这里 下载到。使用时只要在构造函数中传入我们自己的 KeyStore
就可以了。
生成自定义 KeyStore
实例
使用之前的 KeyStore
文件和密语来构造一个自定义的 KeyStore
实例出来。这样我们就可以调用构造函数生成一个我们自定义的 TrustManager
了。
KeyStore localTrustStore = KeyStore.getInstance("BKS");
InputStream input = context.getResources().openRawResource(R.raw.bithertruststore);
try {
localTrustStore.load(input, "bither".toCharArray());
} finally {
input.close();
}
BitherTrustManager trustManager = new BitherTrustManager(localTrustStore);
设为默认
现在我们已经有了一个可以信任我们的自签名证书的自定义 TrustManager
了。我们希望所有我们的 App 发起的 Https
都默认使用这个 TrustManager
。
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, new TrustManager[]{trustManager}, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
结语
现在我们就可以用自签名的证书来为 Android App 提供 Https
服务了。
真的不要再用 StackOverflow 上那些信任任何证书的解决方案了。