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
This commit is contained in:
Eric Biggers
2022-07-29 17:57:53 +00:00
parent 4dd9540f0e
commit 838d33b4be
11 changed files with 49 additions and 115 deletions

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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() {}
}

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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);
}

View File

@@ -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);
}