PackageSignatures 验证
PackageSignatures 是 PMS 把运行时 SigningDetails 保存到包设置的桥梁。它不负责验证 APK 文件本身;APK 文件由 ApkSignatureVerifier 解析,PackageSignatures 负责把当前 signer、scheme version 和 past signer 写入/恢复,并在更新时提供旧状态给 verifySignatures 比较。
1. 两个边界
| 问题 | 主要类 |
|---|---|
| APK 内容和签名块是否合法 | ApkSignatureVerifier |
| 已安装包与新包能否兼容 | PackageManagerServiceUtils.verifySignatures |
| 已安装签名如何持久化 | PackageSignatures / Settings |
2. 签名状态
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageSignatures.java
class PackageSignatures {
@NonNull SigningDetails mSigningDetails;
PackageSignatures() {
mSigningDetails = SigningDetails.UNKNOWN;
}
PackageSignatures(SigningDetails signingDetails) {
mSigningDetails = signingDetails;
}
PackageSignatures(PackageSignatures orig) {
if (orig != null
&& orig.mSigningDetails != SigningDetails.UNKNOWN) {
mSigningDetails = new SigningDetails(
orig.mSigningDetails);
} else {
mSigningDetails = SigningDetails.UNKNOWN;
}
}
}空构造使用 UNKNOWN,避免 packages.xml 尚未读完时误把“没有签名”当成“签名为空”。拷贝构造复制 SigningDetails,用于 PackageSetting 快照或克隆。
3. 写入 XML
void writeXml(TypedXmlSerializer serializer, String tagName,
ArrayList<Signature> writtenSignatures) throws IOException {
if (mSigningDetails.getSignatures() == null) {
return;
}
serializer.startTag(null, tagName);
serializer.attributeInt(null, "count",
mSigningDetails.getSignatures().length);
serializer.attributeInt(null, "schemeVersion",
mSigningDetails.getSignatureSchemeVersion());
if (android.security.Flags.apkPqcHybridSigning()
&& mSigningDetails.getSignatureSchemeMinorVersion()
!= SignatureSchemeMinorVersion.MINOR_VERSION_DEFAULT) {
serializer.attributeInt(null, "schemeMinorVersion",
mSigningDetails.getSignatureSchemeMinorVersion());
}
writeCertsListXml(serializer, writtenSignatures,
mSigningDetails.getSignatures(), false);
if (mSigningDetails.getPastSigningCertificates() != null) {
serializer.startTag(null, "pastSigs");
serializer.attributeInt(null, "count",
mSigningDetails.getPastSigningCertificates().length);
writeCertsListXml(serializer, writtenSignatures,
mSigningDetails.getPastSigningCertificates(), true);
serializer.endTag(null, "pastSigs");
}
serializer.endTag(null, tagName);
}未知签名不写 <sigs>。正常记录包含当前 signer 数量、scheme version,可选的 scheme minor version 和 past signer lineage。Android 17 的 PQC hybrid minor version 只有在对应 flag 开启且不是默认值时才落盘。
4. 证书全局去重
private void writeCertsListXml(TypedXmlSerializer serializer,
ArrayList<Signature> writtenSignatures,
Signature[] signatures, boolean isPastSigs) throws IOException {
for (int i = 0; i < signatures.length; i++) {
serializer.startTag(null, "cert");
Signature sig = signatures[i];
int sigHash = sig.hashCode();
int j;
for (j = 0; j < writtenSignatures.size(); j++) {
Signature writtenSig = writtenSignatures.get(j);
if (writtenSig.hashCode() == sigHash
&& writtenSig.equals(sig)) {
serializer.attributeInt(null, "index", j);
break;
}
}
if (j >= writtenSignatures.size()) {
writtenSignatures.add(sig);
serializer.attributeInt(null, "index",
writtenSignatures.size() - 1);
sig.writeToXmlAttributeBytesHex(
serializer, null, "key");
}
if (isPastSigs) {
serializer.attributeInt(null, "flags", sig.getFlags());
}
serializer.endTag(null, "cert");
}
}writtenSignatures 在多个包之间共享。同一证书首次出现时写 key,后续只写 index;past signer 仍要在每个包的 <cert> 上写 flags,因为同一历史证书在不同包 lineage 中可以拥有不同 capability。去重的是证书字节,不是 capability。
5. 从 XML 恢复
void readXml(TypedXmlPullParser parser,
ArrayList<Signature> readSignatures)
throws IOException, XmlPullParserException {
SigningDetails.Builder builder = new SigningDetails.Builder();
final int count = parser.getAttributeInt(null, "count", -1);
if (count == -1) {
PackageManagerService.reportSettingsProblem(
Log.WARN, "<sigs> has no count");
XmlUtils.skipCurrentTag(parser);
return;
}
final int schemeVersion = parser.getAttributeInt(
null, "schemeVersion", SignatureSchemeVersion.UNKNOWN);
builder.setSignatureSchemeVersion(schemeVersion);
final int minorVersion = parser.getAttributeInt(
null, "schemeMinorVersion",
SignatureSchemeMinorVersion.MINOR_VERSION_DEFAULT);
builder.setSignatureSchemeMinorVersion(minorVersion);
ArrayList<Signature> signatureList = new ArrayList<>();
int pos = readCertsListXml(parser, readSignatures,
signatureList, count, false, builder);
builder.setSignatures(signatureList.toArray(new Signature[0]));
if (pos < count) {
PackageManagerService.reportSettingsProblem(
Log.WARN, "<sigs> count does not match cert entries");
}
try {
mSigningDetails = builder.build();
} catch (CertificateException e) {
PackageManagerService.reportSettingsProblem(
Log.WARN, "unable to convert certificate(s) to public key(s)");
mSigningDetails = SigningDetails.UNKNOWN;
}
}恢复先读取 count 和 scheme version,再通过全局 readSignatures 解析 cert。count 不匹配只记录设置问题;证书无法转换为公钥时,整个对象回到 UNKNOWN,后续安装/更新比较不会拿半有效状态继续授权。
if (key == null && index >= 0
&& index < readSignatures.size()) {
Signature sig = readSignatures.get(index);
if (sig != null) {
signatures.add(isPastSigs
? new Signature(sig) : sig);
signatureParsed = true;
}
}
if (key != null) {
Signature sig = new Signature(key);
while (readSignatures.size() <= index) {
readSignatures.add(null);
}
readSignatures.set(index, sig);
signatures.add(sig);
}pastSigs 使用新的 Signature 实例,是为了让本包的 flags 不覆盖全局证书对象或其他包的 lineage flags;当前 signer 可以直接复用全局对象。
6. 旧签名来源
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
PackageSetting signatureCheckPs = ps;
if (signatureCheckPs == null && parsedPackage.isSdkLibrary()) {
signatureCheckPs = mPm.mSettings
.getPackageLPr(libraryInfo.getPackageName());
}
if (signatureCheckPs != null) {
SigningDetails oldSigningDetails =
signatureCheckPs.getSigningDetails();
boolean compareCompat = ...;
boolean compareRecover = ...;
PackageManagerServiceUtils.verifySignatures(
signatureCheckPs, signatureCheckSus,
disabledSystemPackageSetting,
parsedPackage.getSigningDetails(),
compareCompat, compareRecover, isRollback);
}更新普通包时旧签名来自 PackageSetting;SDK library 等特殊包可能使用其 library setting。系统应用更新还会传入 disabled system package,要求新包同时与数据包和出厂包签名一致。
7. 主比较流程
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
boolean match = parsedSignatures.checkCapability(
pkgSetting.getSigningDetails(),
SigningDetails.CertCapabilities.INSTALLED_DATA)
|| pkgSetting.getSigningDetails().checkCapability(
parsedSignatures,
SigningDetails.CertCapabilities.ROLLBACK);
if (match && disabledPkgSetting != null
&& disabledPkgSetting.getSigningDetails()
!= SigningDetails.UNKNOWN) {
match = matchSignatureInSystem(
packageName, parsedSignatures, disabledPkgSetting);
}
if (!match && compareCompat) {
match = matchSignaturesCompat(
packageName, pkgSetting.getSignatures(), parsedSignatures);
}
if (!match && compareRecover) {
match = matchSignaturesRecover(
packageName, pkgSetting.getSigningDetails(),
parsedSignatures,
SigningDetails.CertCapabilities.INSTALLED_DATA)
|| matchSignaturesRecover(
packageName, parsedSignatures,
pkgSetting.getSigningDetails(),
SigningDetails.CertCapabilities.ROLLBACK);
}判断顺序是 lineage capability → 出厂系统包 → 兼容比较 → 恢复比较。每一步失败才进入下一步;所有路径失败才抛 INSTALL_FAILED_UPDATE_INCOMPATIBLE。
8. 兼容与恢复路径
private static boolean matchSignaturesRecover(
String packageName,
SigningDetails existingSignatures,
SigningDetails parsedSignatures,
int flags) {
try {
if (parsedSignatures.checkCapabilityRecover(
existingSignatures, flags)) {
logCriticalInfo(Log.INFO,
"Recovered effectively matching certificates for "
+ packageName);
return true;
}
} catch (CertificateException e) {
logCriticalInfo(Log.INFO,
"Failed to recover certificates for " + packageName);
}
return false;
}recover 用于旧存储签名信息不完整但仍可能从证书链恢复等价关系的场景。它可能抛 CertificateException,异常被转为日志和 false,不会把不确定状态当成匹配成功。
private static boolean matchSignatureInSystem(
String packageName, SigningDetails signingDetails,
PackageSetting disabledPkgSetting) {
return signingDetails.checkCapability(
disabledPkgSetting.getSigningDetails(),
SigningDetails.CertCapabilities.INSTALLED_DATA)
|| disabledPkgSetting.getSigningDetails().checkCapability(
signingDetails,
SigningDetails.CertCapabilities.ROLLBACK);
}更新系统应用必须同时满足 disabled system package 的签名能力;否则即使与 /data 中旧包兼容,也会因系统镜像签名不一致而拒绝。
9. shared UID 比较
if (sharedUserSetting != null) {
SigningDetails shared =
sharedUserSetting.getSigningDetails();
boolean sharedMatch = parsedSignatures.checkCapability(
shared, SigningDetails.CertCapabilities.SHARED_USER_ID)
|| shared.checkCapability(
parsedSignatures,
SigningDetails.CertCapabilities.SHARED_USER_ID);
if (!sharedMatch) {
throw new PackageManagerException(
INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
"signatures do not match shared user");
}
}shared UID 比较使用 SHARED_USER_ID capability,和普通更新的 INSTALLED_DATA 不同。一个包能更新旧数据,不代表它可以加入另一个 shared UID 组。
10. 调试顺序
- 从 PackageSetting 确认旧
SigningDetails是否为 UNKNOWN。 - 查看 packages.xml 的 count、schemeVersion、cert index 和 pastSigs flags。
- 区分 exact match、capability match、compat match 和 recover match。
- 系统应用更新同时检查 disabled system package。
- shared UID 问题检查
SHARED_USER_IDcapability。 - 出现
INSTALL_FAILED_UPDATE_INCOMPATIBLE时,结合verifySignatures的顺序判断失败在哪一层。
11. 源码阅读路线
PackageSignatures.writeXml/readXml:持久化格式与证书去重。SigningDetails:lineage、capability 和 exact 比较。InstallPackageHelper:旧包、系统包和 shared UID 签名来源。PackageManagerServiceUtils.verifySignatures:更新兼容主流程。matchSignaturesCompat/matchSignaturesRecover:旧格式恢复边界。PackageInstallerSession与ApkSignatureVerifier:把文件签名验证和已安装包比较区分开。
PackageSignatures 解决的是“签名状态如何可靠地保存和恢复”;verifySignatures 解决的是“新包是否被允许替换旧包”。只有把持久化、lineage capability、系统包和 shared UID 四条约束放在同一条调用链上,才能解释签名更新为什么成功或失败。
