14

秒懂 Https 之如何在 Android 中使用自签名证书

 3 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzIxNzU1Nzk3OQ%3D%3D&%3Bmid=2247491282&%3Bidx=1&%3Bsn=9eb5defd7e06b2ad00fa01debf3445b2
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

code小生 一个专注大前端领域的技术平台 公众号回复 Android 加入安卓技术群

作者:ShuSheng007

链接:blog.csdn.net/ShuSheng0007/article/details/107838108

声明:本文已获 ShuSheng007 授权发表,转发等请联系原作者授权

概述

在秒懂Https之CA证书与自签名证书漫谈中我们谈到了如何生成自签名证书的问题。这篇文章我们看一下,如何在Android中使用生成的自签名证书。

前提

本文基于网络请求库Retrofit2(OkHttp)

使用方式

未来方案

为什么说是未来方案呢?因为只有Android7.0及以上才可以使用,而时至今日一款成熟的App至少要支持到Android5.0,甚至Android4.4。但我们还是要简单的介绍一下,毕竟再过几年人家就是主角了。

从Android7.0(API Level 24)开始Android引入一套网络安全配置的机制,使用这种机制可以非常容易使用自签名证书。使用步骤如下:

  1. 将自签名证书放到App的src/main/res/raw文件下,例如我的证书叫sng_certificate.。

  2. 在App的src/main/res下建立一个xml文件夹,在里面创建一个叫network_security_config.xml的文件,名称可以任意叫,其内容如下

  <?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="true">你服务器的域名</domain>
<trust-anchors>
<certificates src="@raw/sng_certificate"/>
</trust-anchors>
</domain-config>
</network-security-config>
  1. 将此文件配置到AndroidManifest.xml文件中

 <?xml version="1.0" encoding="utf-8"?>
<manifest ... >
<application
android:networkSecurityConfig="@xml/network_security_config"
... >

...
</application>
</manifest>

至此即大功告成。

注意:此种方式要求自签名证书的格式为 PEM 或 DER 。详情可以参考官网 网络安全配置

现实的方案

这个才是本文的重点,毕竟我们的App是要上线的,所以要争取更多的用户。

现在Retrofit基本上已经成为Android网络请求的标配了,而其是基于OkHttp的,所以此处处理证书的问题实际上还是仰赖于OkHttp.

具体通过调用:OkHttpClient.Builder 里的

fun sslSocketFactory(sslSocketFactory: SSLSocketFactory,  trustManager: X509TrustManager )

方法。问题进而演变为如何获得这个方法的两个入参的问题,接下来我们就来解决这个问题。

  1. 将自签名证书放到App的src/main/res/raw文件下,例如我的证书叫sng_certificate.crt。

  2. 获得第一个参数SSLSocketFactory

private SSLContext getSslContext(InputStream certificateIs) throws GeneralSecurityException {
//从证书文件中读入证书
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
Collection<? extends Certificate> certificates = certificateFactory.generateCertificates(certificateIs);
if (certificates.isEmpty()) {
throw new IllegalArgumentException("certificate can not be empty");
}

//将读取的证书放入KeyStore, 密码可以为任意值
char[] password = "ss007".toCharArray();
KeyStore keyStore = getEmptyKeyStore(password);
int index = 0;
for (Certificate certificate : certificates) {
String certificateAlias = Integer.toString(index++);
keyStore.setCertificateEntry(certificateAlias, certificate);
}

// 使用 keyStore去构建一个X509信任管理器
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, password);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();

//构建信任的证书管理器构建SSLContext
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagers, null);
return sslContext;
}

//获取一个空keystore
private KeyStore getEmptyKeyStore(char[] password) throws GeneralSecurityException {
try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());//此处使用.crt文件,所以使用getDefaultType即可
InputStream in = null; //'null' creates an empty key store.
keyStore.load(in, password);
return keyStore;
} catch (IOException e) {
throw new AssertionError(e);
}
}

通过以上方法可以获得SSLContext,进而获得SSLSocketFactory,如下所示

InputStream is = context.getResources().openRawResource(R.raw.sng_certificate);
SSLSocketFactory sslSocketFactory= getSslContext(is).getSocketFactory();
  1. 获得第二个参数X509TrustManager

private X509TrustManager getSystemDefaultTrustManager() throws GeneralSecurityException{
TrustManagerFactory trustManagerFactory=TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
}
return (X509TrustManager) trustManagers[0];
}
  1. 配置OkHttpClient.Builder

//放弃验证证书中的域名信息
builder.hostnameVerifier((hostname, session) -> true);
builder.sslSocketFactory(getSslContext(is).getSocketFactory(), getSystemDefaultTrustManager());

注意: 如果你生成证书时包含的域名或者ip地址和你服务器的域名或者ip不匹配时,默认是会报错的,需要使用hostnameVerifier来放弃验证域名。

经过以上4步就完成了自签名证书在Android中使用的功能。

总结

如果此文帮助到了你,请不要吝啬你的赞美,随手点赞转发,去帮助更多的小伙伴…

相关阅读

1 Android 系统各个版本上https的抓包

Andriod Studio两种签名机制V1和V2的区别

LiveData+Retrofit 网络请求实战

【平台开发】如何有效的收集 Android 日志?

那些好玩的 android 小事

B3Q7faa.jpg!mobile

如果你有写博客的好习惯

欢迎投稿

赞+在看,小生感恩 :heart:


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK