Loading services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java +18 −1 Original line number Diff line number Diff line Loading @@ -71,11 +71,13 @@ public class KeySyncTask implements Runnable { private final PlatformKeyManager.Factory mPlatformKeyManagerFactory; private final VaultKeySupplier mVaultKeySupplier; private final RecoverySnapshotStorage mRecoverySnapshotStorage; private final RecoverySnapshotListenersStorage mSnapshotListenersStorage; public static KeySyncTask newInstance( Context context, RecoverableKeyStoreDb recoverableKeyStoreDb, RecoverySnapshotStorage snapshotStorage, RecoverySnapshotListenersStorage recoverySnapshotListenersStorage, int userId, int credentialType, String credential Loading @@ -83,6 +85,7 @@ public class KeySyncTask implements Runnable { return new KeySyncTask( recoverableKeyStoreDb, snapshotStorage, recoverySnapshotListenersStorage, userId, credentialType, credential, Loading @@ -107,11 +110,13 @@ public class KeySyncTask implements Runnable { KeySyncTask( RecoverableKeyStoreDb recoverableKeyStoreDb, RecoverySnapshotStorage snapshotStorage, RecoverySnapshotListenersStorage recoverySnapshotListenersStorage, int userId, int credentialType, String credential, PlatformKeyManager.Factory platformKeyManagerFactory, VaultKeySupplier vaultKeySupplier) { mSnapshotListenersStorage = recoverySnapshotListenersStorage; mRecoverableKeyStoreDb = recoverableKeyStoreDb; mUserId = userId; mCredentialType = credentialType; Loading @@ -136,6 +141,18 @@ public class KeySyncTask implements Runnable { return; } int recoveryAgentUid = mRecoverableKeyStoreDb.getRecoveryAgentUid(mUserId); if (recoveryAgentUid == -1) { Log.w(TAG, "No recovery agent initialized for user " + mUserId); return; } if (!mSnapshotListenersStorage.hasListener(recoveryAgentUid)) { Log.w(TAG, "No pending intent registered for recovery agent " + recoveryAgentUid); return; } byte[] salt = generateSalt(); byte[] localLskfHash = hashCredentials(salt, mCredential); Loading Loading @@ -192,7 +209,6 @@ public class KeySyncTask implements Runnable { return; } // TODO: why is the secret sent here? I thought it wasn't sent in the raw at all. KeyStoreRecoveryMetadata metadata = new KeyStoreRecoveryMetadata( /*userSecretType=*/ TYPE_LOCKSCREEN, /*lockScreenUiFormat=*/ mCredentialType, Loading @@ -207,6 +223,7 @@ public class KeySyncTask implements Runnable { /*recoveryMetadata=*/ metadataList, /*applicationKeyBlobs=*/ createApplicationKeyEntries(encryptedApplicationKeys), /*encryptedRecoveryKeyblob=*/ encryptedRecoveryKey)); mSnapshotListenersStorage.recoverySnapshotAvailable(recoveryAgentUid); } private PublicKey getVaultPublicKey() { Loading services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +13 −8 Original line number Diff line number Diff line Loading @@ -45,7 +45,6 @@ import java.security.PublicKey; import java.security.UnrecoverableKeyException; import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; Loading Loading @@ -74,7 +73,7 @@ public class RecoverableKeyStoreManager { private final RecoverableKeyStoreDb mDatabase; private final RecoverySessionStorage mRecoverySessionStorage; private final ExecutorService mExecutorService; private final ListenersStorage mListenersStorage; private final RecoverySnapshotListenersStorage mListenersStorage; private final RecoverableKeyGenerator mRecoverableKeyGenerator; private final RecoverySnapshotStorage mSnapshotStorage; Loading @@ -91,8 +90,8 @@ public class RecoverableKeyStoreManager { db, new RecoverySessionStorage(), Executors.newSingleThreadExecutor(), ListenersStorage.getInstance(), new RecoverySnapshotStorage()); new RecoverySnapshotStorage(), new RecoverySnapshotListenersStorage()); } return mInstance; } Loading @@ -103,8 +102,8 @@ public class RecoverableKeyStoreManager { RecoverableKeyStoreDb recoverableKeyStoreDb, RecoverySessionStorage recoverySessionStorage, ExecutorService executorService, ListenersStorage listenersStorage, RecoverySnapshotStorage snapshotStorage) { RecoverySnapshotStorage snapshotStorage, RecoverySnapshotListenersStorage listenersStorage) { mContext = context; mDatabase = recoverableKeyStoreDb; mRecoverySessionStorage = recoverySessionStorage; Loading Loading @@ -462,7 +461,7 @@ public class RecoverableKeyStoreManager { /** * This function can only be used inside LockSettingsService. * * @param storedHashType from {@Code CredentialHash} * @param storedHashType from {@code CredentialHash} * @param credential - unencrypted String. Password length should be at most 16 symbols {@code * mPasswordMaxLength} * @param userId for user who just unlocked the device. Loading @@ -473,7 +472,13 @@ public class RecoverableKeyStoreManager { // So as not to block the critical path unlocking the phone, defer to another thread. try { mExecutorService.execute(KeySyncTask.newInstance( mContext, mDatabase, mSnapshotStorage, userId, storedHashType, credential)); mContext, mDatabase, mSnapshotStorage, mListenersStorage, userId, storedHashType, credential)); } catch (NoSuchAlgorithmException e) { Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e); } catch (KeyStoreException e) { Loading services/core/java/com/android/server/locksettings/recoverablekeystore/ListenersStorage.java→services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorage.java +76 −0 Original line number Diff line number Diff line Loading @@ -18,50 +18,58 @@ package com.android.server.locksettings.recoverablekeystore; import android.annotation.Nullable; import android.app.PendingIntent; import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import java.util.Map; import java.util.HashMap; import com.android.internal.annotations.GuardedBy; /** * In memory storage for listeners to be notified when new recovery snapshot is available. * Note: implementation is not thread safe and it is used to mock final {@link PendingIntent} * class. * In memory storage for listeners to be notified when new recovery snapshot is available. This * class is thread-safe. It is used on two threads - the service thread and the thread that runs the * {@link KeySyncTask}. * * @hide */ public class ListenersStorage { private Map<Integer, PendingIntent> mAgentIntents = new HashMap<>(); public class RecoverySnapshotListenersStorage { private static final String TAG = "RecoverySnapshotLstnrs"; private static final ListenersStorage mInstance = new ListenersStorage(); public static ListenersStorage getInstance() { return mInstance; } @GuardedBy("this") private SparseArray<PendingIntent> mAgentIntents = new SparseArray<>(); /** * Sets new listener for the recovery agent, identified by {@code uid} * Sets new listener for the recovery agent, identified by {@code uid}. * * @param recoveryAgentUid uid * @param intent PendingIntent which will be triggered than new snapshot is available. * @param recoveryAgentUid uid of the recovery agent. * @param intent PendingIntent which will be triggered when new snapshot is available. */ public void setSnapshotListener(int recoveryAgentUid, @Nullable PendingIntent intent) { public synchronized void setSnapshotListener( int recoveryAgentUid, @Nullable PendingIntent intent) { Log.i(TAG, "Registered listener for agent with uid " + recoveryAgentUid); mAgentIntents.put(recoveryAgentUid, intent); } /** * Notifies recovery agent, that new snapshot is available. * Does nothing if a listener was not registered. * Returns {@code true} if a listener has been set for the recovery agent. */ public synchronized boolean hasListener(int recoveryAgentUid) { return mAgentIntents.get(recoveryAgentUid) != null; } /** * Notifies recovery agent that new snapshot is available. Does nothing if a listener was not * registered. * * @param recoveryAgentUid uid. * @param recoveryAgentUid uid of recovery agent. */ public void recoverySnapshotAvailable(int recoveryAgentUid) { public synchronized void recoverySnapshotAvailable(int recoveryAgentUid) { PendingIntent intent = mAgentIntents.get(recoveryAgentUid); if (intent != null) { try { intent.send(); } catch (PendingIntent.CanceledException e) { // Ignore - sending intent is not allowed. Log.e(TAG, "Failed to trigger PendingIntent for " + recoveryAgentUid, e); } } } Loading services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java +30 −0 Original line number Diff line number Diff line Loading @@ -329,6 +329,36 @@ public class RecoverableKeyStoreDb { RecoveryServiceMetadataEntry.TABLE_NAME, values, selection, selectionArguments); } /** * Returns the uid of the recovery agent for the given user, or -1 if none is set. */ public int getRecoveryAgentUid(int userId) { SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); String[] projection = { RecoveryServiceMetadataEntry.COLUMN_NAME_UID }; String selection = RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ?"; String[] selectionArguments = { Integer.toString(userId) }; try ( Cursor cursor = db.query( RecoveryServiceMetadataEntry.TABLE_NAME, projection, selection, selectionArguments, /*groupBy=*/ null, /*having=*/ null, /*orderBy=*/ null) ) { int count = cursor.getCount(); if (count == 0) { return -1; } cursor.moveToFirst(); return cursor.getInt( cursor.getColumnIndexOrThrow(RecoveryServiceMetadataEntry.COLUMN_NAME_UID)); } } /** * Returns the public key of the recovery service. * Loading services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java +41 −0 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; Loading Loading @@ -70,6 +71,7 @@ public class KeySyncTaskTest { private static final String DATABASE_FILE_NAME = "recoverablekeystore.db"; private static final int TEST_USER_ID = 1000; private static final int TEST_APP_UID = 10009; private static final int TEST_RECOVERY_AGENT_UID = 90873; private static final String TEST_APP_KEY_ALIAS = "rcleaver"; private static final int TEST_GENERATION_ID = 2; private static final int TEST_CREDENTIAL_TYPE = CREDENTIAL_TYPE_PASSWORD; Loading @@ -78,6 +80,7 @@ public class KeySyncTaskTest { "V1 THM_encrypted_recovery_key".getBytes(StandardCharsets.UTF_8); @Mock private PlatformKeyManager mPlatformKeyManager; @Mock private RecoverySnapshotListenersStorage mSnapshotListenersStorage; private RecoverySnapshotStorage mRecoverySnapshotStorage; private RecoverableKeyStoreDb mRecoverableKeyStoreDb; Loading @@ -102,6 +105,7 @@ public class KeySyncTaskTest { mKeySyncTask = new KeySyncTask( mRecoverableKeyStoreDb, mRecoverySnapshotStorage, mSnapshotListenersStorage, TEST_USER_ID, TEST_CREDENTIAL_TYPE, TEST_CREDENTIAL, Loading Loading @@ -201,6 +205,39 @@ public class KeySyncTaskTest { assertNull(mRecoverySnapshotStorage.get(TEST_USER_ID)); } @Test public void run_doesNotSendAnythingIfNoRecoveryAgentSet() throws Exception { SecretKey applicationKey = generateKey(); mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID); mRecoverableKeyStoreDb.insertKey( TEST_USER_ID, TEST_APP_UID, TEST_APP_KEY_ALIAS, WrappedKey.fromSecretKey(mEncryptKey, applicationKey)); when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true); mKeySyncTask.run(); assertNull(mRecoverySnapshotStorage.get(TEST_USER_ID)); } @Test public void run_doesNotSendAnythingIfNoRecoveryAgentPendingIntentRegistered() throws Exception { SecretKey applicationKey = generateKey(); mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID); mRecoverableKeyStoreDb.insertKey( TEST_USER_ID, TEST_APP_UID, TEST_APP_KEY_ALIAS, WrappedKey.fromSecretKey(mEncryptKey, applicationKey)); mRecoverableKeyStoreDb.setRecoveryServicePublicKey( TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic()); mKeySyncTask.run(); assertNull(mRecoverySnapshotStorage.get(TEST_USER_ID)); } @Test public void run_sendsEncryptedKeysIfAvailableToSync() throws Exception { SecretKey applicationKey = generateKey(); Loading @@ -210,6 +247,9 @@ public class KeySyncTaskTest { TEST_APP_UID, TEST_APP_KEY_ALIAS, WrappedKey.fromSecretKey(mEncryptKey, applicationKey)); mRecoverableKeyStoreDb.setRecoveryServicePublicKey( TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic()); when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true); mKeySyncTask.run(); Loading @@ -218,6 +258,7 @@ public class KeySyncTaskTest { recoveryData.getRecoveryMetadata().get(0).getKeyDerivationParameters(); assertEquals(KeyDerivationParameters.ALGORITHM_SHA256, keyDerivationParameters.getAlgorithm()); verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID); byte[] lockScreenHash = KeySyncTask.hashCredentials( keyDerivationParameters.getSalt(), TEST_CREDENTIAL); Loading Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java +18 −1 Original line number Diff line number Diff line Loading @@ -71,11 +71,13 @@ public class KeySyncTask implements Runnable { private final PlatformKeyManager.Factory mPlatformKeyManagerFactory; private final VaultKeySupplier mVaultKeySupplier; private final RecoverySnapshotStorage mRecoverySnapshotStorage; private final RecoverySnapshotListenersStorage mSnapshotListenersStorage; public static KeySyncTask newInstance( Context context, RecoverableKeyStoreDb recoverableKeyStoreDb, RecoverySnapshotStorage snapshotStorage, RecoverySnapshotListenersStorage recoverySnapshotListenersStorage, int userId, int credentialType, String credential Loading @@ -83,6 +85,7 @@ public class KeySyncTask implements Runnable { return new KeySyncTask( recoverableKeyStoreDb, snapshotStorage, recoverySnapshotListenersStorage, userId, credentialType, credential, Loading @@ -107,11 +110,13 @@ public class KeySyncTask implements Runnable { KeySyncTask( RecoverableKeyStoreDb recoverableKeyStoreDb, RecoverySnapshotStorage snapshotStorage, RecoverySnapshotListenersStorage recoverySnapshotListenersStorage, int userId, int credentialType, String credential, PlatformKeyManager.Factory platformKeyManagerFactory, VaultKeySupplier vaultKeySupplier) { mSnapshotListenersStorage = recoverySnapshotListenersStorage; mRecoverableKeyStoreDb = recoverableKeyStoreDb; mUserId = userId; mCredentialType = credentialType; Loading @@ -136,6 +141,18 @@ public class KeySyncTask implements Runnable { return; } int recoveryAgentUid = mRecoverableKeyStoreDb.getRecoveryAgentUid(mUserId); if (recoveryAgentUid == -1) { Log.w(TAG, "No recovery agent initialized for user " + mUserId); return; } if (!mSnapshotListenersStorage.hasListener(recoveryAgentUid)) { Log.w(TAG, "No pending intent registered for recovery agent " + recoveryAgentUid); return; } byte[] salt = generateSalt(); byte[] localLskfHash = hashCredentials(salt, mCredential); Loading Loading @@ -192,7 +209,6 @@ public class KeySyncTask implements Runnable { return; } // TODO: why is the secret sent here? I thought it wasn't sent in the raw at all. KeyStoreRecoveryMetadata metadata = new KeyStoreRecoveryMetadata( /*userSecretType=*/ TYPE_LOCKSCREEN, /*lockScreenUiFormat=*/ mCredentialType, Loading @@ -207,6 +223,7 @@ public class KeySyncTask implements Runnable { /*recoveryMetadata=*/ metadataList, /*applicationKeyBlobs=*/ createApplicationKeyEntries(encryptedApplicationKeys), /*encryptedRecoveryKeyblob=*/ encryptedRecoveryKey)); mSnapshotListenersStorage.recoverySnapshotAvailable(recoveryAgentUid); } private PublicKey getVaultPublicKey() { Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +13 −8 Original line number Diff line number Diff line Loading @@ -45,7 +45,6 @@ import java.security.PublicKey; import java.security.UnrecoverableKeyException; import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; Loading Loading @@ -74,7 +73,7 @@ public class RecoverableKeyStoreManager { private final RecoverableKeyStoreDb mDatabase; private final RecoverySessionStorage mRecoverySessionStorage; private final ExecutorService mExecutorService; private final ListenersStorage mListenersStorage; private final RecoverySnapshotListenersStorage mListenersStorage; private final RecoverableKeyGenerator mRecoverableKeyGenerator; private final RecoverySnapshotStorage mSnapshotStorage; Loading @@ -91,8 +90,8 @@ public class RecoverableKeyStoreManager { db, new RecoverySessionStorage(), Executors.newSingleThreadExecutor(), ListenersStorage.getInstance(), new RecoverySnapshotStorage()); new RecoverySnapshotStorage(), new RecoverySnapshotListenersStorage()); } return mInstance; } Loading @@ -103,8 +102,8 @@ public class RecoverableKeyStoreManager { RecoverableKeyStoreDb recoverableKeyStoreDb, RecoverySessionStorage recoverySessionStorage, ExecutorService executorService, ListenersStorage listenersStorage, RecoverySnapshotStorage snapshotStorage) { RecoverySnapshotStorage snapshotStorage, RecoverySnapshotListenersStorage listenersStorage) { mContext = context; mDatabase = recoverableKeyStoreDb; mRecoverySessionStorage = recoverySessionStorage; Loading Loading @@ -462,7 +461,7 @@ public class RecoverableKeyStoreManager { /** * This function can only be used inside LockSettingsService. * * @param storedHashType from {@Code CredentialHash} * @param storedHashType from {@code CredentialHash} * @param credential - unencrypted String. Password length should be at most 16 symbols {@code * mPasswordMaxLength} * @param userId for user who just unlocked the device. Loading @@ -473,7 +472,13 @@ public class RecoverableKeyStoreManager { // So as not to block the critical path unlocking the phone, defer to another thread. try { mExecutorService.execute(KeySyncTask.newInstance( mContext, mDatabase, mSnapshotStorage, userId, storedHashType, credential)); mContext, mDatabase, mSnapshotStorage, mListenersStorage, userId, storedHashType, credential)); } catch (NoSuchAlgorithmException e) { Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e); } catch (KeyStoreException e) { Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/ListenersStorage.java→services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorage.java +76 −0 Original line number Diff line number Diff line Loading @@ -18,50 +18,58 @@ package com.android.server.locksettings.recoverablekeystore; import android.annotation.Nullable; import android.app.PendingIntent; import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import java.util.Map; import java.util.HashMap; import com.android.internal.annotations.GuardedBy; /** * In memory storage for listeners to be notified when new recovery snapshot is available. * Note: implementation is not thread safe and it is used to mock final {@link PendingIntent} * class. * In memory storage for listeners to be notified when new recovery snapshot is available. This * class is thread-safe. It is used on two threads - the service thread and the thread that runs the * {@link KeySyncTask}. * * @hide */ public class ListenersStorage { private Map<Integer, PendingIntent> mAgentIntents = new HashMap<>(); public class RecoverySnapshotListenersStorage { private static final String TAG = "RecoverySnapshotLstnrs"; private static final ListenersStorage mInstance = new ListenersStorage(); public static ListenersStorage getInstance() { return mInstance; } @GuardedBy("this") private SparseArray<PendingIntent> mAgentIntents = new SparseArray<>(); /** * Sets new listener for the recovery agent, identified by {@code uid} * Sets new listener for the recovery agent, identified by {@code uid}. * * @param recoveryAgentUid uid * @param intent PendingIntent which will be triggered than new snapshot is available. * @param recoveryAgentUid uid of the recovery agent. * @param intent PendingIntent which will be triggered when new snapshot is available. */ public void setSnapshotListener(int recoveryAgentUid, @Nullable PendingIntent intent) { public synchronized void setSnapshotListener( int recoveryAgentUid, @Nullable PendingIntent intent) { Log.i(TAG, "Registered listener for agent with uid " + recoveryAgentUid); mAgentIntents.put(recoveryAgentUid, intent); } /** * Notifies recovery agent, that new snapshot is available. * Does nothing if a listener was not registered. * Returns {@code true} if a listener has been set for the recovery agent. */ public synchronized boolean hasListener(int recoveryAgentUid) { return mAgentIntents.get(recoveryAgentUid) != null; } /** * Notifies recovery agent that new snapshot is available. Does nothing if a listener was not * registered. * * @param recoveryAgentUid uid. * @param recoveryAgentUid uid of recovery agent. */ public void recoverySnapshotAvailable(int recoveryAgentUid) { public synchronized void recoverySnapshotAvailable(int recoveryAgentUid) { PendingIntent intent = mAgentIntents.get(recoveryAgentUid); if (intent != null) { try { intent.send(); } catch (PendingIntent.CanceledException e) { // Ignore - sending intent is not allowed. Log.e(TAG, "Failed to trigger PendingIntent for " + recoveryAgentUid, e); } } } Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java +30 −0 Original line number Diff line number Diff line Loading @@ -329,6 +329,36 @@ public class RecoverableKeyStoreDb { RecoveryServiceMetadataEntry.TABLE_NAME, values, selection, selectionArguments); } /** * Returns the uid of the recovery agent for the given user, or -1 if none is set. */ public int getRecoveryAgentUid(int userId) { SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); String[] projection = { RecoveryServiceMetadataEntry.COLUMN_NAME_UID }; String selection = RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ?"; String[] selectionArguments = { Integer.toString(userId) }; try ( Cursor cursor = db.query( RecoveryServiceMetadataEntry.TABLE_NAME, projection, selection, selectionArguments, /*groupBy=*/ null, /*having=*/ null, /*orderBy=*/ null) ) { int count = cursor.getCount(); if (count == 0) { return -1; } cursor.moveToFirst(); return cursor.getInt( cursor.getColumnIndexOrThrow(RecoveryServiceMetadataEntry.COLUMN_NAME_UID)); } } /** * Returns the public key of the recovery service. * Loading
services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java +41 −0 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; Loading Loading @@ -70,6 +71,7 @@ public class KeySyncTaskTest { private static final String DATABASE_FILE_NAME = "recoverablekeystore.db"; private static final int TEST_USER_ID = 1000; private static final int TEST_APP_UID = 10009; private static final int TEST_RECOVERY_AGENT_UID = 90873; private static final String TEST_APP_KEY_ALIAS = "rcleaver"; private static final int TEST_GENERATION_ID = 2; private static final int TEST_CREDENTIAL_TYPE = CREDENTIAL_TYPE_PASSWORD; Loading @@ -78,6 +80,7 @@ public class KeySyncTaskTest { "V1 THM_encrypted_recovery_key".getBytes(StandardCharsets.UTF_8); @Mock private PlatformKeyManager mPlatformKeyManager; @Mock private RecoverySnapshotListenersStorage mSnapshotListenersStorage; private RecoverySnapshotStorage mRecoverySnapshotStorage; private RecoverableKeyStoreDb mRecoverableKeyStoreDb; Loading @@ -102,6 +105,7 @@ public class KeySyncTaskTest { mKeySyncTask = new KeySyncTask( mRecoverableKeyStoreDb, mRecoverySnapshotStorage, mSnapshotListenersStorage, TEST_USER_ID, TEST_CREDENTIAL_TYPE, TEST_CREDENTIAL, Loading Loading @@ -201,6 +205,39 @@ public class KeySyncTaskTest { assertNull(mRecoverySnapshotStorage.get(TEST_USER_ID)); } @Test public void run_doesNotSendAnythingIfNoRecoveryAgentSet() throws Exception { SecretKey applicationKey = generateKey(); mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID); mRecoverableKeyStoreDb.insertKey( TEST_USER_ID, TEST_APP_UID, TEST_APP_KEY_ALIAS, WrappedKey.fromSecretKey(mEncryptKey, applicationKey)); when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true); mKeySyncTask.run(); assertNull(mRecoverySnapshotStorage.get(TEST_USER_ID)); } @Test public void run_doesNotSendAnythingIfNoRecoveryAgentPendingIntentRegistered() throws Exception { SecretKey applicationKey = generateKey(); mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID); mRecoverableKeyStoreDb.insertKey( TEST_USER_ID, TEST_APP_UID, TEST_APP_KEY_ALIAS, WrappedKey.fromSecretKey(mEncryptKey, applicationKey)); mRecoverableKeyStoreDb.setRecoveryServicePublicKey( TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic()); mKeySyncTask.run(); assertNull(mRecoverySnapshotStorage.get(TEST_USER_ID)); } @Test public void run_sendsEncryptedKeysIfAvailableToSync() throws Exception { SecretKey applicationKey = generateKey(); Loading @@ -210,6 +247,9 @@ public class KeySyncTaskTest { TEST_APP_UID, TEST_APP_KEY_ALIAS, WrappedKey.fromSecretKey(mEncryptKey, applicationKey)); mRecoverableKeyStoreDb.setRecoveryServicePublicKey( TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic()); when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true); mKeySyncTask.run(); Loading @@ -218,6 +258,7 @@ public class KeySyncTaskTest { recoveryData.getRecoveryMetadata().get(0).getKeyDerivationParameters(); assertEquals(KeyDerivationParameters.ALGORITHM_SHA256, keyDerivationParameters.getAlgorithm()); verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID); byte[] lockScreenHash = KeySyncTask.hashCredentials( keyDerivationParameters.getSalt(), TEST_CREDENTIAL); Loading