diff options
author | Kenny Root <kroot@google.com> | 2011-08-10 15:39:41 -0700 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2011-08-10 15:39:41 -0700 |
commit | 260e9105d7fc7b11c26a6843658df89b98d65971 (patch) | |
tree | 9956b0f0cdd1ff3f870a388d0d7d46c26f267e61 | |
parent | 03f9c572cd06a5c943ef6e4ea5d495456b98dfe6 (diff) | |
parent | bcc954d772e8cd5ef640060cbc0be50e7e4778f2 (diff) | |
download | frameworks_base-260e9105d7fc7b11c26a6843658df89b98d65971.zip frameworks_base-260e9105d7fc7b11c26a6843658df89b98d65971.tar.gz frameworks_base-260e9105d7fc7b11c26a6843658df89b98d65971.tar.bz2 |
Merge "Manifest digest stored during package scanning"
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); + } +} |