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

Commit ee580764 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Use all certs for computing package signing sha256" into oc-mr1-dev

parents 29091ec5 cdd685c0
Loading
Loading
Loading
Loading
+11 −2
Original line number Diff line number Diff line
@@ -188,7 +188,16 @@ public class PackageInfo implements Parcelable {

    /**
     * Array of all signatures read from the package file. This is only filled
     * in if the flag {@link PackageManager#GET_SIGNATURES} was set.
     * in if the flag {@link PackageManager#GET_SIGNATURES} was set. A package
     * must be singed with at least one certificate which is at position zero.
     * The package can be signed with additional certificates which appear as
     * subsequent entries.
     *
     * <strong>Note:</strong> Signature ordering is not guaranteed to be
     * stable which means that a package signed with certificates A and B is
     * equivalent to being signed with certificates B and A. This means that
     * in case multiple signatures are reported you cannot assume the one at
     * the first position to be the same across updates.
     */
    public Signature[] signatures;
    
+74 −12
Original line number Diff line number Diff line
@@ -97,6 +97,7 @@ import com.android.internal.util.XmlUtils;

import libcore.io.IoUtils;

import libcore.util.EmptyArray;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

@@ -2824,14 +2825,14 @@ public class PackageParser {
                com.android.internal.R.styleable.AndroidManifestUsesLibrary_name);
        final int version = sa.getInt(
                com.android.internal.R.styleable.AndroidManifestUsesStaticLibrary_version, -1);
        String certSha256 = sa.getNonResourceString(com.android.internal.R.styleable
        String certSha256Digest = sa.getNonResourceString(com.android.internal.R.styleable
                .AndroidManifestUsesStaticLibrary_certDigest);
        sa.recycle();

        // Since an APK providing a static shared lib can only provide the lib - fail if malformed
        if (lname == null || version < 0 || certSha256 == null) {
        if (lname == null || version < 0 || certSha256Digest == null) {
            outError[0] = "Bad uses-static-library declaration name: " + lname + " version: "
                    + version + " certDigest" + certSha256;
                    + version + " certDigest" + certSha256Digest;
            mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
            XmlUtils.skipCurrentTag(parser);
            return false;
@@ -2848,16 +2849,73 @@ public class PackageParser {
        lname = lname.intern();
        // We allow ":" delimiters in the SHA declaration as this is the format
        // emitted by the certtool making it easy for developers to copy/paste.
        certSha256 = certSha256.replace(":", "").toLowerCase();
        certSha256Digest = certSha256Digest.replace(":", "").toLowerCase();

        // Fot apps targeting O-MR1 we require explicit enumeration of all certs.
        String[] additionalCertSha256Digests = EmptyArray.STRING;
        if (pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.O) {
            additionalCertSha256Digests = parseAdditionalCertificates(res, parser, outError);
            if (additionalCertSha256Digests == null) {
                return false;
            }
        } else {
            XmlUtils.skipCurrentTag(parser);
        }

        final String[] certSha256Digests = new String[additionalCertSha256Digests.length + 1];
        certSha256Digests[0] = certSha256Digest;
        System.arraycopy(additionalCertSha256Digests, 0, certSha256Digests,
                1, additionalCertSha256Digests.length);

        pkg.usesStaticLibraries = ArrayUtils.add(pkg.usesStaticLibraries, lname);
        pkg.usesStaticLibrariesVersions = ArrayUtils.appendInt(
                pkg.usesStaticLibrariesVersions, version, true);
        pkg.usesStaticLibrariesCertDigests = ArrayUtils.appendElement(String.class,
                pkg.usesStaticLibrariesCertDigests, certSha256, true);
        pkg.usesStaticLibrariesCertDigests = ArrayUtils.appendElement(String[].class,
                pkg.usesStaticLibrariesCertDigests, certSha256Digests, true);

        return true;
    }

    private String[] parseAdditionalCertificates(Resources resources, XmlResourceParser parser,
            String[] outError) throws XmlPullParserException, IOException {
        String[] certSha256Digests = EmptyArray.STRING;

        int outerDepth = parser.getDepth();
        int type;
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                continue;
            }

            final String nodeName = parser.getName();
            if (nodeName.equals("additional-certificate")) {
                final TypedArray sa = resources.obtainAttributes(parser, com.android.internal.
                        R.styleable.AndroidManifestAdditionalCertificate);
                String certSha256Digest = sa.getNonResourceString(com.android.internal.
                        R.styleable.AndroidManifestAdditionalCertificate_certDigest);
                sa.recycle();

                if (TextUtils.isEmpty(certSha256Digest)) {
                    outError[0] = "Bad additional-certificate declaration with empty"
                            + " certDigest:" + certSha256Digest;
                    mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                    XmlUtils.skipCurrentTag(parser);
                    sa.recycle();
                    return null;
                }

        return true;
                // We allow ":" delimiters in the SHA declaration as this is the format
                // emitted by the certtool making it easy for developers to copy/paste.
                certSha256Digest = certSha256Digest.replace(":", "").toLowerCase();
                certSha256Digests = ArrayUtils.appendElement(String.class,
                        certSha256Digests, certSha256Digest);
            } else {
                XmlUtils.skipCurrentTag(parser);
            }
        }

        return certSha256Digests;
    }

    private boolean parseUsesPermission(Package pkg, Resources res, XmlResourceParser parser)
@@ -5820,7 +5878,7 @@ public class PackageParser {
        public ArrayList<String> usesLibraries = null;
        public ArrayList<String> usesStaticLibraries = null;
        public int[] usesStaticLibrariesVersions = null;
        public String[] usesStaticLibrariesCertDigests = null;
        public String[][] usesStaticLibrariesCertDigests = null;
        public ArrayList<String> usesOptionalLibraries = null;
        public String[] usesLibraryFiles = null;

@@ -6318,8 +6376,10 @@ public class PackageParser {
                internStringArrayList(usesStaticLibraries);
                usesStaticLibrariesVersions = new int[libCount];
                dest.readIntArray(usesStaticLibrariesVersions);
                usesStaticLibrariesCertDigests = new String[libCount];
                dest.readStringArray(usesStaticLibrariesCertDigests);
                usesStaticLibrariesCertDigests = new String[libCount][];
                for (int i = 0; i < libCount; i++) {
                    usesStaticLibrariesCertDigests[i] = dest.createStringArray();
                }
            }

            preferredActivityFilters = new ArrayList<>();
@@ -6465,7 +6525,9 @@ public class PackageParser {
                dest.writeInt(usesStaticLibraries.size());
                dest.writeStringList(usesStaticLibraries);
                dest.writeIntArray(usesStaticLibrariesVersions);
                dest.writeStringArray(usesStaticLibrariesCertDigests);
                for (String[] usesStaticLibrariesCertDigest : usesStaticLibrariesCertDigests) {
                    dest.writeStringArray(usesStaticLibrariesCertDigest);
                }
            }

            dest.writeParcelableList(preferredActivityFilters, flags);
+58 −22
Original line number Diff line number Diff line
@@ -18,12 +18,13 @@ package android.util;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

/**
 * Helper functions applicable to packages.
@@ -36,32 +37,67 @@ public final class PackageUtils {
    }

    /**
     * Computes the SHA256 digest of the signing cert for a package.
     * @param packageManager The package manager.
     * @param packageName The package for which to generate the digest.
     * @param userId The user for which to generate the digest.
     * @return The digest or null if the package does not exist for this user.
     * Computes the SHA256 digests of a list of signatures. Items in the
     * resulting array of hashes correspond to the signatures in the
     * input array.
     * @param signatures The signatures.
     * @return The digest array.
     */
    public static @Nullable String computePackageCertSha256Digest(
            @NonNull PackageManager packageManager,
            @NonNull String packageName, int userId) {
        final PackageInfo packageInfo;
        try {
            packageInfo = packageManager.getPackageInfoAsUser(packageName,
                    PackageManager.GET_SIGNATURES, userId);
        } catch (PackageManager.NameNotFoundException e) {
            return null;
    public static @NonNull String[] computeSignaturesSha256Digests(
            @NonNull Signature[] signatures) {
        final int signatureCount = signatures.length;
        final String[] digests = new String[signatureCount];
        for (int i = 0; i < signatureCount; i++) {
            digests[i] = computeSha256Digest(signatures[i].toByteArray());
        }
        return computeCertSha256Digest(packageInfo.signatures[0]);
        return digests;
    }
    /**
     * Computes a SHA256 digest of the signatures' SHA256 digests. First,
     * individual hashes for each signature is derived in a hexademical
     * form, then these strings are sorted based the natural ordering, and
     * finally a hash is derived from these strings' bytes.
     * @param signatures The signatures.
     * @return The digest.
     */
    public static @NonNull String computeSignaturesSha256Digest(
            @NonNull Signature[] signatures) {
        // Shortcut for optimization - most apps singed by a single cert
        if (signatures.length == 1) {
            return computeSha256Digest(signatures[0].toByteArray());
        }

        // Make sure these are sorted to handle reversed certificates
        final String[] sha256Digests = computeSignaturesSha256Digests(signatures);
        return computeSignaturesSha256Digest(sha256Digests);
    }

    /**
     * Computes the SHA256 digest of a cert.
     * @param signature The signature.
     * @return The digest or null if an error occurs.
     * Computes a SHA256 digest in of the signatures SHA256 digests. First,
     * the strings are sorted based the natural ordering, and then a hash is
     * derived from these strings' bytes.
     * @param sha256Digests Signature SHA256 hashes in hexademical form.
     * @return The digest.
     */
    public static @Nullable String computeCertSha256Digest(@NonNull Signature signature) {
        return computeSha256Digest(signature.toByteArray());
    public static @NonNull String computeSignaturesSha256Digest(
            @NonNull String[] sha256Digests) {
        // Shortcut for optimization - most apps singed by a single cert
        if (sha256Digests.length == 1) {
            return sha256Digests[0];
        }

        // Make sure these are sorted to handle reversed certificates
        Arrays.sort(sha256Digests);

        final ByteArrayOutputStream bytes = new ByteArrayOutputStream();
        for (String sha256Digest : sha256Digests) {
            try {
                bytes.write(sha256Digest.getBytes());
            } catch (IOException e) {
                /* ignore - can't happen */
            }
        }
        return computeSha256Digest(bytes.toByteArray());
    }

    /**
+16 −0
Original line number Diff line number Diff line
@@ -1779,6 +1779,11 @@
         the library at build time while it offers apps to share code defined in such
         libraries. Hence, static libraries are strictly required.

         <p>On devices running O MR1 or higher, if the library is singed with multiple
         signing certificates you must to specify the SHA-256 hashes of the additional
         certificates via adding
         {@link #AndroidManifestAdditionalCertificate additional-certificate} tags.

         <p>This appears as a child tag of the
         {@link #AndroidManifestApplication application} tag. -->
    <declare-styleable name="AndroidManifestUsesStaticLibrary" parent="AndroidManifestApplication">
@@ -1790,6 +1795,17 @@
        <attr name="certDigest" format="string" />
    </declare-styleable>

    <!-- The <code>additional-certificate</code> specifies the SHA-256 digest of a static
         shared library's additional signing certificate. You need to use this tag if the
         library is singed with more than one certificate.

         <p>This appears as a child tag of the
         {@link #AndroidManifestUsesStaticLibrary uses-static-library} tag. -->
    <declare-styleable name="AndroidManifestAdditionalCertificate" parent="AndroidManifestUsesStaticLibrary">
        <!-- The SHA-256 digest of the library signing certificate. -->
        <attr name="certDigest" />
    </declare-styleable>

    <!-- The <code>supports-screens</code> specifies the screen dimensions an
         application supports.  By default a modern application supports all
         screen sizes and must explicitly disable certain screen sizes here;
+25 −5
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.annotation.IntRange;
import android.annotation.NonNull;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.UserHandle;
@@ -30,6 +31,7 @@ import android.text.TextUtils;
import android.util.Log;
import android.util.PackageUtils;
import android.util.Pair;
import android.util.Slog;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageMonitor;
@@ -126,9 +128,18 @@ public final class AccountManagerBackupHelper {
            } catch (PackageManager.NameNotFoundException e) {
                return false;
            }
            String currentCertDigest = PackageUtils.computeCertSha256Digest(
                    packageInfo.signatures[0]);
            if (!certDigest.equals(currentCertDigest)) {

            // Before we used only the first signature to compute the SHA 256 but some
            // apps could be singed by multiple certs and the cert order is undefined.
            // We prefer the modern computation procedure where all certs are taken
            // into account but also allow the value from the old computation to allow
            // restoring backed up grants on an older platform version.
            final String[] signaturesSha256Digests = PackageUtils.computeSignaturesSha256Digests(
                    packageInfo.signatures);
            final String signaturesSha256Digest = PackageUtils.computeSignaturesSha256Digest(
                    signaturesSha256Digests);
            if (!certDigest.equals(signaturesSha256Digest) && (packageInfo.signatures.length <= 1
                    || !certDigest.equals(signaturesSha256Digests[0]))) {
                return false;
            }
            final int uid = packageInfo.applicationInfo.uid;
@@ -169,8 +180,17 @@ public final class AccountManagerBackupHelper {
                        }

                        for (String packageName : packageNames) {
                            String digest = PackageUtils.computePackageCertSha256Digest(
                                    packageManager, packageName, userId);
                            final PackageInfo packageInfo;
                            try {
                                packageInfo = packageManager.getPackageInfoAsUser(packageName,
                                        PackageManager.GET_SIGNATURES, userId);
                            } catch (PackageManager.NameNotFoundException e) {
                                Slog.i(TAG, "Skipping backup of account access grant for"
                                        + " non-existing package: " + packageName);
                                continue;
                            }
                            final String digest = PackageUtils.computeSignaturesSha256Digest(
                                    packageInfo.signatures);
                            if (digest != null) {
                                serializer.startTag(null, TAG_PERMISSION);
                                serializer.attribute(null, ATTR_ACCOUNT_SHA_256,
Loading