Merge changes I203018d7,I099c8315,I21e958d3
* changes: locksettings: use ArrayUtils.concat(byte[]...) ArrayUtils: add concat() for byte arrays ArrayUtils: rename concatElements() to concat()
This commit is contained in:
@@ -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,15 +350,16 @@ 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 <T> The class of the array elements (inferred from kind).
|
||||
* @return A single array containing all the elements of the parameter arrays.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static @NonNull <T> T[] concatElements(Class<T> kind, @Nullable T[]... arrays) {
|
||||
public static @NonNull <T> T[] concat(Class<T> kind, @Nullable T[]... arrays) {
|
||||
if (arrays == null || arrays.length == 0) {
|
||||
return createEmptyArray(kind);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,107 @@ 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 testConcat_zeroByteArrays() {
|
||||
// empty varargs array
|
||||
assertArrayEquals(new byte[] {}, ArrayUtils.concat());
|
||||
// null varargs array
|
||||
assertArrayEquals(new byte[] {}, ArrayUtils.concat((byte[][]) null));
|
||||
}
|
||||
|
||||
public void testConcatElements_oneNullElement() {
|
||||
String[] expectation = new String[0];
|
||||
|
||||
String[] concatResult = ArrayUtils.concatElements(String.class, null);
|
||||
assertArrayEquals(expectation, concatResult);
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -875,16 +875,16 @@ public class SettingsBackupAgent extends BackupAgentHelper {
|
||||
String[] whitelist;
|
||||
Map<String, Validator> 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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() != AES_GCM_TAG_SIZE * 8) {
|
||||
throw new IllegalArgumentException("Invalid tag length: " + spec.getTLen() + " bits");
|
||||
}
|
||||
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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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() {}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user