Loading services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +97 −12 Original line number Diff line number Diff line Loading @@ -49,6 +49,9 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.HexDump; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockPatternView; import com.android.internal.widget.LockscreenCredential; import com.android.internal.widget.VerifyCredentialResponse; import com.android.security.SecureBox; import com.android.server.locksettings.LockSettingsService; import com.android.server.locksettings.recoverablekeystore.certificate.CertParsingException; Loading @@ -65,6 +68,7 @@ import com.android.server.locksettings.recoverablekeystore.storage.RemoteLockscr import com.android.server.locksettings.recoverablekeystore.storage.RemoteLockscreenValidationSessionStorage.LockscreenVerificationSession; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; Loading Loading @@ -97,6 +101,9 @@ import javax.crypto.AEADBadTagException; public class RecoverableKeyStoreManager { private static final String TAG = "RecoverableKeyStoreMgr"; private static final long SYNC_DELAY_MILLIS = 2000; private static final int INVALID_REMOTE_GUESS_LIMIT = 5; public static final byte[] ENCRYPTED_REMOTE_CREDENTIALS_HEADER = "encrypted_remote_credentials".getBytes(StandardCharsets.UTF_8); private static RecoverableKeyStoreManager mInstance; Loading Loading @@ -995,7 +1002,7 @@ public class RecoverableKeyStoreManager { * Starts a session to verify lock screen credentials provided by a remote device. */ public StartLockscreenValidationRequest startRemoteLockscreenValidation( LockSettingsService lockSettingService) { LockSettingsService lockSettingsService) { if (mRemoteLockscreenValidationSessionStorage == null) { throw new UnsupportedOperationException("Under development"); } Loading @@ -1004,40 +1011,118 @@ public class RecoverableKeyStoreManager { int savedCredentialType; final long token = Binder.clearCallingIdentity(); try { savedCredentialType = lockSettingService.getCredentialType(userId); savedCredentialType = lockSettingsService.getCredentialType(userId); } finally { Binder.restoreCallingIdentity(token); } int keyguardCredentailsType = lockPatternUtilsToKeyguardType(savedCredentialType); int keyguardCredentialsType = lockPatternUtilsToKeyguardType(savedCredentialType); LockscreenVerificationSession session = mRemoteLockscreenValidationSessionStorage.startSession(userId); PublicKey publicKey = session.getKeyPair().getPublic(); byte[] encodedPublicKey = SecureBox.encodePublicKey(publicKey); int badGuesses = mDatabase.getBadRemoteGuessCounter(userId); int remainingAttempts = Math.max(INVALID_REMOTE_GUESS_LIMIT - badGuesses, 0); // TODO(b/254335492): Schedule task to remove inactive session return new StartLockscreenValidationRequest.Builder() .setLockscreenUiType(keyguardCredentailsType) .setSourcePublicKey(new byte[]{}) .setLockscreenUiType(keyguardCredentialsType) .setRemainingAttempts(remainingAttempts) .setSourcePublicKey(encodedPublicKey) .build(); } /** * Verifies encrypted credentials guess from a remote device. */ public RemoteLockscreenValidationResult validateRemoteLockscreen( public synchronized RemoteLockscreenValidationResult validateRemoteLockscreen( @NonNull byte[] encryptedCredential, LockSettingsService lockSettingService) { if (mRemoteLockscreenValidationSessionStorage == null) { throw new UnsupportedOperationException("Under development"); } LockSettingsService lockSettingsService) { checkVerifyRemoteLockscreenPermission(); int userId = UserHandle.getCallingUserId(); LockscreenVerificationSession session = mRemoteLockscreenValidationSessionStorage.get(userId); int badGuesses = mDatabase.getBadRemoteGuessCounter(userId); int remainingAttempts = INVALID_REMOTE_GUESS_LIMIT - badGuesses; if (remainingAttempts <= 0) { return new RemoteLockscreenValidationResult.Builder() .setResultCode(RemoteLockscreenValidationResult.RESULT_NO_REMAINING_ATTEMPTS) .build(); } if (session == null) { throw new IllegalStateException("There is no active lock screen check session"); } // TODO(b/254335492): Call lockSettingService.verifyCredential return new RemoteLockscreenValidationResult.Builder().build(); byte[] decryptedCredentials; try { decryptedCredentials = SecureBox.decrypt( session.getKeyPair().getPrivate(), /* sharedSecret= */ null, ENCRYPTED_REMOTE_CREDENTIALS_HEADER, encryptedCredential); } catch (NoSuchAlgorithmException e) { Log.wtf(TAG, "Missing SecureBox algorithm. AOSP required to support this.", e); throw new IllegalStateException(e); } catch (InvalidKeyException e) { Log.e(TAG, "Got InvalidKeyException during lock screen credentials decryption"); throw new IllegalStateException(e); } catch (AEADBadTagException e) { throw new IllegalStateException("Could not decrypt credentials guess", e); } int savedCredentialType; final long token = Binder.clearCallingIdentity(); try { savedCredentialType = lockSettingsService.getCredentialType(userId); int keyguardCredentialsType = lockPatternUtilsToKeyguardType(savedCredentialType); try (LockscreenCredential credential = createLockscreenCredential(keyguardCredentialsType, decryptedCredentials)) { // TODO(b/254335492): remove decryptedCredentials VerifyCredentialResponse verifyResponse = lockSettingsService.verifyCredential(credential, userId, 0); return handleVerifyCredentialResponse(verifyResponse, userId); } } finally { Binder.restoreCallingIdentity(token); } } private RemoteLockscreenValidationResult handleVerifyCredentialResponse( VerifyCredentialResponse response, int userId) { if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) { mDatabase.setBadRemoteGuessCounter(userId, 0); mRemoteLockscreenValidationSessionStorage.finishSession(userId); return new RemoteLockscreenValidationResult.Builder() .setResultCode(RemoteLockscreenValidationResult.RESULT_GUESS_VALID) .build(); } if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) { long timeout = (long) response.getTimeout(); return new RemoteLockscreenValidationResult.Builder() .setResultCode(RemoteLockscreenValidationResult.RESULT_LOCKOUT) .setTimeoutMillis(timeout) .build(); } // Invalid guess int badGuesses = mDatabase.getBadRemoteGuessCounter(userId); mDatabase.setBadRemoteGuessCounter(userId, badGuesses + 1); return new RemoteLockscreenValidationResult.Builder() .setResultCode(RemoteLockscreenValidationResult.RESULT_GUESS_INVALID) .build(); } private LockscreenCredential createLockscreenCredential( int lockType, byte[] password) { switch (lockType) { case KeyguardManager.PASSWORD: CharSequence passwordStr = new String(password, StandardCharsets.UTF_8); return LockscreenCredential.createPassword(passwordStr); case KeyguardManager.PIN: CharSequence pinStr = new String(password); return LockscreenCredential.createPin(pinStr); case KeyguardManager.PATTERN: List<LockPatternView.Cell> pattern = LockPatternUtils.byteArrayToPattern(password); return LockscreenCredential.createPattern(pattern); default: throw new IllegalStateException("Lockscreen is not set"); } } private void checkVerifyRemoteLockscreenPermission() { Loading services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java +125 −16 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ import static org.mockito.Mockito.when; import android.Manifest; import android.app.KeyguardManager; import android.app.PendingIntent; import android.app.RemoteLockscreenValidationResult; import android.app.StartLockscreenValidationRequest; import android.content.Context; import android.content.Intent; Loading @@ -61,6 +62,8 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.util.ArrayUtils; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockscreenCredential; import com.android.internal.widget.VerifyCredentialResponse; import com.android.security.SecureBox; import com.android.server.locksettings.LockSettingsService; import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKeyStorage; Loading @@ -84,10 +87,12 @@ import org.mockito.Spy; import java.io.File; import java.nio.charset.StandardCharsets; import java.security.PublicKey; import java.security.cert.CertPath; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Map; import java.util.Random; import java.util.concurrent.ScheduledExecutorService; Loading Loading @@ -155,6 +160,10 @@ public class RecoverableKeyStoreManagerTest { .setKeyDerivationParams(KeyDerivationParams.createSha256Params(TEST_SALT)) .setSecret(TEST_SECRET) .build(); private static final byte[] VALID_GUESS = getUtf8Bytes("password123"); private static final byte[] INVALID_GUESS = getUtf8Bytes("not_password"); private static final byte[] GUESS_LOCKOUT = getUtf8Bytes("need_to_wait"); private static final int TIMEOUT_MILLIS = 30 * 1000; @Mock private Context mMockContext; @Mock private RecoverySnapshotListenersStorage mMockListenersStorage; Loading Loading @@ -208,10 +217,20 @@ public class RecoverableKeyStoreManagerTest { mTestOnlyInsecureCertificateHelper, mCleanupManager, mRemoteLockscreenValidationSessionStorage); when(mLockSettingsService.verifyCredential( any(LockscreenCredential.class), anyInt(), anyInt())).thenAnswer(args -> { LockscreenCredential argument = (LockscreenCredential) args.getArguments()[0]; if (Arrays.equals(argument.getCredential(), VALID_GUESS)) { return VerifyCredentialResponse.OK; } else if (Arrays.equals(argument.getCredential(), INVALID_GUESS)) { return VerifyCredentialResponse.ERROR; } else return VerifyCredentialResponse.fromTimeout(TIMEOUT_MILLIS); }); } @After public void tearDown() { mRemoteLockscreenValidationSessionStorage.finishSession(mUserId); mRecoverableKeyStoreDb.close(); mDatabaseFile.delete(); } Loading Loading @@ -1289,73 +1308,163 @@ public class RecoverableKeyStoreManagerTest { assertThat(e.getMessage()).contains("not set"); } verify(mLockSettingsService).getCredentialType(mUserId); mRemoteLockscreenValidationSessionStorage.finishSession(mUserId); } @Test public void startRemoteLockscreenValidation_checksPermission() throws Exception { when(mLockSettingsService.getCredentialType(anyInt())).thenReturn( LockPatternUtils.CREDENTIAL_TYPE_PIN); mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService); // TODO(b/254335492): Check new system permission verify(mMockContext, times(1)) .enforceCallingOrSelfPermission( eq(Manifest.permission.RECOVER_KEYSTORE), any()); mRemoteLockscreenValidationSessionStorage.finishSession(mUserId); } @Test public void startRemoteLockscreenValidation_returnsCredentailsType() throws Exception { when(mLockSettingsService.getCredentialType(anyInt())).thenReturn( LockPatternUtils.CREDENTIAL_TYPE_PIN); StartLockscreenValidationRequest request = mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService); int credetialsType = request.getLockscreenUiType(); int credetialsType = request.getLockscreenUiType(); assertThat(credetialsType).isEqualTo(KeyguardManager.PIN); assertThat(request.getRemainingAttempts()).isEqualTo(5); verify(mLockSettingsService).getCredentialType(anyInt()); mRemoteLockscreenValidationSessionStorage.finishSession(mUserId); } @Test public void startRemoteLockscreenValidation_returnsRemainingAttempts() throws Exception { when(mLockSettingsService.getCredentialType(anyInt())).thenReturn( LockPatternUtils.CREDENTIAL_TYPE_PATTERN); mRecoverableKeyStoreDb.setBadRemoteGuessCounter(mUserId, 3); StartLockscreenValidationRequest request = mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService); int credetialsType = request.getLockscreenUiType(); assertThat(credetialsType).isEqualTo(KeyguardManager.PATTERN); // TODO(b/254335492): Verify returned value mRemoteLockscreenValidationSessionStorage.finishSession(mUserId); assertThat(request.getRemainingAttempts()).isEqualTo(2); } @Test public void startRemoteLockscreenValidation_password() throws Exception { when(mLockSettingsService.getCredentialType(anyInt())).thenReturn( LockPatternUtils.CREDENTIAL_TYPE_PASSWORD); mRecoverableKeyStoreDb.setBadRemoteGuessCounter(mUserId, 3); mRecoverableKeyStoreDb.setBadRemoteGuessCounter(mUserId, 7); StartLockscreenValidationRequest request = mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService); int credetialsType = request.getLockscreenUiType(); assertThat(request.getRemainingAttempts()).isEqualTo(0); assertThat(credetialsType).isEqualTo(KeyguardManager.PASSWORD); mRemoteLockscreenValidationSessionStorage.finishSession(mUserId); } @Test public void validateRemoteLockscreen_noActiveSession() throws Exception { when(mLockSettingsService.getCredentialType(anyInt())).thenReturn( LockPatternUtils.CREDENTIAL_TYPE_NONE); byte[] invalidGuess = new byte[]{1, 2, 3}; try { mRecoverableKeyStoreManager.validateRemoteLockscreen(invalidGuess, mRecoverableKeyStoreManager.validateRemoteLockscreen(INVALID_GUESS, mLockSettingsService); fail("should have thrown"); } catch (IllegalStateException e) { assertThat(e.getMessage()).contains("session"); } } @Test public void validateRemoteLockscreen_decryptionError() throws Exception { when(mLockSettingsService.getCredentialType(anyInt())).thenReturn( LockPatternUtils.CREDENTIAL_TYPE_PASSWORD); mRecoverableKeyStoreDb.setBadRemoteGuessCounter(mUserId, 4); mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService); try { mRecoverableKeyStoreManager.validateRemoteLockscreen( new byte[] {1, 2, 3}, mLockSettingsService); fail("should have thrown"); } catch (IllegalStateException e) { // Decryption error } } @Test public void validateRemoteLockscreen_zeroRemainingAttempts() throws Exception { when(mLockSettingsService.getCredentialType(anyInt())).thenReturn( LockPatternUtils.CREDENTIAL_TYPE_PASSWORD); mRecoverableKeyStoreDb.setBadRemoteGuessCounter(mUserId, 5); mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService); RemoteLockscreenValidationResult result = mRecoverableKeyStoreManager.validateRemoteLockscreen( encryptCredentialsForNewSession(VALID_GUESS), mLockSettingsService); assertThat(result.getResultCode()).isEqualTo( RemoteLockscreenValidationResult.RESULT_NO_REMAINING_ATTEMPTS); } @Test public void validateRemoteLockscreen_guessValid() throws Exception { when(mLockSettingsService.getCredentialType(anyInt())).thenReturn( LockPatternUtils.CREDENTIAL_TYPE_PASSWORD); mRecoverableKeyStoreDb.setBadRemoteGuessCounter(mUserId, 4); mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService); RemoteLockscreenValidationResult result = mRecoverableKeyStoreManager.validateRemoteLockscreen( encryptCredentialsForNewSession(VALID_GUESS), mLockSettingsService); assertThat(result.getResultCode()).isEqualTo( RemoteLockscreenValidationResult.RESULT_GUESS_VALID); // Valid guess resets counter assertThat(mRecoverableKeyStoreDb.getBadRemoteGuessCounter(mUserId)).isEqualTo(0); } @Test public void validateRemoteLockscreen_timeout() throws Exception { when(mLockSettingsService.getCredentialType(anyInt())).thenReturn( LockPatternUtils.CREDENTIAL_TYPE_PASSWORD); mRecoverableKeyStoreDb.setBadRemoteGuessCounter(mUserId, 4); RemoteLockscreenValidationResult result = mRecoverableKeyStoreManager.validateRemoteLockscreen( encryptCredentialsForNewSession(GUESS_LOCKOUT), mLockSettingsService); assertThat(result.getResultCode()).isEqualTo( RemoteLockscreenValidationResult.RESULT_LOCKOUT); assertThat(result.getTimeoutMillis()).isEqualTo((long) TIMEOUT_MILLIS); // Counter was not changed assertThat(mRecoverableKeyStoreDb.getBadRemoteGuessCounter(mUserId)).isEqualTo(4); } @Test public void validateRemoteLockscreen_guessInvalid() throws Exception { when(mLockSettingsService.getCredentialType(anyInt())).thenReturn( LockPatternUtils.CREDENTIAL_TYPE_PASSWORD); mRecoverableKeyStoreDb.setBadRemoteGuessCounter(mUserId, 4); mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService); RemoteLockscreenValidationResult result = mRecoverableKeyStoreManager.validateRemoteLockscreen( encryptCredentialsForNewSession(INVALID_GUESS), mLockSettingsService); assertThat(result.getResultCode()).isEqualTo( RemoteLockscreenValidationResult.RESULT_GUESS_INVALID); assertThat(mRecoverableKeyStoreDb.getBadRemoteGuessCounter(mUserId)).isEqualTo(5); } private byte[] encryptCredentialsForNewSession(byte[] credentials) throws Exception { StartLockscreenValidationRequest request = mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService); PublicKey publicKey = SecureBox.decodePublicKey(request.getSourcePublicKey()); return SecureBox.encrypt( publicKey, /* sharedSecret= */ null, RecoverableKeyStoreManager.ENCRYPTED_REMOTE_CREDENTIALS_HEADER, credentials); } private static byte[] encryptedApplicationKey( SecretKey recoveryKey, byte[] applicationKey) throws Exception { Loading Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +97 −12 Original line number Diff line number Diff line Loading @@ -49,6 +49,9 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.HexDump; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockPatternView; import com.android.internal.widget.LockscreenCredential; import com.android.internal.widget.VerifyCredentialResponse; import com.android.security.SecureBox; import com.android.server.locksettings.LockSettingsService; import com.android.server.locksettings.recoverablekeystore.certificate.CertParsingException; Loading @@ -65,6 +68,7 @@ import com.android.server.locksettings.recoverablekeystore.storage.RemoteLockscr import com.android.server.locksettings.recoverablekeystore.storage.RemoteLockscreenValidationSessionStorage.LockscreenVerificationSession; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; Loading Loading @@ -97,6 +101,9 @@ import javax.crypto.AEADBadTagException; public class RecoverableKeyStoreManager { private static final String TAG = "RecoverableKeyStoreMgr"; private static final long SYNC_DELAY_MILLIS = 2000; private static final int INVALID_REMOTE_GUESS_LIMIT = 5; public static final byte[] ENCRYPTED_REMOTE_CREDENTIALS_HEADER = "encrypted_remote_credentials".getBytes(StandardCharsets.UTF_8); private static RecoverableKeyStoreManager mInstance; Loading Loading @@ -995,7 +1002,7 @@ public class RecoverableKeyStoreManager { * Starts a session to verify lock screen credentials provided by a remote device. */ public StartLockscreenValidationRequest startRemoteLockscreenValidation( LockSettingsService lockSettingService) { LockSettingsService lockSettingsService) { if (mRemoteLockscreenValidationSessionStorage == null) { throw new UnsupportedOperationException("Under development"); } Loading @@ -1004,40 +1011,118 @@ public class RecoverableKeyStoreManager { int savedCredentialType; final long token = Binder.clearCallingIdentity(); try { savedCredentialType = lockSettingService.getCredentialType(userId); savedCredentialType = lockSettingsService.getCredentialType(userId); } finally { Binder.restoreCallingIdentity(token); } int keyguardCredentailsType = lockPatternUtilsToKeyguardType(savedCredentialType); int keyguardCredentialsType = lockPatternUtilsToKeyguardType(savedCredentialType); LockscreenVerificationSession session = mRemoteLockscreenValidationSessionStorage.startSession(userId); PublicKey publicKey = session.getKeyPair().getPublic(); byte[] encodedPublicKey = SecureBox.encodePublicKey(publicKey); int badGuesses = mDatabase.getBadRemoteGuessCounter(userId); int remainingAttempts = Math.max(INVALID_REMOTE_GUESS_LIMIT - badGuesses, 0); // TODO(b/254335492): Schedule task to remove inactive session return new StartLockscreenValidationRequest.Builder() .setLockscreenUiType(keyguardCredentailsType) .setSourcePublicKey(new byte[]{}) .setLockscreenUiType(keyguardCredentialsType) .setRemainingAttempts(remainingAttempts) .setSourcePublicKey(encodedPublicKey) .build(); } /** * Verifies encrypted credentials guess from a remote device. */ public RemoteLockscreenValidationResult validateRemoteLockscreen( public synchronized RemoteLockscreenValidationResult validateRemoteLockscreen( @NonNull byte[] encryptedCredential, LockSettingsService lockSettingService) { if (mRemoteLockscreenValidationSessionStorage == null) { throw new UnsupportedOperationException("Under development"); } LockSettingsService lockSettingsService) { checkVerifyRemoteLockscreenPermission(); int userId = UserHandle.getCallingUserId(); LockscreenVerificationSession session = mRemoteLockscreenValidationSessionStorage.get(userId); int badGuesses = mDatabase.getBadRemoteGuessCounter(userId); int remainingAttempts = INVALID_REMOTE_GUESS_LIMIT - badGuesses; if (remainingAttempts <= 0) { return new RemoteLockscreenValidationResult.Builder() .setResultCode(RemoteLockscreenValidationResult.RESULT_NO_REMAINING_ATTEMPTS) .build(); } if (session == null) { throw new IllegalStateException("There is no active lock screen check session"); } // TODO(b/254335492): Call lockSettingService.verifyCredential return new RemoteLockscreenValidationResult.Builder().build(); byte[] decryptedCredentials; try { decryptedCredentials = SecureBox.decrypt( session.getKeyPair().getPrivate(), /* sharedSecret= */ null, ENCRYPTED_REMOTE_CREDENTIALS_HEADER, encryptedCredential); } catch (NoSuchAlgorithmException e) { Log.wtf(TAG, "Missing SecureBox algorithm. AOSP required to support this.", e); throw new IllegalStateException(e); } catch (InvalidKeyException e) { Log.e(TAG, "Got InvalidKeyException during lock screen credentials decryption"); throw new IllegalStateException(e); } catch (AEADBadTagException e) { throw new IllegalStateException("Could not decrypt credentials guess", e); } int savedCredentialType; final long token = Binder.clearCallingIdentity(); try { savedCredentialType = lockSettingsService.getCredentialType(userId); int keyguardCredentialsType = lockPatternUtilsToKeyguardType(savedCredentialType); try (LockscreenCredential credential = createLockscreenCredential(keyguardCredentialsType, decryptedCredentials)) { // TODO(b/254335492): remove decryptedCredentials VerifyCredentialResponse verifyResponse = lockSettingsService.verifyCredential(credential, userId, 0); return handleVerifyCredentialResponse(verifyResponse, userId); } } finally { Binder.restoreCallingIdentity(token); } } private RemoteLockscreenValidationResult handleVerifyCredentialResponse( VerifyCredentialResponse response, int userId) { if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) { mDatabase.setBadRemoteGuessCounter(userId, 0); mRemoteLockscreenValidationSessionStorage.finishSession(userId); return new RemoteLockscreenValidationResult.Builder() .setResultCode(RemoteLockscreenValidationResult.RESULT_GUESS_VALID) .build(); } if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) { long timeout = (long) response.getTimeout(); return new RemoteLockscreenValidationResult.Builder() .setResultCode(RemoteLockscreenValidationResult.RESULT_LOCKOUT) .setTimeoutMillis(timeout) .build(); } // Invalid guess int badGuesses = mDatabase.getBadRemoteGuessCounter(userId); mDatabase.setBadRemoteGuessCounter(userId, badGuesses + 1); return new RemoteLockscreenValidationResult.Builder() .setResultCode(RemoteLockscreenValidationResult.RESULT_GUESS_INVALID) .build(); } private LockscreenCredential createLockscreenCredential( int lockType, byte[] password) { switch (lockType) { case KeyguardManager.PASSWORD: CharSequence passwordStr = new String(password, StandardCharsets.UTF_8); return LockscreenCredential.createPassword(passwordStr); case KeyguardManager.PIN: CharSequence pinStr = new String(password); return LockscreenCredential.createPin(pinStr); case KeyguardManager.PATTERN: List<LockPatternView.Cell> pattern = LockPatternUtils.byteArrayToPattern(password); return LockscreenCredential.createPattern(pattern); default: throw new IllegalStateException("Lockscreen is not set"); } } private void checkVerifyRemoteLockscreenPermission() { Loading
services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java +125 −16 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ import static org.mockito.Mockito.when; import android.Manifest; import android.app.KeyguardManager; import android.app.PendingIntent; import android.app.RemoteLockscreenValidationResult; import android.app.StartLockscreenValidationRequest; import android.content.Context; import android.content.Intent; Loading @@ -61,6 +62,8 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.util.ArrayUtils; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockscreenCredential; import com.android.internal.widget.VerifyCredentialResponse; import com.android.security.SecureBox; import com.android.server.locksettings.LockSettingsService; import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKeyStorage; Loading @@ -84,10 +87,12 @@ import org.mockito.Spy; import java.io.File; import java.nio.charset.StandardCharsets; import java.security.PublicKey; import java.security.cert.CertPath; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Map; import java.util.Random; import java.util.concurrent.ScheduledExecutorService; Loading Loading @@ -155,6 +160,10 @@ public class RecoverableKeyStoreManagerTest { .setKeyDerivationParams(KeyDerivationParams.createSha256Params(TEST_SALT)) .setSecret(TEST_SECRET) .build(); private static final byte[] VALID_GUESS = getUtf8Bytes("password123"); private static final byte[] INVALID_GUESS = getUtf8Bytes("not_password"); private static final byte[] GUESS_LOCKOUT = getUtf8Bytes("need_to_wait"); private static final int TIMEOUT_MILLIS = 30 * 1000; @Mock private Context mMockContext; @Mock private RecoverySnapshotListenersStorage mMockListenersStorage; Loading Loading @@ -208,10 +217,20 @@ public class RecoverableKeyStoreManagerTest { mTestOnlyInsecureCertificateHelper, mCleanupManager, mRemoteLockscreenValidationSessionStorage); when(mLockSettingsService.verifyCredential( any(LockscreenCredential.class), anyInt(), anyInt())).thenAnswer(args -> { LockscreenCredential argument = (LockscreenCredential) args.getArguments()[0]; if (Arrays.equals(argument.getCredential(), VALID_GUESS)) { return VerifyCredentialResponse.OK; } else if (Arrays.equals(argument.getCredential(), INVALID_GUESS)) { return VerifyCredentialResponse.ERROR; } else return VerifyCredentialResponse.fromTimeout(TIMEOUT_MILLIS); }); } @After public void tearDown() { mRemoteLockscreenValidationSessionStorage.finishSession(mUserId); mRecoverableKeyStoreDb.close(); mDatabaseFile.delete(); } Loading Loading @@ -1289,73 +1308,163 @@ public class RecoverableKeyStoreManagerTest { assertThat(e.getMessage()).contains("not set"); } verify(mLockSettingsService).getCredentialType(mUserId); mRemoteLockscreenValidationSessionStorage.finishSession(mUserId); } @Test public void startRemoteLockscreenValidation_checksPermission() throws Exception { when(mLockSettingsService.getCredentialType(anyInt())).thenReturn( LockPatternUtils.CREDENTIAL_TYPE_PIN); mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService); // TODO(b/254335492): Check new system permission verify(mMockContext, times(1)) .enforceCallingOrSelfPermission( eq(Manifest.permission.RECOVER_KEYSTORE), any()); mRemoteLockscreenValidationSessionStorage.finishSession(mUserId); } @Test public void startRemoteLockscreenValidation_returnsCredentailsType() throws Exception { when(mLockSettingsService.getCredentialType(anyInt())).thenReturn( LockPatternUtils.CREDENTIAL_TYPE_PIN); StartLockscreenValidationRequest request = mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService); int credetialsType = request.getLockscreenUiType(); int credetialsType = request.getLockscreenUiType(); assertThat(credetialsType).isEqualTo(KeyguardManager.PIN); assertThat(request.getRemainingAttempts()).isEqualTo(5); verify(mLockSettingsService).getCredentialType(anyInt()); mRemoteLockscreenValidationSessionStorage.finishSession(mUserId); } @Test public void startRemoteLockscreenValidation_returnsRemainingAttempts() throws Exception { when(mLockSettingsService.getCredentialType(anyInt())).thenReturn( LockPatternUtils.CREDENTIAL_TYPE_PATTERN); mRecoverableKeyStoreDb.setBadRemoteGuessCounter(mUserId, 3); StartLockscreenValidationRequest request = mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService); int credetialsType = request.getLockscreenUiType(); assertThat(credetialsType).isEqualTo(KeyguardManager.PATTERN); // TODO(b/254335492): Verify returned value mRemoteLockscreenValidationSessionStorage.finishSession(mUserId); assertThat(request.getRemainingAttempts()).isEqualTo(2); } @Test public void startRemoteLockscreenValidation_password() throws Exception { when(mLockSettingsService.getCredentialType(anyInt())).thenReturn( LockPatternUtils.CREDENTIAL_TYPE_PASSWORD); mRecoverableKeyStoreDb.setBadRemoteGuessCounter(mUserId, 3); mRecoverableKeyStoreDb.setBadRemoteGuessCounter(mUserId, 7); StartLockscreenValidationRequest request = mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService); int credetialsType = request.getLockscreenUiType(); assertThat(request.getRemainingAttempts()).isEqualTo(0); assertThat(credetialsType).isEqualTo(KeyguardManager.PASSWORD); mRemoteLockscreenValidationSessionStorage.finishSession(mUserId); } @Test public void validateRemoteLockscreen_noActiveSession() throws Exception { when(mLockSettingsService.getCredentialType(anyInt())).thenReturn( LockPatternUtils.CREDENTIAL_TYPE_NONE); byte[] invalidGuess = new byte[]{1, 2, 3}; try { mRecoverableKeyStoreManager.validateRemoteLockscreen(invalidGuess, mRecoverableKeyStoreManager.validateRemoteLockscreen(INVALID_GUESS, mLockSettingsService); fail("should have thrown"); } catch (IllegalStateException e) { assertThat(e.getMessage()).contains("session"); } } @Test public void validateRemoteLockscreen_decryptionError() throws Exception { when(mLockSettingsService.getCredentialType(anyInt())).thenReturn( LockPatternUtils.CREDENTIAL_TYPE_PASSWORD); mRecoverableKeyStoreDb.setBadRemoteGuessCounter(mUserId, 4); mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService); try { mRecoverableKeyStoreManager.validateRemoteLockscreen( new byte[] {1, 2, 3}, mLockSettingsService); fail("should have thrown"); } catch (IllegalStateException e) { // Decryption error } } @Test public void validateRemoteLockscreen_zeroRemainingAttempts() throws Exception { when(mLockSettingsService.getCredentialType(anyInt())).thenReturn( LockPatternUtils.CREDENTIAL_TYPE_PASSWORD); mRecoverableKeyStoreDb.setBadRemoteGuessCounter(mUserId, 5); mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService); RemoteLockscreenValidationResult result = mRecoverableKeyStoreManager.validateRemoteLockscreen( encryptCredentialsForNewSession(VALID_GUESS), mLockSettingsService); assertThat(result.getResultCode()).isEqualTo( RemoteLockscreenValidationResult.RESULT_NO_REMAINING_ATTEMPTS); } @Test public void validateRemoteLockscreen_guessValid() throws Exception { when(mLockSettingsService.getCredentialType(anyInt())).thenReturn( LockPatternUtils.CREDENTIAL_TYPE_PASSWORD); mRecoverableKeyStoreDb.setBadRemoteGuessCounter(mUserId, 4); mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService); RemoteLockscreenValidationResult result = mRecoverableKeyStoreManager.validateRemoteLockscreen( encryptCredentialsForNewSession(VALID_GUESS), mLockSettingsService); assertThat(result.getResultCode()).isEqualTo( RemoteLockscreenValidationResult.RESULT_GUESS_VALID); // Valid guess resets counter assertThat(mRecoverableKeyStoreDb.getBadRemoteGuessCounter(mUserId)).isEqualTo(0); } @Test public void validateRemoteLockscreen_timeout() throws Exception { when(mLockSettingsService.getCredentialType(anyInt())).thenReturn( LockPatternUtils.CREDENTIAL_TYPE_PASSWORD); mRecoverableKeyStoreDb.setBadRemoteGuessCounter(mUserId, 4); RemoteLockscreenValidationResult result = mRecoverableKeyStoreManager.validateRemoteLockscreen( encryptCredentialsForNewSession(GUESS_LOCKOUT), mLockSettingsService); assertThat(result.getResultCode()).isEqualTo( RemoteLockscreenValidationResult.RESULT_LOCKOUT); assertThat(result.getTimeoutMillis()).isEqualTo((long) TIMEOUT_MILLIS); // Counter was not changed assertThat(mRecoverableKeyStoreDb.getBadRemoteGuessCounter(mUserId)).isEqualTo(4); } @Test public void validateRemoteLockscreen_guessInvalid() throws Exception { when(mLockSettingsService.getCredentialType(anyInt())).thenReturn( LockPatternUtils.CREDENTIAL_TYPE_PASSWORD); mRecoverableKeyStoreDb.setBadRemoteGuessCounter(mUserId, 4); mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService); RemoteLockscreenValidationResult result = mRecoverableKeyStoreManager.validateRemoteLockscreen( encryptCredentialsForNewSession(INVALID_GUESS), mLockSettingsService); assertThat(result.getResultCode()).isEqualTo( RemoteLockscreenValidationResult.RESULT_GUESS_INVALID); assertThat(mRecoverableKeyStoreDb.getBadRemoteGuessCounter(mUserId)).isEqualTo(5); } private byte[] encryptCredentialsForNewSession(byte[] credentials) throws Exception { StartLockscreenValidationRequest request = mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService); PublicKey publicKey = SecureBox.decodePublicKey(request.getSourcePublicKey()); return SecureBox.encrypt( publicKey, /* sharedSecret= */ null, RecoverableKeyStoreManager.ENCRYPTED_REMOTE_CREDENTIALS_HEADER, credentials); } private static byte[] encryptedApplicationKey( SecretKey recoveryKey, byte[] applicationKey) throws Exception { Loading