Skip to content

PackageSignatures 验证

分析 PackageSignatures 的持久化格式、签名恢复和安装更新比较逻辑。

AndroidPMS签名

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

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 ​

java
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. 证书全局去重 ​

java
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 恢复 ​

java
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,后续安装/更新比较不会拿半有效状态继续授权。

java
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

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

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. 兼容与恢复路径 ​

java
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,不会把不确定状态当成匹配成功。

java
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 比较 ​

java
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. 调试顺序 ​

  1. 从 PackageSetting 确认旧 SigningDetails 是否为 UNKNOWN。
  2. 查看 packages.xml 的 count、schemeVersion、cert index 和 pastSigs flags。
  3. 区分 exact match、capability match、compat match 和 recover match。
  4. 系统应用更新同时检查 disabled system package。
  5. shared UID 问题检查 SHARED_USER_ID capability。
  6. 出现 INSTALL_FAILED_UPDATE_INCOMPATIBLE 时,结合 verifySignatures 的顺序判断失败在哪一层。

11. 源码阅读路线 ​

  1. PackageSignatures.writeXml/readXml:持久化格式与证书去重。
  2. SigningDetails:lineage、capability 和 exact 比较。
  3. InstallPackageHelper:旧包、系统包和 shared UID 签名来源。
  4. PackageManagerServiceUtils.verifySignatures:更新兼容主流程。
  5. matchSignaturesCompat / matchSignaturesRecover:旧格式恢复边界。
  6. PackageInstallerSession 与 ApkSignatureVerifier:把文件签名验证和已安装包比较区分开。

PackageSignatures 解决的是“签名状态如何可靠地保存和恢复”;verifySignatures 解决的是“新包是否被允许替换旧包”。只有把持久化、lineage capability、系统包和 shared UID 四条约束放在同一条调用链上,才能解释签名更新为什么成功或失败。