summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKenny Root <kroot@google.com>2011-08-10 15:39:41 -0700
committerAndroid (Google) Code Review <android-gerrit@google.com>2011-08-10 15:39:41 -0700
commit260e9105d7fc7b11c26a6843658df89b98d65971 (patch)
tree9956b0f0cdd1ff3f870a388d0d7d46c26f267e61
parent03f9c572cd06a5c943ef6e4ea5d495456b98dfe6 (diff)
parentbcc954d772e8cd5ef640060cbc0be50e7e4778f2 (diff)
downloadframeworks_base-260e9105d7fc7b11c26a6843658df89b98d65971.zip
frameworks_base-260e9105d7fc7b11c26a6843658df89b98d65971.tar.gz
frameworks_base-260e9105d7fc7b11c26a6843658df89b98d65971.tar.bz2
Merge "Manifest digest stored during package scanning"
-rwxr-xr-xcore/java/android/content/pm/ManifestDigest.aidl19
-rw-r--r--core/java/android/content/pm/ManifestDigest.java114
-rw-r--r--core/java/android/content/pm/PackageParser.java33
-rw-r--r--core/tests/coretests/src/android/content/pm/ManifestDigestTest.java74
4 files changed, 233 insertions, 7 deletions
diff --git a/core/java/android/content/pm/ManifestDigest.aidl b/core/java/android/content/pm/ManifestDigest.aidl
new file mode 100755
index 0000000..ebabab0
--- /dev/null
+++ b/core/java/android/content/pm/ManifestDigest.aidl
@@ -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;
diff --git a/core/java/android/content/pm/ManifestDigest.java b/core/java/android/content/pm/ManifestDigest.java
new file mode 100644
index 0000000..f5e72e0
--- /dev/null
+++ b/core/java/android/content/pm/ManifestDigest.java
@@ -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];
+ }
+ };
+
+} \ No newline at end of file
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 5f4625b..c61e32f 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -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;
diff --git a/core/tests/coretests/src/android/content/pm/ManifestDigestTest.java b/core/tests/coretests/src/android/content/pm/ManifestDigestTest.java
new file mode 100644
index 0000000..8922f27
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/ManifestDigestTest.java
@@ -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);
+ }
+}