From 1174b2fd681164499fb12082743e391e4c99994c Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Fri, 29 Jul 2022 17:57:51 +0000 Subject: [PATCH 1/3] ArrayUtils: rename concatElements() to concat() concatElements() was probably named as it is to be like addElement() and removeElement(). However, that doesn't really make sense because the parameters of concatElements() are only arrays, not array elements. Rename it to concat(), as that is just as clear and follows the example of Guava's ObjectArrays.concat(). And I'd like to add a variant that operates on byte arrays, and would like to call it concat(). Also clean up the tests a bit. Test: atest com.android.internal.util.ArrayUtilsTest BYPASS_INCLUSIVE_LANGUAGE_REASON=existing code in SettingsBackupAgent Change-Id: I21e958d3d7b3366ec724e757ac4ccc50b36abee1 --- .../com/android/internal/util/ArrayUtils.java | 2 +- .../android/internal/util/ArrayUtilsTest.java | 74 +++++++++---------- .../settings/SettingsBackupAgent.java | 6 +- 3 files changed, 38 insertions(+), 44 deletions(-) diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java index 6eae34bf19993..6ef0c0e51e827 100644 --- a/core/java/com/android/internal/util/ArrayUtils.java +++ b/core/java/com/android/internal/util/ArrayUtils.java @@ -359,7 +359,7 @@ public class ArrayUtils { * @return A single array containing all the elements of the parameter arrays. */ @SuppressWarnings("unchecked") - public static @NonNull T[] concatElements(Class kind, @Nullable T[]... arrays) { + public static @NonNull T[] concat(Class kind, @Nullable T[]... arrays) { if (arrays == null || arrays.length == 0) { return createEmptyArray(kind); } diff --git a/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java index cb30b3fe5b826..61f06bd515174 100644 --- a/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java +++ b/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java @@ -16,8 +16,6 @@ package com.android.internal.util; -import static com.android.internal.util.ArrayUtils.concatElements; - import static org.junit.Assert.assertArrayEquals; import junit.framework.TestCase; @@ -156,61 +154,57 @@ public class ArrayUtilsTest extends TestCase { ArrayUtils.removeLong(new long[] { 1, 2, 3, 1 }, 1)); } - public void testConcatEmpty() throws Exception { - assertArrayEquals(new Long[] {}, - concatElements(Long.class, null, null)); - assertArrayEquals(new Long[] {}, - concatElements(Long.class, new Long[] {}, null)); - assertArrayEquals(new Long[] {}, - concatElements(Long.class, null, new Long[] {})); - assertArrayEquals(new Long[] {}, - concatElements(Long.class, new Long[] {}, new Long[] {})); + public void testConcat_zeroObjectArrays() { + // empty varargs array + assertArrayEquals(new String[] {}, ArrayUtils.concat(String.class)); + // null varargs array + assertArrayEquals(new String[] {}, ArrayUtils.concat(String.class, (String[][]) null)); } - public void testconcatElements() throws Exception { + public void testConcat_oneObjectArray() { + assertArrayEquals(new String[] { "1", "2" }, + ArrayUtils.concat(String.class, new String[] { "1", "2" })); + } + + public void testConcat_oneEmptyObjectArray() { + assertArrayEquals(new String[] {}, ArrayUtils.concat(String.class, (String[]) null)); + assertArrayEquals(new String[] {}, ArrayUtils.concat(String.class, new String[] {})); + } + + public void testConcat_twoObjectArrays() { assertArrayEquals(new Long[] { 1L }, - concatElements(Long.class, new Long[] { 1L }, new Long[] {})); + ArrayUtils.concat(Long.class, new Long[] { 1L }, new Long[] {})); assertArrayEquals(new Long[] { 1L }, - concatElements(Long.class, new Long[] {}, new Long[] { 1L })); + ArrayUtils.concat(Long.class, new Long[] {}, new Long[] { 1L })); assertArrayEquals(new Long[] { 1L, 2L }, - concatElements(Long.class, new Long[] { 1L }, new Long[] { 2L })); + ArrayUtils.concat(Long.class, new Long[] { 1L }, new Long[] { 2L })); assertArrayEquals(new Long[] { 1L, 2L, 3L, 4L }, - concatElements(Long.class, new Long[] { 1L, 2L }, new Long[] { 3L, 4L })); + ArrayUtils.concat(Long.class, new Long[] { 1L, 2L }, new Long[] { 3L, 4L })); } - public void testConcatElements_threeWay() { + public void testConcat_twoEmptyObjectArrays() { + assertArrayEquals(new Long[] {}, ArrayUtils.concat(Long.class, null, null)); + assertArrayEquals(new Long[] {}, ArrayUtils.concat(Long.class, new Long[] {}, null)); + assertArrayEquals(new Long[] {}, ArrayUtils.concat(Long.class, null, new Long[] {})); + assertArrayEquals(new Long[] {}, + ArrayUtils.concat(Long.class, new Long[] {}, new Long[] {})); + } + + public void testConcat_threeObjectArrays() { String[] array1 = { "1", "2" }; String[] array2 = { "3", "4" }; String[] array3 = { "5", "6" }; - String[] expectation = {"1", "2", "3", "4", "5", "6"}; + String[] expectation = { "1", "2", "3", "4", "5", "6" }; - String[] concatResult = ArrayUtils.concatElements(String.class, array1, array2, array3); - assertArrayEquals(expectation, concatResult); + assertArrayEquals(expectation, ArrayUtils.concat(String.class, array1, array2, array3)); } - - public void testConcatElements_threeWayWithNull() { + public void testConcat_threeObjectArraysWithNull() { String[] array1 = { "1", "2" }; String[] array2 = null; String[] array3 = { "5", "6" }; - String[] expectation = {"1", "2", "5", "6"}; + String[] expectation = { "1", "2", "5", "6" }; - String[] concatResult = ArrayUtils.concatElements(String.class, array1, array2, array3); - assertArrayEquals(expectation, concatResult); + assertArrayEquals(expectation, ArrayUtils.concat(String.class, array1, array2, array3)); } - - public void testConcatElements_zeroElements() { - String[] expectation = new String[0]; - - String[] concatResult = ArrayUtils.concatElements(String.class); - assertArrayEquals(expectation, concatResult); - } - - public void testConcatElements_oneNullElement() { - String[] expectation = new String[0]; - - String[] concatResult = ArrayUtils.concatElements(String.class, null); - assertArrayEquals(expectation, concatResult); - } - } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java index d3afccc9dcb1a..6aa08f22c3336 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java @@ -875,16 +875,16 @@ public class SettingsBackupAgent extends BackupAgentHelper { String[] whitelist; Map validators = null; if (contentUri.equals(Settings.Secure.CONTENT_URI)) { - whitelist = ArrayUtils.concatElements(String.class, SecureSettings.SETTINGS_TO_BACKUP, + whitelist = ArrayUtils.concat(String.class, SecureSettings.SETTINGS_TO_BACKUP, Settings.Secure.LEGACY_RESTORE_SETTINGS, DeviceSpecificSettings.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP); validators = SecureSettingsValidators.VALIDATORS; } else if (contentUri.equals(Settings.System.CONTENT_URI)) { - whitelist = ArrayUtils.concatElements(String.class, SystemSettings.SETTINGS_TO_BACKUP, + whitelist = ArrayUtils.concat(String.class, SystemSettings.SETTINGS_TO_BACKUP, Settings.System.LEGACY_RESTORE_SETTINGS); validators = SystemSettingsValidators.VALIDATORS; } else if (contentUri.equals(Settings.Global.CONTENT_URI)) { - whitelist = ArrayUtils.concatElements(String.class, GlobalSettings.SETTINGS_TO_BACKUP, + whitelist = ArrayUtils.concat(String.class, GlobalSettings.SETTINGS_TO_BACKUP, Settings.Global.LEGACY_RESTORE_SETTINGS); validators = GlobalSettingsValidators.VALIDATORS; } else { From 4dd9540f0e1d157b233a4c0d27c7f350b93f18c0 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Fri, 29 Jul 2022 17:57:52 +0000 Subject: [PATCH 2/3] ArrayUtils: add concat() for byte arrays Concatenating byte arrays is a common thing that needs to be done, especially in the locksettings package, so add a utility method for it. Test: atest com.android.internal.util.ArrayUtilsTest Change-Id: I099c83155456139729f4d73f5873a172590ee7e9 --- .../com/android/internal/util/ArrayUtils.java | 31 ++++++++++-- .../android/internal/util/ArrayUtilsTest.java | 50 +++++++++++++++++++ 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java index 6ef0c0e51e827..b64771b88e25d 100644 --- a/core/java/com/android/internal/util/ArrayUtils.java +++ b/core/java/com/android/internal/util/ArrayUtils.java @@ -39,8 +39,7 @@ import java.util.Set; import java.util.function.IntFunction; /** - * ArrayUtils contains some methods that you can call to find out - * the most efficient increments by which to grow arrays. + * Static utility methods for arrays that aren't already included in {@link java.util.Arrays}. */ public class ArrayUtils { private static final int CACHE_SIZE = 73; @@ -351,10 +350,11 @@ public class ArrayUtils { } /** - * Combine multiple arrays into a single array. + * Returns the concatenation of the given arrays. Only works for object arrays, not for + * primitive arrays. See {@link #concat(byte[]...)} for a variant that works on byte arrays. * * @param kind The class of the array elements - * @param arrays The arrays to combine + * @param arrays The arrays to concatenate. Null arrays are treated as empty. * @param The class of the array elements (inferred from kind). * @return A single array containing all the elements of the parameter arrays. */ @@ -400,6 +400,29 @@ public class ArrayUtils { return (T[]) Array.newInstance(kind, 0); } + /** + * Returns the concatenation of the given byte arrays. Null arrays are treated as empty. + */ + public static @NonNull byte[] concat(@Nullable byte[]... arrays) { + if (arrays == null) { + return new byte[0]; + } + int totalLength = 0; + for (byte[] a : arrays) { + if (a != null) { + totalLength += a.length; + } + } + final byte[] result = new byte[totalLength]; + int pos = 0; + for (byte[] a : arrays) { + if (a != null) { + System.arraycopy(a, 0, result, pos, a.length); + pos += a.length; + } + } + return result; + } /** * Adds value to given array if not already present, providing set-like diff --git a/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java index 61f06bd515174..c66a743d8f530 100644 --- a/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java +++ b/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java @@ -207,4 +207,54 @@ public class ArrayUtilsTest extends TestCase { assertArrayEquals(expectation, ArrayUtils.concat(String.class, array1, array2, array3)); } + + public void testConcat_zeroByteArrays() { + // empty varargs array + assertArrayEquals(new byte[] {}, ArrayUtils.concat()); + // null varargs array + assertArrayEquals(new byte[] {}, ArrayUtils.concat((byte[][]) null)); + } + + public void testConcat_oneByteArray() { + assertArrayEquals(new byte[] { 1, 2 }, ArrayUtils.concat(new byte[] { 1, 2 })); + } + + public void testConcat_oneEmptyByteArray() { + assertArrayEquals(new byte[] {}, ArrayUtils.concat((byte[]) null)); + assertArrayEquals(new byte[] {}, ArrayUtils.concat(new byte[] {})); + } + + public void testConcat_twoByteArrays() { + assertArrayEquals(new byte[] { 1 }, ArrayUtils.concat(new byte[] { 1 }, new byte[] {})); + assertArrayEquals(new byte[] { 1 }, ArrayUtils.concat(new byte[] {}, new byte[] { 1 })); + assertArrayEquals(new byte[] { 1, 2 }, + ArrayUtils.concat(new byte[] { 1 }, new byte[] { 2 })); + assertArrayEquals(new byte[] { 1, 2, 3, 4 }, + ArrayUtils.concat(new byte[] { 1, 2 }, new byte[] { 3, 4 })); + } + + public void testConcat_twoEmptyByteArrays() { + assertArrayEquals(new byte[] {}, ArrayUtils.concat((byte[]) null, null)); + assertArrayEquals(new byte[] {}, ArrayUtils.concat(new byte[] {}, null)); + assertArrayEquals(new byte[] {}, ArrayUtils.concat((byte[]) null, new byte[] {})); + assertArrayEquals(new byte[] {}, ArrayUtils.concat(new byte[] {}, new byte[] {})); + } + + public void testConcat_threeByteArrays() { + byte[] array1 = { 1, 2 }; + byte[] array2 = { 3, 4 }; + byte[] array3 = { 5, 6 }; + byte[] expectation = { 1, 2, 3, 4, 5, 6 }; + + assertArrayEquals(expectation, ArrayUtils.concat(array1, array2, array3)); + } + + public void testConcat_threeByteArraysWithNull() { + byte[] array1 = { 1, 2 }; + byte[] array2 = null; + byte[] array3 = { 5, 6 }; + byte[] expectation = { 1, 2, 5, 6 }; + + assertArrayEquals(expectation, ArrayUtils.concat(array1, array2, array3)); + } } From 838d33b4be5151842ff012e04151f02358888556 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Fri, 29 Jul 2022 17:57:53 +0000 Subject: [PATCH 3/3] locksettings: use ArrayUtils.concat(byte[]...) Use the new byte array concatenation utility method where appropriate. No change in behavior intended; this is just a cleanup. The new method accepts null arrays, whereas most of the previous workarounds didn't, but I don't see anywhere where this would make a difference. Also use HexEncoding.encodeToString() in a couple places. Test: atest com.android.server.locksettings Test: atest LockscreenCredentialTest Change-Id: I203018d7cba2721a9d0c4988708df7147e396dbe --- .../internal/widget/LockscreenCredential.java | 16 +++------ .../locksettings/LockSettingsService.java | 21 ++++-------- .../ManagedProfilePasswordCache.java | 4 +-- .../locksettings/SyntheticPasswordCrypto.java | 8 ++--- .../SyntheticPasswordManager.java | 10 ++---- .../recoverablekeystore/KeySyncUtils.java | 33 ++++--------------- .../recoverablekeystore/SecureBox.java | 30 ++++------------- .../recoverablekeystore/KeySyncTaskTest.java | 3 +- .../recoverablekeystore/KeySyncUtilsTest.java | 28 +++++----------- .../RecoverableKeyStoreManagerTest.java | 3 +- .../recoverablekeystore/SecureBoxTest.java | 8 +++-- 11 files changed, 49 insertions(+), 115 deletions(-) diff --git a/core/java/com/android/internal/widget/LockscreenCredential.java b/core/java/com/android/internal/widget/LockscreenCredential.java index f93e28040d5a0..9f3f335f31737 100644 --- a/core/java/com/android/internal/widget/LockscreenCredential.java +++ b/core/java/com/android/internal/widget/LockscreenCredential.java @@ -29,6 +29,7 @@ import android.os.Parcelable; import android.os.storage.StorageManager; import android.text.TextUtils; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.Preconditions; import libcore.util.HexEncoding; @@ -280,7 +281,7 @@ public class LockscreenCredential implements Parcelable, AutoCloseable { sha256.update(hashFactor); sha256.update(passwordToHash); sha256.update(salt); - return new String(HexEncoding.encode(sha256.digest())); + return HexEncoding.encodeToString(sha256.digest()); } catch (NoSuchAlgorithmException e) { throw new AssertionError("Missing digest algorithm: ", e); } @@ -302,21 +303,12 @@ public class LockscreenCredential implements Parcelable, AutoCloseable { } try { - // Previously the password was passed as a String with the following code: - // byte[] saltedPassword = (password + salt).getBytes(); - // The code below creates the identical digest preimage using byte arrays: - byte[] saltedPassword = Arrays.copyOf(password, password.length + salt.length); - System.arraycopy(salt, 0, saltedPassword, password.length, salt.length); + byte[] saltedPassword = ArrayUtils.concat(password, salt); byte[] sha1 = MessageDigest.getInstance("SHA-1").digest(saltedPassword); byte[] md5 = MessageDigest.getInstance("MD5").digest(saltedPassword); - byte[] combined = new byte[sha1.length + md5.length]; - System.arraycopy(sha1, 0, combined, 0, sha1.length); - System.arraycopy(md5, 0, combined, sha1.length, md5.length); - - final char[] hexEncoded = HexEncoding.encode(combined); Arrays.fill(saltedPassword, (byte) 0); - return new String(hexEncoded); + return HexEncoding.encodeToString(ArrayUtils.concat(sha1, md5)); } catch (NoSuchAlgorithmException e) { throw new AssertionError("Missing digest algorithm: ", e); } diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 9b6d0e7c09e49..692a3de91888a 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -122,6 +122,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.notification.SystemNotificationChannels; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; @@ -147,7 +148,6 @@ import com.android.server.wm.WindowManagerInternal; import libcore.util.HexEncoding; -import java.io.ByteArrayOutputStream; import java.io.FileDescriptor; import java.io.FileNotFoundException; import java.io.IOException; @@ -1847,8 +1847,8 @@ public class LockSettingsService extends ILockSettings.Stub { @VisibleForTesting /** Note: this method is overridden in unit tests */ protected void tieProfileLockToParent(int userId, LockscreenCredential password) { if (DEBUG) Slog.v(TAG, "tieProfileLockToParent for user: " + userId); - byte[] encryptionResult; - byte[] iv; + final byte[] iv; + final byte[] ciphertext; try { KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES); keyGenerator.init(new SecureRandom()); @@ -1877,7 +1877,7 @@ public class LockSettingsService extends ILockSettings.Stub { KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE); cipher.init(Cipher.ENCRYPT_MODE, keyStoreEncryptionKey); - encryptionResult = cipher.doFinal(password.getCredential()); + ciphertext = cipher.doFinal(password.getCredential()); iv = cipher.getIV(); } finally { // The original key can now be discarded. @@ -1888,17 +1888,10 @@ public class LockSettingsService extends ILockSettings.Stub { | NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) { throw new IllegalStateException("Failed to encrypt key", e); } - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - try { - if (iv.length != PROFILE_KEY_IV_SIZE) { - throw new IllegalArgumentException("Invalid iv length: " + iv.length); - } - outputStream.write(iv); - outputStream.write(encryptionResult); - } catch (IOException e) { - throw new IllegalStateException("Failed to concatenate byte arrays", e); + if (iv.length != PROFILE_KEY_IV_SIZE) { + throw new IllegalArgumentException("Invalid iv length: " + iv.length); } - mStorage.writeChildProfileLock(userId, outputStream.toByteArray()); + mStorage.writeChildProfileLock(userId, ArrayUtils.concat(iv, ciphertext)); } private void setUserKeyProtection(int userId, byte[] key) { diff --git a/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java b/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java index 672c3f739413e..e43d4e8ce4a6f 100644 --- a/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java +++ b/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java @@ -26,6 +26,7 @@ import android.security.keystore.UserNotAuthenticatedException; import android.util.Slog; import android.util.SparseArray; +import com.android.internal.util.ArrayUtils; import com.android.internal.widget.LockscreenCredential; import java.security.GeneralSecurityException; @@ -117,8 +118,7 @@ public class ManagedProfilePasswordCache { cipher.init(Cipher.ENCRYPT_MODE, key); byte[] ciphertext = cipher.doFinal(password.getCredential()); byte[] iv = cipher.getIV(); - byte[] block = Arrays.copyOf(iv, ciphertext.length + iv.length); - System.arraycopy(ciphertext, 0, block, iv.length, ciphertext.length); + byte[] block = ArrayUtils.concat(iv, ciphertext); mEncryptedPasswords.put(userId, block); } catch (GeneralSecurityException e) { Slog.d(TAG, "Cannot encrypt", e); diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java index b06af8e5a385e..7a28fde4e6a3e 100644 --- a/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java +++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java @@ -25,7 +25,8 @@ import android.system.keystore2.Domain; import android.system.keystore2.KeyDescriptor; import android.util.Slog; -import java.io.ByteArrayOutputStream; +import com.android.internal.util.ArrayUtils; + import java.io.IOException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; @@ -94,10 +95,7 @@ public class SyntheticPasswordCrypto { if (spec.getTLen() != DEFAULT_TAG_LENGTH_BITS) { throw new IllegalArgumentException("Invalid tag length: " + spec.getTLen()); } - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - outputStream.write(iv); - outputStream.write(ciphertext); - return outputStream.toByteArray(); + return ArrayUtils.concat(iv, ciphertext); } public static byte[] encrypt(byte[] keyBytes, byte[] personalization, byte[] message) { diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java index 1f807f9f69ca8..ee76e374f6b66 100644 --- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java +++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java @@ -1347,19 +1347,13 @@ public class SyntheticPasswordManager { private byte[] transformUnderWeaverSecret(byte[] data, byte[] secret) { byte[] weaverSecret = SyntheticPasswordCrypto.personalizedHash( PERSONALIZATION_WEAVER_PASSWORD, secret); - byte[] result = new byte[data.length + weaverSecret.length]; - System.arraycopy(data, 0, result, 0, data.length); - System.arraycopy(weaverSecret, 0, result, data.length, weaverSecret.length); - return result; + return ArrayUtils.concat(data, weaverSecret); } private byte[] transformUnderSecdiscardable(byte[] data, byte[] rawSecdiscardable) { byte[] secdiscardable = SyntheticPasswordCrypto.personalizedHash( PERSONALIZATION_SECDISCARDABLE, rawSecdiscardable); - byte[] result = new byte[data.length + secdiscardable.length]; - System.arraycopy(data, 0, result, 0, data.length); - System.arraycopy(secdiscardable, 0, result, data.length, secdiscardable.length); - return result; + return ArrayUtils.concat(data, secdiscardable); } private byte[] createSecdiscardable(long protectorId, int userId) { diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java index 24d575e27bdd8..7921619977c7c 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java @@ -20,6 +20,7 @@ import android.annotation.Nullable; import android.util.Pair; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -88,7 +89,7 @@ public class KeySyncUtils { ) throws NoSuchAlgorithmException, InvalidKeyException { byte[] encryptedRecoveryKey = locallyEncryptRecoveryKey(lockScreenHash, recoveryKey); byte[] thmKfHash = calculateThmKfHash(lockScreenHash); - byte[] header = concat(THM_ENCRYPTED_RECOVERY_KEY_HEADER, vaultParams); + byte[] header = ArrayUtils.concat(THM_ENCRYPTED_RECOVERY_KEY_HEADER, vaultParams); return SecureBox.encrypt( /*theirPublicKey=*/ publicKey, /*sharedSecret=*/ thmKfHash, @@ -171,7 +172,7 @@ public class KeySyncUtils { // Note that Android P devices do not have the API to provide the optional metadata, // so all the keys with non-empty metadata stored on Android Q+ devices cannot be // recovered on Android P devices. - header = concat(ENCRYPTED_APPLICATION_KEY_HEADER, metadata); + header = ArrayUtils.concat(ENCRYPTED_APPLICATION_KEY_HEADER, metadata); } byte[] encryptedKey = SecureBox.encrypt( /*theirPublicKey=*/ null, @@ -218,8 +219,8 @@ public class KeySyncUtils { return SecureBox.encrypt( publicKey, /*sharedSecret=*/ null, - /*header=*/ concat(RECOVERY_CLAIM_HEADER, vaultParams, challenge), - /*payload=*/ concat(thmKfHash, keyClaimant)); + /*header=*/ ArrayUtils.concat(RECOVERY_CLAIM_HEADER, vaultParams, challenge), + /*payload=*/ ArrayUtils.concat(thmKfHash, keyClaimant)); } /** @@ -240,7 +241,7 @@ public class KeySyncUtils { return SecureBox.decrypt( /*ourPrivateKey=*/ null, /*sharedSecret=*/ keyClaimant, - /*header=*/ concat(RECOVERY_RESPONSE_HEADER, vaultParams), + /*header=*/ ArrayUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams), /*encryptedPayload=*/ encryptedResponse); } @@ -280,7 +281,7 @@ public class KeySyncUtils { if (applicationKeyMetadata == null) { header = ENCRYPTED_APPLICATION_KEY_HEADER; } else { - header = concat(ENCRYPTED_APPLICATION_KEY_HEADER, applicationKeyMetadata); + header = ArrayUtils.concat(ENCRYPTED_APPLICATION_KEY_HEADER, applicationKeyMetadata); } return SecureBox.decrypt( /*ourPrivateKey=*/ null, @@ -333,26 +334,6 @@ public class KeySyncUtils { .array(); } - /** - * Returns the concatenation of all the given {@code arrays}. - */ - @VisibleForTesting - static byte[] concat(byte[]... arrays) { - int length = 0; - for (byte[] array : arrays) { - length += array.length; - } - - byte[] concatenated = new byte[length]; - int pos = 0; - for (byte[] array : arrays) { - System.arraycopy(array, /*srcPos=*/ 0, concatenated, pos, array.length); - pos += array.length; - } - - return concatenated; - } - // Statics only private KeySyncUtils() {} } diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java index 807ee034e2692..51a37b34e2cec 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java @@ -18,6 +18,7 @@ package com.android.server.locksettings.recoverablekeystore; import android.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; import java.math.BigInteger; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; @@ -69,7 +70,7 @@ public class SecureBox { private static final byte[] VERSION = new byte[] {(byte) 0x02, 0}; // LITTLE_ENDIAN_TWO_BYTES(2) private static final byte[] HKDF_SALT = - concat("SECUREBOX".getBytes(StandardCharsets.UTF_8), VERSION); + ArrayUtils.concat("SECUREBOX".getBytes(StandardCharsets.UTF_8), VERSION); private static final byte[] HKDF_INFO_WITH_PUBLIC_KEY = "P256 HKDF-SHA-256 AES-128-GCM".getBytes(StandardCharsets.UTF_8); private static final byte[] HKDF_INFO_WITHOUT_PUBLIC_KEY = @@ -199,13 +200,13 @@ public class SecureBox { } byte[] randNonce = genRandomNonce(); - byte[] keyingMaterial = concat(dhSecret, sharedSecret); + byte[] keyingMaterial = ArrayUtils.concat(dhSecret, sharedSecret); SecretKey encryptionKey = hkdfDeriveKey(keyingMaterial, HKDF_SALT, hkdfInfo); byte[] ciphertext = aesGcmEncrypt(encryptionKey, randNonce, payload, header); if (senderKeyPair == null) { - return concat(VERSION, randNonce, ciphertext); + return ArrayUtils.concat(VERSION, randNonce, ciphertext); } else { - return concat( + return ArrayUtils.concat( VERSION, encodePublicKey(senderKeyPair.getPublic()), randNonce, ciphertext); } } @@ -268,7 +269,7 @@ public class SecureBox { byte[] randNonce = readEncryptedPayload(ciphertextBuffer, GCM_NONCE_LEN_BYTES); byte[] ciphertext = readEncryptedPayload(ciphertextBuffer, ciphertextBuffer.remaining()); - byte[] keyingMaterial = concat(dhSecret, sharedSecret); + byte[] keyingMaterial = ArrayUtils.concat(dhSecret, sharedSecret); SecretKey decryptionKey = hkdfDeriveKey(keyingMaterial, HKDF_SALT, hkdfInfo); return aesGcmDecrypt(decryptionKey, randNonce, ciphertext, header); } @@ -446,25 +447,6 @@ public class SecureBox { return nonce; } - @VisibleForTesting - static byte[] concat(byte[]... inputs) { - int length = 0; - for (int i = 0; i < inputs.length; i++) { - if (inputs[i] == null) { - inputs[i] = EMPTY_BYTE_ARRAY; - } - length += inputs[i].length; - } - - byte[] output = new byte[length]; - int outputPos = 0; - for (byte[] input : inputs) { - System.arraycopy(input, /*srcPos=*/ 0, output, outputPos, input.length); - outputPos += input.length; - } - return output; - } - private static byte[] emptyByteArrayIfNull(@Nullable byte[] input) { return input == null ? EMPTY_BYTE_ARRAY : input; } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java index d9af51f819c31..c2e83f23ae86f 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java @@ -54,6 +54,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.util.ArrayUtils; import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb; import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage; @@ -835,7 +836,7 @@ public class KeySyncTaskTest { byte[] locallyEncryptedKey = SecureBox.decrypt( TestData.getInsecurePrivateKeyForEndpoint1(), /*sharedSecret=*/ KeySyncUtils.calculateThmKfHash(lockScreenHash), - /*header=*/ KeySyncUtils.concat(THM_ENCRYPTED_RECOVERY_KEY_HEADER, vaultParams), + /*header=*/ ArrayUtils.concat(THM_ENCRYPTED_RECOVERY_KEY_HEADER, vaultParams), encryptedKey ); return KeySyncUtils.decryptRecoveryKey(lockScreenHash, locallyEncryptedKey); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java index 178fd104a1ae9..19a606e002729 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java @@ -27,6 +27,7 @@ import android.util.Pair; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.util.ArrayUtils; import com.google.common.collect.ImmutableMap; import org.junit.Test; @@ -116,17 +117,6 @@ public class KeySyncUtilsTest { assertFalse(Arrays.equals(a, b)); } - @Test - public void concat_concatenatesArrays() { - assertArrayEquals( - utf8Bytes("hello, world!"), - KeySyncUtils.concat( - utf8Bytes("hello"), - utf8Bytes(", "), - utf8Bytes("world"), - utf8Bytes("!"))); - } - @Test public void decryptApplicationKey_decryptsAnApplicationKey_nullMetadata() throws Exception { String alias = "phoebe"; @@ -253,7 +243,7 @@ public class KeySyncUtilsTest { byte[] encryptedPayload = SecureBox.encrypt( /*theirPublicKey=*/ null, /*sharedSecret=*/ keyClaimant, - /*header=*/ KeySyncUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams), + /*header=*/ ArrayUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams), /*payload=*/ recoveryKey); byte[] decrypted = KeySyncUtils.decryptRecoveryClaimResponse( @@ -269,7 +259,7 @@ public class KeySyncUtilsTest { byte[] encryptedPayload = SecureBox.encrypt( /*theirPublicKey=*/ null, /*sharedSecret=*/ KeySyncUtils.generateKeyClaimant(), - /*header=*/ KeySyncUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams), + /*header=*/ ArrayUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams), /*payload=*/ recoveryKey); try { @@ -298,9 +288,9 @@ public class KeySyncUtilsTest { byte[] decrypted = SecureBox.decrypt( keyPair.getPrivate(), /*sharedSecret=*/ null, - /*header=*/ KeySyncUtils.concat(RECOVERY_CLAIM_HEADER, vaultParams, challenge), + /*header=*/ ArrayUtils.concat(RECOVERY_CLAIM_HEADER, vaultParams, challenge), encryptedRecoveryClaim); - assertArrayEquals(KeySyncUtils.concat(LOCK_SCREEN_HASH_1, keyClaimant), decrypted); + assertArrayEquals(ArrayUtils.concat(LOCK_SCREEN_HASH_1, keyClaimant), decrypted); } @Test @@ -320,7 +310,7 @@ public class KeySyncUtilsTest { SecureBox.decrypt( keyPair.getPrivate(), /*sharedSecret=*/ null, - /*header=*/ KeySyncUtils.concat( + /*header=*/ ArrayUtils.concat( RECOVERY_CLAIM_HEADER, vaultParams, randomBytes(32)), encryptedRecoveryClaim); fail("Should throw if challenge is incorrect."); @@ -346,7 +336,7 @@ public class KeySyncUtilsTest { SecureBox.decrypt( SecureBox.genKeyPair().getPrivate(), /*sharedSecret=*/ null, - /*header=*/ KeySyncUtils.concat( + /*header=*/ ArrayUtils.concat( RECOVERY_CLAIM_HEADER, vaultParams, challenge), encryptedRecoveryClaim); fail("Should throw if secret key is incorrect."); @@ -372,7 +362,7 @@ public class KeySyncUtilsTest { SecureBox.decrypt( keyPair.getPrivate(), /*sharedSecret=*/ null, - /*header=*/ KeySyncUtils.concat( + /*header=*/ ArrayUtils.concat( RECOVERY_CLAIM_HEADER, randomBytes(100), challenge), encryptedRecoveryClaim); fail("Should throw if vault params is incorrect."); @@ -399,7 +389,7 @@ public class KeySyncUtilsTest { SecureBox.decrypt( keyPair.getPrivate(), /*sharedSecret=*/ null, - /*header=*/ KeySyncUtils.concat(randomBytes(10), vaultParams, challenge), + /*header=*/ ArrayUtils.concat(randomBytes(10), vaultParams, challenge), encryptedRecoveryClaim); fail("Should throw if header is incorrect."); } catch (AEADBadTagException e) { diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java index 035249e32d74e..aceae61b8b9d3 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java @@ -58,6 +58,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.util.ArrayUtils; import com.android.internal.widget.LockPatternUtils; import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKeyStorage; import com.android.server.locksettings.recoverablekeystore.storage.CleanupManager; @@ -1287,7 +1288,7 @@ public class RecoverableKeyStoreManagerTest { return SecureBox.encrypt( /*theirPublicKey=*/ null, /*sharedSecret=*/ keyClaimant, - /*header=*/ KeySyncUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams), + /*header=*/ ArrayUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams), /*payload=*/ locallyEncryptedRecoveryKey); } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java index 15b070829cdb3..34235bd957424 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java @@ -27,6 +27,8 @@ import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; +import com.android.internal.util.ArrayUtils; + import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; @@ -176,9 +178,9 @@ public class SecureBoxTest { SecureBox.decrypt( THM_PRIVATE_KEY, /*sharedSecret=*/ null, - SecureBox.concat(getBytes("V1 KF_claim"), VAULT_PARAMS, VAULT_CHALLENGE), + ArrayUtils.concat(getBytes("V1 KF_claim"), VAULT_PARAMS, VAULT_CHALLENGE), RECOVERY_CLAIM); - assertThat(claimContent).isEqualTo(SecureBox.concat(THM_KF_HASH, KEY_CLAIMANT)); + assertThat(claimContent).isEqualTo(ArrayUtils.concat(THM_KF_HASH, KEY_CLAIMANT)); } @Test @@ -186,7 +188,7 @@ public class SecureBoxTest { SecureBox.decrypt( THM_PRIVATE_KEY, THM_KF_HASH, - SecureBox.concat(getBytes("V1 THM_encrypted_recovery_key"), VAULT_PARAMS), + ArrayUtils.concat(getBytes("V1 THM_encrypted_recovery_key"), VAULT_PARAMS), ENCRYPTED_RECOVERY_KEY); }