summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Dmitry Dementyev <dementyev@google.com> 2023-02-07 07:48:22 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2023-02-07 07:48:22 +0000
commit03e132f6348281ddedac359c863a47d8fd9459a5 (patch)
tree4abdcfc7199dc794646050ad69d0b8b09434757f
parent93a81c6c5fe711086c89c44731899c0a1bf4eb9b (diff)
parent68a84962bc12a6320453f99a7410563e113041c7 (diff)
Merge "Implement validateRemoteLockscreen."
-rw-r--r--services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java109
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java141
2 files changed, 222 insertions, 28 deletions
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index d9815699e726..33dc7efe0a9b 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -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;
@@ -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;
@@ -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;
@@ -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");
}
@@ -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() {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
index b9adc3042d04..32cb8c4e0a91 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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();
}
@@ -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 {