Loading services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java +1 −1 Original line number Diff line number Diff line Loading @@ -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); Loading services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java +60 −7 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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. Loading @@ -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); Loading @@ -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. Loading @@ -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); Loading Loading @@ -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), Loading @@ -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 { Loading services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java +21 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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)}); } /** Loading services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java +36 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading Loading @@ -257,6 +259,40 @@ public class PlatformKeyManagerTest { any()); } @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); Loading services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java +23 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java +1 −1 Original line number Diff line number Diff line Loading @@ -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); Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java +60 −7 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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. Loading @@ -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); Loading @@ -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. Loading @@ -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); Loading Loading @@ -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), Loading @@ -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 { Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java +21 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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)}); } /** Loading
services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java +36 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading Loading @@ -257,6 +259,40 @@ public class PlatformKeyManagerTest { any()); } @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); Loading
services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java +23 −0 Original line number Diff line number Diff line Loading @@ -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; Loading