diff --git a/core/java/android/app/RemoteLockscreenValidationResult.aidl b/core/java/android/app/RemoteLockscreenValidationResult.aidl new file mode 100644 index 0000000000000..504f78f97c328 --- /dev/null +++ b/core/java/android/app/RemoteLockscreenValidationResult.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +/** {@hide} */ +parcelable RemoteLockscreenValidationResult; diff --git a/core/java/android/app/RemoteLockscreenValidationResult.java b/core/java/android/app/RemoteLockscreenValidationResult.java new file mode 100644 index 0000000000000..4f15be209bce1 --- /dev/null +++ b/core/java/android/app/RemoteLockscreenValidationResult.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import android.annotation.DurationMillisLong; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +/** + * Result of lock screen credentials verification. + * + * @hide + */ +public final class RemoteLockscreenValidationResult implements Parcelable { + + /** + * The guess was correct + */ + public static final int RESULT_GUESS_VALID = 1; + + /** + * Remote device provided incorrect credentials. + */ + public static final int RESULT_GUESS_INVALID = 2; + + /** + * The operation was canceled because the API is locked out due to too many attempts. It + * usually happens after 5 failed attempts and API may be called again after a short + * delay specified by {@code getTimeoutMillis}. + */ + public static final int RESULT_LOCKOUT = 3; + + /** + * There were too many invalid guesses. + */ + public static final int RESULT_NO_REMAINING_ATTEMPTS = 4; + + @IntDef({RESULT_GUESS_VALID, + RESULT_GUESS_INVALID, + RESULT_LOCKOUT, + RESULT_NO_REMAINING_ATTEMPTS}) + @Retention(RetentionPolicy.SOURCE) + @interface ResultCode {} + + private int mResultCode; + private long mTimeoutMillis; + + public static final @NonNull Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public RemoteLockscreenValidationResult createFromParcel(Parcel source) { + return new RemoteLockscreenValidationResult(source); + } + + @Override + public RemoteLockscreenValidationResult[] newArray(int size) { + return new RemoteLockscreenValidationResult[size]; + } + }; + + /** + * Builder for {@code RemoteLockscreenValidationResult} + */ + public static final class Builder { + private RemoteLockscreenValidationResult mInstance = new RemoteLockscreenValidationResult(); + + /** + * Sets the result code. + */ + public @NonNull Builder setResultCode(@ResultCode int resultCode) { + mInstance.mResultCode = resultCode; + return this; + } + + /** + * Sets timeout for {@code RESULT_LOCKOUT}. + * Default value is {@code 0}. + */ + public @NonNull Builder setTimeoutMillis(@DurationMillisLong long timeoutMillis) { + mInstance.mTimeoutMillis = timeoutMillis; + return this; + } + + /** + * Creates {@code RemoteLockscreenValidationResult}. + * + * @throws IllegalStateException if result code was not set. + */ + public @NonNull RemoteLockscreenValidationResult build() { + if (mInstance.mResultCode == 0) { + throw new IllegalStateException("Result code must be set"); + } + return mInstance; + } + } + + /** + * Gets the result code. + */ + public @ResultCode int getResultCode() { + return mResultCode; + } + + /** + * Delay before next attempt to verify credentials. + * + * Default value is {@code 0}. + */ + public @DurationMillisLong long getTimeoutMillis() { + return mTimeoutMillis; + } + + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeInt(mResultCode); + out.writeLong(mTimeoutMillis); + } + + private RemoteLockscreenValidationResult() { + } + + private RemoteLockscreenValidationResult(Parcel in) { + mResultCode = in.readInt(); + mTimeoutMillis = in.readLong(); + } + + @Override + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/app/StartLockscreenValidationRequest.aidl b/core/java/android/app/StartLockscreenValidationRequest.aidl new file mode 100644 index 0000000000000..367dfeeb0089e --- /dev/null +++ b/core/java/android/app/StartLockscreenValidationRequest.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +/** {@hide} */ +parcelable StartLockscreenValidationRequest; diff --git a/core/java/android/app/StartLockscreenValidationRequest.java b/core/java/android/app/StartLockscreenValidationRequest.java new file mode 100644 index 0000000000000..69c268bcbec63 --- /dev/null +++ b/core/java/android/app/StartLockscreenValidationRequest.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import android.annotation.NonNull; +import android.app.KeyguardManager.LockTypes; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Provides information necessary to perform remote lock screen credentials check. + * + * @hide + */ +public final class StartLockscreenValidationRequest implements Parcelable { + + @LockTypes + private int mLockscreenUiType; + + private byte[] mSourcePublicKey; + + private int mRemainingAttempts; + + public static final @NonNull Parcelable.Creator CREATOR = new + Parcelable.Creator() { + @Override + public StartLockscreenValidationRequest createFromParcel(Parcel source) { + return new StartLockscreenValidationRequest(source); + } + + @Override + public StartLockscreenValidationRequest[] newArray(int size) { + return new StartLockscreenValidationRequest[size]; + } + }; + + + /** + * Builder for {@code StartLockscreenValidationRequest} + */ + public static final class Builder { + private StartLockscreenValidationRequest mInstance = new StartLockscreenValidationRequest(); + + /** + * Sets UI type. + * Default value is {@code LockTypes.PASSWORD} + * + * @param lockscreenUiType The UI format + * @return This builder. + */ + public @NonNull Builder setLockscreenUiType(@LockTypes int lockscreenUiType) { + mInstance.mLockscreenUiType = lockscreenUiType; + return this; + } + + /** + * Sets public key using secure box encoding + * @return This builder. + */ + public @NonNull Builder setSourcePublicKey(@NonNull byte[] publicKey) { + mInstance.mSourcePublicKey = publicKey; + return this; + } + + /** + * Sets the number of remaining credentials check + * Default value is {@code 0} + * + * @return This builder. + */ + public @NonNull Builder setRemainingAttempts(int remainingAttempts) { + mInstance.mRemainingAttempts = remainingAttempts; + return this; + } + + /** + * Creates {@code StartLockscreenValidationRequest} + * + * @throws NullPointerException if required fields are not set. + */ + public @NonNull StartLockscreenValidationRequest build() { + Objects.requireNonNull(mInstance.mSourcePublicKey); + return mInstance; + } + } + + /** + * Specifies lock screen credential type. + */ + public @LockTypes int getLockscreenUiType() { + return mLockscreenUiType; + } + + /** + * Public key used to send encrypted credentials. + */ + public @NonNull byte[] getSourcePublicKey() { + return mSourcePublicKey; + } + + /** + * Number of remaining attempts to verify credentials. + * + *

After correct guess counter is reset to {@code 5}. + */ + public int getRemainingAttempts() { + return mRemainingAttempts; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeInt(mLockscreenUiType); + out.writeByteArray(mSourcePublicKey); + out.writeInt(mRemainingAttempts); + } + + private StartLockscreenValidationRequest() { + } + + private StartLockscreenValidationRequest(Parcel in) { + mLockscreenUiType = in.readInt(); + mSourcePublicKey = in.createByteArray(); + mRemainingAttempts = in.readInt(); + } + + @Override + public int describeContents() { + return 0; + } +} diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 48e0c300979c0..a88925a0a2a5a 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -56,6 +56,8 @@ import android.app.KeyguardManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; +import android.app.RemoteLockscreenValidationResult; +import android.app.StartLockscreenValidationRequest; import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.DeviceStateCache; @@ -114,7 +116,6 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.EventLog; -import android.util.FeatureFlagUtils; import android.util.LongSparseArray; import android.util.Slog; import android.util.SparseArray; @@ -2507,23 +2508,18 @@ public class LockSettingsService extends ILockSettings.Stub { /** * Starts a session to verify lock screen credentials provided by a remote device. */ - public void startRemoteLockscreenValidation() { - if (!FeatureFlagUtils.isEnabled(mContext, - FeatureFlagUtils.SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API)) { - throw new UnsupportedOperationException("Under development"); - } - mRecoverableKeyStoreManager.startRemoteLockscreenValidation(); + @NonNull + public StartLockscreenValidationRequest startRemoteLockscreenValidation() { + return mRecoverableKeyStoreManager.startRemoteLockscreenValidation(this); } /** - * Verifies credentials guess from a remote device. + * Verifies encrypted credentials guess from a remote device. */ - public void validateRemoteLockscreen(@NonNull byte[] encryptedCredential) { - if (!FeatureFlagUtils.isEnabled(mContext, - FeatureFlagUtils.SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API)) { - throw new UnsupportedOperationException("Under development"); - } - mRecoverableKeyStoreManager.validateRemoteLockscreen(encryptedCredential); + @NonNull + public RemoteLockscreenValidationResult + validateRemoteLockScreen2(@NonNull byte[] encryptedCredential) { + return mRecoverableKeyStoreManager.validateRemoteLockscreen(encryptedCredential, this); } // Reading these settings needs the contacts permission 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 b437421b658a9..d9815699e7266 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java @@ -28,7 +28,10 @@ import static android.security.keystore.recovery.RecoveryController.ERROR_SESSIO import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.KeyguardManager; import android.app.PendingIntent; +import android.app.RemoteLockscreenValidationResult; +import android.app.StartLockscreenValidationRequest; import android.content.Context; import android.os.Binder; import android.os.RemoteException; @@ -40,11 +43,14 @@ import android.security.keystore.recovery.RecoveryCertPath; import android.security.keystore.recovery.RecoveryController; import android.security.keystore.recovery.WrappedApplicationKey; import android.util.ArrayMap; +import android.util.FeatureFlagUtils; 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.security.SecureBox; +import com.android.server.locksettings.LockSettingsService; import com.android.server.locksettings.recoverablekeystore.certificate.CertParsingException; import com.android.server.locksettings.recoverablekeystore.certificate.CertUtils; import com.android.server.locksettings.recoverablekeystore.certificate.CertValidationException; @@ -55,6 +61,8 @@ import com.android.server.locksettings.recoverablekeystore.storage.CleanupManage import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb; import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage; import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage; +import com.android.server.locksettings.recoverablekeystore.storage.RemoteLockscreenValidationSessionStorage; +import com.android.server.locksettings.recoverablekeystore.storage.RemoteLockscreenValidationSessionStorage.LockscreenVerificationSession; import java.io.IOException; import java.security.InvalidKeyException; @@ -103,6 +111,9 @@ public class RecoverableKeyStoreManager { private final ApplicationKeyStorage mApplicationKeyStorage; private final TestOnlyInsecureCertificateHelper mTestCertHelper; private final CleanupManager mCleanupManager; + // only set if SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API is enabled. + @Nullable private final RemoteLockscreenValidationSessionStorage + mRemoteLockscreenValidationSessionStorage; /** * Returns a new or existing instance. @@ -112,7 +123,18 @@ public class RecoverableKeyStoreManager { public static synchronized RecoverableKeyStoreManager getInstance(Context context) { if (mInstance == null) { - RecoverableKeyStoreDb db = RecoverableKeyStoreDb.newInstance(context); + RecoverableKeyStoreDb db; + RemoteLockscreenValidationSessionStorage lockscreenCheckSessions; + if (FeatureFlagUtils.isEnabled(context, + FeatureFlagUtils.SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API)) { + // TODO(b/254335492): Remove flag check when feature is launched. + db = RecoverableKeyStoreDb.newInstance(context, 7); + lockscreenCheckSessions = new RemoteLockscreenValidationSessionStorage(); + } else { + db = RecoverableKeyStoreDb.newInstance(context); + lockscreenCheckSessions = null; + } + PlatformKeyManager platformKeyManager; ApplicationKeyStorage applicationKeyStorage; try { @@ -142,7 +164,8 @@ public class RecoverableKeyStoreManager { platformKeyManager, applicationKeyStorage, new TestOnlyInsecureCertificateHelper(), - cleanupManager); + cleanupManager, + lockscreenCheckSessions); } return mInstance; } @@ -158,7 +181,8 @@ public class RecoverableKeyStoreManager { PlatformKeyManager platformKeyManager, ApplicationKeyStorage applicationKeyStorage, TestOnlyInsecureCertificateHelper testOnlyInsecureCertificateHelper, - CleanupManager cleanupManager) { + CleanupManager cleanupManager, + RemoteLockscreenValidationSessionStorage remoteLockscreenValidationSessionStorage) { mContext = context; mDatabase = recoverableKeyStoreDb; mRecoverySessionStorage = recoverySessionStorage; @@ -177,6 +201,7 @@ public class RecoverableKeyStoreManager { Log.wtf(TAG, "AES keygen algorithm not available. AOSP must support this.", e); throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage()); } + mRemoteLockscreenValidationSessionStorage = remoteLockscreenValidationSessionStorage; } /** @@ -969,19 +994,50 @@ public class RecoverableKeyStoreManager { /** * Starts a session to verify lock screen credentials provided by a remote device. */ - public void startRemoteLockscreenValidation() { + public StartLockscreenValidationRequest startRemoteLockscreenValidation( + LockSettingsService lockSettingService) { + if (mRemoteLockscreenValidationSessionStorage == null) { + throw new UnsupportedOperationException("Under development"); + } checkVerifyRemoteLockscreenPermission(); - // TODO(b/254335492): Create session in memory - return; + int userId = UserHandle.getCallingUserId(); + int savedCredentialType; + final long token = Binder.clearCallingIdentity(); + try { + savedCredentialType = lockSettingService.getCredentialType(userId); + } finally { + Binder.restoreCallingIdentity(token); + } + int keyguardCredentailsType = lockPatternUtilsToKeyguardType(savedCredentialType); + LockscreenVerificationSession session = + mRemoteLockscreenValidationSessionStorage.startSession(userId); + PublicKey publicKey = session.getKeyPair().getPublic(); + byte[] encodedPublicKey = SecureBox.encodePublicKey(publicKey); + int badGuesses = mDatabase.getBadRemoteGuessCounter(userId); + return new StartLockscreenValidationRequest.Builder() + .setLockscreenUiType(keyguardCredentailsType) + .setSourcePublicKey(new byte[]{}) + .build(); } /** * Verifies encrypted credentials guess from a remote device. */ - public void validateRemoteLockscreen(@NonNull byte[] encryptedCredential) { + public RemoteLockscreenValidationResult validateRemoteLockscreen( + @NonNull byte[] encryptedCredential, + LockSettingsService lockSettingService) { + if (mRemoteLockscreenValidationSessionStorage == null) { + throw new UnsupportedOperationException("Under development"); + } checkVerifyRemoteLockscreenPermission(); - // TODO(b/254335492): Decrypt and verify credentials - return; + int userId = UserHandle.getCallingUserId(); + LockscreenVerificationSession session = + mRemoteLockscreenValidationSessionStorage.get(userId); + 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(); } private void checkVerifyRemoteLockscreenPermission() { @@ -995,6 +1051,21 @@ public class RecoverableKeyStoreManager { mCleanupManager.registerRecoveryAgent(userId, uid); } + private int lockPatternUtilsToKeyguardType(int credentialsType) { + switch(credentialsType) { + case LockPatternUtils.CREDENTIAL_TYPE_NONE: + throw new IllegalStateException("Screen lock is not set"); + case LockPatternUtils.CREDENTIAL_TYPE_PATTERN: + return KeyguardManager.PATTERN; + case LockPatternUtils.CREDENTIAL_TYPE_PIN: + return KeyguardManager.PIN; + case LockPatternUtils.CREDENTIAL_TYPE_PASSWORD: + return KeyguardManager.PASSWORD; + default: + throw new IllegalStateException("Screen lock is not set"); + } + } + private void checkRecoverKeyStorePermission() { mContext.enforceCallingOrSelfPermission( Manifest.permission.RECOVER_KEYSTORE, 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 bb79fd89982a3..b9adc3042d041 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.StartLockscreenValidationRequest; import android.content.Context; import android.content.Intent; import android.os.Binder; @@ -61,11 +62,13 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.util.ArrayUtils; import com.android.internal.widget.LockPatternUtils; import com.android.security.SecureBox; +import com.android.server.locksettings.LockSettingsService; import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKeyStorage; import com.android.server.locksettings.recoverablekeystore.storage.CleanupManager; import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb; import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage; import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage; +import com.android.server.locksettings.recoverablekeystore.storage.RemoteLockscreenValidationSessionStorage; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -160,14 +163,17 @@ public class RecoverableKeyStoreManagerTest { @Mock private ApplicationKeyStorage mApplicationKeyStorage; @Mock private CleanupManager mCleanupManager; @Mock private ScheduledExecutorService mExecutorService; + @Mock private LockSettingsService mLockSettingsService; @Spy private TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper; + private int mUserId; private RecoverableKeyStoreDb mRecoverableKeyStoreDb; private File mDatabaseFile; private RecoverableKeyStoreManager mRecoverableKeyStoreManager; private RecoverySessionStorage mRecoverySessionStorage; private RecoverySnapshotStorage mRecoverySnapshotStorage; private PlatformEncryptionKey mPlatformEncryptionKey; + private RemoteLockscreenValidationSessionStorage mRemoteLockscreenValidationSessionStorage; @Before public void setUp() throws Exception { @@ -177,7 +183,9 @@ public class RecoverableKeyStoreManagerTest { mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME); mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context); + mUserId = UserHandle.getCallingUserId(); mRecoverySessionStorage = new RecoverySessionStorage(); + mRemoteLockscreenValidationSessionStorage = new RemoteLockscreenValidationSessionStorage(); when(mMockContext.getSystemService(anyString())).thenReturn(mKeyguardManager); when(mMockContext.getSystemServiceName(any())).thenReturn("test"); @@ -198,7 +206,8 @@ public class RecoverableKeyStoreManagerTest { mPlatformKeyManager, mApplicationKeyStorage, mTestOnlyInsecureCertificateHelper, - mCleanupManager); + mCleanupManager, + mRemoteLockscreenValidationSessionStorage); } @After @@ -1269,6 +1278,85 @@ public class RecoverableKeyStoreManagerTest { verify(mExecutorService).schedule(any(Runnable.class), anyLong(), any()); } + @Test + public void startRemoteLockscreenValidation_credentialsNotSet() throws Exception { + when(mLockSettingsService.getCredentialType(anyInt())).thenReturn( + LockPatternUtils.CREDENTIAL_TYPE_NONE); + try { + mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService); + fail("should have thrown"); + } catch (IllegalStateException e) { + 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(); + + assertThat(credetialsType).isEqualTo(KeyguardManager.PIN); + + 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); + } + + @Test + public void startRemoteLockscreenValidation_password() throws Exception { + when(mLockSettingsService.getCredentialType(anyInt())).thenReturn( + LockPatternUtils.CREDENTIAL_TYPE_PASSWORD); + mRecoverableKeyStoreDb.setBadRemoteGuessCounter(mUserId, 3); + StartLockscreenValidationRequest request = + mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService); + int credetialsType = request.getLockscreenUiType(); + 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, + mLockSettingsService); + fail("should have thrown"); + } catch (IllegalStateException e) { + assertThat(e.getMessage()).contains("session"); + } + } + private static byte[] encryptedApplicationKey( SecretKey recoveryKey, byte[] applicationKey) throws Exception { return KeySyncUtils.encryptKeysWithRecoveryKey(recoveryKey, ImmutableMap.of(