15. Keystore密钥链:证书链管理、自签名证书、CA证书导入、信任锚点配置
说到密钥链,很多人第一反应就是「把证书串起来」。嗯,这个理解没错,但实际做起来坑不少。我最早接触证书链是在做企业级应用的时候,那时候要对接内部CA,结果证书链没配好,SSL握手一直报错,排查了整整一个下午。说白了,证书链就是一条信任的传递路径——从你的设备证书,一路追溯到根CA。
15.1 证书链的核心逻辑
证书链,也叫证书路径。它由三部分组成:
- 终端实体证书:你的App或设备持有的证书
- 中间CA证书:签发终端证书的机构
- 根CA证书:整个信任链的起点,自签名
你想想看,如果只有终端证书,对方怎么验证你是可信的?它需要沿着证书链往上找,直到找到一个它信任的根证书。这个过程就是证书链验证。
关键点:Android Keystore支持存储完整的证书链,但你必须自己构建好链,再导入。系统不会自动帮你补全中间证书。
15.2 自签名证书的生成与导入
自签名证书,说白了就是自己给自己签发。开发环境、内部测试经常用。我习惯用KeyTool命令行生成,然后导入Keystore。
// 生成自签名证书(命令行)
keytool -genkey -v -keystore mykeystore.bks -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider -alias myalias -keyalg RSA -keysize 2048 -validity 3650 -storepass password -keypass password -dname "CN=MyApp, OU=Dev, O=Company, L=City, ST=State, C=CN"
注意,Android Keystore不支持直接导入PKCS12或JKS格式的密钥库。你需要先把证书和私钥分开,再通过KeyChain API导入。
// Android代码导入自签名证书
byte[] certBytes = ...; // 从文件读取
X509Certificate cert = CertificateFactory.getInstance("X.509")
.generateCertificate(new ByteArrayInputStream(certBytes));
Intent intent = KeyChain.createInstallIntent();
intent.putExtra(KeyChain.EXTRA_CERTIFICATE, cert.getEncoded());
startActivityForResult(intent, REQUEST_INSTALL_CERT);
个人经验:自签名证书的CN字段一定要写对。我曾经因为CN写成了IP地址,结果在Android 9以上版本直接报错「No subject alternative names present」。建议用域名,或者加上SAN扩展。
15.3 CA证书导入:信任链的搭建
CA证书导入,说白了就是把第三方CA的根证书或中间证书装进设备。Android系统预置了上百个根证书,但企业自建CA的证书不在其中。这时候就需要手动导入。
我遇到过最典型的情况:公司内部App要访问内网服务器,服务器证书由内部CA签发。如果不导入CA证书,SSL握手直接失败。
// 导入CA证书到信任存储
try {
// 读取CA证书文件
InputStream caInput = new BufferedInputStream(
getAssets().open("internal_ca.cer"));
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate ca = cf.generateCertificate(caInput);
// 获取系统信任存储
KeyStore trustStore = KeyStore.getInstance("AndroidCAStore");
trustStore.load(null, null);
// 注意:Android不允许直接修改系统信任存储
// 你需要使用Network Security Config或自定义TrustManager
} catch (Exception e) {
e.printStackTrace();
}
避坑指南:Android 4.2以上不允许App直接修改系统信任存储。我曾经试图用反射去添加证书,结果在Android 7上直接崩溃。正确的做法是用Network Security Config,或者自定义SSLSocketFactory。
15.4 信任锚点配置
信任锚点,就是验证链的起点。说白了,你信任谁,谁就是锚点。Android的信任锚点默认是系统预置的根证书。但你可以通过两种方式自定义:
- Network Security Config:声明式配置,简单安全
- 自定义TrustManager:代码控制,灵活但容易出错
我个人更推荐Network Security Config,因为它不需要修改代码,而且Google官方维护。
<!-- res/xml/network_security_config.xml -->
<network-security-config>
<base-config>
<trust-anchors>
<!-- 系统预置证书 -->
<certificates src="system" />
<!-- 自定义CA证书 -->
<certificates src="@raw/my_ca" />
</trust-anchors>
</base-config>
<domain-config>
<domain includeSubdomains="true">internal.company.com</domain>
<trust-anchors>
<!-- 仅信任自定义CA -->
<certificates src="@raw/internal_ca" />
</trust-anchors>
</domain-config>
</network-security-config>
注意:如果你同时配置了系统证书和自定义证书,Android会先检查自定义证书,再检查系统证书。这个顺序在Android 10以上有变化,建议显式指定。
15.5 证书链验证的完整流程
我画了一张图,帮你理清证书链验证的完整流程:
15.6 实战中的证书链管理
在实际项目中,我总结了几条经验:
- 证书链要完整:导入时确保包含所有中间证书,缺一个就验证失败
- 注意证书格式:Android支持DER和PEM格式,但建议用DER二进制格式,解析更快
- 证书过期处理:提前30天预警,我习惯在证书到期前两个月就开始提醒
- 信任锚点不要乱改:除非必要,否则不要禁用系统证书验证
我的习惯:每次导入证书链后,我都会写一个单元测试,用代码验证整个链的完整性。这样部署到生产环境前就能发现问题,而不是等到用户反馈才去排查。
15.7 证书链的存储与更新
Android Keystore本身不直接管理证书链的更新。你需要自己实现更新逻辑。我常用的方案是:
- 把证书链文件放在assets目录或远程服务器
- App启动时检查证书有效期
- 如果即将过期,从服务器下载新证书链
- 通过KeyChain API更新
// 检查证书是否即将过期
X509Certificate cert = ...;
Date expiryDate = cert.getNotAfter();
Date now = new Date();
long daysUntilExpiry = (expiryDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24);
if (daysUntilExpiry < 30) {
// 触发证书更新流程
downloadAndInstallNewCertChain();
}
曾经踩过的坑:证书更新时,如果旧证书还没过期,新证书就导入,会导致系统里有两份相同的证书。我后来加了一个去重逻辑,导入前先检查别名是否已存在。
证书链管理,说白了就是信任的传递。你只要把根证书、中间证书、终端证书这条链搭好,Android系统就能自动完成验证。但要注意,这条链上任何一个环节出问题,整个信任就崩塌了。所以,证书链的完整性和时效性,是你在开发中必须时刻关注的。