Skip to content

扫描签名校验

追踪扫描阶段的证书收集、签名缓存、V1-V4 验证、已知包比对和 shared user 签名边界。

基于android-17.0.0_r1
AndroidPackageManagerService包扫描签名SigningDetailsKeySet源码阅读

扫描签名校验 ​

本文面向已经读过 扫描并行化 和 ScanRequest 与 Result 的读者,继续追踪一个 APK 从解析结果到可注册包之间的签名路径。前文解释 parser 线程如何交付 ParsedPackage,本文解释证书何时收集、何时复用、与哪个历史对象比较,以及失败之后由谁决定清理或拒绝。

本文只讨论 Android 17 android-17.0.0_r1 的扫描/安装校验,不把 APK 签名方案本身写成独立密码学教程,也不把“证书收集成功”误写成“包已经和旧版本兼容”。在当前源码中,至少要区分四个层次:APK 内容签名验证、base/split 证书一致性、与 PackageSetting/disabled setting 的兼容性比较、与 shared user 或 upgrade keyset 的关系检查。

读完后,读者应能从 InstallPackageHelper.scanPackageForInitLI() 找到 collectCertificatesLI() 的调用,判断何时命中签名缓存、何时跳过完整性校验、何时强制重新收集;能够沿 ParsingPackageUtils 和 ApkSignatureVerifier 解释 V4/V3/V2/V1 的尝试顺序;还能根据错误码区分证书缺失、证书不一致、包升级签名不兼容和 shared user 不兼容。

1. 校验分层 ​

1.1 四层状态 ​

源码文件:

  • frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
  • frameworks/base/services/core/java/com/android/server/pm/ScanPackageUtils.java
  • frameworks/base/services/core/java/com/android/server/pm/ReconcilePackageUtils.java

签名相关对象和动作可以按下表定位:

层次输入核心实现输出或失败
APK 验证base/split APK 文件ApkSignatureVerifierSigningDetails 或 parse error
包内一致性base 的 SigningDetails、每个 splitParsingPackageUtils.getSigningDetails()所有 split 使用同一签名,否则 INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES
缓存复用PackageSetting 路径、时间、版本信息ScanPackageUtils.collectCertificatesLI()复用或重新设置 ParsedPackage.signingDetails
已知包兼容新签名、旧 PackageSetting、disabled settingPackageManagerServiceUtils.verifySignatures()允许、兼容升级、回滚或 INSTALL_FAILED_UPDATE_INCOMPATIBLE
shared user/KeySet新签名、SharedUserSetting、upgrade keysetReconcilePackageUtils允许加入或 shared user/keyset 失败

前两层回答“这个 APK 的签名是否真实且内部一致”,后三层回答“它是否能替换当前系统中的对象”。同一签名验证错误可能在不同层得到不同日志:Failed to parse 表示收集阶段失败,Existing package ... signatures do not match 表示已知包兼容性失败。

1.2 调用时机 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java

java
final boolean forceCollect = scanSystemPartition ? isUpgrade
        : pkgAlreadyExists && needSignatureMatchToSystem(pkgSetting.getPackageName());

// APK verification can be skipped during certificate collection, only if the file is in
// a verified partition.
final boolean skipVerify = scanSystemPartition;
ScanPackageUtils.collectCertificatesLI(pkgSetting, parsedPackage,
        mPm.getSettingsVersionForPackage(parsedPackage), forceCollect, skipVerify,
        mPm.isPreNMR1Upgrade());

metrics.setIsFsiEnabled(forceCollect)
        .setPackageName(parsedPackage.getPackageName())
        .setNumApkSplits(parsedPackage.getSplitCodePaths() == null
                ? 0 : parsedPackage.getSplitCodePaths().length)
        .setSignatureSchemeVersion(
                parsedPackage.getSigningDetails().getSignatureSchemeVersion());

签名收集发生在 scanPackageForInitLI() 的系统包恢复/初始化路径中,位于版本和路径判断之后、scanPackageNew() 生成 ScanResult 之前。普通安装路径也会在 installPackageLI() 中直接调用 ParsingPackageUtils.getSigningDetails(),但本文主线关注扫描阶段的 collectCertificatesLI()。

forceCollect 与 skipVerify 是两个独立开关:前者决定能否复用 PackageSetting 中的签名缓存,后者决定重新收集时是否做完整内容验证。系统分区因为受 verified partition 保护可以跳过完整性验证,但仍需要拿到证书用于后续比较;data 包不能因为 forceCollect=false 就跳过旧包兼容性检查。

2. 缓存收集 ​

2.1 命中条件 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/ScanPackageUtils.java

java
public static void collectCertificatesLI(PackageSetting ps, ParsedPackage parsedPackage,
        Settings.VersionInfo settingsVersionForPackage, boolean forceCollect,
        boolean skipVerify, boolean isPreNMR1Upgrade)
        throws PackageManagerException {
    // When upgrading from pre-N MR1, verify the package time stamp using the package
    // directory and not the APK file.
    final long lastModifiedTime = isPreNMR1Upgrade
            ? new File(parsedPackage.getPath()).lastModified()
            : getLastModifiedTime(parsedPackage);
    if (ps != null && !forceCollect
            && ps.getPathString().equals(parsedPackage.getPath())
            && ps.getLastModifiedTime() == lastModifiedTime
            && !ReconcilePackageUtils.isCompatSignatureUpdateNeeded(settingsVersionForPackage)
            && !ReconcilePackageUtils.isRecoverSignatureUpdateNeeded(
            settingsVersionForPackage)) {
        if (ps.getSigningDetails().getSignatures() != null
                && ps.getSigningDetails().getSignatures().length != 0
                && ps.getSigningDetails().getSignatureSchemeVersion()
                != SigningDetails.SignatureSchemeVersion.UNKNOWN) {
            // Optimization: reuse the existing cached signing data
            // if the package appears to be unchanged.
            parsedPackage.setSigningDetails(
                    new SigningDetails(ps.getSigningDetails()));
            return;
        }

        Slog.w(TAG, "PackageSetting for " + ps.getPackageName()
                + " is missing signatures.  Collecting certs again to recover them.");
    } else {
        Slog.i(TAG, parsedPackage.getPath() + " changed; collecting certs"
                + (forceCollect ? " (forced)" : ""));
    }

缓存命中需要同时满足五个条件:存在旧设置、没有强制收集、路径相同、修改时间相同、Settings 版本不要求兼容性或恢复性签名升级。即使这五项满足,旧 SigningDetails 还必须有非空签名数组且 scheme version 不能是 UNKNOWN;否则只记录 warning 并重新收集。

isPreNMR1Upgrade 是历史兼容分支:升级自旧版本时,时间戳取包路径的 File.lastModified();其他情况使用 getLastModifiedTime(parsedPackage),后者可以处理当前包模型支持的 base/split/APEX 路径。不要把时间戳相同解释成内容一定相同,它只是 PMS 当前的缓存失效条件。

2.2 缓存副本 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/ScanPackageUtils.java

java
try {
    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
    final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
    final ParseResult<SigningDetails> result = ParsingPackageUtils.getSigningDetails(
            input, parsedPackage, skipVerify);
    if (result.isError()) {
        throw new PackageManagerException(
                result.getErrorCode(), result.getErrorMessage(), result.getException());
    }
    parsedPackage.setSigningDetails(result.getResult());
} finally {
    Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}

缓存命中时使用 new SigningDetails(ps.getSigningDetails()),不会把 PackageSetting 内部对象直接暴露给当前 ParsedPackage。缓存失效时,getSigningDetails() 返回 ParseResult;错误被转换为 PackageManagerException,成功结果才写入 ParsedPackage。Trace 在异常路径也必须结束,否则启动 trace 会留下不完整区间。

2.3 强制收集场景 ​

scanPackageForInitLI() 中的 forceCollect 在扫描 system 分区时取 isUpgrade,在扫描 data 分区时取“已有包且该包在严格 system signature 检查名单中”。这意味着 OTA/system upgrade 和特定预装包会放弃缓存,即便路径和时间没有变化。

缓存命中只跳过证书收集,不跳过后续的 verifySignatures()、KeySet 和 shared user 比较。旧缓存可能证明“上次这个文件拿到过某证书”,但不能替代本次安装请求与当前历史设置的兼容性判断。

3. APK 验证 ​

3.1 解析工具入口 ​

源码文件:frameworks/base/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java

java
/**
 * Collect certificates from all the APKs described in the given package. Also asserts that
 * all APK contents are signed correctly and consistently.
 */
@CheckResult
public static ParseResult<SigningDetails> getSigningDetails(ParseInput input,
        ParsedPackage pkg, boolean skipVerify) {
    return getSigningDetails(input, pkg.getBaseApkPath(), pkg.isStaticSharedLibrary(),
            pkg.getTargetSdkVersion(), pkg.getSplitCodePaths(), skipVerify);
}

@CheckResult
public static ParseResult<SigningDetails> getSigningDetails(ParseInput input,
        String baseApkPath, boolean isStaticSharedLibrary, int targetSdkVersion,
        String[] splitCodePaths, boolean skipVerify) {
    SigningDetails signingDetails = SigningDetails.UNKNOWN;

    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
    try {
        ParseResult<SigningDetails> result = getSigningDetails(
                input, baseApkPath, skipVerify, isStaticSharedLibrary,
                signingDetails, targetSdkVersion);
        if (result.isError()) {
            return input.error(result);
        }

        signingDetails = result.getResult();
        final File frameworkRes = new File(Environment.getRootDirectory(),
                "framework/framework-res.apk");
        boolean isFrameworkResSplit = frameworkRes.getAbsolutePath().equals(baseApkPath);
        if (!ArrayUtils.isEmpty(splitCodePaths) && !isFrameworkResSplit) {
            for (int i = 0; i < splitCodePaths.length; i++) {
                result = getSigningDetails(input, splitCodePaths[i], skipVerify,
                        isStaticSharedLibrary, signingDetails, targetSdkVersion);
                if (result.isError()) {
                    return input.error(result);
                }
            }
        }
        return result;
    } finally {
        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
    }
}

解析工具先验证 base APK,再逐个验证 split,并把先前得到的 SigningDetails 作为 existingSigningDetails 传入。framework-res.apk 被特殊排除在 split 一致性循环之外,这是 Android 17 源码的真实条件,不能泛化成“所有 split 都必定逐个比较”。

3.2 skipVerify 语义 ​

源码文件:frameworks/base/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java

java
@CheckResult
public static ParseResult<SigningDetails> getSigningDetails(ParseInput input,
        String baseCodePath, boolean skipVerify, boolean isStaticSharedLibrary,
        @NonNull SigningDetails existingSigningDetails, int targetSdk) {
    int minSignatureScheme = ApkSignatureVerifier.getMinimumSignatureSchemeVersionForTargetSdk(
            targetSdk);
    if (isStaticSharedLibrary) {
        // must use v2 signing scheme
        minSignatureScheme = SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2;
    }
    final ParseResult<SigningDetails> verified;
    if (skipVerify) {
        // systemDir APKs are already trusted, save time by not verifying
        verified = ApkSignatureVerifier.unsafeGetCertsWithoutVerification(input, baseCodePath,
                minSignatureScheme);
    } else {
        verified = ApkSignatureVerifier.verify(input, baseCodePath, minSignatureScheme);
    }

    if (verified.isError()) {
        return input.error(verified);
    }

    if (existingSigningDetails == SigningDetails.UNKNOWN) {
        return verified;
    } else {
        if (!Signature.areExactMatch(existingSigningDetails, verified.getResult())) {
            return input.error(INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
                    baseCodePath + " has mismatched certificates");
        }
        return input.success(existingSigningDetails);
    }
}

skipVerify=true 并不是“不读取签名”,而是调用 unsafeGetCertsWithoutVerification() 获取证书,跳过 APK 内容完整性验证。源码只把它用于已经信任的 systemDir APK;data APK 走 ApkSignatureVerifier.verify()。

静态共享库无论 target SDK 是多少,都把最低 scheme 强制设置为 V2。普通 APK 的最低 scheme 由 target SDK 决定。base 之后的 split 使用 Signature.areExactMatch(),要求证书集合完全一致;签名 lineage 兼容性不是这里判断的主题,而在已知包比较阶段由 SigningDetails.checkCapability() 处理。

3.3 方案尝试顺序 ​

源码文件:frameworks/base/core/java/android/util/apk/ApkSignatureVerifier.java

java
public static ParseResult<SigningDetailsWithDigests> verifySignaturesInternal(ParseInput input,
        String apkPath, @SignatureSchemeVersion int minSignatureSchemeVersion,
        boolean verifyFull) {
    if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V4) {
        return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                "No signature found in package of version " + minSignatureSchemeVersion
                        + " or newer for package " + apkPath);
    }

    // first try v4
    try {
        return verifyV4Signature(input, apkPath, minSignatureSchemeVersion, verifyFull);
    } catch (SignatureNotFoundException e) {
        // not signed with v4, try older if allowed
        if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V4) {
            return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                    "No APK Signature Scheme v4 signature in package " + apkPath, e);
        }
    }

    if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V3) {
        return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                "No signature found in package of version " + minSignatureSchemeVersion
                        + " or newer for package " + apkPath);
    }

    return verifyV3AndBelowSignatures(input, apkPath, minSignatureSchemeVersion, verifyFull);
}

private static ParseResult<SigningDetailsWithDigests> verifyV3AndBelowSignatures(
        ParseInput input, String apkPath, @SignatureSchemeVersion int minSignatureSchemeVersion,
        boolean verifyFull) {
    // try v3
    try {
        return verifyV3Signature(input, apkPath, verifyFull);
    } catch (SignatureNotFoundException e) {
        if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V3) {
            return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                    "No APK Signature Scheme v3 signature in package " + apkPath, e);
        }
    }

    if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V2) {
        return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                "No signature found in package of version " + minSignatureSchemeVersion
                        + " or newer for package " + apkPath);
    }

    // try v2
    try {
        return verifyV2Signature(input, apkPath, verifyFull);
    } catch (SignatureNotFoundException e) {
        if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V2) {
            return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                    "No APK Signature Scheme v2 signature in package " + apkPath, e);
        }
    }

    if (minSignatureSchemeVersion > SignatureSchemeVersion.JAR) {
        return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                "No signature found in package of version " + minSignatureSchemeVersion
                        + " or newer for package " + apkPath);
    }

    // v2 didn't work, try jarsigner
    return verifyV1Signature(input, apkPath, verifyFull);
}

“V1-V4 逐级降级”只有在最低要求允许降级时才成立:算法先尝试 V4,再到 V3、V2、V1;如果 minSignatureSchemeVersion 已经是 V2,V2 缺失后不会再接受 V1。V4 是 add-on,需要结合 V2/V3 证书和 digest;它不是简单地替换 V3。

3.4 最低 scheme ​

源码文件:frameworks/base/core/java/android/util/apk/ApkSignatureVerifier.java

java
/**
 * Returns the minimum signature scheme version required for an app targeting the specified
 * {@code targetSdk}.
 */
public static int getMinimumSignatureSchemeVersionForTargetSdk(int targetSdk) {
    if (targetSdk >= Build.VERSION_CODES.R) {
        return SignatureSchemeVersion.SIGNING_BLOCK_V2;
    }
    return SignatureSchemeVersion.JAR;
}

Android 17 当前源码的最低 scheme 映射非常具体:target SDK >= R 要求至少 V2,低于 R 仍允许 JAR/V1。不能凭“Android 9 引入 V3”推断 target SDK >= 28 就强制 V3;当前方法没有这样的分支。静态共享库的 V2 要求来自 ParsingPackageUtils 的额外覆盖。

4. 已知包比较 ​

4.1 比较入口 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/ReconcilePackageUtils.java

java
final PackageSetting signatureCheckPs =
        lastStaticSharedLibSetting != null
                ? lastStaticSharedLibSetting
                : installRequest.getScannedPackageSetting();
boolean removeAppKeySetData = false;
boolean sharedUserSignaturesChanged = false;
SigningDetails signingDetails = null;
if (parsedPackage != null) {
    signingDetails = parsedPackage.getSigningDetails();
}
final boolean isSystemPackage =
        ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0);
final boolean isApex = (scanFlags & SCAN_AS_APEX) != 0;
SharedUserSetting sharedUserSetting = settings.getSharedUserSettingLPr(signatureCheckPs);

if (ksms.shouldCheckUpgradeKeySetLocked(signatureCheckPs, sharedUserSetting, scanFlags)) {
    if (ksms.checkUpgradeKeySetLocked(signatureCheckPs, parsedPackage)) {
        // The package is signed by an allowed upgrade key.
    } else {
        if (!isSystemPackage) {
            throw new ReconcileFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
                    "Package " + parsedPackage.getPackageName()
                            + " upgrade keys do not match the previously installed version");
        } else {
            String msg = "System package " + parsedPackage.getPackageName()
                    + " signature changed; retaining data.";
            PackageManagerService.reportSettingsProblem(Log.WARN, msg);
        }
    }
} else {
    // verifySignatures(...) is called here for ordinary certificate matching.
}

reconcile 先决定比较对象:静态共享库使用该库的最新 PackageSetting,普通包使用扫描结果设置。随后优先判断 KeySet 是否要求升级密钥检查;只有不走 upgrade keyset 分支时,才进入 PackageManagerServiceUtils.verifySignatures() 的普通证书比较。

system package 与非 system package 的失败策略不同:非 system 包的 upgrade keyset 不匹配抛 INSTALL_FAILED_UPDATE_INCOMPATIBLE;system 包记录“signature changed; retaining data”的设置问题。这里已经不是 APK 内容验证,而是安装关系验证。

4.2 旧包与回滚 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java

java
public static boolean verifySignatures(PackageSetting pkgSetting,
        @Nullable SharedUserSetting sharedUserSetting,
        PackageSetting disabledPkgSetting, SigningDetails parsedSignatures,
        boolean compareCompat, boolean compareRecover, boolean isRollback)
        throws PackageManagerException {
    final String packageName = pkgSetting.getPackageName();
    boolean compatMatch = false;
    if (pkgSetting.getSigningDetails().getSignatures() != null) {
        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);
            compatMatch = match;
        }
        if (!match && compareRecover) {
            match = matchSignaturesRecover(packageName, pkgSetting.getSigningDetails(),
                    parsedSignatures, SigningDetails.CertCapabilities.INSTALLED_DATA)
                    || matchSignaturesRecover(packageName, parsedSignatures,
                    pkgSetting.getSigningDetails(), SigningDetails.CertCapabilities.ROLLBACK);
        }
        if (!match && isRollback) {
            // A rollback may return to a previous signer in the known lineage.
            match = pkgSetting.getSigningDetails().hasAncestorOrSelf(parsedSignatures);
        }
        if (!match) {
            throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
                    "Existing package " + packageName
                            + " signatures do not match newer version; ignoring!");
        }
    }
    return compatMatch;
}

普通比较先尝试 INSTALLED_DATA 能力,再尝试旧包授予新签名的 ROLLBACK 能力;disabled system package 存在且签名不是 UNKNOWN 时还必须通过 system 版本比较。兼容升级和恢复升级是由 Settings 版本信息控制的额外分支,回滚则可以在已知 lineage 中回到祖先签名。

签名 lineage 的“同一证书”不能只用字节数组相等解释。SigningDetails.checkCapability() 会考虑当前 signer、past signing certificates 和 capability flags;这也是 key rotation 能够在特定能力下继续更新的原因。

4.3 shared user ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java

java
if (sharedUserSetting != null
        && sharedUserSetting.getSigningDetails() != SigningDetails.UNKNOWN) {
    boolean match = canJoinSharedUserId(packageName, parsedSignatures, sharedUserSetting,
            pkgSetting.getSigningDetails().getSignatures() != null
                    ? SHARED_USER_ID_JOIN_TYPE_UPDATE : SHARED_USER_ID_JOIN_TYPE_INSTALL);
    if (!match && compareCompat) {
        match = matchSignaturesCompat(packageName, sharedUserSetting.signatures,
                parsedSignatures);
    }
    if (!match && compareRecover) {
        match = matchSignaturesRecover(packageName,
                sharedUserSetting.signatures.mSigningDetails, parsedSignatures,
                SigningDetails.CertCapabilities.SHARED_USER_ID)
                || matchSignaturesRecover(packageName, parsedSignatures,
                sharedUserSetting.signatures.mSigningDetails,
                SigningDetails.CertCapabilities.SHARED_USER_ID);
        compatMatch |= match;
    }
    if (!match) {
        throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
                "Package " + packageName
                        + " has no signatures that match those in shared user "
                        + sharedUserSetting.name + "; ignoring!");
    }
    if (!parsedSignatures.hasCommonAncestor(
            sharedUserSetting.signatures.mSigningDetails)) {
        throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
                "Package " + packageName + " has a signing lineage "
                        + "that diverges from the lineage of the sharedUserId");
    }
}

shared user 比较有两个独立条件:新包必须能以 install/update join type 加入共享 UID,且 signing lineage 不能与共享用户的 lineage 分叉。即使某个 signer 通过 capability 比较,也不能绕过 lineage divergence 检查。

5. lineage 数据 ​

5.1 SigningDetails ​

源码文件:frameworks/base/core/java/android/content/pm/SigningDetails.java

java
@IntDef({SignatureSchemeVersion.UNKNOWN,
        SignatureSchemeVersion.JAR,
        SignatureSchemeVersion.SIGNING_BLOCK_V2,
        SignatureSchemeVersion.SIGNING_BLOCK_V3,
        SignatureSchemeVersion.SIGNING_BLOCK_V4})
public @interface SignatureSchemeVersion {
    int UNKNOWN = 0;
    int JAR = 1;
    int SIGNING_BLOCK_V2 = 2;
    int SIGNING_BLOCK_V3 = 3;
    int SIGNING_BLOCK_V4 = 4;
}

private final @Nullable Signature[] mSignatures;
private final @SignatureSchemeVersion int mSignatureSchemeVersion;
private final @Nullable ArraySet<PublicKey> mPublicKeys;
private final @Nullable Signature[] mPastSigningCertificates;

SigningDetails 不只保存当前 Signature[] 和 scheme version,还保存 public keys、past signing certificates 以及 capability。PackageSetting 缓存的是整个 SigningDetails,因此 key rotation 的历史不会在缓存复用时丢失。

5.2 lineage 与能力 ​

源码文件:frameworks/base/core/java/android/content/pm/SigningDetails.java

java
public boolean hasAncestorOrSelf(@NonNull SigningDetails oldDetails) {
    if (this == UNKNOWN || oldDetails == UNKNOWN) {
        return false;
    }
    if (oldDetails.mSignatures.length > 1) {
        // multiple-signer packages cannot rotate signing certs, so we just compare current
        // signers for an exact match
        return signaturesMatchExactly(oldDetails);
    } else {
        // check whether the old signer was one of our old signing certificates
        return hasCertificate(oldDetails.mSignatures[0]);
    }
}

多 signer 包不能进行 signing certificate rotation,因此 hasAncestorOrSelf() 对多 signer 走 exact match;单 signer 包才沿 past signing certificates 检查祖先关系。这个分支解释了为什么“证书内容看起来相同”并不足以判断所有升级场景。

5.3 设置持久化 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java

java
public android.content.pm.SigningDetails getSigningDetails() {
    return signatures.mSigningDetails;
}

public PackageSetting setSigningDetails(SigningDetails signingDetails) {
    // TODO: Immutability
    signatures.mSigningDetails = signingDetails;
    onChanged();
    return this;
}

PackageSetting 通过 PackageSignatures 持有签名详情,setSigningDetails() 会触发 onChanged(),使设置观察者知道状态发生变化。扫描阶段把证书写入结果设置后,真正的 packages XML 持久化仍由 Settings 写回阶段完成;仅修改 ParsedPackage 不会自动持久化。

6. 失败与清理 ​

6.1 解析失败 ​

ParsingPackageUtils.getSigningDetails() 或 ApkSignatureVerifier 失败时,错误通常是 INSTALL_PARSE_FAILED_NO_CERTIFICATES、INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING、INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION 或 split 的 INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES。这些错误发生在证书收集阶段,尚未进入旧包兼容性比较。

6.2 扫描失败 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java

java
if (throwable == null) {
    try {
        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "addForInitLI");
        addForInitLI(result.parsedPackage, scanParams.parseFlags, scanParams.scanFlags,
                new UserHandle(UserHandle.USER_SYSTEM), scanParams.apexInfo);
    } catch (PackageManagerException e) {
        errorCode = e.error;
        errorMsg = "Failed to scan " + result.scanFile + ": " + e.getMessage();
        Slog.w(TAG, errorMsg);
    } finally {
        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
    }
} else if (throwable instanceof PackageManagerException) {
    PackageManagerException e = (PackageManagerException) throwable;
    errorCode = e.error;
    errorMsg = "Failed to parse " + result.scanFile + ": " + e.getMessage();
    Slog.w(TAG, errorMsg);
}

if ((scanParams.scanFlags & SCAN_AS_SYSTEM) == 0
        && errorCode != PackageManager.INSTALL_SUCCEEDED) {
    logCriticalInfo(Log.WARN,
            "Deleting invalid package at " + result.scanFile + " (" + errorMsg + ")");
    mRemovePackageHelper.removeCodePath(result.scanFile);
}

并行 parser 返回的 PackageManagerException 会在 processParseResult() 被标记为 parse 失败;addForInitLI() 抛出的异常则标记为 scan 失败。二者都可能最终导致 data code path 清理,但 system 输入带 SCAN_AS_SYSTEM 时不会走删除分支。签名不兼容若发生在 reconcile,可能在 addForInitLI() 内以 scan failure 表现;因此日志前缀和错误码需要同时查看。

6.3 system 与 data ​

系统包签名不匹配时,初始化逻辑可能删除 data 上的冲突用户包、隐藏 system 版本、启用 system 版本或保留数据,具体由版本、路径和 SigningDetails capability 共同决定。证书收集本身不会决定最终选择哪个 code path;它只提供后续决策所需的可信签名详情。

APK-in-APEX 还有额外边界:system 包扫描失败会影响 APEX 安装报告,data 上较新版本的 APK-in-APEX 不应因为 system 侧较旧版本比较而直接让 APEX 安装失败,相关条件在 scanPackageForInitLI() 中单独处理。

7. 测试与诊断 ​

7.1 签名持久化测试 ​

源码文件:frameworks/base/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageSignaturesTest.java

java
@Test
public void testReadXmlWithSigningLineage() throws Exception {
    // Verifies the good path of reading a single sigs tag including pastSigs with the
    // signing lineage returns the expected signatures and lineage for two and three signers
    // in the lineage.
    verifyReadXmlReturnsExpectedSignaturesAndLineage("xml/two-signers-in-lineage.xml", 3,
            FIRST_EXPECTED_SIGNATURE, SECOND_EXPECTED_SIGNATURE);
    verifyReadXmlReturnsExpectedSignaturesAndLineage("xml/three-signers-in-lineage.xml", 3,
            FIRST_EXPECTED_SIGNATURE, SECOND_EXPECTED_SIGNATURE, THIRD_EXPECTED_SIGNATURE);
}

private void verifyReadXmlReturnsExpectedSignaturesAndLineage(String xmlFile,
        int schemeVersion, String... expectedSignatureValues) throws Exception {
    TypedXmlPullParser parser = getXMLFromResources(xmlFile);
    ArrayList<Signature> signatures = new ArrayList<>();
    mPackageSetting.getSignatures().readXml(parser, signatures);
    Set<String> expectedSignatures = createSetOfSignatures(expectedSignatureValues);
    verifySignaturesContainExpectedValues(signatures, expectedSignatures);
    assertEquals("The returned signature scheme is not the expected value", schemeVersion,
            mPackageSetting.getSigningDetails().getSignatureSchemeVersion());
    for (Signature signature : signatures) {
        String signatureValue = HexDump.toHexString(signature.toByteArray(), false);
        int expectedCapabilities = SIGNATURE_TO_CAPABILITY_MAP.get(signatureValue);
        assertTrue(mPackageSetting.getSigningDetails().hasCertificate(
                signature, expectedCapabilities));
    }
}

这个测试从 XML fixture 读取 sigs/pastSigs,断言签名数量、scheme version 和 lineage capability。它证明 Settings 持久化后的签名历史可以恢复,不证明 APK 文件本身在本次启动时重新验证成功,也不证明 shared user 比较通过。

7.2 损坏 fixture ​

源码文件:frameworks/base/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageSignaturesTest.java

测试还覆盖:

  • 缺失 sigs count 或 scheme version 时,设置可以退化到 UNKNOWN 或保留可读签名;
  • cert index、cert key、cert tag 缺失或非法时,读取过程不应无界崩溃;
  • past signer 的 flags 缺失/非法时,签名可读取但 capability 不完整;
  • 多余 cert tag 被忽略,过少 cert tag 只保留能够读取的签名;
  • 启用 APK PQC hybrid signing flag 时,scheme minor version 也被验证。

这些 fixture 测试的是 XML 恢复容错,不应反推 ApkSignatureVerifier 对真实 APK 的验证行为。真实 APK 证书错误仍要回到 ParsingPackageUtils 和 ApkSignatureVerifier 的 ParseResult 错误码。

7.3 扫描结果测试 ​

源码文件:frameworks/base/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java

可与签名主线对照的扫描测试包括:

  • newInstallSimpleAllNominal:验证新包能生成扫描结果,但不包含真实证书 fixture;
  • updateSystemApp_applicationInfoFlagSet:注入 system/disabled setting,断言扫描结果派生出 FLAG_UPDATED_SYSTEM_APP;
  • scanSystemApp_isOrphanedTrue:断言 system package 扫描后设置为 orphaned;
  • executeScan():直接调用 scanPackageOnly(),并手动 hideAsFinal(),说明测试没有经过完整 reconcile/commit。

这些测试证明扫描状态承接了 SigningDetails 所在的 PackageSetting,但签名兼容性边界仍由 ReconcilePackageUtils/PackageManagerServiceUtils 负责。若要验证真实签名替换失败,必须使用带不同证书的 APK 或对应安装测试,而不能只看 ScanResult 非空。

7.4 只读诊断 ​

设备上可以使用:

text
adb shell dumpsys package <package-name>
adb shell logcat -s PackageManager PackageManagerService ApkSignatureVerifier
adb shell dumpsys package packages

诊断时按顺序记录:

  1. code path、版本、是否 system/updated system/APEX;
  2. SigningDetails 的 scheme version、当前 signer 和 lineage 信息;
  3. PackageSetting 与 disabled system setting 是否同时存在;
  4. 错误是 NO_CERTIFICATES、INCONSISTENT_CERTIFICATES、UPDATE_INCOMPATIBLE 还是 SHARED_USER_INCOMPATIBLE;
  5. 失败后 data code path 是否被删除,system package 是否被隐藏/恢复,shared user 是否被裁剪。

日志中出现“changed; collecting certs”只说明缓存未命中;出现“signature mismatch”还需要判断它来自 APK 验证、split 一致性、旧包比较还是 shared user。不同 owner 的日志不能互相替代。

8. 源码路线 ​

遇到签名相关问题时,可以沿下面的顺序追踪:

  1. InstallPackageHelper.scanPackageForInitLI():确认 forceCollect、skipVerify 和 collectCertificatesLI() 调用时机。
  2. ScanPackageUtils.collectCertificatesLI():核对路径、时间、Settings 版本、缓存签名和重收集条件。
  3. ParsingPackageUtils.getSigningDetails():确认 base/split、static shared library 和 skipVerify 分支。
  4. ApkSignatureVerifier.verifySignaturesInternal():确认最低 scheme、V4→V3→V2→V1 尝试和错误码。
  5. ReconcilePackageUtils.reconcilePackages():确认 upgrade keyset 是否优先于普通证书比较。
  6. PackageManagerServiceUtils.verifySignatures():确认旧包、disabled system、compat/recover/rollback 和 shared user 比较。
  7. SigningDetails:确认 lineage、ancestor 和 capability,而不是只比较当前证书数组。
  8. processParseResult() 与清理 helper:确认失败日志、APEX 报告和 data code path 删除边界。

一个实用练习是:给定“APK 使用 V1、target SDK 35、安装在 /data/app、旧包使用 V2、shared user 有一条不同 lineage”的现象,分别指出它会在哪一层失败、哪个错误码最先出现、是否会调用 removeCodePath(),以及需要查看哪些测试 fixture 或 dumpsys 字段。只有把证书真实性、包内一致性和历史兼容性分开,才能正确解释结果。

9. 设计收束 ​

Android 17 的扫描签名校验不是一个单独的 verify() 调用,而是一条分层链路:

  • collectCertificatesLI() 用路径、修改时间和 Settings 版本控制缓存;缓存只减少证书收集成本,不替代兼容性比较。
  • ParsingPackageUtils 先验证 base,再验证 split;skipVerify 只跳过受信任 system APK 的内容完整性检查,仍然返回证书。
  • ApkSignatureVerifier 按 V4、V3、V2、V1 尝试,但最低 scheme 由 target SDK/静态共享库规则限制;Android 17 对 target SDK >= R 的普通 APK 最低要求是 V2。
  • ReconcilePackageUtils 先处理 upgrade keyset,再进入旧包、disabled system、compat/recover/rollback 和 shared user 签名比较。
  • SigningDetails 的 lineage 与 capability 决定 key rotation、回滚和 shared user 加入是否可行;多 signer 包不能沿 lineage 轮换。
  • 失败发生在 parser、scan、reconcile 的不同阶段,日志前缀、错误码和 code path 清理范围都不同;system 输入和 data 输入不能用同一清理规则解释。

后续专题会转向 native library 扫描,说明签名通过后 PackageSetting 如何继续承载 ABI、native library path 和卸载清理状态。