diff options
| author | 2016-03-29 19:25:48 +0000 | |
|---|---|---|
| committer | 2016-03-29 19:25:49 +0000 | |
| commit | 021a48d50eb857aa912e5ca22ae0fff4a8b4c9dd (patch) | |
| tree | 504e82c4995df6e73c2f5bca64b76d8b2091a48a | |
| parent | fd48a321c9be5e46224222c46be8667bec6f631a (diff) | |
| parent | 590096a0e372f640fb41d4cb97d8bc68fb7b8ea2 (diff) | |
Merge "Extract signature related utilities" into nyc-dev
4 files changed, 256 insertions, 94 deletions
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index e84f65b2ee65..6288b56d1f1d 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -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"); diff --git a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java index f197c1e07665..09f240f4240c 100644 --- a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java +++ b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java @@ -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; diff --git a/services/core/java/com/android/server/backup/BackupUtils.java b/services/core/java/com/android/server/backup/BackupUtils.java new file mode 100644 index 000000000000..e5d564dec459 --- /dev/null +++ b/services/core/java/com/android/server/backup/BackupUtils.java @@ -0,0 +1,132 @@ +/* + * 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; + } +} diff --git a/services/tests/servicestests/src/com/android/server/pm/backup/BackupUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/backup/BackupUtilsTest.java new file mode 100644 index 000000000000..c016e6104755 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pm/backup/BackupUtilsTest.java @@ -0,0 +1,118 @@ +/* + * 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)); + } +} |