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

Commit 021a48d5 authored by Makoto Onuki's avatar Makoto Onuki Committed by Android (Google) Code Review
Browse files

Merge "Extract signature related utilities" into nyc-dev

parents fd48a321 590096a0
Loading
Loading
Loading
Loading
+1 −72
Original line number Diff line number Diff line
@@ -7617,77 +7617,6 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF

    // ----- Restore handling -----

    // new style: we only store the SHA-1 hashes of each sig, not the full block
    static boolean signaturesMatch(ArrayList<byte[]> storedSigHashes, PackageInfo target) {
        if (target == null) {
            return false;
        }

        // If the target resides on the system partition, we allow it to restore
        // data from the like-named package in a restore set even if the signatures
        // do not match.  (Unlike general applications, those flashed to the system
        // partition will be signed with the device's platform certificate, so on
        // different phones the same system app will have different signatures.)
        if ((target.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
            if (MORE_DEBUG) Slog.v(TAG, "System app " + target.packageName + " - skipping sig check");
            return true;
        }

        // Allow unsigned apps, but not signed on one device and unsigned on the other
        // !!! TODO: is this the right policy?
        Signature[] deviceSigs = target.signatures;
        if (MORE_DEBUG) Slog.v(TAG, "signaturesMatch(): stored=" + storedSigHashes
                + " device=" + deviceSigs);
        if ((storedSigHashes == null || storedSigHashes.size() == 0)
                && (deviceSigs == null || deviceSigs.length == 0)) {
            return true;
        }
        if (storedSigHashes == null || deviceSigs == null) {
            return false;
        }

        // !!! TODO: this demands that every stored signature match one
        // that is present on device, and does not demand the converse.
        // Is this this right policy?
        final int nStored = storedSigHashes.size();
        final int nDevice = deviceSigs.length;

        // hash each on-device signature
        ArrayList<byte[]> deviceHashes = new ArrayList<byte[]>(nDevice);
        for (int i = 0; i < nDevice; i++) {
            deviceHashes.add(hashSignature(deviceSigs[i]));
        }

        // now ensure that each stored sig (hash) matches an on-device sig (hash)
        for (int n = 0; n < nStored; n++) {
            boolean match = false;
            final byte[] storedHash = storedSigHashes.get(n);
            for (int i = 0; i < nDevice; i++) {
                if (Arrays.equals(storedHash, deviceHashes.get(i))) {
                    match = true;
                    break;
                }
            }
            // match is false when no on-device sig matched one of the stored ones
            if (!match) {
                return false;
            }
        }

        return true;
    }

    static byte[] hashSignature(Signature sig) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            digest.update(sig.toByteArray());
            return digest.digest();
        } catch (NoSuchAlgorithmException e) {
            Slog.w(TAG, "No SHA-256 algorithm found!");
        }
        return null;
    }

    // Old style: directly match the stored vs on device signature blocks
    static boolean signaturesMatch(Signature[] storedSigs, PackageInfo target) {
        if (target == null) {
@@ -8200,7 +8129,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
            }

            Metadata metaInfo = mPmAgent.getRestoredMetadata(packageName);
            if (!signaturesMatch(metaInfo.sigHashes, mCurrentPackage)) {
            if (!BackupUtils.signaturesMatch(metaInfo.sigHashes, mCurrentPackage)) {
                Slog.w(TAG, "Signature mismatch restoring " + packageName);
                EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
                        "Signature mismatch");
+5 −22
Original line number Diff line number Diff line
@@ -205,7 +205,7 @@ public class PackageManagerBackupAgent extends BackupAgent {
                        PackageManager.GET_SIGNATURES);
                homeInstaller = mPackageManager.getInstallerPackageName(home.getPackageName());
                homeVersion = homeInfo.versionCode;
                homeSigHashes = hashSignatureArray(homeInfo.signatures);
                homeSigHashes = BackupUtils.hashSignatureArray(homeInfo.signatures);
            } catch (NameNotFoundException e) {
                Slog.w(TAG, "Can't access preferred home info");
                // proceed as though there were no preferred home set
@@ -222,7 +222,7 @@ public class PackageManagerBackupAgent extends BackupAgent {
            final boolean needHomeBackup = (homeVersion != mStoredHomeVersion)
                    || !Objects.equals(home, mStoredHomeComponent)
                    || (home != null
                        && !BackupManagerService.signaturesMatch(mStoredHomeSigHashes, homeInfo));
                        && !BackupUtils.signaturesMatch(mStoredHomeSigHashes, homeInfo));
            if (needHomeBackup) {
                if (DEBUG) {
                    Slog.i(TAG, "Home preference changed; backing up new state " + home);
@@ -309,7 +309,7 @@ public class PackageManagerBackupAgent extends BackupAgent {
                    outputBuffer.reset();
                    outputBufferStream.writeInt(info.versionCode);
                    writeSignatureHashArray(outputBufferStream,
                            hashSignatureArray(info.signatures));
                            BackupUtils.hashSignatureArray(info.signatures));

                    if (DEBUG) {
                        Slog.v(TAG, "+ writing metadata for " + packName
@@ -432,18 +432,6 @@ public class PackageManagerBackupAgent extends BackupAgent {
        mRestoredSignatures = sigMap;
    }

    private static ArrayList<byte[]> hashSignatureArray(Signature[] sigs) {
        if (sigs == null) {
            return null;
        }

        ArrayList<byte[]> hashes = new ArrayList<byte[]>(sigs.length);
        for (Signature s : sigs) {
            hashes.add(BackupManagerService.hashSignature(s));
        }
        return hashes;
    }

    private static void writeSignatureHashArray(DataOutputStream out, ArrayList<byte[]> hashes)
            throws IOException {
        // the number of entries in the array
@@ -492,13 +480,8 @@ public class PackageManagerBackupAgent extends BackupAgent {
            }

            if (nonHashFound) {
                ArrayList<byte[]> hashes =
                        new ArrayList<byte[]>(sigs.size());
                for (int i = 0; i < sigs.size(); i++) {
                    Signature s = new Signature(sigs.get(i));
                    hashes.add(BackupManagerService.hashSignature(s));
                }
                sigs = hashes;
                // Replace with the hashes.
                sigs = BackupUtils.hashSignatureArray(sigs);
            }

            return sigs;
+132 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 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 com.android.server.backup;

import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.Signature;
import android.util.Slog;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class BackupUtils {
    private static final String TAG = "BackupUtils";

    private static final boolean DEBUG = false; // STOPSHIP if true

    public static boolean signaturesMatch(ArrayList<byte[]> storedSigHashes, PackageInfo target) {
        if (target == null) {
            return false;
        }

        // If the target resides on the system partition, we allow it to restore
        // data from the like-named package in a restore set even if the signatures
        // do not match.  (Unlike general applications, those flashed to the system
        // partition will be signed with the device's platform certificate, so on
        // different phones the same system app will have different signatures.)
        if ((target.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
            if (DEBUG) Slog.v(TAG, "System app " + target.packageName + " - skipping sig check");
            return true;
        }

        // Allow unsigned apps, but not signed on one device and unsigned on the other
        // !!! TODO: is this the right policy?
        Signature[] deviceSigs = target.signatures;
        if (DEBUG) Slog.v(TAG, "signaturesMatch(): stored=" + storedSigHashes
                + " device=" + deviceSigs);
        if ((storedSigHashes == null || storedSigHashes.size() == 0)
                && (deviceSigs == null || deviceSigs.length == 0)) {
            return true;
        }
        if (storedSigHashes == null || deviceSigs == null) {
            return false;
        }

        // !!! TODO: this demands that every stored signature match one
        // that is present on device, and does not demand the converse.
        // Is this this right policy?
        final int nStored = storedSigHashes.size();
        final int nDevice = deviceSigs.length;

        // hash each on-device signature
        ArrayList<byte[]> deviceHashes = new ArrayList<byte[]>(nDevice);
        for (int i = 0; i < nDevice; i++) {
            deviceHashes.add(hashSignature(deviceSigs[i]));
        }

        // now ensure that each stored sig (hash) matches an on-device sig (hash)
        for (int n = 0; n < nStored; n++) {
            boolean match = false;
            final byte[] storedHash = storedSigHashes.get(n);
            for (int i = 0; i < nDevice; i++) {
                if (Arrays.equals(storedHash, deviceHashes.get(i))) {
                    match = true;
                    break;
                }
            }
            // match is false when no on-device sig matched one of the stored ones
            if (!match) {
                return false;
            }
        }

        return true;
    }

    public static byte[] hashSignature(byte[] signature) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            digest.update(signature);
            return digest.digest();
        } catch (NoSuchAlgorithmException e) {
            Slog.w(TAG, "No SHA-256 algorithm found!");
        }
        return null;
    }

    public static byte[] hashSignature(Signature signature) {
        return hashSignature(signature.toByteArray());
    }

    public static ArrayList<byte[]> hashSignatureArray(Signature[] sigs) {
        if (sigs == null) {
            return null;
        }

        ArrayList<byte[]> hashes = new ArrayList<>(sigs.length);
        for (Signature s : sigs) {
            hashes.add(hashSignature(s));
        }
        return hashes;
    }

    public static ArrayList<byte[]> hashSignatureArray(List<byte[]> sigs) {
        if (sigs == null) {
            return null;
        }

        ArrayList<byte[]> hashes = new ArrayList<>(sigs.size());
        for (byte[] s : sigs) {
            hashes.add(hashSignature(s));
        }
        return hashes;
    }
}
+118 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 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 com.android.server.pm.backup;

import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageParser.Package;
import android.content.pm.Signature;
import android.test.AndroidTestCase;
import android.test.MoreAsserts;
import android.test.suitebuilder.annotation.SmallTest;

import com.android.server.backup.BackupUtils;

import java.util.ArrayList;
import java.util.Arrays;

@SmallTest
public class BackupUtilsTest extends AndroidTestCase {

    private Signature[] genSignatures(String... signatures) {
        final Signature[] sigs = new Signature[signatures.length];
        for (int i = 0; i < signatures.length; i++){
            sigs[i] = new Signature(signatures[i].getBytes());
        }
        return sigs;
    }

    private PackageInfo genPackage(String... signatures) {
        final PackageInfo pi = new PackageInfo();
        pi.packageName = "package";
        pi.applicationInfo = new ApplicationInfo();
        pi.signatures = genSignatures(signatures);

        return pi;
    }

    public void testSignaturesMatch() {
        final ArrayList<byte[]> stored1 = BackupUtils.hashSignatureArray(Arrays.asList(
                "abc".getBytes()));
        final ArrayList<byte[]> stored2 = BackupUtils.hashSignatureArray(Arrays.asList(
                "abc".getBytes(), "def".getBytes()));

        PackageInfo pi;

        // False for null package.
        assertFalse(BackupUtils.signaturesMatch(stored1, null));

        // If it's a system app, signatures don't matter.
        pi = genPackage("xyz");
        pi.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
        assertTrue(BackupUtils.signaturesMatch(stored1, pi));

        // Non system apps.
        assertTrue(BackupUtils.signaturesMatch(stored1, genPackage("abc")));

        // Superset is okay.
        assertTrue(BackupUtils.signaturesMatch(stored1, genPackage("abc", "xyz")));
        assertTrue(BackupUtils.signaturesMatch(stored1, genPackage("xyz", "abc")));

        assertFalse(BackupUtils.signaturesMatch(stored1, genPackage("xyz")));
        assertFalse(BackupUtils.signaturesMatch(stored1, genPackage("xyz", "def")));

        assertTrue(BackupUtils.signaturesMatch(stored2, genPackage("def", "abc")));
        assertTrue(BackupUtils.signaturesMatch(stored2, genPackage("x", "def", "abc", "y")));

        // Subset is not okay.
        assertFalse(BackupUtils.signaturesMatch(stored2, genPackage("abc")));
        assertFalse(BackupUtils.signaturesMatch(stored2, genPackage("def")));
    }

    public void testHashSignature() {
        final byte[] sig1 = "abc".getBytes();
        final byte[] sig2 = "def".getBytes();

        final byte[] hash1a = BackupUtils.hashSignature(sig1);
        final byte[] hash1b = BackupUtils.hashSignature(new Signature(sig1));

        final byte[] hash2a = BackupUtils.hashSignature(sig2);
        final byte[] hash2b = BackupUtils.hashSignature(new Signature(sig2));

        assertEquals(32, hash1a.length);
        MoreAsserts.assertEquals(hash1a, hash1b);

        assertEquals(32, hash2a.length);
        MoreAsserts.assertEquals(hash2a, hash2b);

        assertFalse(Arrays.equals(hash1a, hash2a));

        final ArrayList<byte[]> listA = BackupUtils.hashSignatureArray(Arrays.asList(
                "abc".getBytes(), "def".getBytes()));

        final ArrayList<byte[]> listB = BackupUtils.hashSignatureArray(new Signature[]{
                new Signature("abc".getBytes()), new Signature("def".getBytes())});

        assertEquals(2, listA.size());
        assertEquals(2, listB.size());

        MoreAsserts.assertEquals(hash1a, listA.get(0));
        MoreAsserts.assertEquals(hash1a, listB.get(0));

        MoreAsserts.assertEquals(hash2a, listA.get(1));
        MoreAsserts.assertEquals(hash2a, listB.get(1));
    }
}