app在安装是会采集签名信息来验证apk的完成性
apk经过v1签名,解压后会得到META-INF文件夹,里面主要有三个文件CERT.RSA CERT.SF MANIFEST.MF,三者的介绍参考https://www.iteye.com/blog/myeyeofjava-2125348
http://www.blogjava.net/zh-weir/archive/2011/07/19/354663.html
在apk安装时会先对安装包惊醒解析,PackageParser.java类就是负责解析apk的,其中的collectCertificates函数开始收集签名信息,代码如下
private static void collectCertificates(Package pkg, File apkFile, boolean skipVerify)
throws PackageParserException {
final String apkPath = apkFile.getAbsolutePath();
int minSignatureScheme = SigningDetails.SignatureSchemeVersion.JAR;
if (pkg.applicationInfo.isStaticSharedLibrary()) {
// must use v2 signing scheme
minSignatureScheme = SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2;
}
SigningDetails verified;
if (skipVerify) {
// systemDir APKs are already trusted, save time by not verifying
verified = ApkSignatureVerifier.plsCertsNoVerifyOnlyCerts(
apkPath, minSignatureScheme);
} else {
verified = ApkSignatureVerifier.verify(apkPath, minSignatureScheme);
}
// Verify that entries are signed consistently with the first pkg
// we encountered. Note that for splits, certificates may have
// already been populated during an earlier parse of a base APK.
if (pkg.mSigningDetails == SigningDetails.UNKNOWN) {
pkg.mSigningDetails = verified;
} else {
if (!Signature.areExactMatch(pkg.mSigningDetails.signatures, verified.signatures)) {
throw new PackageParserException(
INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
apkPath + " has mismatched certificates");
}
}
}
安装过程中 skipVerify 当然是false,所以调用
ApkSignatureVerifier.verify(apkPath, minSignatureScheme);
public static PackageParser.SigningDetails verify(String apkPath,
@SignatureSchemeVersion int minSignatureSchemeVersion)
throws PackageParserException {
if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V3) {
// V3 and before are older than the requested minimum signing version
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"No signature found in package of version " + minSignatureSchemeVersion
+ " or newer for package " + apkPath);
}
// first try v3
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV3");
try {
ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner =
ApkSignatureSchemeV3Verifier.verify(apkPath);
Certificate[][] signerCerts = new Certificate[][] { vSigner.certs };
Signature[] signerSigs = convertToSignatures(signerCerts);
Signature[] pastSignerSigs = null;
int[] pastSignerSigsFlags = null;
if (vSigner.por != null) {
// populate proof-of-rotation information
pastSignerSigs = new Signature[vSigner.por.certs.size()];
pastSignerSigsFlags = new int[vSigner.por.flagsList.size()];
for (int i = 0; i < pastSignerSigs.length; i++) {
pastSignerSigs[i] = new Signature(vSigner.por.certs.get(i).getEncoded());
pastSignerSigsFlags[i] = vSigner.por.flagsList.get(i);
}
}
return new PackageParser.SigningDetails(
signerSigs, SignatureSchemeVersion.SIGNING_BLOCK_V3,
pastSignerSigs, pastSignerSigsFlags);
} catch (SignatureNotFoundException e) {
// not signed with v3, try older if allowed
if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V3) {
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"No APK Signature Scheme v3 signature in package " + apkPath, e);
}
} catch (Exception e) {
// APK Signature Scheme v2 signature found but did not verify
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"Failed to collect certificates from " + apkPath
+ " using APK Signature Scheme v3", e);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
// redundant, protective version check
if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V2) {
// V2 and before are older than the requested minimum signing version
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"No signature found in package of version " + minSignatureSchemeVersion
+ " or newer for package " + apkPath);
}
// try v2
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV2");
try {
Certificate[][] signerCerts = ApkSignatureSchemeV2Verifier.verify(apkPath);
Signature[] signerSigs = convertToSignatures(signerCerts);
return new PackageParser.SigningDetails(
signerSigs, SignatureSchemeVersion.SIGNING_BLOCK_V2);
} catch (SignatureNotFoundException e) {
// not signed with v2, try older if allowed
if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V2) {
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"No APK Signature Scheme v2 signature in package " + apkPath, e);
}
} catch (Exception e) {
// APK Signature Scheme v2 signature found but did not verify
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"Failed to collect certificates from " + apkPath
+ " using APK Signature Scheme v2", e);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
// redundant, protective version check
if (minSignatureSchemeVersion > SignatureSchemeVersion.JAR) {
// V1 and is older than the requested minimum signing version
throw new PackageParserException(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(apkPath, true);
}
此方法中会判断签名版本,此次主要分析V1,也就是
verifyV1Signature(apkPath, true);
private static PackageParser.SigningDetails verifyV1Signature(
String apkPath, boolean verifyFull)
throws PackageParserException {
int objectNumber = verifyFull ? NUMBER_OF_CORES : 1;
boolean isPerfLockAcquired = false;
StrictJarFile[] jarFile = new StrictJarFile[objectNumber];
final ArrayMap strictJarFiles = new ArrayMap();
try {
final Certificate[][] lastCerts;
final Signature[] lastSigs;
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "strictJarFileCtor");
if (sPerfBoost == null) {
sPerfBoost = new BoostFramework();
}
if (sPerfBoost != null && !isPerfLockAcquired && verifyFull) {
//Use big enough number here to hold the perflock for entire PackageInstall session
sPerfBoost.perfHint(BoostFramework.VENDOR_HINT_PACKAGE_INSTALL_BOOST,
null, VERIFICATION_TIME_OUT, -1);
Slog.d(TAG, " Perflock acquired for PackageInstall ");
isPerfLockAcquired = true;
}
// we still pass verify = true to ctor to collect certs, even though we're not checking
// the whole jar.
for (int i = 0; i < objectNumber; i++) {
jarFile[i] = new StrictJarFile(
apkPath,
true, // collect certs
verifyFull); // whether to reject APK with stripped v2 signatures (b/27887819)
}
final List toVerify = new ArrayList<>();
// Gather certs from AndroidManifest.xml, which every APK must have, as an optimization
// to not need to verify the whole APK when verifyFUll == false.
final ZipEntry manifestEntry = jarFile[0].findEntry(
PackageParser.ANDROID_MANIFEST_FILENAME);
if (manifestEntry == null) {
throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
"Package " + apkPath + " has no manifest");
}
lastCerts = loadCertificates(jarFile[0], manifestEntry);
if (ArrayUtils.isEmpty(lastCerts)) {
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "Package "
+ apkPath + " has no certificates at entry "
+ PackageParser.ANDROID_MANIFEST_FILENAME);
}
lastSigs = convertToSignatures(lastCerts);
// fully verify all contents, except for AndroidManifest.xml and the META-INF/ files.
if (verifyFull) {
final Iterator i = jarFile[0].iterator();
while (i.hasNext()) {
final ZipEntry entry = i.next();
if (entry.isDirectory()) continue;
final String entryName = entry.getName();
if (entryName.startsWith("META-INF/")) continue;
if (entryName.equals(PackageParser.ANDROID_MANIFEST_FILENAME)) continue;
toVerify.add(entry);
}
class VerificationData {
public Exception exception;
public int exceptionFlag;
public boolean wait;
public int index;
public Object objWaitAll;
public boolean shutDown;
}
VerificationData vData = new VerificationData();
vData.objWaitAll = new Object();
final ThreadPoolExecutor verificationExecutor = new ThreadPoolExecutor(
NUMBER_OF_CORES,
NUMBER_OF_CORES,
1,/*keep alive time*/
TimeUnit.SECONDS,
new LinkedBlockingQueue());
for (ZipEntry entry : toVerify) {
Runnable verifyTask = new Runnable(){
public void run() {
try {
if (vData.exceptionFlag != 0 ) {
Slog.w(TAG, "VerifyV1 exit with exception " + vData.exceptionFlag);
return;
}
String tid = Long.toString(Thread.currentThread().getId());
StrictJarFile tempJarFile;
synchronized (strictJarFiles) {
tempJarFile = strictJarFiles.get(tid);
if (tempJarFile == null) {
if (vData.index >= NUMBER_OF_CORES) {
vData.index = 0;
}
tempJarFile = jarFile[vData.index++];
strictJarFiles.put(tid, tempJarFile);
}
}
final Certificate[][] entryCerts = loadCertificates(tempJarFile, entry);
if (ArrayUtils.isEmpty(entryCerts)) {
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"Package " + apkPath + " has no certificates at entry "
+ entry.getName());
}
// make sure all entries use the same signing certs
final Signature[] entrySigs = convertToSignatures(entryCerts);
if (!Signature.areExactMatch(lastSigs, entrySigs)) {
throw new PackageParserException(
INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
"Package " + apkPath + " has mismatched certificates at entry "
+ entry.getName());
}
} catch (GeneralSecurityException e) {
synchronized (vData.objWaitAll) {
vData.exceptionFlag = INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING;
vData.exception = e;
}
} catch (PackageParserException e) {
synchronized (vData.objWaitAll) {
vData.exceptionFlag = INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION;
vData.exception = e;
}
}
}};
synchronized (vData.objWaitAll) {
if (vData.exceptionFlag == 0) {
verificationExecutor.execute(verifyTask);
}
}
}
vData.wait = true;
verificationExecutor.shutdown();
while (vData.wait) {
try {
if (vData.exceptionFlag != 0 && !vData.shutDown) {
Slog.w(TAG, "verifyV1 Exception " + vData.exceptionFlag);
verificationExecutor.shutdownNow();
vData.shutDown = true;
}
vData.wait = !verificationExecutor.awaitTermination(50,
TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Slog.w(TAG,"VerifyV1 interrupted while awaiting all threads done...");
}
}
if (vData.exceptionFlag != 0)
throw new PackageParserException(vData.exceptionFlag,
"Failed to collect certificates from " + apkPath, vData.exception);
}
return new PackageParser.SigningDetails(lastSigs, SignatureSchemeVersion.JAR);
} 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 {
if (isPerfLockAcquired && sPerfBoost != null) {
sPerfBoost.perfLockRelease();
isPerfLockAcquired = false;
Slog.d(TAG, " Perflock released for PackageInstall ");
}
strictJarFiles.clear();
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
for (int i = 0; i < objectNumber ; i++) {
closeQuietly(jarFile[i]);
}
}
}
此方法比较长,先看
jarFile[i] = new StrictJarFile(
apkPath,
true, // collect certs
verifyFull); // whether to reject APK with stripped v2 signatures (b/27887819)
private StrictJarFile(String name,
FileDescriptor fd,
boolean verify,
boolean signatureSchemeRollbackProtectionsEnforced)
throws IOException, SecurityException {
this.nativeHandle = nativeOpenJarFile(name, fd.getInt$());
this.fd = fd;
try {
// Read the MANIFEST and signature files up front and try to
// parse them. We never want to accept a JAR File with broken signatures`
// or manifests, so it's best to throw as early as possible.
if (verify) {
HashMap metaEntries = getMetaEntries();
this.manifest = new StrictJarManifest(metaEntries.get(JarFile.MANIFEST_NAME), true);
this.verifier =
new StrictJarVerifier(
name,
manifest,
metaEntries,
signatureSchemeRollbackProtectionsEnforced);
Set files = manifest.getEntries().keySet();
for (String file : files) {
if (findEntry(file) == null) {
throw new SecurityException("File " + file + " in manifest does not exist");
}
}
isSigned = verifier.readCertificates() && verifier.isSignedJar();
} else {
isSigned = false;
this.manifest = null;
this.verifier = null;
}
} catch (IOException | SecurityException e) {
nativeClose(this.nativeHandle);
IoUtils.closeQuietly(fd);
closed = true;
throw e;
}
guard.open("close");
}
通过 getMetaEntries() 读取 META-INF/ 文件夹下的所有文件,其中包括签名信息等,然后将所有文件信息传递给StrictJarVerifier,之后开始校验
isSigned = verifier.readCertificates() && verifier.isSignedJar();
查看readCertificates方法
synchronized boolean readCertificates() {
if (metaEntries.isEmpty()) {
return false;
}
Iterator it = metaEntries.keySet().iterator();
while (it.hasNext()) {
String key = it.next();
Log.d("readCertificates","key="+key);
if (key.endsWith(".DSA") || key.endsWith(".RSA") || key.endsWith(".EC")) {
verifyCertificate(key);
it.remove();
}
}
return true;
}
可以看到检验只支持三种加密方法DSA RSA EC,之后查看verifyCertificate方法
private void verifyCertificate(String certFile) {
Log.d("StrictJarVerifier","liyang--verifyCertificate:"+certFile);
// Found Digital Sig, .SF should already have been read
String signatureFile = certFile.substring(0, certFile.lastIndexOf('.')) + ".SF";
byte[] sfBytes = metaEntries.get(signatureFile);
if (sfBytes == null) {
return;
}
byte[] manifestBytes = metaEntries.get(JarFile.MANIFEST_NAME);
// Manifest entry is required for any verifications.
if (manifestBytes == null) {
return;
}
byte[] sBlockBytes = metaEntries.get(certFile);
try {
Certificate[] signerCertChain = verifyBytes(sBlockBytes, sfBytes);
if (signerCertChain != null) {
certificates.put(signatureFile, signerCertChain);
}
} catch (GeneralSecurityException e) {
throw failedVerification(jarName, signatureFile, e);
}
// Verify manifest hash in .sf file
Attributes attributes = new Attributes();
HashMap entries = new HashMap();
try {
StrictJarManifestReader im = new StrictJarManifestReader(sfBytes, attributes);
im.readEntries(entries, null);
} catch (IOException e) {
return;
}
// If requested, check whether a newer APK Signature Scheme signature was stripped.
if (signatureSchemeRollbackProtectionsEnforced) {
String apkSignatureSchemeIdList =
attributes.getValue(SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME);
if (apkSignatureSchemeIdList != null) {
// This field contains a comma-separated list of APK signature scheme IDs which
// were used to sign this APK. If an ID is known to us, it means signatures of that
// scheme were stripped from the APK because otherwise we wouldn't have fallen back
// to verifying the APK using the JAR signature scheme.
boolean v2SignatureGenerated = false;
boolean v3SignatureGenerated = false;
StringTokenizer tokenizer = new StringTokenizer(apkSignatureSchemeIdList, ",");
while (tokenizer.hasMoreTokens()) {
String idText = tokenizer.nextToken().trim();
if (idText.isEmpty()) {
continue;
}
int id;
try {
id = Integer.parseInt(idText);
} catch (Exception ignored) {
continue;
}
if (id == ApkSignatureSchemeV2Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID) {
// This APK was supposed to be signed with APK Signature Scheme v2 but no
// such signature was found.
v2SignatureGenerated = true;
break;
}
if (id == ApkSignatureSchemeV3Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID) {
// This APK was supposed to be signed with APK Signature Scheme v3 but no
// such signature was found.
v3SignatureGenerated = true;
break;
}
}
if (v2SignatureGenerated) {
throw new SecurityException(signatureFile + " indicates " + jarName
+ " is signed using APK Signature Scheme v2, but no such signature was"
+ " found. Signature stripped?");
}
if (v3SignatureGenerated) {
throw new SecurityException(signatureFile + " indicates " + jarName
+ " is signed using APK Signature Scheme v3, but no such signature was"
+ " found. Signature stripped?");
}
}
}
// Do we actually have any signatures to look at?
if (attributes.get(Attributes.Name.SIGNATURE_VERSION) == null) {
return;
}
boolean createdBySigntool = false;
String createdBy = attributes.getValue("Created-By");
if (createdBy != null) {
createdBySigntool = createdBy.indexOf("signtool") != -1;
}
// Use .SF to verify the mainAttributes of the manifest
// If there is no -Digest-Manifest-Main-Attributes entry in .SF
// file, such as those created before java 1.5, then we ignore
// such verification.
Log.d("StrictJarVerifier","liyang--verifyCertificate:mainAttributesEnd="+mainAttributesEnd+", createdBySigntool="+createdBySigntool);
if (mainAttributesEnd > 0 && !createdBySigntool) {
String digestAttribute = "-Digest-Manifest-Main-Attributes";
if (!verify(attributes, digestAttribute, manifestBytes, 0, mainAttributesEnd, false, true)) {
throw failedVerification(jarName, signatureFile);
}
}
// Use .SF to verify the whole manifest.
String digestAttribute = createdBySigntool ? "-Digest" : "-Digest-Manifest";
if (!verify(attributes, digestAttribute, manifestBytes, 0, manifestBytes.length, false, false)) {
Iterator> it = entries.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = it.next();
StrictJarManifest.Chunk chunk = manifest.getChunk(entry.getKey());
if (chunk == null) {
return;
}
if (!verify(entry.getValue(), "-Digest", manifestBytes,
chunk.start, chunk.end, createdBySigntool, false)) {
throw invalidDigest(signatureFile, entry.getKey(), jarName);
}
}
}
metaEntries.put(signatureFile, null);
signatures.put(signatureFile, entries);
}
此方法是最主要的.开始读取摘要信息并进行解密比对,代码很好理解,重点关注verify方法,使用到了MessageDigest验证摘要信息