当前位置:   article > 正文

Android App签名(证书)校验过程源码分析_app 程序启动时是否校验签名证书

app 程序启动时是否校验签名证书

  Android App安装是需要证书支持的,我们在Eclipse或者Android Studio中开发App时,并没有注意关于证书的事,也能正确安装App。这是因为使用了默认的debug证书。在Android App升级的时候,证书发挥的作用就尤为明显了。只有证书相同时,才能对App进行升级。证书也是为了防止App伪造的,属于Android安全策略的一部分。另外,Android沙箱机制中,也和证书有关。两个App如要共享文件,代码,或者资源时,需要使用shareUid属性,只有证书相同的App的才能shareUid。才外,如果一个App中申明了signature级别的权限,也是只有和那个App签名相同的App才能申请到对应的权限。

  虽然之前也了解过Android App的签名校验过程,但都是根据别人总结的结果,没有自己动手分析Android源码。所以本篇Blog将从源码出发分析Android App的签名校验过程,分析完源码之后,也会和网上大多数的资料一样给出总结。

  注意:由于签名校验过程是在App安装时进行的,所以源码分析的起始点是上篇Blog:PackageInstaller源码分析。不过不想了解PackageInstall源码也没有关系,只要不纠结程序的起点,分析过程就是App 签名校验模块。

一、 源码分析

  上篇BlogPackageInstaller源码分析中,程序安装过程调用了installPackageLI()方法。而在installPackageLI()方法内部,调用了collectCertificates()方法,从而进入了App的签名检验过程。下面我们查看collectCertificates()的源码实现,源码路径:/frameworks/base/core/java/android/content/pm/PackageParser.java

public void collectCertificates(Package pkg, int flags) throws PackageParserException {
    pkg.mCertificates = null;
    pkg.mSignatures = null;
    pkg.mSigningKeys = null;

    collectCertificates(pkg, new File(pkg.baseCodePath), flags);

    if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
        for (String splitCodePath : pkg.splitCodePaths) {
            collectCertificates(pkg, new File(splitCodePath), flags);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
private static void collectCertificates(Package pkg, File apkFile, int flags)
        throws PackageParserException {
    final String apkPath = apkFile.getAbsolutePath();

    StrictJarFile jarFile = null;
    try {
        jarFile = new StrictJarFile(apkPath);

        // Always verify manifest, regardless of source
        final ZipEntry manifestEntry = jarFile.findEntry(ANDROID_MANIFEST_FILENAME);
        if (manifestEntry == null) {
            throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
                    "Package " + apkPath + " has no manifest");
        }

        final List<ZipEntry> toVerify = new ArrayList<>();
        toVerify.add(manifestEntry);

        // If we're parsing an untrusted package, verify all contents
        if ((flags & PARSE_IS_SYSTEM) == 0) {
            final Iterator<ZipEntry> i = jarFile.iterator();
            while (i.hasNext()) {
                final ZipEntry entry = i.next();

                if (entry.isDirectory()) continue;
                if (entry.getName().startsWith("META-INF/")) continue;
                if (entry.getName().equals(ANDROID_MANIFEST_FILENAME)) continue;

                toVerify.add(entry);
            }
        }

        // Verify that entries are signed consistently with the first entry
        // we encountered. Note that for splits, certificates may have
        // already been populated during an earlier parse of a base APK.
        for (ZipEntry entry : toVerify) {
            final Certificate[][] entryCerts = loadCertificates(jarFile, entry);
            if (ArrayUtils.isEmpty(entryCerts)) {
                throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                        "Package " + apkPath + " has no certificates at entry "
                        + entry.getName());
            }
            final Signature[] entrySignatures = convertToSignatures(entryCerts);

            if (pkg.mCertificates == null) {
                pkg.mCertificates = entryCerts;
                pkg.mSignatures = entrySignatures;
                pkg.mSigningKeys = new ArraySet<PublicKey>();
                for (int i=0; i < entryCerts.length; i++) {
                    pkg.mSigningKeys.add(entryCerts[i][0].getPublicKey());
                }
            } else {
                if (!Signature.areExactMatch(pkg.mSignatures, entrySignatures)) {
                    throw new PackageParserException(
                            INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, "Package " + apkPath
                                    + " has mismatched certificates at entry "
                                    + entry.getName());
                }
            }
        }
    } catch (GeneralSecurityException e) {
        throw new PackageParserException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING,
                "Failed to collect certificates from " + apkPath, e);
    } catch (IOException | RuntimeException e) {
        throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                "Failed to collect certificates from " + apkPath, e);
    } finally {
        closeQuietly(jarFile);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70

  在collectCertificates(Package pkg, File apkFile, int flags)函数里面,首先提取apk的manifest.xml文件。

final ZipEntry manifestEntry = jarFile.findEntry(ANDROID_MANIFEST_FILENAME);
if (manifestEntry == null) {
   throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
           "Package " + apkPath + " has no manifest");
}
final List<ZipEntry> toVerify = new ArrayList<>();
toVerify.add(manifestEntry);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

  然后,程序遍历apk文件的所有文件节点,把除了META-INF/文件夹里面的文外外的所以文件加入待检验List。

// If we're parsing an untrusted package, verify all contents
if ((flags & PARSE_IS_SYSTEM) == 0) {
    final Iterator<ZipEntry> i = jarFile.iterator();
    while (i.hasNext()) {
        final ZipEntry entry = i.next();

        if (entry.isDirectory()) continue;
        if (entry.getName().startsWith("META-INF/")) continue;
        if (entry.getName().equals(ANDROID_MANIFEST_FILENAME)) continue;

        toVerify.add(entry);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

  紧接着把所以节点传入loadCertificates()方法,

for (ZipEntry entry : toVerify) {
   final Certificate[][] entryCerts = loadCertificates(jarFile, entry);
   if (ArrayUtils.isEmpty(entryCerts)) {
       throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
               "Package " + apkPath + " has no certificates at entry "
               + entry.getName());
   }
   final Signature[] entrySignatures = convertToSignatures(entryCerts);

   if (pkg.mCertificates == null) {
       pkg.mCertificates = entryCerts;
       pkg.mSignatures = entrySignatures;
       pkg.mSigningKeys = new ArraySet<PublicKey>();
       for (int i=0; i < entryCerts.length; i++) {
           pkg.mSigningKeys.add(entryCerts[i][0].getPublicKey());
       }
   } else {
       if (!Signature.areExactMatch(pkg.mSignatures, entrySignatures)) {
           throw new PackageParserException(
                   INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, "Package " + apkPath
                           + " has mismatched certificates at entry "
                           + entry.getName());
       }
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

  要知道loadCertificates()的作用需要分析其方法实现原型。在PackageParser.java中实现了loadCertificates()方法。

private static Certificate[][] loadCertificates(StrictJarFile jarFile, ZipEntry entry)throws PackageParserException {
   InputStream is = null;
   try {
       // We must read the stream for the JarEntry to retrieve
       // its certificates.
       is = jarFile.getInputStream(entry);
       readFullyIgnoringContents(is);
       return jarFile.getCertificateChains(entry);
   } catch (IOException | RuntimeException e) {
       throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
               "Failed reading " + entry.getName() + " in " + jarFile, e);
   } finally {
       IoUtils.closeQuietly(is);
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

  在StrictJarFile.java中,实现了getCertificateChains()方法,代码路径/libcore/luni/src/main/java/java/util/jar/StrictJarFile.java。

public Certificate[][] getCertificateChains(ZipEntry ze) {
if (isSigned) {
   return verifier.getCertificateChains(ze.getName());
}

return null;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

  StrictJarFile.java中的getCertificateChains()继续调用JarVerifier中的getCertificateChains()方法,代码路径:/libcore/luni/src/main/java/java/util/jar/JarVerifier.java。

Certificate[][] getCertificateChains(String name) {
    return verifiedEntries.get(name);
}
  • 1
  • 2
  • 3
private final Hashtable<String, Certificate[][]> verifiedEntries=new Hashtable<String, 
  • 1
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/324190
推荐阅读
相关标签
  

闽ICP备14008679号