Loading core/java/android/content/pm/ManifestDigest.aidl 0 → 100755 +19 −0 Original line number Diff line number Diff line /* * 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; core/java/android/content/pm/ManifestDigest.java 0 → 100644 +114 −0 Original line number Diff line number Diff line 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 core/java/android/content/pm/PackageParser.java +26 −7 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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 Loading @@ -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 " Loading Loading @@ -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 " Loading Loading @@ -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; Loading core/tests/coretests/src/android/content/pm/ManifestDigestTest.java 0 → 100644 +74 −0 Original line number Diff line number Diff line 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); } } Loading
core/java/android/content/pm/ManifestDigest.aidl 0 → 100755 +19 −0 Original line number Diff line number Diff line /* * 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;
core/java/android/content/pm/ManifestDigest.java 0 → 100644 +114 −0 Original line number Diff line number Diff line 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
core/java/android/content/pm/PackageParser.java +26 −7 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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 Loading @@ -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 " Loading Loading @@ -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 " Loading Loading @@ -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; Loading
core/tests/coretests/src/android/content/pm/ManifestDigestTest.java 0 → 100644 +74 −0 Original line number Diff line number Diff line 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); } }