Implement startRemoteLockscreenValidation.
Define parcelables for new APIs - go/setup-lskf-apis
They are needed to test implementation and will be added to SystemApi in a separate change - ag/21091787
Move feature flag check to RecoverableKeyStoreManager initialization.
Bug: 254335492
Test: atest com.android.server.locksettings.recoverablekeystore
Change-Id: Idfeb9583fcc29676a18116a5ef27dba9012aac09
Change-Id: Ie436b302ab670b4e6e77b8d2a581fd61a47529fc
diff --git a/core/java/android/app/RemoteLockscreenValidationResult.aidl b/core/java/android/app/RemoteLockscreenValidationResult.aidl
new file mode 100644
index 0000000..504f78f
--- /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 0000000..4f15be2
--- /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<RemoteLockscreenValidationResult> CREATOR =
+ new Parcelable.Creator<RemoteLockscreenValidationResult>() {
+ @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 0000000..367dfee
--- /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 0000000..69c268bcb
--- /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<StartLockscreenValidationRequest> CREATOR = new
+ Parcelable.Creator<StartLockscreenValidationRequest>() {
+ @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.
+ *
+ * <p>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 48e0c30..a88925a 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.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.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 @@
/**
* 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 b437421..d981569 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 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.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.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 @@
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 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 @@
platformKeyManager,
applicationKeyStorage,
new TestOnlyInsecureCertificateHelper(),
- cleanupManager);
+ cleanupManager,
+ lockscreenCheckSessions);
}
return mInstance;
}
@@ -158,7 +181,8 @@
PlatformKeyManager platformKeyManager,
ApplicationKeyStorage applicationKeyStorage,
TestOnlyInsecureCertificateHelper testOnlyInsecureCertificateHelper,
- CleanupManager cleanupManager) {
+ CleanupManager cleanupManager,
+ RemoteLockscreenValidationSessionStorage remoteLockscreenValidationSessionStorage) {
mContext = context;
mDatabase = recoverableKeyStoreDb;
mRecoverySessionStorage = recoverySessionStorage;
@@ -177,6 +201,7 @@
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 @@
/**
* 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 @@
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 bb79fd8..b9adc30 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 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 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 @@
@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 @@
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 @@
mPlatformKeyManager,
mApplicationKeyStorage,
mTestOnlyInsecureCertificateHelper,
- mCleanupManager);
+ mCleanupManager,
+ mRemoteLockscreenValidationSessionStorage);
}
@After
@@ -1269,6 +1278,85 @@
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(