Merge changes Id0e18bef,Ie2b5f559

* changes:
  Wrap the escrow data with key from keystore
  Load and generate key from keystore in reboot escrow
This commit is contained in:
Tianjie Xu
2021-01-14 00:52:37 +00:00
committed by Gerrit Code Review
6 changed files with 390 additions and 92 deletions

View File

@@ -0,0 +1,109 @@
/*
* Copyright (C) 2020 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.locksettings;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Objects;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
class AesEncryptionUtil {
/** The algorithm used for the encryption of the key blob. */
private static final String CIPHER_ALGO = "AES/GCM/NoPadding";
private AesEncryptionUtil() {}
static byte[] decrypt(SecretKey key, DataInputStream cipherStream) throws IOException {
Objects.requireNonNull(key);
Objects.requireNonNull(cipherStream);
int ivSize = cipherStream.readInt();
if (ivSize < 0 || ivSize > 32) {
throw new IOException("IV out of range: " + ivSize);
}
byte[] iv = new byte[ivSize];
cipherStream.readFully(iv);
int rawCipherTextSize = cipherStream.readInt();
if (rawCipherTextSize < 0) {
throw new IOException("Invalid cipher text size: " + rawCipherTextSize);
}
byte[] rawCipherText = new byte[rawCipherTextSize];
cipherStream.readFully(rawCipherText);
final byte[] plainText;
try {
Cipher c = Cipher.getInstance(CIPHER_ALGO);
c.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv));
plainText = c.doFinal(rawCipherText);
} catch (NoSuchAlgorithmException | InvalidKeyException | BadPaddingException
| IllegalBlockSizeException | NoSuchPaddingException
| InvalidAlgorithmParameterException e) {
throw new IOException("Could not decrypt cipher text", e);
}
return plainText;
}
static byte[] decrypt(SecretKey key, byte[] cipherText) throws IOException {
Objects.requireNonNull(key);
Objects.requireNonNull(cipherText);
DataInputStream cipherStream = new DataInputStream(new ByteArrayInputStream(cipherText));
return decrypt(key, cipherStream);
}
static byte[] encrypt(SecretKey key, byte[] plainText) throws IOException {
Objects.requireNonNull(key);
Objects.requireNonNull(plainText);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
final byte[] cipherText;
final byte[] iv;
try {
Cipher cipher = Cipher.getInstance(CIPHER_ALGO);
cipher.init(Cipher.ENCRYPT_MODE, key);
cipherText = cipher.doFinal(plainText);
iv = cipher.getIV();
} catch (NoSuchAlgorithmException | BadPaddingException | IllegalBlockSizeException
| NoSuchPaddingException | InvalidKeyException e) {
throw new IOException("Could not encrypt input data", e);
}
dos.writeInt(iv.length);
dos.write(iv);
dos.writeInt(cipherText.length);
dos.write(cipherText);
return bos.toByteArray();
}
}

View File

@@ -16,22 +16,14 @@
package com.android.server.locksettings;
import com.android.internal.util.Preconditions;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Objects;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.SecretKey;
/**
* Holds the data necessary to complete a reboot escrow of the Synthetic Password.
@@ -41,22 +33,17 @@ class RebootEscrowData {
* This is the current version of the escrow data format. This should be incremented if the
* format on disk is changed.
*/
private static final int CURRENT_VERSION = 1;
private static final int CURRENT_VERSION = 2;
/** The algorithm used for the encryption of the key blob. */
private static final String CIPHER_ALGO = "AES/GCM/NoPadding";
private RebootEscrowData(byte spVersion, byte[] iv, byte[] syntheticPassword, byte[] blob,
private RebootEscrowData(byte spVersion, byte[] syntheticPassword, byte[] blob,
RebootEscrowKey key) {
mSpVersion = spVersion;
mIv = iv;
mSyntheticPassword = syntheticPassword;
mBlob = blob;
mKey = key;
}
private final byte mSpVersion;
private final byte[] mIv;
private final byte[] mSyntheticPassword;
private final byte[] mBlob;
private final RebootEscrowKey mKey;
@@ -65,10 +52,6 @@ class RebootEscrowData {
return mSpVersion;
}
public byte[] getIv() {
return mIv;
}
public byte[] getSyntheticPassword() {
return mSyntheticPassword;
}
@@ -81,76 +64,43 @@ class RebootEscrowData {
return mKey;
}
static RebootEscrowData fromEncryptedData(RebootEscrowKey key, byte[] blob)
static RebootEscrowData fromEncryptedData(RebootEscrowKey ks, byte[] blob, SecretKey kk)
throws IOException {
Preconditions.checkNotNull(key);
Preconditions.checkNotNull(blob);
Objects.requireNonNull(ks);
Objects.requireNonNull(blob);
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(blob));
int version = dis.readInt();
if (version != CURRENT_VERSION) {
throw new IOException("Unsupported version " + version);
}
byte spVersion = dis.readByte();
int ivSize = dis.readInt();
if (ivSize < 0 || ivSize > 32) {
throw new IOException("IV out of range: " + ivSize);
}
byte[] iv = new byte[ivSize];
dis.readFully(iv);
// Decrypt the blob with the key from keystore first, then decrypt again with the reboot
// escrow key.
byte[] ksEncryptedBlob = AesEncryptionUtil.decrypt(kk, dis);
final byte[] syntheticPassword = AesEncryptionUtil.decrypt(ks.getKey(), ksEncryptedBlob);
int cipherTextSize = dis.readInt();
if (cipherTextSize < 0) {
throw new IOException("Invalid cipher text size: " + cipherTextSize);
}
byte[] cipherText = new byte[cipherTextSize];
dis.readFully(cipherText);
final byte[] syntheticPassword;
try {
Cipher c = Cipher.getInstance(CIPHER_ALGO);
c.init(Cipher.DECRYPT_MODE, key.getKey(), new IvParameterSpec(iv));
syntheticPassword = c.doFinal(cipherText);
} catch (NoSuchAlgorithmException | InvalidKeyException | BadPaddingException
| IllegalBlockSizeException | NoSuchPaddingException
| InvalidAlgorithmParameterException e) {
throw new IOException("Could not decrypt ciphertext", e);
}
return new RebootEscrowData(spVersion, iv, syntheticPassword, blob, key);
return new RebootEscrowData(spVersion, syntheticPassword, blob, ks);
}
static RebootEscrowData fromSyntheticPassword(RebootEscrowKey key, byte spVersion,
byte[] syntheticPassword)
static RebootEscrowData fromSyntheticPassword(RebootEscrowKey ks, byte spVersion,
byte[] syntheticPassword, SecretKey kk)
throws IOException {
Preconditions.checkNotNull(syntheticPassword);
Objects.requireNonNull(syntheticPassword);
// Encrypt synthetic password with the escrow key first; then encrypt the blob again with
// the key from keystore.
byte[] ksEncryptedBlob = AesEncryptionUtil.encrypt(ks.getKey(), syntheticPassword);
byte[] kkEncryptedBlob = AesEncryptionUtil.encrypt(kk, ksEncryptedBlob);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
final byte[] cipherText;
final byte[] iv;
try {
Cipher cipher = Cipher.getInstance(CIPHER_ALGO);
cipher.init(Cipher.ENCRYPT_MODE, key.getKey());
cipherText = cipher.doFinal(syntheticPassword);
iv = cipher.getIV();
} catch (NoSuchAlgorithmException | BadPaddingException | IllegalBlockSizeException
| NoSuchPaddingException | InvalidKeyException e) {
throw new IOException("Could not encrypt reboot escrow data", e);
}
dos.writeInt(CURRENT_VERSION);
dos.writeByte(spVersion);
dos.writeInt(iv.length);
dos.write(iv);
dos.writeInt(cipherText.length);
dos.write(cipherText);
dos.write(kkEncryptedBlob);
return new RebootEscrowData(spVersion, iv, syntheticPassword, bos.toByteArray(),
key);
return new RebootEscrowData(spVersion, syntheticPassword, bos.toByteArray(), ks);
}
}

View File

@@ -0,0 +1,134 @@
/*
* Copyright (C) 2020 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.locksettings;
import android.security.keystore.AndroidKeyStoreSpi;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.security.keystore2.AndroidKeyStoreLoadStoreParameter;
import android.security.keystore2.AndroidKeyStoreProvider;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
/**
* This class loads and generates the key used for resume on reboot from android keystore.
*/
public class RebootEscrowKeyStoreManager {
private static final String TAG = "RebootEscrowKeyStoreManager";
/**
* The key alias in keystore. This key is used to wrap both escrow key and escrow data.
*/
public static final String REBOOT_ESCROW_KEY_STORE_ENCRYPTION_KEY_NAME =
"reboot_escrow_key_store_encryption_key";
public static final int KEY_LENGTH = 256;
/**
* Use keystore2 once it's installed.
*/
private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeystore";
/**
* The selinux namespace for resume_on_reboot_key
*/
private static final int KEY_STORE_NAMESPACE = 120;
/**
* Hold this lock when getting or generating the encryption key in keystore.
*/
private final Object mKeyStoreLock = new Object();
@GuardedBy("mKeyStoreLock")
private SecretKey getKeyStoreEncryptionKeyLocked() {
try {
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE_PROVIDER);
KeyStore.LoadStoreParameter loadStoreParameter = null;
// Load from the specific namespace if keystore2 is enabled.
if (AndroidKeyStoreProvider.isInstalled()) {
loadStoreParameter = new AndroidKeyStoreLoadStoreParameter(KEY_STORE_NAMESPACE);
}
keyStore.load(loadStoreParameter);
return (SecretKey) keyStore.getKey(REBOOT_ESCROW_KEY_STORE_ENCRYPTION_KEY_NAME,
null);
} catch (IOException | GeneralSecurityException e) {
Slog.e(TAG, "Unable to get encryption key from keystore.", e);
}
return null;
}
protected SecretKey getKeyStoreEncryptionKey() {
synchronized (mKeyStoreLock) {
return getKeyStoreEncryptionKeyLocked();
}
}
protected void clearKeyStoreEncryptionKey() {
synchronized (mKeyStoreLock) {
try {
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE_PROVIDER);
KeyStore.LoadStoreParameter loadStoreParameter = null;
// Load from the specific namespace if keystore2 is enabled.
if (AndroidKeyStoreProvider.isInstalled()) {
loadStoreParameter = new AndroidKeyStoreLoadStoreParameter(KEY_STORE_NAMESPACE);
}
keyStore.load(loadStoreParameter);
keyStore.deleteEntry(REBOOT_ESCROW_KEY_STORE_ENCRYPTION_KEY_NAME);
} catch (IOException | GeneralSecurityException e) {
Slog.e(TAG, "Unable to delete encryption key in keystore.", e);
}
}
}
protected SecretKey generateKeyStoreEncryptionKeyIfNeeded() {
synchronized (mKeyStoreLock) {
SecretKey kk = getKeyStoreEncryptionKeyLocked();
if (kk != null) {
return kk;
}
try {
KeyGenerator generator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES, AndroidKeyStoreSpi.NAME);
KeyGenParameterSpec.Builder parameterSpecBuilder = new KeyGenParameterSpec.Builder(
REBOOT_ESCROW_KEY_STORE_ENCRYPTION_KEY_NAME,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setKeySize(KEY_LENGTH)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE);
// Generate the key with the correct namespace if keystore2 is enabled.
if (AndroidKeyStoreProvider.isInstalled()) {
parameterSpecBuilder.setNamespace(KEY_STORE_NAMESPACE);
}
generator.init(parameterSpecBuilder.build());
return generator.generateKey();
} catch (GeneralSecurityException e) {
// Should never happen.
Slog.e(TAG, "Unable to generate key from keystore.", e);
}
return null;
}
}
}

View File

@@ -40,6 +40,18 @@ import java.util.Date;
import java.util.List;
import java.util.Locale;
import javax.crypto.SecretKey;
/**
* This class aims to persists the synthetic password(SP) across reboot in a secure way. In
* particular, it manages the encryption of the sp before reboot, and decryption of the sp after
* reboot. Here are the meaning of some terms.
* SP: synthetic password
* K_s: The RebootEscrowKey, i.e. AES-GCM key stored in memory
* K_k: AES-GCM key in android keystore
* RebootEscrowData: The synthetic password and its encrypted blob. We encrypt SP with K_s first,
* then with K_k, i.e. E(K_k, E(K_s, SP))
*/
class RebootEscrowManager {
private static final String TAG = "RebootEscrowManager";
@@ -101,6 +113,8 @@ class RebootEscrowManager {
private final Callbacks mCallbacks;
private final RebootEscrowKeyStoreManager mKeyStoreManager;
interface Callbacks {
boolean isUserSecure(int userId);
@@ -109,11 +123,13 @@ class RebootEscrowManager {
static class Injector {
protected Context mContext;
private final RebootEscrowKeyStoreManager mKeyStoreManager;
private final RebootEscrowProviderInterface mRebootEscrowProvider;
Injector(Context context) {
mContext = context;
mKeyStoreManager = new RebootEscrowKeyStoreManager();
RebootEscrowProviderInterface rebootEscrowProvider = null;
// TODO(xunchang) add implementation for server based ror.
if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_OTA,
@@ -138,6 +154,10 @@ class RebootEscrowManager {
return (UserManager) mContext.getSystemService(Context.USER_SERVICE);
}
public RebootEscrowKeyStoreManager getKeyStoreManager() {
return mKeyStoreManager;
}
public RebootEscrowProviderInterface getRebootEscrowProvider() {
return mRebootEscrowProvider;
}
@@ -168,6 +188,7 @@ class RebootEscrowManager {
mStorage = storage;
mUserManager = injector.getUserManager();
mEventLog = injector.getEventLog();
mKeyStoreManager = injector.getKeyStoreManager();
}
void loadRebootEscrowDataIfAvailable() {
@@ -183,8 +204,12 @@ class RebootEscrowManager {
return;
}
RebootEscrowKey escrowKey = getAndClearRebootEscrowKey();
if (escrowKey == null) {
// Fetch the key from keystore to decrypt the escrow data & escrow key; this key is
// generated before reboot. Note that we will clear the escrow key even if the keystore key
// is null.
SecretKey kk = mKeyStoreManager.getKeyStoreEncryptionKey();
RebootEscrowKey escrowKey = getAndClearRebootEscrowKey(kk);
if (kk == null || escrowKey == null) {
Slog.w(TAG, "Had reboot escrow data for users, but no key; removing escrow storage.");
for (UserInfo user : users) {
mStorage.removeRebootEscrow(user.id);
@@ -197,7 +222,7 @@ class RebootEscrowManager {
boolean allUsersUnlocked = true;
for (UserInfo user : rebootEscrowUsers) {
allUsersUnlocked &= restoreRebootEscrowForUser(user.id, escrowKey);
allUsersUnlocked &= restoreRebootEscrowForUser(user.id, escrowKey, kk);
}
onEscrowRestoreComplete(allUsersUnlocked);
}
@@ -212,7 +237,7 @@ class RebootEscrowManager {
}
}
private RebootEscrowKey getAndClearRebootEscrowKey() {
private RebootEscrowKey getAndClearRebootEscrowKey(SecretKey kk) {
RebootEscrowProviderInterface rebootEscrowProvider = mInjector.getRebootEscrowProvider();
if (rebootEscrowProvider == null) {
Slog.w(TAG,
@@ -220,14 +245,16 @@ class RebootEscrowManager {
return null;
}
RebootEscrowKey key = rebootEscrowProvider.getAndClearRebootEscrowKey(null);
// The K_s blob maybe encrypted by K_k as well.
RebootEscrowKey key = rebootEscrowProvider.getAndClearRebootEscrowKey(kk);
if (key != null) {
mEventLog.addEntry(RebootEscrowEvent.RETRIEVED_STORED_KEK);
}
return key;
}
private boolean restoreRebootEscrowForUser(@UserIdInt int userId, RebootEscrowKey key) {
private boolean restoreRebootEscrowForUser(@UserIdInt int userId, RebootEscrowKey ks,
SecretKey kk) {
if (!mStorage.hasRebootEscrow(userId)) {
return false;
}
@@ -236,7 +263,7 @@ class RebootEscrowManager {
byte[] blob = mStorage.readRebootEscrow(userId);
mStorage.removeRebootEscrow(userId);
RebootEscrowData escrowData = RebootEscrowData.fromEncryptedData(key, blob);
RebootEscrowData escrowData = RebootEscrowData.fromEncryptedData(ks, blob, kk);
mCallbacks.onRebootEscrowRestored(escrowData.getSpVersion(),
escrowData.getSyntheticPassword(), userId);
@@ -246,6 +273,9 @@ class RebootEscrowManager {
} catch (IOException e) {
Slog.w(TAG, "Could not load reboot escrow data for user " + userId, e);
return false;
} finally {
// Clear the old key in keystore. A new key will be generated by new RoR requests.
mKeyStoreManager.clearKeyStoreEncryptionKey();
}
}
@@ -267,11 +297,16 @@ class RebootEscrowManager {
return;
}
SecretKey kk = mKeyStoreManager.generateKeyStoreEncryptionKeyIfNeeded();
if (kk == null) {
Slog.e(TAG, "Failed to generate encryption key from keystore.");
return;
}
final RebootEscrowData escrowData;
try {
// TODO(xunchang) further wrap the escrowData with a key from keystore.
escrowData = RebootEscrowData.fromSyntheticPassword(escrowKey, spVersion,
syntheticPassword);
syntheticPassword, kk);
} catch (IOException e) {
setRebootEscrowReady(false);
Slog.w(TAG, "Could not escrow reboot data", e);
@@ -348,7 +383,13 @@ class RebootEscrowManager {
return false;
}
boolean armedRebootEscrow = rebootEscrowProvider.storeRebootEscrowKey(escrowKey, null);
// We will use the same key from keystore to encrypt the escrow key and escrow data blob.
SecretKey kk = mKeyStoreManager.getKeyStoreEncryptionKey();
if (kk == null) {
Slog.e(TAG, "Failed to get encryption key from keystore.");
return false;
}
boolean armedRebootEscrow = rebootEscrowProvider.storeRebootEscrowKey(escrowKey, kk);
if (armedRebootEscrow) {
mStorage.setInt(REBOOT_ESCROW_ARMED_KEY, mInjector.getBootCount(), USER_SYSTEM);
mEventLog.addEntry(RebootEscrowEvent.SET_ARMED_STATUS);

View File

@@ -19,22 +19,44 @@ package com.android.server.locksettings;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.security.GeneralSecurityException;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
/**
* atest FrameworksServicesTests:RebootEscrowDataTest
*/
@RunWith(AndroidJUnit4.class)
public class RebootEscrowDataTest {
private RebootEscrowKey mKey;
private SecretKey mKeyStoreEncryptionKey;
private SecretKey generateNewRebootEscrowEncryptionKey() throws GeneralSecurityException {
KeyGenerator generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
generator.init(new KeyGenParameterSpec.Builder(
"reboot_escrow_data_test_key",
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setKeySize(256)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build());
return generator.generateKey();
}
@Before
public void generateKey() throws Exception {
mKey = RebootEscrowKey.generate();
mKeyStoreEncryptionKey = generateNewRebootEscrowEncryptionKey();
}
private static byte[] getTestSp() {
@@ -47,36 +69,49 @@ public class RebootEscrowDataTest {
@Test(expected = NullPointerException.class)
public void fromEntries_failsOnNull() throws Exception {
RebootEscrowData.fromSyntheticPassword(mKey, (byte) 2, null);
RebootEscrowData.fromSyntheticPassword(mKey, (byte) 2, null, mKeyStoreEncryptionKey);
}
@Test(expected = NullPointerException.class)
public void fromEncryptedData_failsOnNullData() throws Exception {
byte[] testSp = getTestSp();
RebootEscrowData expected = RebootEscrowData.fromSyntheticPassword(mKey, (byte) 2, testSp);
RebootEscrowData expected = RebootEscrowData.fromSyntheticPassword(mKey, (byte) 2, testSp,
mKeyStoreEncryptionKey);
RebootEscrowKey key = RebootEscrowKey.fromKeyBytes(expected.getKey().getKeyBytes());
RebootEscrowData.fromEncryptedData(key, null);
RebootEscrowData.fromEncryptedData(key, null, mKeyStoreEncryptionKey);
}
@Test(expected = NullPointerException.class)
public void fromEncryptedData_failsOnNullKey() throws Exception {
byte[] testSp = getTestSp();
RebootEscrowData expected = RebootEscrowData.fromSyntheticPassword(mKey, (byte) 2, testSp);
RebootEscrowData.fromEncryptedData(null, expected.getBlob());
RebootEscrowData expected = RebootEscrowData.fromSyntheticPassword(mKey, (byte) 2, testSp,
mKeyStoreEncryptionKey);
RebootEscrowData.fromEncryptedData(null, expected.getBlob(), mKeyStoreEncryptionKey);
}
@Test
public void fromEntries_loopback_success() throws Exception {
byte[] testSp = getTestSp();
RebootEscrowData expected = RebootEscrowData.fromSyntheticPassword(mKey, (byte) 2, testSp);
RebootEscrowData expected = RebootEscrowData.fromSyntheticPassword(mKey, (byte) 2, testSp,
mKeyStoreEncryptionKey);
RebootEscrowKey key = RebootEscrowKey.fromKeyBytes(expected.getKey().getKeyBytes());
RebootEscrowData actual = RebootEscrowData.fromEncryptedData(key, expected.getBlob());
RebootEscrowData actual = RebootEscrowData.fromEncryptedData(key, expected.getBlob(),
mKeyStoreEncryptionKey);
assertThat(actual.getSpVersion(), is(expected.getSpVersion()));
assertThat(actual.getIv(), is(expected.getIv()));
assertThat(actual.getKey().getKeyBytes(), is(expected.getKey().getKeyBytes()));
assertThat(actual.getBlob(), is(expected.getBlob()));
assertThat(actual.getSyntheticPassword(), is(expected.getSyntheticPassword()));
}
@Test
public void aesEncryptedBlob_loopback_success() throws Exception {
byte[] testSp = getTestSp();
byte [] encrypted = AesEncryptionUtil.encrypt(mKeyStoreEncryptionKey, testSp);
byte [] decrypted = AesEncryptionUtil.decrypt(mKeyStoreEncryptionKey, encrypted);
assertThat(decrypted, is(testSp));
}
}

View File

@@ -61,6 +61,9 @@ import org.mockito.ArgumentCaptor;
import java.io.File;
import java.util.ArrayList;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
@SmallTest
@Presubmit
@RunWith(AndroidJUnit4.class)
@@ -77,15 +80,25 @@ public class RebootEscrowManagerTests {
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
};
// Hex encoding of a randomly generated AES key for test.
private static final byte[] TEST_AES_KEY = new byte[] {
0x48, 0x19, 0x12, 0x54, 0x13, 0x13, 0x52, 0x31,
0x44, 0x74, 0x61, 0x54, 0x29, 0x74, 0x37, 0x61,
0x70, 0x70, 0x75, 0x25, 0x27, 0x31, 0x49, 0x09,
0x26, 0x52, 0x72, 0x63, 0x63, 0x61, 0x78, 0x23,
};
private Context mContext;
private UserManager mUserManager;
private RebootEscrowManager.Callbacks mCallbacks;
private IRebootEscrow mRebootEscrow;
private RebootEscrowKeyStoreManager mKeyStoreManager;
LockSettingsStorageTestable mStorage;
private MockableRebootEscrowInjected mInjected;
private RebootEscrowManager mService;
private SecretKey mAesKey;
public interface MockableRebootEscrowInjected {
int getBootCount();
@@ -98,9 +111,11 @@ public class RebootEscrowManagerTests {
private final RebootEscrowProviderInterface mRebootEscrowProvider;
private final UserManager mUserManager;
private final MockableRebootEscrowInjected mInjected;
private final RebootEscrowKeyStoreManager mKeyStoreManager;
MockInjector(Context context, UserManager userManager,
IRebootEscrow rebootEscrow,
RebootEscrowKeyStoreManager keyStoreManager,
MockableRebootEscrowInjected injected) {
super(context);
mRebootEscrow = rebootEscrow;
@@ -114,6 +129,7 @@ public class RebootEscrowManagerTests {
};
mRebootEscrowProvider = new RebootEscrowProviderHalImpl(halInjector);
mUserManager = userManager;
mKeyStoreManager = keyStoreManager;
mInjected = injected;
}
@@ -127,6 +143,11 @@ public class RebootEscrowManagerTests {
return mRebootEscrowProvider;
}
@Override
public RebootEscrowKeyStoreManager getKeyStoreManager() {
return mKeyStoreManager;
}
@Override
public int getBootCount() {
return mInjected.getBootCount();
@@ -144,6 +165,11 @@ public class RebootEscrowManagerTests {
mUserManager = mock(UserManager.class);
mCallbacks = mock(RebootEscrowManager.Callbacks.class);
mRebootEscrow = mock(IRebootEscrow.class);
mKeyStoreManager = mock(RebootEscrowKeyStoreManager.class);
mAesKey = new SecretKeySpec(TEST_AES_KEY, "AES");
when(mKeyStoreManager.getKeyStoreEncryptionKey()).thenReturn(mAesKey);
when(mKeyStoreManager.generateKeyStoreEncryptionKeyIfNeeded()).thenReturn(mAesKey);
mStorage = new LockSettingsStorageTestable(mContext,
new File(InstrumentationRegistry.getContext().getFilesDir(), "locksettings"));
@@ -160,7 +186,7 @@ public class RebootEscrowManagerTests {
when(mCallbacks.isUserSecure(SECURE_SECONDARY_USER_ID)).thenReturn(true);
mInjected = mock(MockableRebootEscrowInjected.class);
mService = new RebootEscrowManager(new MockInjector(mContext, mUserManager, mRebootEscrow,
mInjected), mCallbacks, mStorage);
mKeyStoreManager, mInjected), mCallbacks, mStorage);
}
@Test
@@ -213,6 +239,7 @@ public class RebootEscrowManagerTests {
assertNotNull(
mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_ARMED_KEY, null, USER_SYSTEM));
verify(mRebootEscrow).storeKey(any());
verify(mKeyStoreManager).getKeyStoreEncryptionKey();
assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
assertFalse(mStorage.hasRebootEscrow(NONSECURE_SECONDARY_USER_ID));
@@ -300,6 +327,7 @@ public class RebootEscrowManagerTests {
ArgumentCaptor<byte[]> keyByteCaptor = ArgumentCaptor.forClass(byte[].class);
assertTrue(mService.armRebootEscrowIfNeeded());
verify(mRebootEscrow).storeKey(keyByteCaptor.capture());
verify(mKeyStoreManager).getKeyStoreEncryptionKey();
assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
assertFalse(mStorage.hasRebootEscrow(NONSECURE_SECONDARY_USER_ID));
@@ -314,6 +342,7 @@ public class RebootEscrowManagerTests {
mService.loadRebootEscrowDataIfAvailable();
verify(mRebootEscrow).retrieveKey();
assertTrue(metricsSuccessCaptor.getValue());
verify(mKeyStoreManager).clearKeyStoreEncryptionKey();
}
@Test