summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java2
-rw-r--r--services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java67
-rw-r--r--services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java22
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java36
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java23
5 files changed, 141 insertions, 9 deletions
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
index e1e769cdfb16..db46c3d4df3d 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
@@ -305,7 +305,7 @@ public class KeySyncTask implements Runnable {
NoSuchAlgorithmException, NoSuchPaddingException, BadPlatformKeyException,
InvalidKeyException, InvalidAlgorithmParameterException {
PlatformKeyManager platformKeyManager = mPlatformKeyManagerFactory.newInstance();
- PlatformDecryptionKey decryptKey = platformKeyManager.getDecryptKey(mUserId);
+ PlatformDecryptionKey decryptKey = platformKeyManager.getDecryptKey(mUserId);;
Map<String, WrappedKey> wrappedKeys = mRecoverableKeyStoreDb.getAllKeys(
mUserId, recoveryAgentUid, decryptKey.getGenerationId());
return WrappedKey.unwrapKeys(decryptKey, wrappedKeys);
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
index 7005de5a6dd5..ee6a89312988 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
@@ -131,6 +131,7 @@ public class PlatformKeyManager {
/**
* Generates a new key and increments the generation ID. Should be invoked if the platform key
* is corrupted and needs to be rotated.
+ * Updates status of old keys to {@code RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE}.
*
* @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.
@@ -158,6 +159,7 @@ public class PlatformKeyManager {
/**
* Returns the platform key used for encryption.
+ * Tries to regenerate key one time if it is permanently invalid.
*
* @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.
@@ -170,6 +172,30 @@ public class PlatformKeyManager {
public PlatformEncryptionKey getEncryptKey(int userId) throws KeyStoreException,
UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException {
init(userId);
+ try {
+ return getEncryptKeyInternal(userId);
+ } catch (UnrecoverableKeyException e) {
+ Log.i(TAG, String.format(Locale.US,
+ "Regenerating permanently invalid Platform key for user %d.",
+ userId));
+ regenerate(userId);
+ return getEncryptKeyInternal(userId);
+ }
+ }
+
+ /**
+ * 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
+ */
+ private PlatformEncryptionKey getEncryptKeyInternal(int userId) throws KeyStoreException,
+ UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException {
int generationId = getGenerationId(userId);
AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey(
getEncryptAlias(userId, generationId), /*password=*/ null);
@@ -178,6 +204,7 @@ public class PlatformKeyManager {
/**
* Returns the platform key used for decryption. Only works after a recent screen unlock.
+ * Tries to regenerate key one time if it is permanently invalid.
*
* @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.
@@ -190,6 +217,30 @@ public class PlatformKeyManager {
public PlatformDecryptionKey getDecryptKey(int userId) throws KeyStoreException,
UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException {
init(userId);
+ try {
+ return getDecryptKeyInternal(userId);
+ } catch (UnrecoverableKeyException e) {
+ Log.i(TAG, String.format(Locale.US,
+ "Regenerating permanently invalid Platform key for user %d.",
+ userId));
+ regenerate(userId);
+ return getDecryptKeyInternal(userId);
+ }
+ }
+
+ /**
+ * 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
+ */
+ private PlatformDecryptionKey getDecryptKeyInternal(int userId) throws KeyStoreException,
+ UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException {
int generationId = getGenerationId(userId);
AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey(
getDecryptAlias(userId, generationId), /*password=*/ null);
@@ -294,13 +345,7 @@ public class PlatformKeyManager {
String decryptAlias = getDecryptAlias(userId, generationId);
SecretKey secretKey = generateAesKey();
- mKeyStore.setEntry(
- encryptAlias,
- new KeyStore.SecretKeyEntry(secretKey),
- new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT)
- .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
- .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
- .build());
+ // Store Since decryption key first since it is more likely to fail.
mKeyStore.setEntry(
decryptAlias,
new KeyStore.SecretKeyEntry(secretKey),
@@ -313,6 +358,14 @@ public class PlatformKeyManager {
.setBoundToSpecificSecureUserId(userId)
.build());
+ mKeyStore.setEntry(
+ encryptAlias,
+ new KeyStore.SecretKeyEntry(secretKey),
+ new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT)
+ .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+ .build());
+
setGenerationId(userId, generationId);
try {
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
index f2e71b37b7f9..b96208d66b01 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
@@ -22,6 +22,7 @@ import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
+import android.security.keystore.recovery.RecoveryController;
import android.text.TextUtils;
import android.util.Log;
@@ -289,8 +290,27 @@ public class RecoverableKeyStoreDb {
ContentValues values = new ContentValues();
values.put(UserMetadataEntry.COLUMN_NAME_USER_ID, userId);
values.put(UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID, generationId);
- return db.replace(
+ long result = db.replace(
UserMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null, values);
+ if (result != -1) {
+ invalidateKeysWithOldGenerationId(userId, generationId);
+ }
+ return result;
+ }
+
+ /**
+ * Updates status of old keys to {@code RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE}.
+ */
+ public void invalidateKeysWithOldGenerationId(int userId, int newGenerationId) {
+ SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put(KeysEntry.COLUMN_NAME_RECOVERY_STATUS,
+ RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE);
+ String selection =
+ KeysEntry.COLUMN_NAME_USER_ID + " = ? AND "
+ + KeysEntry.COLUMN_NAME_GENERATION_ID + " < ?";
+ db.update(KeysEntry.TABLE_NAME, values, selection,
+ new String[] {String.valueOf(userId), String.valueOf(newGenerationId)});
}
/**
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
index b1bff70422d4..6fc9e082bb7c 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
@@ -23,6 +23,7 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -48,6 +49,7 @@ import org.mockito.MockitoAnnotations;
import java.io.File;
import java.security.KeyStore;
+import java.security.UnrecoverableKeyException;
import java.util.List;
@SmallTest
@@ -258,6 +260,40 @@ public class PlatformKeyManagerTest {
}
@Test
+ public void getEncryptKey_generatesNewKeyIfOldOneIsInvalid() throws Exception {
+ doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey(
+ eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
+ any());
+
+ mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
+
+ verify(mKeyStoreProxy).getKey(
+ eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
+ any());
+ // Attempt to get regenerated key.
+ verify(mKeyStoreProxy).getKey(
+ eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
+ any());
+ }
+
+ @Test
+ public void getDecryptKey_generatesNewKeyIfOldOneIsInvalid() throws Exception {
+ doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey(
+ eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+ any());
+
+ mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);
+
+ verify(mKeyStoreProxy).getKey(
+ eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+ any());
+ // Attempt to get regenerated key.
+ verify(mKeyStoreProxy).getKey(
+ eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+ any());
+ }
+
+ @Test
public void getEncryptKey_getsDecryptKeyWithCorrectAlias() throws Exception {
mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
index f0254c6d5dfe..097d2141d9e0 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
@@ -315,6 +315,29 @@ public class RecoverableKeyStoreDbTest {
assertThat(statuses).hasSize(0);
}
+ public void testInvalidateKeysWithOldGenerationId_withSingleKey() {
+ int userId = 12;
+ int uid = 1009;
+ int generationId = 6;
+ int status = 120;
+ int status2 = 121;
+ String alias = "test";
+ byte[] nonce = getUtf8Bytes("nonce");
+ byte[] keyMaterial = getUtf8Bytes("keymaterial");
+ WrappedKey wrappedKey = new WrappedKey(nonce, keyMaterial, generationId, status);
+ mRecoverableKeyStoreDb.insertKey(userId, uid, alias, wrappedKey);
+
+ WrappedKey retrievedKey = mRecoverableKeyStoreDb.getKey(uid, alias);
+ assertThat(retrievedKey.getRecoveryStatus()).isEqualTo(status);
+
+ mRecoverableKeyStoreDb.setRecoveryStatus(uid, alias, status2);
+ mRecoverableKeyStoreDb.invalidateKeysWithOldGenerationId(userId, generationId + 1);
+
+ retrievedKey = mRecoverableKeyStoreDb.getKey(uid, alias);
+ assertThat(retrievedKey.getRecoveryStatus())
+ .isEqualTo(RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE);
+ }
+
@Test
public void setRecoveryServicePublicKey_replaceOldKey() throws Exception {
int userId = 12;