Loading services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java +2 −8 Original line number Diff line number Diff line Loading @@ -172,7 +172,7 @@ public class KeySyncTask implements Runnable { private void syncKeysForAgent(int recoveryAgentUid) { boolean recreateCurrentVersion = false; if (!shoudCreateSnapshot(recoveryAgentUid)) { if (!shouldCreateSnapshot(recoveryAgentUid)) { recreateCurrentVersion = (mRecoverableKeyStoreDb.getSnapshotVersion(mUserId, recoveryAgentUid) != null) && (mRecoverySnapshotStorage.get(recoveryAgentUid) == null); Loading @@ -184,11 +184,6 @@ public class KeySyncTask implements Runnable { } } if (!mSnapshotListenersStorage.hasListener(recoveryAgentUid)) { Log.w(TAG, "No pending intent registered for recovery agent " + recoveryAgentUid); return; } PublicKey publicKey; CertPath certPath = mRecoverableKeyStoreDb.getRecoveryServiceCertPath(mUserId, recoveryAgentUid); Loading Loading @@ -313,7 +308,6 @@ public class KeySyncTask implements Runnable { return; } mRecoverySnapshotStorage.put(recoveryAgentUid, keyChainSnapshotBuilder.build()); mSnapshotListenersStorage.recoverySnapshotAvailable(recoveryAgentUid); } Loading Loading @@ -354,7 +348,7 @@ public class KeySyncTask implements Runnable { * Returns {@code true} if a sync is pending. * @param recoveryAgentUid uid of the recovery agent. */ private boolean shoudCreateSnapshot(int recoveryAgentUid) { private boolean shouldCreateSnapshot(int recoveryAgentUid) { int[] types = mRecoverableKeyStoreDb.getRecoverySecretTypes(mUserId, recoveryAgentUid); if (!ArrayUtils.contains(types, KeyChainProtectionParams.TYPE_LOCKSCREEN)) { // Only lockscreen type is supported. Loading services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorage.java +37 −10 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.server.locksettings.recoverablekeystore; import android.annotation.Nullable; import android.app.PendingIntent; import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; Loading @@ -36,6 +37,9 @@ public class RecoverySnapshotListenersStorage { @GuardedBy("this") private SparseArray<PendingIntent> mAgentIntents = new SparseArray<>(); @GuardedBy("this") private ArraySet<Integer> mAgentsWithPendingSnapshots = new ArraySet<>(); /** * Sets new listener for the recovery agent, identified by {@code uid}. * Loading @@ -46,6 +50,11 @@ public class RecoverySnapshotListenersStorage { int recoveryAgentUid, @Nullable PendingIntent intent) { Log.i(TAG, "Registered listener for agent with uid " + recoveryAgentUid); mAgentIntents.put(recoveryAgentUid, intent); if (mAgentsWithPendingSnapshots.contains(recoveryAgentUid)) { Log.i(TAG, "Snapshot already created for agent. Immediately triggering intent."); tryToSendIntent(recoveryAgentUid, intent); } } /** Loading @@ -56,21 +65,39 @@ public class RecoverySnapshotListenersStorage { } /** * Notifies recovery agent that new snapshot is available. Does nothing if a listener was not * registered. * Notifies recovery agent that new snapshot is available. If a recovery agent has not yet * registered a {@link PendingIntent}, remembers that a snapshot is pending for it, so that * when it does register, that intent is immediately triggered. * * @param recoveryAgentUid uid of recovery agent. */ public synchronized void recoverySnapshotAvailable(int recoveryAgentUid) { PendingIntent intent = mAgentIntents.get(recoveryAgentUid); if (intent != null) { if (intent == null) { Log.i(TAG, "Snapshot available for agent " + recoveryAgentUid + " but agent has not yet initialized. Will notify agent when it does."); mAgentsWithPendingSnapshots.add(recoveryAgentUid); return; } tryToSendIntent(recoveryAgentUid, intent); } /** * Attempts to send {@code intent} for the recovery agent. If this fails, remembers to notify * the recovery agent immediately if it registers a new intent. */ private synchronized void tryToSendIntent(int recoveryAgentUid, PendingIntent intent) { try { intent.send(); mAgentsWithPendingSnapshots.remove(recoveryAgentUid); Log.d(TAG, "Successfully notified listener."); } catch (PendingIntent.CanceledException e) { Log.e(TAG, "Failed to trigger PendingIntent for " + recoveryAgentUid, e); } // As it failed to trigger, trigger immediately if a new intent is registered later. mAgentsWithPendingSnapshots.add(recoveryAgentUid); } } } services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java +11 −15 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.never; Loading Loading @@ -240,10 +241,8 @@ public class KeySyncTaskTest { } @Test public void run_doesNotSendAnythingIfNoRecoveryAgentPendingIntentRegistered() throws Exception { public void run_doesNotSendAnythingIfNoDeviceIdIsSet() throws Exception { SecretKey applicationKey = generateKey(); mRecoverableKeyStoreDb.setServerParams( TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE); mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID); mRecoverableKeyStoreDb.insertKey( TEST_USER_ID, Loading @@ -252,6 +251,7 @@ public class KeySyncTaskTest { 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 @@ -259,21 +259,18 @@ public class KeySyncTaskTest { } @Test public void run_doesNotSendAnythingIfNoDeviceIdIsSet() throws Exception { SecretKey applicationKey = generateKey(); public void run_stillCreatesSnapshotIfNoRecoveryAgentPendingIntentRegistered() throws Exception { mRecoverableKeyStoreDb.setServerParams( TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE); mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID); mRecoverableKeyStoreDb.insertKey( TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS, WrappedKey.fromSecretKey(mEncryptKey, applicationKey)); addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS); mRecoverableKeyStoreDb.setRecoveryServicePublicKey( TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic()); when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true); mKeySyncTask.run(); assertNull(mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID)); assertNotNull(mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID)); } @Test Loading Loading @@ -502,7 +499,7 @@ public class KeySyncTaskTest { } @Test public void run_doesNotSendKeyToNonregisteredAgent() throws Exception { public void run_notifiesNonregisteredAgent() throws Exception { mRecoverableKeyStoreDb.setRecoveryServicePublicKey( TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic()); mRecoverableKeyStoreDb.setRecoveryServicePublicKey( Loading @@ -514,8 +511,7 @@ public class KeySyncTaskTest { mKeySyncTask.run(); verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID); verify(mSnapshotListenersStorage, never()). recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID2); verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID2); } @Test Loading services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorageTest.java +57 −1 Original line number Diff line number Diff line Loading @@ -4,7 +4,10 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; Loading @@ -12,10 +15,18 @@ import android.support.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @SmallTest @RunWith(AndroidJUnit4.class) public class RecoverySnapshotListenersStorageTest { private static final String TEST_INTENT_ACTION = "com.android.server.locksettings.recoverablekeystore.RECOVERY_SNAPSHOT_TEST_ACTION"; private static final int TEST_TIMEOUT_SECONDS = 1; private final RecoverySnapshotListenersStorage mStorage = new RecoverySnapshotListenersStorage(); Loading @@ -34,4 +45,49 @@ public class RecoverySnapshotListenersStorageTest { assertTrue(mStorage.hasListener(recoveryAgentUid)); } @Test public void setSnapshotListener_invokesIntentImmediatelyIfPreviouslyNotified() throws Exception { Context context = InstrumentationRegistry.getTargetContext(); int recoveryAgentUid = 1000; mStorage.recoverySnapshotAvailable(recoveryAgentUid); PendingIntent intent = PendingIntent.getBroadcast( context, /*requestCode=*/ 0, new Intent(TEST_INTENT_ACTION), /*flags=*/0); CountDownLatch latch = new CountDownLatch(1); context.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { context.unregisterReceiver(this); latch.countDown(); } }, new IntentFilter(TEST_INTENT_ACTION)); mStorage.setSnapshotListener(recoveryAgentUid, intent); assertTrue(latch.await(TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)); } @Test public void setSnapshotListener_doesNotRepeatedlyInvokeListener() throws Exception { Context context = InstrumentationRegistry.getTargetContext(); int recoveryAgentUid = 1000; mStorage.recoverySnapshotAvailable(recoveryAgentUid); PendingIntent intent = PendingIntent.getBroadcast( context, /*requestCode=*/ 0, new Intent(TEST_INTENT_ACTION), /*flags=*/0); CountDownLatch latch = new CountDownLatch(2); BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { latch.countDown(); } }; context.registerReceiver(broadcastReceiver, new IntentFilter(TEST_INTENT_ACTION)); mStorage.setSnapshotListener(recoveryAgentUid, intent); mStorage.setSnapshotListener(recoveryAgentUid, intent); assertFalse(latch.await(TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)); context.unregisterReceiver(broadcastReceiver); } } Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java +2 −8 Original line number Diff line number Diff line Loading @@ -172,7 +172,7 @@ public class KeySyncTask implements Runnable { private void syncKeysForAgent(int recoveryAgentUid) { boolean recreateCurrentVersion = false; if (!shoudCreateSnapshot(recoveryAgentUid)) { if (!shouldCreateSnapshot(recoveryAgentUid)) { recreateCurrentVersion = (mRecoverableKeyStoreDb.getSnapshotVersion(mUserId, recoveryAgentUid) != null) && (mRecoverySnapshotStorage.get(recoveryAgentUid) == null); Loading @@ -184,11 +184,6 @@ public class KeySyncTask implements Runnable { } } if (!mSnapshotListenersStorage.hasListener(recoveryAgentUid)) { Log.w(TAG, "No pending intent registered for recovery agent " + recoveryAgentUid); return; } PublicKey publicKey; CertPath certPath = mRecoverableKeyStoreDb.getRecoveryServiceCertPath(mUserId, recoveryAgentUid); Loading Loading @@ -313,7 +308,6 @@ public class KeySyncTask implements Runnable { return; } mRecoverySnapshotStorage.put(recoveryAgentUid, keyChainSnapshotBuilder.build()); mSnapshotListenersStorage.recoverySnapshotAvailable(recoveryAgentUid); } Loading Loading @@ -354,7 +348,7 @@ public class KeySyncTask implements Runnable { * Returns {@code true} if a sync is pending. * @param recoveryAgentUid uid of the recovery agent. */ private boolean shoudCreateSnapshot(int recoveryAgentUid) { private boolean shouldCreateSnapshot(int recoveryAgentUid) { int[] types = mRecoverableKeyStoreDb.getRecoverySecretTypes(mUserId, recoveryAgentUid); if (!ArrayUtils.contains(types, KeyChainProtectionParams.TYPE_LOCKSCREEN)) { // Only lockscreen type is supported. Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorage.java +37 −10 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.server.locksettings.recoverablekeystore; import android.annotation.Nullable; import android.app.PendingIntent; import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; Loading @@ -36,6 +37,9 @@ public class RecoverySnapshotListenersStorage { @GuardedBy("this") private SparseArray<PendingIntent> mAgentIntents = new SparseArray<>(); @GuardedBy("this") private ArraySet<Integer> mAgentsWithPendingSnapshots = new ArraySet<>(); /** * Sets new listener for the recovery agent, identified by {@code uid}. * Loading @@ -46,6 +50,11 @@ public class RecoverySnapshotListenersStorage { int recoveryAgentUid, @Nullable PendingIntent intent) { Log.i(TAG, "Registered listener for agent with uid " + recoveryAgentUid); mAgentIntents.put(recoveryAgentUid, intent); if (mAgentsWithPendingSnapshots.contains(recoveryAgentUid)) { Log.i(TAG, "Snapshot already created for agent. Immediately triggering intent."); tryToSendIntent(recoveryAgentUid, intent); } } /** Loading @@ -56,21 +65,39 @@ public class RecoverySnapshotListenersStorage { } /** * Notifies recovery agent that new snapshot is available. Does nothing if a listener was not * registered. * Notifies recovery agent that new snapshot is available. If a recovery agent has not yet * registered a {@link PendingIntent}, remembers that a snapshot is pending for it, so that * when it does register, that intent is immediately triggered. * * @param recoveryAgentUid uid of recovery agent. */ public synchronized void recoverySnapshotAvailable(int recoveryAgentUid) { PendingIntent intent = mAgentIntents.get(recoveryAgentUid); if (intent != null) { if (intent == null) { Log.i(TAG, "Snapshot available for agent " + recoveryAgentUid + " but agent has not yet initialized. Will notify agent when it does."); mAgentsWithPendingSnapshots.add(recoveryAgentUid); return; } tryToSendIntent(recoveryAgentUid, intent); } /** * Attempts to send {@code intent} for the recovery agent. If this fails, remembers to notify * the recovery agent immediately if it registers a new intent. */ private synchronized void tryToSendIntent(int recoveryAgentUid, PendingIntent intent) { try { intent.send(); mAgentsWithPendingSnapshots.remove(recoveryAgentUid); Log.d(TAG, "Successfully notified listener."); } catch (PendingIntent.CanceledException e) { Log.e(TAG, "Failed to trigger PendingIntent for " + recoveryAgentUid, e); } // As it failed to trigger, trigger immediately if a new intent is registered later. mAgentsWithPendingSnapshots.add(recoveryAgentUid); } } }
services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java +11 −15 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.never; Loading Loading @@ -240,10 +241,8 @@ public class KeySyncTaskTest { } @Test public void run_doesNotSendAnythingIfNoRecoveryAgentPendingIntentRegistered() throws Exception { public void run_doesNotSendAnythingIfNoDeviceIdIsSet() throws Exception { SecretKey applicationKey = generateKey(); mRecoverableKeyStoreDb.setServerParams( TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE); mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID); mRecoverableKeyStoreDb.insertKey( TEST_USER_ID, Loading @@ -252,6 +251,7 @@ public class KeySyncTaskTest { 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 @@ -259,21 +259,18 @@ public class KeySyncTaskTest { } @Test public void run_doesNotSendAnythingIfNoDeviceIdIsSet() throws Exception { SecretKey applicationKey = generateKey(); public void run_stillCreatesSnapshotIfNoRecoveryAgentPendingIntentRegistered() throws Exception { mRecoverableKeyStoreDb.setServerParams( TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE); mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID); mRecoverableKeyStoreDb.insertKey( TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS, WrappedKey.fromSecretKey(mEncryptKey, applicationKey)); addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS); mRecoverableKeyStoreDb.setRecoveryServicePublicKey( TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic()); when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true); mKeySyncTask.run(); assertNull(mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID)); assertNotNull(mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID)); } @Test Loading Loading @@ -502,7 +499,7 @@ public class KeySyncTaskTest { } @Test public void run_doesNotSendKeyToNonregisteredAgent() throws Exception { public void run_notifiesNonregisteredAgent() throws Exception { mRecoverableKeyStoreDb.setRecoveryServicePublicKey( TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic()); mRecoverableKeyStoreDb.setRecoveryServicePublicKey( Loading @@ -514,8 +511,7 @@ public class KeySyncTaskTest { mKeySyncTask.run(); verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID); verify(mSnapshotListenersStorage, never()). recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID2); verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID2); } @Test Loading
services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorageTest.java +57 −1 Original line number Diff line number Diff line Loading @@ -4,7 +4,10 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; Loading @@ -12,10 +15,18 @@ import android.support.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @SmallTest @RunWith(AndroidJUnit4.class) public class RecoverySnapshotListenersStorageTest { private static final String TEST_INTENT_ACTION = "com.android.server.locksettings.recoverablekeystore.RECOVERY_SNAPSHOT_TEST_ACTION"; private static final int TEST_TIMEOUT_SECONDS = 1; private final RecoverySnapshotListenersStorage mStorage = new RecoverySnapshotListenersStorage(); Loading @@ -34,4 +45,49 @@ public class RecoverySnapshotListenersStorageTest { assertTrue(mStorage.hasListener(recoveryAgentUid)); } @Test public void setSnapshotListener_invokesIntentImmediatelyIfPreviouslyNotified() throws Exception { Context context = InstrumentationRegistry.getTargetContext(); int recoveryAgentUid = 1000; mStorage.recoverySnapshotAvailable(recoveryAgentUid); PendingIntent intent = PendingIntent.getBroadcast( context, /*requestCode=*/ 0, new Intent(TEST_INTENT_ACTION), /*flags=*/0); CountDownLatch latch = new CountDownLatch(1); context.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { context.unregisterReceiver(this); latch.countDown(); } }, new IntentFilter(TEST_INTENT_ACTION)); mStorage.setSnapshotListener(recoveryAgentUid, intent); assertTrue(latch.await(TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)); } @Test public void setSnapshotListener_doesNotRepeatedlyInvokeListener() throws Exception { Context context = InstrumentationRegistry.getTargetContext(); int recoveryAgentUid = 1000; mStorage.recoverySnapshotAvailable(recoveryAgentUid); PendingIntent intent = PendingIntent.getBroadcast( context, /*requestCode=*/ 0, new Intent(TEST_INTENT_ACTION), /*flags=*/0); CountDownLatch latch = new CountDownLatch(2); BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { latch.countDown(); } }; context.registerReceiver(broadcastReceiver, new IntentFilter(TEST_INTENT_ACTION)); mStorage.setSnapshotListener(recoveryAgentUid, intent); mStorage.setSnapshotListener(recoveryAgentUid, intent); assertFalse(latch.await(TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)); context.unregisterReceiver(broadcastReceiver); } }