Manifest digest stored during package scanning
This allows things that scanned an APK to pass a hash of the manifest to others that would then be able to compare it. Change-Id: I465f5edf3acab2a952c3d321c8df2247ffe012ea
This commit is contained in:
19
core/java/android/content/pm/ManifestDigest.aidl
Executable file
19
core/java/android/content/pm/ManifestDigest.aidl
Executable file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2011, The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.content.pm;
|
||||
|
||||
parcelable ManifestDigest;
|
||||
114
core/java/android/content/pm/ManifestDigest.java
Normal file
114
core/java/android/content/pm/ManifestDigest.java
Normal file
@@ -0,0 +1,114 @@
|
||||
package android.content.pm;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Base64;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.jar.Attributes;
|
||||
|
||||
/**
|
||||
* Represents the manifest digest for a package. This is suitable for comparison
|
||||
* of two packages to know whether the manifests are identical.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class ManifestDigest implements Parcelable {
|
||||
/** The digest of the manifest in our preferred order. */
|
||||
private final byte[] mDigest;
|
||||
|
||||
/** Digest field names to look for in preferred order. */
|
||||
private static final String[] DIGEST_TYPES = {
|
||||
"SHA1-Digest", "SHA-Digest", "MD5-Digest",
|
||||
};
|
||||
|
||||
/** What we print out first when toString() is called. */
|
||||
private static final String TO_STRING_PREFIX = "ManifestDigest {mDigest=";
|
||||
|
||||
ManifestDigest(byte[] digest) {
|
||||
mDigest = digest;
|
||||
}
|
||||
|
||||
private ManifestDigest(Parcel source) {
|
||||
mDigest = source.createByteArray();
|
||||
}
|
||||
|
||||
static ManifestDigest fromAttributes(Attributes attributes) {
|
||||
if (attributes == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String encodedDigest = null;
|
||||
|
||||
for (int i = 0; i < DIGEST_TYPES.length; i++) {
|
||||
final String value = attributes.getValue(DIGEST_TYPES[i]);
|
||||
if (value != null) {
|
||||
encodedDigest = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (encodedDigest == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final byte[] digest = Base64.decode(encodedDigest, Base64.DEFAULT);
|
||||
return new ManifestDigest(digest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof ManifestDigest)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final ManifestDigest other = (ManifestDigest) o;
|
||||
|
||||
return this == other || Arrays.equals(mDigest, other.mDigest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(mDigest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder(TO_STRING_PREFIX.length()
|
||||
+ (mDigest.length * 3) + 1);
|
||||
|
||||
sb.append(TO_STRING_PREFIX);
|
||||
|
||||
final int N = mDigest.length;
|
||||
for (int i = 0; i < N; i++) {
|
||||
final byte b = mDigest[i];
|
||||
IntegralToString.appendByteAsHex(sb, b, false);
|
||||
sb.append(',');
|
||||
}
|
||||
sb.append('}');
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeByteArray(mDigest);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<ManifestDigest> CREATOR
|
||||
= new Parcelable.Creator<ManifestDigest>() {
|
||||
public ManifestDigest createFromParcel(Parcel source) {
|
||||
return new ManifestDigest(source);
|
||||
}
|
||||
|
||||
public ManifestDigest[] newArray(int size) {
|
||||
return new ManifestDigest[size];
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -45,6 +45,7 @@ import java.security.cert.CertificateEncodingException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
@@ -59,6 +60,9 @@ public class PackageParser {
|
||||
private static final boolean DEBUG_PARSER = false;
|
||||
private static final boolean DEBUG_BACKUP = false;
|
||||
|
||||
/** File name in an APK for the Android manifest. */
|
||||
private static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml";
|
||||
|
||||
/** @hide */
|
||||
public static class NewPermissionInfo {
|
||||
public final String name;
|
||||
@@ -402,7 +406,7 @@ public class PackageParser {
|
||||
res = new Resources(assmgr, metrics, null);
|
||||
assmgr.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
Build.VERSION.RESOURCES_SDK_INT);
|
||||
parser = assmgr.openXmlResourceParser(cookie, "AndroidManifest.xml");
|
||||
parser = assmgr.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
|
||||
assetError = false;
|
||||
} else {
|
||||
Slog.w(TAG, "Failed adding asset path:"+mArchiveSourcePath);
|
||||
@@ -484,7 +488,7 @@ public class PackageParser {
|
||||
// can trust it... we'll just use the AndroidManifest.xml
|
||||
// to retrieve its signatures, not validating all of the
|
||||
// files.
|
||||
JarEntry jarEntry = jarFile.getJarEntry("AndroidManifest.xml");
|
||||
JarEntry jarEntry = jarFile.getJarEntry(ANDROID_MANIFEST_FILENAME);
|
||||
certs = loadCertificates(jarFile, jarEntry, readBuffer);
|
||||
if (certs == null) {
|
||||
Slog.e(TAG, "Package " + pkg.packageName
|
||||
@@ -506,21 +510,30 @@ public class PackageParser {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
Enumeration<JarEntry> entries = jarFile.entries();
|
||||
final Manifest manifest = jarFile.getManifest();
|
||||
while (entries.hasMoreElements()) {
|
||||
final JarEntry je = entries.nextElement();
|
||||
if (je.isDirectory()) continue;
|
||||
if (je.getName().startsWith("META-INF/")) continue;
|
||||
|
||||
final Certificate[] localCerts = loadCertificates(jarFile, je,
|
||||
readBuffer);
|
||||
final String name = je.getName();
|
||||
|
||||
if (name.startsWith("META-INF/"))
|
||||
continue;
|
||||
|
||||
if (ANDROID_MANIFEST_FILENAME.equals(name)) {
|
||||
final Attributes attributes = manifest.getAttributes(name);
|
||||
pkg.manifestDigest = ManifestDigest.fromAttributes(attributes);
|
||||
}
|
||||
|
||||
final Certificate[] localCerts = loadCertificates(jarFile, je, readBuffer);
|
||||
if (DEBUG_JAR) {
|
||||
Slog.i(TAG, "File " + mArchiveSourcePath + " entry " + je.getName()
|
||||
+ ": certs=" + certs + " ("
|
||||
+ (certs != null ? certs.length : 0) + ")");
|
||||
}
|
||||
|
||||
if (localCerts == null) {
|
||||
Slog.e(TAG, "Package " + pkg.packageName
|
||||
+ " has no certificates at entry "
|
||||
@@ -609,7 +622,7 @@ public class PackageParser {
|
||||
return null;
|
||||
}
|
||||
|
||||
parser = assmgr.openXmlResourceParser(cookie, "AndroidManifest.xml");
|
||||
parser = assmgr.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
|
||||
} catch (Exception e) {
|
||||
if (assmgr != null) assmgr.close();
|
||||
Slog.w(TAG, "Unable to read AndroidManifest.xml of "
|
||||
@@ -2884,6 +2897,12 @@ public class PackageParser {
|
||||
|
||||
public int installLocation;
|
||||
|
||||
/**
|
||||
* Digest suitable for comparing whether this package's manifest is the
|
||||
* same as another.
|
||||
*/
|
||||
public ManifestDigest manifestDigest;
|
||||
|
||||
public Package(String _name) {
|
||||
packageName = _name;
|
||||
applicationInfo.packageName = _name;
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package android.content.pm;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.test.AndroidTestCase;
|
||||
import android.util.Base64;
|
||||
|
||||
import java.util.jar.Attributes;
|
||||
|
||||
public class ManifestDigestTest extends AndroidTestCase {
|
||||
private static final byte[] DIGEST_1 = {
|
||||
(byte) 0x00, (byte) 0xAA, (byte) 0x55, (byte) 0xFF
|
||||
};
|
||||
|
||||
private static final String DIGEST_1_STR = Base64.encodeToString(DIGEST_1, Base64.DEFAULT);
|
||||
|
||||
private static final byte[] DIGEST_2 = {
|
||||
(byte) 0x0A, (byte) 0xA5, (byte) 0xF0, (byte) 0x5A
|
||||
};
|
||||
|
||||
private static final String DIGEST_2_STR = Base64.encodeToString(DIGEST_2, Base64.DEFAULT);
|
||||
|
||||
private static final Attributes.Name SHA1_DIGEST = new Attributes.Name("SHA1-Digest");
|
||||
|
||||
private static final Attributes.Name MD5_DIGEST = new Attributes.Name("MD5-Digest");
|
||||
|
||||
public void testManifestDigest_FromAttributes_Null() {
|
||||
assertNull("Attributes were null, so ManifestDigest.fromAttributes should return null",
|
||||
ManifestDigest.fromAttributes(null));
|
||||
}
|
||||
|
||||
public void testManifestDigest_FromAttributes_NoAttributes() {
|
||||
Attributes a = new Attributes();
|
||||
|
||||
assertNull("There were no attributes to extract, so ManifestDigest should be null",
|
||||
ManifestDigest.fromAttributes(a));
|
||||
}
|
||||
|
||||
public void testManifestDigest_FromAttributes_SHA1PreferredOverMD5() {
|
||||
Attributes a = new Attributes();
|
||||
a.put(SHA1_DIGEST, DIGEST_1_STR);
|
||||
|
||||
a.put(MD5_DIGEST, DIGEST_2_STR);
|
||||
|
||||
ManifestDigest fromAttributes = ManifestDigest.fromAttributes(a);
|
||||
|
||||
assertNotNull("A valid ManifestDigest should be returned", fromAttributes);
|
||||
|
||||
ManifestDigest created = new ManifestDigest(DIGEST_1);
|
||||
|
||||
assertEquals("SHA-1 should be preferred over MD5: " + created.toString() + " vs. "
|
||||
+ fromAttributes.toString(), created, fromAttributes);
|
||||
|
||||
assertEquals("Hash codes should be the same: " + created.toString() + " vs. "
|
||||
+ fromAttributes.toString(), created.hashCode(), fromAttributes
|
||||
.hashCode());
|
||||
}
|
||||
|
||||
public void testManifestDigest_Parcel() {
|
||||
Attributes a = new Attributes();
|
||||
a.put(SHA1_DIGEST, DIGEST_1_STR);
|
||||
|
||||
ManifestDigest digest = ManifestDigest.fromAttributes(a);
|
||||
|
||||
Parcel p = Parcel.obtain();
|
||||
digest.writeToParcel(p, 0);
|
||||
p.setDataPosition(0);
|
||||
|
||||
ManifestDigest fromParcel = ManifestDigest.CREATOR.createFromParcel(p);
|
||||
|
||||
assertEquals("ManifestDigest going through parceling should be the same as before: "
|
||||
+ digest.toString() + " and " + fromParcel.toString(), digest,
|
||||
fromParcel);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user