Merge changes Id0e18bef,Ie2b5f559 am: 5bf384cae9
Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1531240 MUST ONLY BE SUBMITTED BY AUTOMERGER Change-Id: Ifbdaa08e4e467a950c21f749ea298e1182bb05b0
This commit is contained in:
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,22 +16,14 @@
|
|||||||
|
|
||||||
package com.android.server.locksettings;
|
package com.android.server.locksettings;
|
||||||
|
|
||||||
import com.android.internal.util.Preconditions;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.DataInputStream;
|
import java.io.DataInputStream;
|
||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.util.Objects;
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
|
|
||||||
import javax.crypto.BadPaddingException;
|
import javax.crypto.SecretKey;
|
||||||
import javax.crypto.Cipher;
|
|
||||||
import javax.crypto.IllegalBlockSizeException;
|
|
||||||
import javax.crypto.NoSuchPaddingException;
|
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds the data necessary to complete a reboot escrow of the Synthetic Password.
|
* 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
|
* This is the current version of the escrow data format. This should be incremented if the
|
||||||
* format on disk is changed.
|
* 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 RebootEscrowData(byte spVersion, byte[] syntheticPassword, byte[] blob,
|
||||||
private static final String CIPHER_ALGO = "AES/GCM/NoPadding";
|
|
||||||
|
|
||||||
private RebootEscrowData(byte spVersion, byte[] iv, byte[] syntheticPassword, byte[] blob,
|
|
||||||
RebootEscrowKey key) {
|
RebootEscrowKey key) {
|
||||||
mSpVersion = spVersion;
|
mSpVersion = spVersion;
|
||||||
mIv = iv;
|
|
||||||
mSyntheticPassword = syntheticPassword;
|
mSyntheticPassword = syntheticPassword;
|
||||||
mBlob = blob;
|
mBlob = blob;
|
||||||
mKey = key;
|
mKey = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final byte mSpVersion;
|
private final byte mSpVersion;
|
||||||
private final byte[] mIv;
|
|
||||||
private final byte[] mSyntheticPassword;
|
private final byte[] mSyntheticPassword;
|
||||||
private final byte[] mBlob;
|
private final byte[] mBlob;
|
||||||
private final RebootEscrowKey mKey;
|
private final RebootEscrowKey mKey;
|
||||||
@@ -65,10 +52,6 @@ class RebootEscrowData {
|
|||||||
return mSpVersion;
|
return mSpVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getIv() {
|
|
||||||
return mIv;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getSyntheticPassword() {
|
public byte[] getSyntheticPassword() {
|
||||||
return mSyntheticPassword;
|
return mSyntheticPassword;
|
||||||
}
|
}
|
||||||
@@ -81,76 +64,43 @@ class RebootEscrowData {
|
|||||||
return mKey;
|
return mKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
static RebootEscrowData fromEncryptedData(RebootEscrowKey key, byte[] blob)
|
static RebootEscrowData fromEncryptedData(RebootEscrowKey ks, byte[] blob, SecretKey kk)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
Preconditions.checkNotNull(key);
|
Objects.requireNonNull(ks);
|
||||||
Preconditions.checkNotNull(blob);
|
Objects.requireNonNull(blob);
|
||||||
|
|
||||||
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(blob));
|
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(blob));
|
||||||
int version = dis.readInt();
|
int version = dis.readInt();
|
||||||
if (version != CURRENT_VERSION) {
|
if (version != CURRENT_VERSION) {
|
||||||
throw new IOException("Unsupported version " + version);
|
throw new IOException("Unsupported version " + version);
|
||||||
}
|
}
|
||||||
|
|
||||||
byte spVersion = dis.readByte();
|
byte spVersion = dis.readByte();
|
||||||
|
|
||||||
int ivSize = dis.readInt();
|
// Decrypt the blob with the key from keystore first, then decrypt again with the reboot
|
||||||
if (ivSize < 0 || ivSize > 32) {
|
// escrow key.
|
||||||
throw new IOException("IV out of range: " + ivSize);
|
byte[] ksEncryptedBlob = AesEncryptionUtil.decrypt(kk, dis);
|
||||||
}
|
final byte[] syntheticPassword = AesEncryptionUtil.decrypt(ks.getKey(), ksEncryptedBlob);
|
||||||
byte[] iv = new byte[ivSize];
|
|
||||||
dis.readFully(iv);
|
|
||||||
|
|
||||||
int cipherTextSize = dis.readInt();
|
return new RebootEscrowData(spVersion, syntheticPassword, blob, ks);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static RebootEscrowData fromSyntheticPassword(RebootEscrowKey key, byte spVersion,
|
static RebootEscrowData fromSyntheticPassword(RebootEscrowKey ks, byte spVersion,
|
||||||
byte[] syntheticPassword)
|
byte[] syntheticPassword, SecretKey kk)
|
||||||
throws IOException {
|
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();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
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.writeInt(CURRENT_VERSION);
|
||||||
dos.writeByte(spVersion);
|
dos.writeByte(spVersion);
|
||||||
dos.writeInt(iv.length);
|
dos.write(kkEncryptedBlob);
|
||||||
dos.write(iv);
|
|
||||||
dos.writeInt(cipherText.length);
|
|
||||||
dos.write(cipherText);
|
|
||||||
|
|
||||||
return new RebootEscrowData(spVersion, iv, syntheticPassword, bos.toByteArray(),
|
return new RebootEscrowData(spVersion, syntheticPassword, bos.toByteArray(), ks);
|
||||||
key);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,6 +40,18 @@ import java.util.Date;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
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 {
|
class RebootEscrowManager {
|
||||||
private static final String TAG = "RebootEscrowManager";
|
private static final String TAG = "RebootEscrowManager";
|
||||||
|
|
||||||
@@ -101,6 +113,8 @@ class RebootEscrowManager {
|
|||||||
|
|
||||||
private final Callbacks mCallbacks;
|
private final Callbacks mCallbacks;
|
||||||
|
|
||||||
|
private final RebootEscrowKeyStoreManager mKeyStoreManager;
|
||||||
|
|
||||||
interface Callbacks {
|
interface Callbacks {
|
||||||
boolean isUserSecure(int userId);
|
boolean isUserSecure(int userId);
|
||||||
|
|
||||||
@@ -109,11 +123,13 @@ class RebootEscrowManager {
|
|||||||
|
|
||||||
static class Injector {
|
static class Injector {
|
||||||
protected Context mContext;
|
protected Context mContext;
|
||||||
|
private final RebootEscrowKeyStoreManager mKeyStoreManager;
|
||||||
private final RebootEscrowProviderInterface mRebootEscrowProvider;
|
private final RebootEscrowProviderInterface mRebootEscrowProvider;
|
||||||
|
|
||||||
Injector(Context context) {
|
Injector(Context context) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
|
mKeyStoreManager = new RebootEscrowKeyStoreManager();
|
||||||
|
|
||||||
RebootEscrowProviderInterface rebootEscrowProvider = null;
|
RebootEscrowProviderInterface rebootEscrowProvider = null;
|
||||||
// TODO(xunchang) add implementation for server based ror.
|
// TODO(xunchang) add implementation for server based ror.
|
||||||
if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_OTA,
|
if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_OTA,
|
||||||
@@ -138,6 +154,10 @@ class RebootEscrowManager {
|
|||||||
return (UserManager) mContext.getSystemService(Context.USER_SERVICE);
|
return (UserManager) mContext.getSystemService(Context.USER_SERVICE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RebootEscrowKeyStoreManager getKeyStoreManager() {
|
||||||
|
return mKeyStoreManager;
|
||||||
|
}
|
||||||
|
|
||||||
public RebootEscrowProviderInterface getRebootEscrowProvider() {
|
public RebootEscrowProviderInterface getRebootEscrowProvider() {
|
||||||
return mRebootEscrowProvider;
|
return mRebootEscrowProvider;
|
||||||
}
|
}
|
||||||
@@ -168,6 +188,7 @@ class RebootEscrowManager {
|
|||||||
mStorage = storage;
|
mStorage = storage;
|
||||||
mUserManager = injector.getUserManager();
|
mUserManager = injector.getUserManager();
|
||||||
mEventLog = injector.getEventLog();
|
mEventLog = injector.getEventLog();
|
||||||
|
mKeyStoreManager = injector.getKeyStoreManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
void loadRebootEscrowDataIfAvailable() {
|
void loadRebootEscrowDataIfAvailable() {
|
||||||
@@ -183,8 +204,12 @@ class RebootEscrowManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
RebootEscrowKey escrowKey = getAndClearRebootEscrowKey();
|
// Fetch the key from keystore to decrypt the escrow data & escrow key; this key is
|
||||||
if (escrowKey == null) {
|
// 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.");
|
Slog.w(TAG, "Had reboot escrow data for users, but no key; removing escrow storage.");
|
||||||
for (UserInfo user : users) {
|
for (UserInfo user : users) {
|
||||||
mStorage.removeRebootEscrow(user.id);
|
mStorage.removeRebootEscrow(user.id);
|
||||||
@@ -197,7 +222,7 @@ class RebootEscrowManager {
|
|||||||
|
|
||||||
boolean allUsersUnlocked = true;
|
boolean allUsersUnlocked = true;
|
||||||
for (UserInfo user : rebootEscrowUsers) {
|
for (UserInfo user : rebootEscrowUsers) {
|
||||||
allUsersUnlocked &= restoreRebootEscrowForUser(user.id, escrowKey);
|
allUsersUnlocked &= restoreRebootEscrowForUser(user.id, escrowKey, kk);
|
||||||
}
|
}
|
||||||
onEscrowRestoreComplete(allUsersUnlocked);
|
onEscrowRestoreComplete(allUsersUnlocked);
|
||||||
}
|
}
|
||||||
@@ -212,7 +237,7 @@ class RebootEscrowManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private RebootEscrowKey getAndClearRebootEscrowKey() {
|
private RebootEscrowKey getAndClearRebootEscrowKey(SecretKey kk) {
|
||||||
RebootEscrowProviderInterface rebootEscrowProvider = mInjector.getRebootEscrowProvider();
|
RebootEscrowProviderInterface rebootEscrowProvider = mInjector.getRebootEscrowProvider();
|
||||||
if (rebootEscrowProvider == null) {
|
if (rebootEscrowProvider == null) {
|
||||||
Slog.w(TAG,
|
Slog.w(TAG,
|
||||||
@@ -220,14 +245,16 @@ class RebootEscrowManager {
|
|||||||
return null;
|
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) {
|
if (key != null) {
|
||||||
mEventLog.addEntry(RebootEscrowEvent.RETRIEVED_STORED_KEK);
|
mEventLog.addEntry(RebootEscrowEvent.RETRIEVED_STORED_KEK);
|
||||||
}
|
}
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean restoreRebootEscrowForUser(@UserIdInt int userId, RebootEscrowKey key) {
|
private boolean restoreRebootEscrowForUser(@UserIdInt int userId, RebootEscrowKey ks,
|
||||||
|
SecretKey kk) {
|
||||||
if (!mStorage.hasRebootEscrow(userId)) {
|
if (!mStorage.hasRebootEscrow(userId)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -236,7 +263,7 @@ class RebootEscrowManager {
|
|||||||
byte[] blob = mStorage.readRebootEscrow(userId);
|
byte[] blob = mStorage.readRebootEscrow(userId);
|
||||||
mStorage.removeRebootEscrow(userId);
|
mStorage.removeRebootEscrow(userId);
|
||||||
|
|
||||||
RebootEscrowData escrowData = RebootEscrowData.fromEncryptedData(key, blob);
|
RebootEscrowData escrowData = RebootEscrowData.fromEncryptedData(ks, blob, kk);
|
||||||
|
|
||||||
mCallbacks.onRebootEscrowRestored(escrowData.getSpVersion(),
|
mCallbacks.onRebootEscrowRestored(escrowData.getSpVersion(),
|
||||||
escrowData.getSyntheticPassword(), userId);
|
escrowData.getSyntheticPassword(), userId);
|
||||||
@@ -246,6 +273,9 @@ class RebootEscrowManager {
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Slog.w(TAG, "Could not load reboot escrow data for user " + userId, e);
|
Slog.w(TAG, "Could not load reboot escrow data for user " + userId, e);
|
||||||
return false;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SecretKey kk = mKeyStoreManager.generateKeyStoreEncryptionKeyIfNeeded();
|
||||||
|
if (kk == null) {
|
||||||
|
Slog.e(TAG, "Failed to generate encryption key from keystore.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final RebootEscrowData escrowData;
|
final RebootEscrowData escrowData;
|
||||||
try {
|
try {
|
||||||
// TODO(xunchang) further wrap the escrowData with a key from keystore.
|
|
||||||
escrowData = RebootEscrowData.fromSyntheticPassword(escrowKey, spVersion,
|
escrowData = RebootEscrowData.fromSyntheticPassword(escrowKey, spVersion,
|
||||||
syntheticPassword);
|
syntheticPassword, kk);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
setRebootEscrowReady(false);
|
setRebootEscrowReady(false);
|
||||||
Slog.w(TAG, "Could not escrow reboot data", e);
|
Slog.w(TAG, "Could not escrow reboot data", e);
|
||||||
@@ -348,7 +383,13 @@ class RebootEscrowManager {
|
|||||||
return false;
|
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) {
|
if (armedRebootEscrow) {
|
||||||
mStorage.setInt(REBOOT_ESCROW_ARMED_KEY, mInjector.getBootCount(), USER_SYSTEM);
|
mStorage.setInt(REBOOT_ESCROW_ARMED_KEY, mInjector.getBootCount(), USER_SYSTEM);
|
||||||
mEventLog.addEntry(RebootEscrowEvent.SET_ARMED_STATUS);
|
mEventLog.addEntry(RebootEscrowEvent.SET_ARMED_STATUS);
|
||||||
|
|||||||
@@ -19,22 +19,44 @@ package com.android.server.locksettings;
|
|||||||
import static org.hamcrest.CoreMatchers.is;
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
|
import android.security.keystore.KeyGenParameterSpec;
|
||||||
|
import android.security.keystore.KeyProperties;
|
||||||
|
|
||||||
import androidx.test.runner.AndroidJUnit4;
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
|
||||||
|
import javax.crypto.KeyGenerator;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* atest FrameworksServicesTests:RebootEscrowDataTest
|
* atest FrameworksServicesTests:RebootEscrowDataTest
|
||||||
*/
|
*/
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class RebootEscrowDataTest {
|
public class RebootEscrowDataTest {
|
||||||
private RebootEscrowKey mKey;
|
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
|
@Before
|
||||||
public void generateKey() throws Exception {
|
public void generateKey() throws Exception {
|
||||||
mKey = RebootEscrowKey.generate();
|
mKey = RebootEscrowKey.generate();
|
||||||
|
mKeyStoreEncryptionKey = generateNewRebootEscrowEncryptionKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] getTestSp() {
|
private static byte[] getTestSp() {
|
||||||
@@ -47,36 +69,49 @@ public class RebootEscrowDataTest {
|
|||||||
|
|
||||||
@Test(expected = NullPointerException.class)
|
@Test(expected = NullPointerException.class)
|
||||||
public void fromEntries_failsOnNull() throws Exception {
|
public void fromEntries_failsOnNull() throws Exception {
|
||||||
RebootEscrowData.fromSyntheticPassword(mKey, (byte) 2, null);
|
RebootEscrowData.fromSyntheticPassword(mKey, (byte) 2, null, mKeyStoreEncryptionKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = NullPointerException.class)
|
@Test(expected = NullPointerException.class)
|
||||||
public void fromEncryptedData_failsOnNullData() throws Exception {
|
public void fromEncryptedData_failsOnNullData() throws Exception {
|
||||||
byte[] testSp = getTestSp();
|
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());
|
RebootEscrowKey key = RebootEscrowKey.fromKeyBytes(expected.getKey().getKeyBytes());
|
||||||
RebootEscrowData.fromEncryptedData(key, null);
|
RebootEscrowData.fromEncryptedData(key, null, mKeyStoreEncryptionKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = NullPointerException.class)
|
@Test(expected = NullPointerException.class)
|
||||||
public void fromEncryptedData_failsOnNullKey() throws Exception {
|
public void fromEncryptedData_failsOnNullKey() throws Exception {
|
||||||
byte[] testSp = getTestSp();
|
byte[] testSp = getTestSp();
|
||||||
RebootEscrowData expected = RebootEscrowData.fromSyntheticPassword(mKey, (byte) 2, testSp);
|
RebootEscrowData expected = RebootEscrowData.fromSyntheticPassword(mKey, (byte) 2, testSp,
|
||||||
RebootEscrowData.fromEncryptedData(null, expected.getBlob());
|
mKeyStoreEncryptionKey);
|
||||||
|
RebootEscrowData.fromEncryptedData(null, expected.getBlob(), mKeyStoreEncryptionKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void fromEntries_loopback_success() throws Exception {
|
public void fromEntries_loopback_success() throws Exception {
|
||||||
byte[] testSp = getTestSp();
|
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());
|
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.getSpVersion(), is(expected.getSpVersion()));
|
||||||
assertThat(actual.getIv(), is(expected.getIv()));
|
|
||||||
assertThat(actual.getKey().getKeyBytes(), is(expected.getKey().getKeyBytes()));
|
assertThat(actual.getKey().getKeyBytes(), is(expected.getKey().getKeyBytes()));
|
||||||
assertThat(actual.getBlob(), is(expected.getBlob()));
|
assertThat(actual.getBlob(), is(expected.getBlob()));
|
||||||
assertThat(actual.getSyntheticPassword(), is(expected.getSyntheticPassword()));
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,9 @@ import org.mockito.ArgumentCaptor;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@Presubmit
|
@Presubmit
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
@@ -77,15 +80,25 @@ public class RebootEscrowManagerTests {
|
|||||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
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 Context mContext;
|
||||||
private UserManager mUserManager;
|
private UserManager mUserManager;
|
||||||
private RebootEscrowManager.Callbacks mCallbacks;
|
private RebootEscrowManager.Callbacks mCallbacks;
|
||||||
private IRebootEscrow mRebootEscrow;
|
private IRebootEscrow mRebootEscrow;
|
||||||
|
private RebootEscrowKeyStoreManager mKeyStoreManager;
|
||||||
|
|
||||||
LockSettingsStorageTestable mStorage;
|
LockSettingsStorageTestable mStorage;
|
||||||
|
|
||||||
private MockableRebootEscrowInjected mInjected;
|
private MockableRebootEscrowInjected mInjected;
|
||||||
private RebootEscrowManager mService;
|
private RebootEscrowManager mService;
|
||||||
|
private SecretKey mAesKey;
|
||||||
|
|
||||||
public interface MockableRebootEscrowInjected {
|
public interface MockableRebootEscrowInjected {
|
||||||
int getBootCount();
|
int getBootCount();
|
||||||
@@ -98,9 +111,11 @@ public class RebootEscrowManagerTests {
|
|||||||
private final RebootEscrowProviderInterface mRebootEscrowProvider;
|
private final RebootEscrowProviderInterface mRebootEscrowProvider;
|
||||||
private final UserManager mUserManager;
|
private final UserManager mUserManager;
|
||||||
private final MockableRebootEscrowInjected mInjected;
|
private final MockableRebootEscrowInjected mInjected;
|
||||||
|
private final RebootEscrowKeyStoreManager mKeyStoreManager;
|
||||||
|
|
||||||
MockInjector(Context context, UserManager userManager,
|
MockInjector(Context context, UserManager userManager,
|
||||||
IRebootEscrow rebootEscrow,
|
IRebootEscrow rebootEscrow,
|
||||||
|
RebootEscrowKeyStoreManager keyStoreManager,
|
||||||
MockableRebootEscrowInjected injected) {
|
MockableRebootEscrowInjected injected) {
|
||||||
super(context);
|
super(context);
|
||||||
mRebootEscrow = rebootEscrow;
|
mRebootEscrow = rebootEscrow;
|
||||||
@@ -114,6 +129,7 @@ public class RebootEscrowManagerTests {
|
|||||||
};
|
};
|
||||||
mRebootEscrowProvider = new RebootEscrowProviderHalImpl(halInjector);
|
mRebootEscrowProvider = new RebootEscrowProviderHalImpl(halInjector);
|
||||||
mUserManager = userManager;
|
mUserManager = userManager;
|
||||||
|
mKeyStoreManager = keyStoreManager;
|
||||||
mInjected = injected;
|
mInjected = injected;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,6 +143,11 @@ public class RebootEscrowManagerTests {
|
|||||||
return mRebootEscrowProvider;
|
return mRebootEscrowProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RebootEscrowKeyStoreManager getKeyStoreManager() {
|
||||||
|
return mKeyStoreManager;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getBootCount() {
|
public int getBootCount() {
|
||||||
return mInjected.getBootCount();
|
return mInjected.getBootCount();
|
||||||
@@ -144,6 +165,11 @@ public class RebootEscrowManagerTests {
|
|||||||
mUserManager = mock(UserManager.class);
|
mUserManager = mock(UserManager.class);
|
||||||
mCallbacks = mock(RebootEscrowManager.Callbacks.class);
|
mCallbacks = mock(RebootEscrowManager.Callbacks.class);
|
||||||
mRebootEscrow = mock(IRebootEscrow.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,
|
mStorage = new LockSettingsStorageTestable(mContext,
|
||||||
new File(InstrumentationRegistry.getContext().getFilesDir(), "locksettings"));
|
new File(InstrumentationRegistry.getContext().getFilesDir(), "locksettings"));
|
||||||
@@ -160,7 +186,7 @@ public class RebootEscrowManagerTests {
|
|||||||
when(mCallbacks.isUserSecure(SECURE_SECONDARY_USER_ID)).thenReturn(true);
|
when(mCallbacks.isUserSecure(SECURE_SECONDARY_USER_ID)).thenReturn(true);
|
||||||
mInjected = mock(MockableRebootEscrowInjected.class);
|
mInjected = mock(MockableRebootEscrowInjected.class);
|
||||||
mService = new RebootEscrowManager(new MockInjector(mContext, mUserManager, mRebootEscrow,
|
mService = new RebootEscrowManager(new MockInjector(mContext, mUserManager, mRebootEscrow,
|
||||||
mInjected), mCallbacks, mStorage);
|
mKeyStoreManager, mInjected), mCallbacks, mStorage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -213,6 +239,7 @@ public class RebootEscrowManagerTests {
|
|||||||
assertNotNull(
|
assertNotNull(
|
||||||
mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_ARMED_KEY, null, USER_SYSTEM));
|
mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_ARMED_KEY, null, USER_SYSTEM));
|
||||||
verify(mRebootEscrow).storeKey(any());
|
verify(mRebootEscrow).storeKey(any());
|
||||||
|
verify(mKeyStoreManager).getKeyStoreEncryptionKey();
|
||||||
|
|
||||||
assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
|
assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
|
||||||
assertFalse(mStorage.hasRebootEscrow(NONSECURE_SECONDARY_USER_ID));
|
assertFalse(mStorage.hasRebootEscrow(NONSECURE_SECONDARY_USER_ID));
|
||||||
@@ -300,6 +327,7 @@ public class RebootEscrowManagerTests {
|
|||||||
ArgumentCaptor<byte[]> keyByteCaptor = ArgumentCaptor.forClass(byte[].class);
|
ArgumentCaptor<byte[]> keyByteCaptor = ArgumentCaptor.forClass(byte[].class);
|
||||||
assertTrue(mService.armRebootEscrowIfNeeded());
|
assertTrue(mService.armRebootEscrowIfNeeded());
|
||||||
verify(mRebootEscrow).storeKey(keyByteCaptor.capture());
|
verify(mRebootEscrow).storeKey(keyByteCaptor.capture());
|
||||||
|
verify(mKeyStoreManager).getKeyStoreEncryptionKey();
|
||||||
|
|
||||||
assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
|
assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
|
||||||
assertFalse(mStorage.hasRebootEscrow(NONSECURE_SECONDARY_USER_ID));
|
assertFalse(mStorage.hasRebootEscrow(NONSECURE_SECONDARY_USER_ID));
|
||||||
@@ -314,6 +342,7 @@ public class RebootEscrowManagerTests {
|
|||||||
mService.loadRebootEscrowDataIfAvailable();
|
mService.loadRebootEscrowDataIfAvailable();
|
||||||
verify(mRebootEscrow).retrieveKey();
|
verify(mRebootEscrow).retrieveKey();
|
||||||
assertTrue(metricsSuccessCaptor.getValue());
|
assertTrue(metricsSuccessCaptor.getValue());
|
||||||
|
verify(mKeyStoreManager).clearKeyStoreEncryptionKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user