Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 260e9105 authored by Kenny Root's avatar Kenny Root Committed by Android (Google) Code Review
Browse files

Merge "Manifest digest stored during package scanning"

parents 03f9c572 bcc954d7
Loading
Loading
Loading
Loading
+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;
+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
+26 −7
Original line number Diff line number Diff line
@@ -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;
+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);
    }
}