關于面向Android7.0及以上系統的應用無法通過charles抓包
預設情況下,來自所有應用的安全連接配接(使用TLS和HTTPS之類的協定)均信任預裝的系統CA,而面向6.0及以下系統版本的應用預設情況下還會信任使用者添加的CA憑證。如果我們将targetSdkVersion修改到24以上的時候,應用則不會信任使用者安裝的證書了。
詳細說明見官方文檔。
這時,當我們通過charles或其他抓包工具抓取應用的Https請求時,盡管在手機上暗安裝了charles證書,則仍會顯示證書鍊不被信任。

我們有兩種解決方案,适用不同的場景。
在manifest檔案中配置application的屬性android:networkSecurityConfig=”@xml/network_security_config”
通過network_security_config.xml檔案可以自定義網絡安全設定,包括如下功能:
- 自定義信任的證書機構:适用于自簽名證書或限制應用信任系統預裝證書
- 證書固定:将應用的安全連接配接限制為特定的證書,适用于代理抓包、線下環境調試
- 明文通信選擇退出:防止應用意外使用明文通信
- 僅調試重寫:在debug狀态設定上述限制
詳細的配置可參考官方文檔
那麼我們為了使我們的應用信任charles的證書,可以選擇證書固定或者直接信任使用者安裝的證書。配置如下:
固定證書:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="true">example.com</domain>
<trust-anchors>
<certificates src="@raw/my_ca"/>
</trust-anchors>
</domain-config>
</network-security-config>
注: 以 PEM 或 DER 格式将自簽署或非公共 CA 證書添加到 res/raw/my_ca。
信任使用者安裝的證書:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<trust-anchors>
<certificates src="system"/>
<certificates src="user"/>
</trust-anchors>
</network-security-config>
但這樣設定會忽視掉系統提供的安全的校驗,是以盡可能不要配置到線上版本中,那麼就要用到第四的功能了,僅調試重寫。
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<debug-overrides>
<domain-config>
<domain includeSubdomains="true">example.com</domain>
<trust-anchors>
<certificates src="@raw/my_ca"/>
</trust-anchors>
</domain-config>
</debug-overrides>
</network-security-config>
這種配置方式在不改動代碼的情況下,就支援了debug狀态可抓包的操作,算是比較優雅的方案。
但是有另外的一個問題,如果測試同學也想抓取App的網絡請求怎麼辦?送出測試時建構debug狀态的安裝包麼?貌似不妥吧。debug和release版本的簽名、混淆配置都不同。可能有人會說,隻修改debuggable參數,其他不變。那麼在建構線上安裝包時還要手動改回來麼?(如果可以有動态修改debuggable參數的方式請指教。)
就算是可以動态修改debuggable參數,因為證書是我們内置到工程中的,不能根據不同的人做修改,是以局限性還是蠻大的,僅适用于個人開發時使用。
由于以上限制就引出了第二種配置方案。
為網絡請求庫添加證書配置
由于我們不能動态控制debug狀态,但是我們能控制什麼時候為網絡請求庫配置https證書。例如:僅線上環境時不配置charles證書,其他時候由使用者指定加載charles證書檔案。這種方式就做到了不依賴debuggable環境,而且可以動态修改證書檔案。
不同的網絡用戶端配置證書方式,這裡以okhttp為例做說明。
第一步:為你的網絡用戶端添加一個設定證書流的api,由調用者負責判斷建構環境,并決定是否配置自定義證書。
private List<InputStream> cerFileIsList;
/**
* 設定Https證書檔案
*
* @param cerFileIs 證書檔案流清單
*/
public void addCertFileIs(InputStream cerFileIs) {
if (cerFileIsList == null) {
cerFileIsList = new ArrayList<>();
}
cerFileIsList.add(cerFileIs);
}
第二步,在初始化OkHttpClient時使用證書建構SSLSocketFactory
private SSLSocketFactory createSSLSocketFactory() throws NoSuchAlgorithmException, KeyManagementException {
SSLContext sslContext = SSLContext.getInstance("TLS");
if (cerFileIsList != null && cerFileIsList.size() > ) {
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
//建立一個包含我們信任證書的KeyStore
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
int certIndex = ;
for (InputStream inputStream : cerFileIsList) {
String certAlias = String.valueOf(certIndex++);
keyStore.setCertificateEntry(certAlias, cf.generateCertificate(inputStream));
}
//根據秘鑰庫生成信任管理器
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
sslContext.init(null, tmf.getTrustManagers(), null);
} catch (IOException | CertificateException | KeyStoreException e) {
e.printStackTrace();
} finally {
CloseUtils.closeIO(cerFileIsList);
cerFileIsList.clear();
}
} else {
sslContext.init(null, null, null);
}
return new Tls12SocketFactory(sslContext.getSocketFactory());
}
當測試同學需要抓包時,隻需要下載下傳對應的Charles證書并由App載入後,重新初始化OkHttpClient就可以了。
通過OkHttpClient設定自定義證書時需要注意一點,就是需要把正常服務端接口域名的證書一并設定進來,否則的話App隻信任charles證書,卻不信任後端服務的證書了。