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

Commit cdd685c0 authored by Svetoslav Ganov's avatar Svetoslav Ganov
Browse files

Use all certs for computing package signing sha256

In several places we compute the sha256 of the app's signing certificate
(instant cookie storage, backup account permission grants, static shared
lib matching). It is possible that an app is singed with multiple certs
which unfortunately can appear in a random order. We were using only the
first certificate to compute the hash which may be problematic for apps
signed with multiple certs which are later reordered. If an app update's
certs are reordered for cookie storage the app would not be able to
access the cookie, for account grants the app would not get the grant,
and for shared libs the app would fail to install due to a missing lib.

Test: all cookie CTS tests pass
      all static shared lib CTS tests pass
      added test that cookie data not lost on sha256 computation change
      added test that lib install works when specifying
      multiple certs

bug:64270295

Change-Id: Ib6b55f25da735ff5c2762faf6e9b5888e749041d
parent 0862898a
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