一、前言
今天我们继续来看一下Android中的签名机制的姊妹篇:Android中是如何验证一个Apk的签名。在前一篇文章中我们介绍了,Android中是如何对程序进行签名的,当然在了解我们今天说到的知识点,这篇文章也是需要了解的,不然会有些知识点有些困惑的。
二、知识摘要
在我们没有开始这篇文章之前,我们回顾一下之前说到的签名机制流程:
1、对Apk中的每个文件做一次算法(数据摘要+Base64编码),保存到MANIFEST.MF文件中
2、对MANIFEST.MF整个文件做一次算法(数据摘要+Base64编码),存放到CERT.SF文件的头属性中,在对MANIFEST.MF文件中各个属性块做一次算法(数据摘要+Base64编码),存到到一个属性块中。
3、对CERT.SF文件做签名,内容存档到CERT.RSA中
所以通过上面的流程可以知道,我们今天来验证签名流程也是这三个步骤
三、代码分析
我们既然要了解Android中的应用程序的签名验证过程的话,那么我们肯定需要从一个类来开始看起,那就是PackageManagerService.java,因为这个类是Apk在安装的过程中核心类:frameworks\base\services\core\java\com\android\server\pm\PackageManagerService.java
private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {
……
PackageParser pp = new PackageParser();
……
try {
pp.collectCertificates(pkg, parseFlags);
pp.collectManifestDigest(pkg);
} catch (PackageParserException e) {
res.setError("Failed collect during installPackageLI", e);
return;
}
……
我们可以看到,有一个核心类:PackageParser
frameworks\base\core\java\android\content\pm\PackageParser.java
这个类也是见名知意,就是需要解析Apk包,那么就会涉及到签名信息了,下面我们就从这个类开始入手:
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION;
我们看到了几个我们很熟悉的信息:
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
这个是在安装apk包的时候出现的错误,没有证书:
那么我们就先来查找一下这个字段:看到有一个loadCertficates方法
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、验证Apk中的每个文件的算法(数据摘要+Base64编码)和MANIFEST.MF文件中的对应属性块内容是否配对
首先获取StrictJarFile文件中的InputStream对象
StrictJarFile这个类:libcore\luni\src\main\java\java\util\jar\StrictJarFile.java
public InputStream getInputStream(ZipEntry ze) {
final InputStream is = getZipInputStream(ze);
if (isSigned) {
JarVerifier.VerifierEntry entry = verifier.initEntry(ze.getName());
if (entry == null) {
return is;
}
return new JarFile.JarFileInputStream(is, ze.getSize(), entry);
}
return is;
}
1》获取到VerifierEntry对象entry
在JarVerifier.java:libcore\luni\src\main\java\java\util\jar\JarVerifier.java
VerifierEntry initEntry(String name) {
// If no manifest is present by the time an entry is found,
// verification cannot occur. If no signature files have
// been found, do not verify.
if (manifest == null || signatures.isEmpty()) {
return null;
}
Attributes attributes = manifest.getAttributes(name);
// entry has no digest
if (attributes == null) {
return null;
}
ArrayList<Certificate[]> certChains = new ArrayList<Certificate[]>();
Iterator<Map.Entry<String, HashMap<String, Attributes>>> it = signatures.entrySet().iterator();
while (it.hasNext()) {
Map.Ent