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

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

Merge "Add direct API to get ManifestDigest" into jb-mr2-dev

parents e0b39fc1 6c918cec
Loading
Loading
Loading
Loading
+33 −20
Original line number Diff line number Diff line
@@ -18,10 +18,17 @@ package android.content.pm;

import android.os.Parcel;
import android.os.Parcelable;
import android.util.Base64;

import android.util.Slog;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.jar.Attributes;

import libcore.io.IoUtils;

/**
 * Represents the manifest digest for a package. This is suitable for comparison
@@ -30,17 +37,17 @@ import java.util.jar.Attributes;
 * @hide
 */
public class ManifestDigest implements Parcelable {
    private static final String TAG = "ManifestDigest";

    /** 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=";

    /** Digest algorithm to use. */
    private static final String DIGEST_ALGORITHM = "SHA-256";

    ManifestDigest(byte[] digest) {
        mDigest = digest;
    }
@@ -49,26 +56,32 @@ public class ManifestDigest implements Parcelable {
        mDigest = source.createByteArray();
    }

    static ManifestDigest fromAttributes(Attributes attributes) {
        if (attributes == null) {
    static ManifestDigest fromInputStream(InputStream fileIs) {
        if (fileIs == 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;
            }
        final MessageDigest md;
        try {
            md = MessageDigest.getInstance(DIGEST_ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(DIGEST_ALGORITHM + " must be available", e);
        }

        if (encodedDigest == null) {
        final DigestInputStream dis = new DigestInputStream(new BufferedInputStream(fileIs), md);
        try {
            byte[] readBuffer = new byte[8192];
            while (dis.read(readBuffer, 0, readBuffer.length) != -1) {
                // not using
            }
        } catch (IOException e) {
            Slog.w(TAG, "Could not read manifest");
            return null;
        } finally {
            IoUtils.closeQuietly(dis);
        }

        final byte[] digest = Base64.decode(encodedDigest, Base64.DEFAULT);
        final byte[] digest = md.digest();
        return new ManifestDigest(digest);
    }

+25 −6
Original line number Diff line number Diff line
@@ -24,7 +24,6 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.PatternMatcher;
@@ -54,10 +53,9 @@ import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;

import com.android.internal.util.XmlUtils;

@@ -567,6 +565,28 @@ public class PackageParser {
        return pkg;
    }

    /**
     * Gathers the {@link ManifestDigest} for {@code pkg} if it exists in the
     * APK. If it successfully scanned the package and found the
     * {@code AndroidManifest.xml}, {@code true} is returned.
     */
    public boolean collectManifestDigest(Package pkg) {
        try {
            final JarFile jarFile = new JarFile(mArchiveSourcePath);
            try {
                final ZipEntry je = jarFile.getEntry(ANDROID_MANIFEST_FILENAME);
                if (je != null) {
                    pkg.manifestDigest = ManifestDigest.fromInputStream(jarFile.getInputStream(je));
                }
            } finally {
                jarFile.close();
            }
            return true;
        } catch (IOException e) {
            return false;
        }
    }

    public boolean collectCertificates(Package pkg, int flags) {
        pkg.mSignatures = null;

@@ -618,7 +638,6 @@ 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;
@@ -629,8 +648,8 @@ public class PackageParser {
                        continue;

                    if (ANDROID_MANIFEST_FILENAME.equals(name)) {
                        final Attributes attributes = manifest.getAttributes(name);
                        pkg.manifestDigest = ManifestDigest.fromAttributes(attributes);
                        pkg.manifestDigest =
                                ManifestDigest.fromInputStream(jarFile.getInputStream(je));
                    }

                    final Certificate[] localCerts = loadCertificates(jarFile, je, readBuffer);
+27 −40
Original line number Diff line number Diff line
@@ -18,64 +18,51 @@ package android.content.pm;

import android.os.Parcel;
import android.test.AndroidTestCase;
import android.util.Base64;

import java.util.jar.Attributes;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;

public class ManifestDigestTest extends AndroidTestCase {
    private static final byte[] DIGEST_1 = {
    private static final byte[] MESSAGE_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() {
    public void testManifestDigest_FromInputStream_Null() {
        assertNull("Attributes were null, so ManifestDigest.fromAttributes should return null",
                ManifestDigest.fromAttributes(null));
                ManifestDigest.fromInputStream(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_FromInputStream_ThrowsIoException() {
        InputStream is = new InputStream() {
            @Override
            public int read() throws IOException {
                throw new IOException();
            }
        };

    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);
        assertNull("InputStream threw exception, so ManifestDigest should be null",
                ManifestDigest.fromInputStream(is));
    }

        assertNotNull("A valid ManifestDigest should be returned", fromAttributes);
    public void testManifestDigest_Equals() throws Exception {
        InputStream is = new ByteArrayInputStream(MESSAGE_1);

        ManifestDigest created = new ManifestDigest(DIGEST_1);
        ManifestDigest expected =
                new ManifestDigest(MessageDigest.getInstance("SHA-256").digest(MESSAGE_1));

        assertEquals("SHA-1 should be preferred over MD5: " + created.toString() + " vs. "
                + fromAttributes.toString(), created, fromAttributes);
        ManifestDigest actual = ManifestDigest.fromInputStream(is);
        assertEquals(expected, actual);

        assertEquals("Hash codes should be the same: " + created.toString() + " vs. "
                + fromAttributes.toString(), created.hashCode(), fromAttributes
                .hashCode());
        ManifestDigest unexpected = new ManifestDigest(new byte[0]);
        assertFalse(unexpected.equals(actual));
    }

    public void testManifestDigest_Parcel() {
        Attributes a = new Attributes();
        a.put(SHA1_DIGEST, DIGEST_1_STR);
    public void testManifestDigest_Parcel() throws Exception {
        InputStream is = new ByteArrayInputStream(MESSAGE_1);

        ManifestDigest digest = ManifestDigest.fromAttributes(a);
        ManifestDigest digest = ManifestDigest.fromInputStream(is);

        Parcel p = Parcel.obtain();
        digest.writeToParcel(p, 0);