diff options
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; |