Merge "Refactor PlatformKeyManger to remove mUserId to fix two tests that fail on clean devices without secure screenlocks"
This commit is contained in:
committed by
Android (Google) Code Review
commit
a1c95b7e0b
@@ -89,7 +89,7 @@ public class KeySyncTask implements Runnable {
|
||||
userId,
|
||||
credentialType,
|
||||
credential,
|
||||
() -> PlatformKeyManager.getInstance(context, recoverableKeyStoreDb, userId));
|
||||
() -> PlatformKeyManager.getInstance(context, recoverableKeyStoreDb));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -246,7 +246,7 @@ public class KeySyncTask implements Runnable {
|
||||
throws InsecureUserException, KeyStoreException, UnrecoverableKeyException,
|
||||
NoSuchAlgorithmException, NoSuchPaddingException, BadPlatformKeyException {
|
||||
PlatformKeyManager platformKeyManager = mPlatformKeyManagerFactory.newInstance();
|
||||
PlatformDecryptionKey decryptKey = platformKeyManager.getDecryptKey();
|
||||
PlatformDecryptionKey decryptKey = platformKeyManager.getDecryptKey(mUserId);
|
||||
Map<String, WrappedKey> wrappedKeys = mRecoverableKeyStoreDb.getAllKeys(
|
||||
mUserId, decryptKey.getGenerationId());
|
||||
return WrappedKey.unwrapKeys(decryptKey, wrappedKeys);
|
||||
|
||||
@@ -71,7 +71,6 @@ public class PlatformKeyManager {
|
||||
private final Context mContext;
|
||||
private final KeyStoreProxy mKeyStore;
|
||||
private final RecoverableKeyStoreDb mDatabase;
|
||||
private final int mUserId;
|
||||
|
||||
private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
|
||||
|
||||
@@ -80,34 +79,25 @@ public class PlatformKeyManager {
|
||||
* defined by {@code context}.
|
||||
*
|
||||
* @param context This should be the context of the RecoverableKeyStoreLoader service.
|
||||
* @param userId The ID of the user to whose lock screen the platform key must be bound.
|
||||
* @throws KeyStoreException if failed to initialize AndroidKeyStore.
|
||||
* @throws NoSuchAlgorithmException if AES is unavailable - should never happen.
|
||||
* @throws InsecureUserException if the user does not have a lock screen set.
|
||||
* @throws SecurityException if the caller does not have permission to write to /data/system.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static PlatformKeyManager getInstance(Context context, RecoverableKeyStoreDb database,
|
||||
int userId)
|
||||
throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException {
|
||||
context = context.getApplicationContext();
|
||||
PlatformKeyManager keyManager = new PlatformKeyManager(
|
||||
userId,
|
||||
context,
|
||||
public static PlatformKeyManager getInstance(Context context, RecoverableKeyStoreDb database)
|
||||
throws KeyStoreException, NoSuchAlgorithmException {
|
||||
return new PlatformKeyManager(
|
||||
context.getApplicationContext(),
|
||||
new KeyStoreProxyImpl(getAndLoadAndroidKeyStore()),
|
||||
database);
|
||||
keyManager.init();
|
||||
return keyManager;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
PlatformKeyManager(
|
||||
int userId,
|
||||
Context context,
|
||||
KeyStoreProxy keyStore,
|
||||
RecoverableKeyStoreDb database) {
|
||||
mUserId = userId;
|
||||
mKeyStore = keyStore;
|
||||
mContext = context;
|
||||
mDatabase = database;
|
||||
@@ -118,67 +108,91 @@ public class PlatformKeyManager {
|
||||
* key has to be replaced. (e.g., because the user has removed and then re-added their lock
|
||||
* screen). Returns -1 if no key has been generated yet.
|
||||
*
|
||||
* @param userId The ID of the user to whose lock screen the platform key must be bound.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public int getGenerationId() {
|
||||
return mDatabase.getPlatformKeyGenerationId(mUserId);
|
||||
public int getGenerationId(int userId) {
|
||||
return mDatabase.getPlatformKeyGenerationId(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the platform key is available. A platform key won't be available if
|
||||
* the user has not set up a lock screen.
|
||||
*
|
||||
* @param userId The ID of the user to whose lock screen the platform key must be bound.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public boolean isAvailable() {
|
||||
return mContext.getSystemService(KeyguardManager.class).isDeviceSecure(mUserId);
|
||||
public boolean isAvailable(int userId) {
|
||||
return mContext.getSystemService(KeyguardManager.class).isDeviceSecure(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new key and increments the generation ID. Should be invoked if the platform key
|
||||
* is corrupted and needs to be rotated.
|
||||
*
|
||||
* @param userId The ID of the user to whose lock screen the platform key must be bound.
|
||||
* @throws NoSuchAlgorithmException if AES is unavailable - should never happen.
|
||||
* @throws KeyStoreException if there is an error in AndroidKeyStore.
|
||||
* @throws InsecureUserException if the user does not have a lock screen set.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public void regenerate() throws NoSuchAlgorithmException, KeyStoreException {
|
||||
int nextId = getGenerationId() + 1;
|
||||
generateAndLoadKey(nextId);
|
||||
public void regenerate(int userId)
|
||||
throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException {
|
||||
if (!isAvailable(userId)) {
|
||||
throw new InsecureUserException(String.format(
|
||||
Locale.US, "%d does not have a lock screen set.", userId));
|
||||
}
|
||||
|
||||
int generationId = getGenerationId(userId);
|
||||
int nextId;
|
||||
if (generationId == -1) {
|
||||
nextId = 1;
|
||||
} else {
|
||||
nextId = generationId + 1;
|
||||
}
|
||||
generateAndLoadKey(userId, nextId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the platform key used for encryption.
|
||||
*
|
||||
* @param userId The ID of the user to whose lock screen the platform key must be bound.
|
||||
* @throws KeyStoreException if there was an AndroidKeyStore error.
|
||||
* @throws UnrecoverableKeyException if the key could not be recovered.
|
||||
* @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
|
||||
* @throws InsecureUserException if the user does not have a lock screen set.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public PlatformEncryptionKey getEncryptKey()
|
||||
throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException {
|
||||
int generationId = getGenerationId();
|
||||
public PlatformEncryptionKey getEncryptKey(int userId) throws KeyStoreException,
|
||||
UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException {
|
||||
init(userId);
|
||||
int generationId = getGenerationId(userId);
|
||||
AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey(
|
||||
getEncryptAlias(generationId), /*password=*/ null);
|
||||
getEncryptAlias(userId, generationId), /*password=*/ null);
|
||||
return new PlatformEncryptionKey(generationId, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the platform key used for decryption. Only works after a recent screen unlock.
|
||||
*
|
||||
* @param userId The ID of the user to whose lock screen the platform key must be bound.
|
||||
* @throws KeyStoreException if there was an AndroidKeyStore error.
|
||||
* @throws UnrecoverableKeyException if the key could not be recovered.
|
||||
* @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
|
||||
* @throws InsecureUserException if the user does not have a lock screen set.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public PlatformDecryptionKey getDecryptKey()
|
||||
throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException {
|
||||
int generationId = getGenerationId();
|
||||
public PlatformDecryptionKey getDecryptKey(int userId) throws KeyStoreException,
|
||||
UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException {
|
||||
init(userId);
|
||||
int generationId = getGenerationId(userId);
|
||||
AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey(
|
||||
getDecryptAlias(generationId), /*password=*/ null);
|
||||
getDecryptAlias(userId, generationId), /*password=*/ null);
|
||||
return new PlatformDecryptionKey(generationId, key);
|
||||
}
|
||||
|
||||
@@ -186,38 +200,36 @@ public class PlatformKeyManager {
|
||||
* Initializes the class. If there is no current platform key, and the user has a lock screen
|
||||
* set, will create the platform key and set the generation ID.
|
||||
*
|
||||
* @param userId The ID of the user to whose lock screen the platform key must be bound.
|
||||
* @throws KeyStoreException if there was an error in AndroidKeyStore.
|
||||
* @throws NoSuchAlgorithmException if AES is unavailable - should never happen.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public void init() throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException {
|
||||
if (!isAvailable()) {
|
||||
void init(int userId)
|
||||
throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException {
|
||||
if (!isAvailable(userId)) {
|
||||
throw new InsecureUserException(String.format(
|
||||
Locale.US, "%d does not have a lock screen set.", mUserId));
|
||||
Locale.US, "%d does not have a lock screen set.", userId));
|
||||
}
|
||||
|
||||
int generationId = getGenerationId();
|
||||
if (isKeyLoaded(generationId)) {
|
||||
int generationId = getGenerationId(userId);
|
||||
if (isKeyLoaded(userId, generationId)) {
|
||||
Log.i(TAG, String.format(
|
||||
Locale.US, "Platform key generation %d exists already.", generationId));
|
||||
return;
|
||||
}
|
||||
if (generationId == -1) {
|
||||
Log.i(TAG, "Generating initial platform ID.");
|
||||
generationId = 1;
|
||||
} else {
|
||||
Log.w(TAG, String.format(Locale.US, "Platform generation ID was %d but no "
|
||||
+ "entry was present in AndroidKeyStore. Generating fresh key.", generationId));
|
||||
}
|
||||
|
||||
if (generationId == -1) {
|
||||
generationId = 1;
|
||||
} else {
|
||||
// Had to generate a fresh key, bump the generation id
|
||||
generationId++;
|
||||
}
|
||||
|
||||
generateAndLoadKey(generationId);
|
||||
generateAndLoadKey(userId, generationId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -227,11 +239,12 @@ public class PlatformKeyManager {
|
||||
* <p>These IDs look as follows:
|
||||
* {@code com.security.recoverablekeystore/platform/<user id>/<generation id>/encrypt}
|
||||
*
|
||||
* @param userId The ID of the user to whose lock screen the platform key must be bound.
|
||||
* @param generationId The generation ID.
|
||||
* @return The alias.
|
||||
*/
|
||||
private String getEncryptAlias(int generationId) {
|
||||
return KEY_ALIAS_PREFIX + mUserId + "/" + generationId + "/" + ENCRYPT_KEY_ALIAS_SUFFIX;
|
||||
private String getEncryptAlias(int userId, int generationId) {
|
||||
return KEY_ALIAS_PREFIX + userId + "/" + generationId + "/" + ENCRYPT_KEY_ALIAS_SUFFIX;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -241,18 +254,19 @@ public class PlatformKeyManager {
|
||||
* <p>These IDs look as follows:
|
||||
* {@code com.security.recoverablekeystore/platform/<user id>/<generation id>/decrypt}
|
||||
*
|
||||
* @param userId The ID of the user to whose lock screen the platform key must be bound.
|
||||
* @param generationId The generation ID.
|
||||
* @return The alias.
|
||||
*/
|
||||
private String getDecryptAlias(int generationId) {
|
||||
return KEY_ALIAS_PREFIX + mUserId + "/" + generationId + "/" + DECRYPT_KEY_ALIAS_SUFFIX;
|
||||
private String getDecryptAlias(int userId, int generationId) {
|
||||
return KEY_ALIAS_PREFIX + userId + "/" + generationId + "/" + DECRYPT_KEY_ALIAS_SUFFIX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current generation ID to {@code generationId}.
|
||||
*/
|
||||
private void setGenerationId(int generationId) {
|
||||
mDatabase.setPlatformKeyGenerationId(mUserId, generationId);
|
||||
private void setGenerationId(int userId, int generationId) {
|
||||
mDatabase.setPlatformKeyGenerationId(userId, generationId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -261,9 +275,9 @@ public class PlatformKeyManager {
|
||||
*
|
||||
* @throws KeyStoreException if there was an error checking AndroidKeyStore.
|
||||
*/
|
||||
private boolean isKeyLoaded(int generationId) throws KeyStoreException {
|
||||
return mKeyStore.containsAlias(getEncryptAlias(generationId))
|
||||
&& mKeyStore.containsAlias(getDecryptAlias(generationId));
|
||||
private boolean isKeyLoaded(int userId, int generationId) throws KeyStoreException {
|
||||
return mKeyStore.containsAlias(getEncryptAlias(userId, generationId))
|
||||
&& mKeyStore.containsAlias(getDecryptAlias(userId, generationId));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -274,10 +288,10 @@ public class PlatformKeyManager {
|
||||
* available since API version 1.
|
||||
* @throws KeyStoreException if there was an issue loading the keys into AndroidKeyStore.
|
||||
*/
|
||||
private void generateAndLoadKey(int generationId)
|
||||
private void generateAndLoadKey(int userId, int generationId)
|
||||
throws NoSuchAlgorithmException, KeyStoreException {
|
||||
String encryptAlias = getEncryptAlias(generationId);
|
||||
String decryptAlias = getDecryptAlias(generationId);
|
||||
String encryptAlias = getEncryptAlias(userId, generationId);
|
||||
String decryptAlias = getDecryptAlias(userId, generationId);
|
||||
SecretKey secretKey = generateAesKey();
|
||||
|
||||
mKeyStore.setEntry(
|
||||
@@ -296,10 +310,10 @@ public class PlatformKeyManager {
|
||||
USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS)
|
||||
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
|
||||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
|
||||
.setBoundToSpecificSecureUserId(mUserId)
|
||||
.setBoundToSpecificSecureUserId(userId)
|
||||
.build());
|
||||
|
||||
setGenerationId(generationId);
|
||||
setGenerationId(userId, generationId);
|
||||
|
||||
try {
|
||||
secretKey.destroy();
|
||||
|
||||
@@ -85,22 +85,34 @@ public class RecoverableKeyStoreManager {
|
||||
private final RecoverySnapshotListenersStorage mListenersStorage;
|
||||
private final RecoverableKeyGenerator mRecoverableKeyGenerator;
|
||||
private final RecoverySnapshotStorage mSnapshotStorage;
|
||||
private final PlatformKeyManager mPlatformKeyManager;
|
||||
|
||||
/**
|
||||
* Returns a new or existing instance.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static synchronized RecoverableKeyStoreManager getInstance(Context mContext) {
|
||||
public static synchronized RecoverableKeyStoreManager getInstance(Context context) {
|
||||
if (mInstance == null) {
|
||||
RecoverableKeyStoreDb db = RecoverableKeyStoreDb.newInstance(mContext);
|
||||
RecoverableKeyStoreDb db = RecoverableKeyStoreDb.newInstance(context);
|
||||
PlatformKeyManager platformKeyManager;
|
||||
try {
|
||||
platformKeyManager = PlatformKeyManager.getInstance(context, db);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
// Impossible: all algorithms must be supported by AOSP
|
||||
throw new RuntimeException(e);
|
||||
} catch (KeyStoreException e) {
|
||||
throw new ServiceSpecificException(ERROR_KEYSTORE_INTERNAL_ERROR, e.getMessage());
|
||||
}
|
||||
|
||||
mInstance = new RecoverableKeyStoreManager(
|
||||
mContext.getApplicationContext(),
|
||||
context.getApplicationContext(),
|
||||
db,
|
||||
new RecoverySessionStorage(),
|
||||
Executors.newSingleThreadExecutor(),
|
||||
new RecoverySnapshotStorage(),
|
||||
new RecoverySnapshotListenersStorage());
|
||||
new RecoverySnapshotListenersStorage(),
|
||||
platformKeyManager);
|
||||
}
|
||||
return mInstance;
|
||||
}
|
||||
@@ -112,13 +124,16 @@ public class RecoverableKeyStoreManager {
|
||||
RecoverySessionStorage recoverySessionStorage,
|
||||
ExecutorService executorService,
|
||||
RecoverySnapshotStorage snapshotStorage,
|
||||
RecoverySnapshotListenersStorage listenersStorage) {
|
||||
RecoverySnapshotListenersStorage listenersStorage,
|
||||
PlatformKeyManager platformKeyManager) {
|
||||
mContext = context;
|
||||
mDatabase = recoverableKeyStoreDb;
|
||||
mRecoverySessionStorage = recoverySessionStorage;
|
||||
mExecutorService = executorService;
|
||||
mListenersStorage = listenersStorage;
|
||||
mSnapshotStorage = snapshotStorage;
|
||||
mPlatformKeyManager = platformKeyManager;
|
||||
|
||||
try {
|
||||
mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mDatabase);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
@@ -380,12 +395,10 @@ public class RecoverableKeyStoreManager {
|
||||
int uid = Binder.getCallingUid();
|
||||
int userId = UserHandle.getCallingUserId();
|
||||
|
||||
PlatformEncryptionKey encryptionKey;
|
||||
|
||||
PlatformEncryptionKey encryptionKey;
|
||||
try {
|
||||
PlatformKeyManager platformKeyManager = PlatformKeyManager.getInstance(
|
||||
mContext, mDatabase, userId);
|
||||
encryptionKey = platformKeyManager.getEncryptKey();
|
||||
encryptionKey = mPlatformKeyManager.getEncryptKey(userId);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
// Impossible: all algorithms must be supported by AOSP
|
||||
throw new RuntimeException(e);
|
||||
|
||||
@@ -114,7 +114,7 @@ public class KeySyncTaskTest {
|
||||
|
||||
mWrappingKey = generateAndroidKeyStoreKey();
|
||||
mEncryptKey = new PlatformEncryptionKey(TEST_GENERATION_ID, mWrappingKey);
|
||||
when(mPlatformKeyManager.getDecryptKey()).thenReturn(
|
||||
when(mPlatformKeyManager.getDecryptKey(TEST_USER_ID)).thenReturn(
|
||||
new PlatformDecryptionKey(TEST_GENERATION_ID, mWrappingKey));
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ public class PlatformKeyManagerTest {
|
||||
mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
|
||||
mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
|
||||
mPlatformKeyManager = new PlatformKeyManager(
|
||||
USER_ID_FIXTURE, mContext, mKeyStoreProxy, mRecoverableKeyStoreDb);
|
||||
mContext, mKeyStoreProxy, mRecoverableKeyStoreDb);
|
||||
|
||||
when(mContext.getSystemService(anyString())).thenReturn(mKeyguardManager);
|
||||
when(mContext.getSystemServiceName(any())).thenReturn("test");
|
||||
@@ -93,7 +93,7 @@ public class PlatformKeyManagerTest {
|
||||
|
||||
@Test
|
||||
public void init_createsEncryptKeyWithCorrectAlias() throws Exception {
|
||||
mPlatformKeyManager.init();
|
||||
mPlatformKeyManager.init(USER_ID_FIXTURE);
|
||||
|
||||
verify(mKeyStoreProxy).setEntry(
|
||||
eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
|
||||
@@ -103,14 +103,14 @@ public class PlatformKeyManagerTest {
|
||||
|
||||
@Test
|
||||
public void init_createsEncryptKeyWithCorrectPurposes() throws Exception {
|
||||
mPlatformKeyManager.init();
|
||||
mPlatformKeyManager.init(USER_ID_FIXTURE);
|
||||
|
||||
assertEquals(KeyProperties.PURPOSE_ENCRYPT, getEncryptKeyProtection().getPurposes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void init_createsEncryptKeyWithCorrectPaddings() throws Exception {
|
||||
mPlatformKeyManager.init();
|
||||
mPlatformKeyManager.init(USER_ID_FIXTURE);
|
||||
|
||||
assertArrayEquals(
|
||||
new String[] { KeyProperties.ENCRYPTION_PADDING_NONE },
|
||||
@@ -119,7 +119,7 @@ public class PlatformKeyManagerTest {
|
||||
|
||||
@Test
|
||||
public void init_createsEncryptKeyWithCorrectBlockModes() throws Exception {
|
||||
mPlatformKeyManager.init();
|
||||
mPlatformKeyManager.init(USER_ID_FIXTURE);
|
||||
|
||||
assertArrayEquals(
|
||||
new String[] { KeyProperties.BLOCK_MODE_GCM },
|
||||
@@ -128,14 +128,14 @@ public class PlatformKeyManagerTest {
|
||||
|
||||
@Test
|
||||
public void init_createsEncryptKeyWithoutAuthenticationRequired() throws Exception {
|
||||
mPlatformKeyManager.init();
|
||||
mPlatformKeyManager.init(USER_ID_FIXTURE);
|
||||
|
||||
assertFalse(getEncryptKeyProtection().isUserAuthenticationRequired());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void init_createsDecryptKeyWithCorrectAlias() throws Exception {
|
||||
mPlatformKeyManager.init();
|
||||
mPlatformKeyManager.init(USER_ID_FIXTURE);
|
||||
|
||||
verify(mKeyStoreProxy).setEntry(
|
||||
eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
|
||||
@@ -145,14 +145,14 @@ public class PlatformKeyManagerTest {
|
||||
|
||||
@Test
|
||||
public void init_createsDecryptKeyWithCorrectPurposes() throws Exception {
|
||||
mPlatformKeyManager.init();
|
||||
mPlatformKeyManager.init(USER_ID_FIXTURE);
|
||||
|
||||
assertEquals(KeyProperties.PURPOSE_DECRYPT, getDecryptKeyProtection().getPurposes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void init_createsDecryptKeyWithCorrectPaddings() throws Exception {
|
||||
mPlatformKeyManager.init();
|
||||
mPlatformKeyManager.init(USER_ID_FIXTURE);
|
||||
|
||||
assertArrayEquals(
|
||||
new String[] { KeyProperties.ENCRYPTION_PADDING_NONE },
|
||||
@@ -161,7 +161,7 @@ public class PlatformKeyManagerTest {
|
||||
|
||||
@Test
|
||||
public void init_createsDecryptKeyWithCorrectBlockModes() throws Exception {
|
||||
mPlatformKeyManager.init();
|
||||
mPlatformKeyManager.init(USER_ID_FIXTURE);
|
||||
|
||||
assertArrayEquals(
|
||||
new String[] { KeyProperties.BLOCK_MODE_GCM },
|
||||
@@ -170,14 +170,14 @@ public class PlatformKeyManagerTest {
|
||||
|
||||
@Test
|
||||
public void init_createsDecryptKeyWithAuthenticationRequired() throws Exception {
|
||||
mPlatformKeyManager.init();
|
||||
mPlatformKeyManager.init(USER_ID_FIXTURE);
|
||||
|
||||
assertTrue(getDecryptKeyProtection().isUserAuthenticationRequired());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void init_createsDecryptKeyWithAuthenticationValidFor15Seconds() throws Exception {
|
||||
mPlatformKeyManager.init();
|
||||
mPlatformKeyManager.init(USER_ID_FIXTURE);
|
||||
|
||||
assertEquals(
|
||||
USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS,
|
||||
@@ -186,7 +186,7 @@ public class PlatformKeyManagerTest {
|
||||
|
||||
@Test
|
||||
public void init_createsDecryptKeyBoundToTheUsersAuthentication() throws Exception {
|
||||
mPlatformKeyManager.init();
|
||||
mPlatformKeyManager.init(USER_ID_FIXTURE);
|
||||
|
||||
assertEquals(
|
||||
USER_ID_FIXTURE,
|
||||
@@ -195,7 +195,7 @@ public class PlatformKeyManagerTest {
|
||||
|
||||
@Test
|
||||
public void init_createsBothKeysWithSameMaterial() throws Exception {
|
||||
mPlatformKeyManager.init();
|
||||
mPlatformKeyManager.init(USER_ID_FIXTURE);
|
||||
|
||||
verify(mKeyStoreProxy, times(2)).setEntry(any(), mEntryArgumentCaptor.capture(), any());
|
||||
List<KeyStore.Entry> entries = mEntryArgumentCaptor.getAllValues();
|
||||
@@ -206,7 +206,7 @@ public class PlatformKeyManagerTest {
|
||||
|
||||
@Test
|
||||
public void init_savesGenerationIdToDatabase() throws Exception {
|
||||
mPlatformKeyManager.init();
|
||||
mPlatformKeyManager.init(USER_ID_FIXTURE);
|
||||
|
||||
assertEquals(1,
|
||||
mRecoverableKeyStoreDb.getPlatformKeyGenerationId(USER_ID_FIXTURE));
|
||||
@@ -214,23 +214,23 @@ public class PlatformKeyManagerTest {
|
||||
|
||||
@Test
|
||||
public void init_setsGenerationIdTo1() throws Exception {
|
||||
mPlatformKeyManager.init();
|
||||
mPlatformKeyManager.init(USER_ID_FIXTURE);
|
||||
|
||||
assertEquals(1, mPlatformKeyManager.getGenerationId());
|
||||
assertEquals(1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void init_incrementsGenerationIdIfKeyIsUnavailable() throws Exception {
|
||||
mPlatformKeyManager.init();
|
||||
mPlatformKeyManager.init(USER_ID_FIXTURE);
|
||||
|
||||
mPlatformKeyManager.init();
|
||||
mPlatformKeyManager.init(USER_ID_FIXTURE);
|
||||
|
||||
assertEquals(2, mPlatformKeyManager.getGenerationId());
|
||||
assertEquals(2, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void init_doesNotIncrementGenerationIdIfKeyAvailable() throws Exception {
|
||||
mPlatformKeyManager.init();
|
||||
mPlatformKeyManager.init(USER_ID_FIXTURE);
|
||||
when(mKeyStoreProxy
|
||||
.containsAlias("com.android.server.locksettings.recoverablekeystore/"
|
||||
+ "platform/42/1/decrypt")).thenReturn(true);
|
||||
@@ -238,21 +238,19 @@ public class PlatformKeyManagerTest {
|
||||
.containsAlias("com.android.server.locksettings.recoverablekeystore/"
|
||||
+ "platform/42/1/encrypt")).thenReturn(true);
|
||||
|
||||
mPlatformKeyManager.init();
|
||||
mPlatformKeyManager.init(USER_ID_FIXTURE);
|
||||
|
||||
assertEquals(1, mPlatformKeyManager.getGenerationId());
|
||||
assertEquals(1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getGenerationId_returnsMinusOneIfNotInitialized() throws Exception {
|
||||
assertEquals(-1, mPlatformKeyManager.getGenerationId());
|
||||
assertEquals(-1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDecryptKey_getsDecryptKeyWithCorrectAlias() throws Exception {
|
||||
mPlatformKeyManager.init();
|
||||
|
||||
mPlatformKeyManager.getDecryptKey();
|
||||
mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);
|
||||
|
||||
verify(mKeyStoreProxy).getKey(
|
||||
eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
|
||||
@@ -261,9 +259,7 @@ public class PlatformKeyManagerTest {
|
||||
|
||||
@Test
|
||||
public void getEncryptKey_getsDecryptKeyWithCorrectAlias() throws Exception {
|
||||
mPlatformKeyManager.init();
|
||||
|
||||
mPlatformKeyManager.getEncryptKey();
|
||||
mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
|
||||
|
||||
verify(mKeyStoreProxy).getKey(
|
||||
eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
|
||||
@@ -272,18 +268,18 @@ public class PlatformKeyManagerTest {
|
||||
|
||||
@Test
|
||||
public void regenerate_incrementsTheGenerationId() throws Exception {
|
||||
mPlatformKeyManager.init();
|
||||
mPlatformKeyManager.init(USER_ID_FIXTURE);
|
||||
|
||||
mPlatformKeyManager.regenerate();
|
||||
mPlatformKeyManager.regenerate(USER_ID_FIXTURE);
|
||||
|
||||
assertEquals(2, mPlatformKeyManager.getGenerationId());
|
||||
assertEquals(2, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void regenerate_generatesANewEncryptKeyWithTheCorrectAlias() throws Exception {
|
||||
mPlatformKeyManager.init();
|
||||
mPlatformKeyManager.init(USER_ID_FIXTURE);
|
||||
|
||||
mPlatformKeyManager.regenerate();
|
||||
mPlatformKeyManager.regenerate(USER_ID_FIXTURE);
|
||||
|
||||
verify(mKeyStoreProxy).setEntry(
|
||||
eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
|
||||
@@ -293,9 +289,9 @@ public class PlatformKeyManagerTest {
|
||||
|
||||
@Test
|
||||
public void regenerate_generatesANewDecryptKeyWithTheCorrectAlias() throws Exception {
|
||||
mPlatformKeyManager.init();
|
||||
mPlatformKeyManager.init(USER_ID_FIXTURE);
|
||||
|
||||
mPlatformKeyManager.regenerate();
|
||||
mPlatformKeyManager.regenerate(USER_ID_FIXTURE);
|
||||
|
||||
verify(mKeyStoreProxy).setEntry(
|
||||
eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
|
||||
|
||||
@@ -36,9 +36,11 @@ import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Binder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceSpecificException;
|
||||
import android.os.UserHandle;
|
||||
import android.security.keystore.AndroidKeyStoreSecretKey;
|
||||
import android.security.keystore.KeyGenParameterSpec;
|
||||
import android.security.keystore.KeyProperties;
|
||||
import android.security.recoverablekeystore.KeyDerivationParameters;
|
||||
import android.security.recoverablekeystore.KeyEntryRecoveryData;
|
||||
import android.security.recoverablekeystore.KeyStoreRecoveryMetadata;
|
||||
@@ -67,9 +69,8 @@ import java.util.concurrent.Executors;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
@SmallTest
|
||||
@@ -77,7 +78,6 @@ import javax.crypto.spec.SecretKeySpec;
|
||||
public class RecoverableKeyStoreManagerTest {
|
||||
private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
|
||||
|
||||
private static final String KEY_WRAP_CIPHER_ALGORITHM = "AES/GCM/NoPadding";
|
||||
private static final String TEST_SESSION_ID = "karlin";
|
||||
private static final byte[] TEST_PUBLIC_KEY = new byte[] {
|
||||
(byte) 0x30, (byte) 0x59, (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07, (byte) 0x2a,
|
||||
@@ -97,6 +97,8 @@ public class RecoverableKeyStoreManagerTest {
|
||||
private static final byte[] TEST_SECRET = getUtf8Bytes("password1234");
|
||||
private static final byte[] TEST_VAULT_CHALLENGE = getUtf8Bytes("vault_challenge");
|
||||
private static final byte[] TEST_VAULT_PARAMS = getUtf8Bytes("vault_params");
|
||||
private static final int TEST_GENERATION_ID = 2;
|
||||
private static final int TEST_USER_ID = 10009;
|
||||
private static final int KEY_CLAIMANT_LENGTH_BYTES = 16;
|
||||
private static final byte[] RECOVERY_RESPONSE_HEADER =
|
||||
"V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8);
|
||||
@@ -105,20 +107,24 @@ public class RecoverableKeyStoreManagerTest {
|
||||
private static final int GENERATION_ID = 1;
|
||||
private static final byte[] NONCE = getUtf8Bytes("nonce");
|
||||
private static final byte[] KEY_MATERIAL = getUtf8Bytes("keymaterial");
|
||||
private static final int GCM_TAG_SIZE_BITS = 128;
|
||||
private static final String KEY_ALGORITHM = "AES";
|
||||
private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
|
||||
private static final String WRAPPING_KEY_ALIAS = "RecoverableKeyStoreManagerTest/WrappingKey";
|
||||
|
||||
@Mock private Context mMockContext;
|
||||
@Mock private RecoverySnapshotListenersStorage mMockListenersStorage;
|
||||
@Mock private KeyguardManager mKeyguardManager;
|
||||
@Mock private PlatformKeyManager mPlatformKeyManager;
|
||||
|
||||
private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
|
||||
private File mDatabaseFile;
|
||||
private RecoverableKeyStoreManager mRecoverableKeyStoreManager;
|
||||
private RecoverySessionStorage mRecoverySessionStorage;
|
||||
private RecoverySnapshotStorage mRecoverySnapshotStorage;
|
||||
private PlatformEncryptionKey mPlatformEncryptionKey;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
Context context = InstrumentationRegistry.getTargetContext();
|
||||
@@ -130,7 +136,11 @@ public class RecoverableKeyStoreManagerTest {
|
||||
when(mMockContext.getSystemService(anyString())).thenReturn(mKeyguardManager);
|
||||
when(mMockContext.getSystemServiceName(any())).thenReturn("test");
|
||||
when(mMockContext.getApplicationContext()).thenReturn(mMockContext);
|
||||
when(mKeyguardManager.isDeviceSecure(anyInt())).thenReturn(true);
|
||||
when(mKeyguardManager.isDeviceSecure(TEST_USER_ID)).thenReturn(true);
|
||||
|
||||
mPlatformEncryptionKey =
|
||||
new PlatformEncryptionKey(TEST_GENERATION_ID, generateAndroidKeyStoreKey());
|
||||
when(mPlatformKeyManager.getEncryptKey(anyInt())).thenReturn(mPlatformEncryptionKey);
|
||||
|
||||
mRecoverableKeyStoreManager = new RecoverableKeyStoreManager(
|
||||
mMockContext,
|
||||
@@ -138,7 +148,8 @@ public class RecoverableKeyStoreManagerTest {
|
||||
mRecoverySessionStorage,
|
||||
Executors.newSingleThreadExecutor(),
|
||||
mRecoverySnapshotStorage,
|
||||
mMockListenersStorage);
|
||||
mMockListenersStorage,
|
||||
mPlatformKeyManager);
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -212,7 +223,7 @@ public class RecoverableKeyStoreManagerTest {
|
||||
TEST_VAULT_CHALLENGE,
|
||||
ImmutableList.of());
|
||||
fail("should have thrown");
|
||||
} catch (RemoteException e) {
|
||||
} catch (ServiceSpecificException e) {
|
||||
assertEquals("Only a single KeyStoreRecoveryMetadata is supported", e.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -232,7 +243,7 @@ public class RecoverableKeyStoreManagerTest {
|
||||
KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
|
||||
TEST_SECRET)));
|
||||
fail("should have thrown");
|
||||
} catch (RemoteException e) {
|
||||
} catch (ServiceSpecificException e) {
|
||||
assertEquals("Not a valid X509 key", e.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -273,7 +284,6 @@ public class RecoverableKeyStoreManagerTest {
|
||||
fail("should have thrown");
|
||||
} catch (ServiceSpecificException e) {
|
||||
assertThat(e.getMessage()).startsWith("Failed to decrypt recovery key");
|
||||
//assertEquals("Failed to decrypt recovery key", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,8 +314,8 @@ public class RecoverableKeyStoreManagerTest {
|
||||
/*encryptedRecoveryKey=*/ encryptedClaimResponse,
|
||||
/*applicationKeys=*/ ImmutableList.of(badApplicationKey));
|
||||
fail("should have thrown");
|
||||
} catch (RemoteException e) {
|
||||
assertEquals("Failed to recover key with alias 'nick'", e.getMessage());
|
||||
} catch (ServiceSpecificException e) {
|
||||
assertThat(e.getMessage()).startsWith("Failed to recover key with alias 'nick'");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -466,4 +476,16 @@ public class RecoverableKeyStoreManagerTest {
|
||||
new Random().nextBytes(bytes);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception {
|
||||
KeyGenerator keyGenerator = KeyGenerator.getInstance(
|
||||
KEY_ALGORITHM,
|
||||
ANDROID_KEY_STORE_PROVIDER);
|
||||
keyGenerator.init(new KeyGenParameterSpec.Builder(
|
||||
WRAPPING_KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
|
||||
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
|
||||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
|
||||
.build());
|
||||
return (AndroidKeyStoreSecretKey) keyGenerator.generateKey();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user